mirror of
https://github.com/logos-blockchain/logos-blockchain-testing.git
synced 2026-01-05 23:03:07 +00:00
core: make scenario/topology building fallible
This commit is contained in:
parent
4da09bfe85
commit
47b4e8531d
@ -1,9 +1,11 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn scenario_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn scenario_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||||
.wallets(50)
|
.wallets(50)
|
||||||
.transactions_with(|txs| txs.rate(5).users(20))
|
.transactions_with(|txs| txs.rate(5).users(20))
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
use std::time::Duration;
|
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};
|
use testing_framework_workflows::{ScenarioBuilderExt, workloads::chaos::RandomRestartWorkload};
|
||||||
|
|
||||||
pub fn random_restart_plan() -> testing_framework_core::scenario::Scenario<
|
use crate::SnippetResult;
|
||||||
testing_framework_core::scenario::NodeControlCapability,
|
|
||||||
> {
|
pub fn random_restart_plan() -> SnippetResult<Scenario<NodeControlCapability>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||||
.enable_node_control()
|
.enable_node_control()
|
||||||
.with_workload(RandomRestartWorkload::new(
|
.with_workload(RandomRestartWorkload::new(
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn build_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn build_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)).build() // Construct the final Scenario
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0)).build() // Construct the final Scenario
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub async fn run_test() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(90))
|
.with_run_duration(Duration::from_secs(90))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn expectations_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn expectations_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||||
.expect_consensus_liveness() // Assert blocks are produced continuously
|
.expect_consensus_liveness() // Assert blocks are produced continuously
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn run_duration_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn run_duration_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||||
.with_run_duration(Duration::from_secs(120)) // Run for 120 seconds
|
.with_run_duration(Duration::from_secs(120)) // Run for 120 seconds
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn transactions_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn transactions_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||||
.wallets(50)
|
.wallets(50)
|
||||||
.transactions_with(|txs| {
|
.transactions_with(|txs| {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn wallets_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn wallets_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||||
.wallets(50) // Seed 50 funded wallet accounts
|
.wallets(50) // Seed 50 funded wallet accounts
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@ -3,7 +3,10 @@ use std::time::Duration;
|
|||||||
use testing_framework_core::scenario::{NodeControlCapability, ScenarioBuilder};
|
use testing_framework_core::scenario::{NodeControlCapability, ScenarioBuilder};
|
||||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||||
|
|
||||||
pub fn chaos_plan() -> testing_framework_core::scenario::Scenario<NodeControlCapability> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn chaos_plan()
|
||||||
|
-> SnippetResult<testing_framework_core::scenario::Scenario<NodeControlCapability>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(2))
|
||||||
.enable_node_control() // Enable node control capability
|
.enable_node_control() // Enable node control capability
|
||||||
.chaos_with(|c| {
|
.chaos_with(|c| {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::{Scenario, ScenarioBuilder};
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn da_plan() -> testing_framework_core::scenario::Scenario<()> {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn da_plan() -> SnippetResult<Scenario<()>> {
|
||||||
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(1))
|
||||||
.wallets(50)
|
.wallets(50)
|
||||||
.da_with(|da| {
|
.da_with(|da| {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use testing_framework_workflows::ScenarioBuilderExt;
|
|||||||
pub async fn execution() -> Result<()> {
|
pub async fn execution() -> Result<()> {
|
||||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(1).executors(0))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub async fn aggressive_chaos_test() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(180))
|
.with_run_duration(Duration::from_secs(180))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = ComposeDeployer::default();
|
let deployer = ComposeDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -15,7 +15,7 @@ pub async fn load_progression_test() -> Result<()> {
|
|||||||
.transactions_with(|txs| txs.rate(rate).users(20))
|
.transactions_with(|txs| txs.rate(rate).users(20))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(60))
|
.with_run_duration(Duration::from_secs(60))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = ComposeDeployer::default();
|
let deployer = ComposeDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ pub async fn sustained_load_test() -> Result<()> {
|
|||||||
.da_with(|da| da.channel_rate(2).blob_rate(3))
|
.da_with(|da| da.channel_rate(2).blob_rate(3))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(300))
|
.with_run_duration(Duration::from_secs(300))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = ComposeDeployer::default();
|
let deployer = ComposeDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub async fn chaos_resilience() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(120))
|
.with_run_duration(Duration::from_secs(120))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = ComposeDeployer::default();
|
let deployer = ComposeDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ pub async fn da_and_transactions() -> Result<()> {
|
|||||||
.da_with(|da| da.channel_rate(2).blob_rate(2))
|
.da_with(|da| da.channel_rate(2).blob_rate(2))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(90))
|
.with_run_duration(Duration::from_secs(90))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ pub async fn simple_consensus() -> Result<()> {
|
|||||||
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(30))
|
.with_run_duration(Duration::from_secs(30))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ pub async fn transaction_workload() -> Result<()> {
|
|||||||
.transactions_with(|txs| txs.rate(5).users(10))
|
.transactions_with(|txs| txs.rate(5).users(10))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(60))
|
.with_run_duration(Duration::from_secs(60))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
|
|
||||||
|
use crate::SnippetResult;
|
||||||
|
|
||||||
pub trait YourExpectationDslExt: Sized {
|
pub trait YourExpectationDslExt: Sized {
|
||||||
fn expect_your_condition(self) -> Self;
|
fn expect_your_condition(self) -> Self;
|
||||||
}
|
}
|
||||||
@ -10,8 +12,9 @@ impl<Caps> YourExpectationDslExt for testing_framework_core::scenario::Builder<C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_in_examples() {
|
pub fn use_in_examples() -> SnippetResult<()> {
|
||||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||||
.expect_your_condition()
|
.expect_your_condition()
|
||||||
.build();
|
.build()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
|
|
||||||
|
use crate::SnippetResult;
|
||||||
|
|
||||||
pub struct YourWorkloadBuilder;
|
pub struct YourWorkloadBuilder;
|
||||||
|
|
||||||
impl YourWorkloadBuilder {
|
impl YourWorkloadBuilder {
|
||||||
@ -24,8 +26,9 @@ impl<Caps> YourWorkloadDslExt for testing_framework_core::scenario::Builder<Caps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_in_examples() {
|
pub fn use_in_examples() -> SnippetResult<()> {
|
||||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(3).executors(0))
|
||||||
.your_workload_with(|w| w.some_config())
|
.your_workload_with(|w| w.some_config())
|
||||||
.build();
|
.build()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#![allow(dead_code, unused_imports, unused_variables)]
|
#![allow(dead_code, unused_imports, unused_variables)]
|
||||||
|
|
||||||
|
pub type SnippetResult<T> = Result<T, testing_framework_core::scenario::ScenarioBuildError>;
|
||||||
|
|
||||||
mod architecture_overview_builder_api;
|
mod architecture_overview_builder_api;
|
||||||
mod chaos_workloads_random_restart;
|
mod chaos_workloads_random_restart;
|
||||||
mod custom_workload_example_expectation;
|
mod custom_workload_example_expectation;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ pub async fn run_with_env_overrides() -> Result<()> {
|
|||||||
// Uses NOMOS_DEMO_* env vars (or legacy *_DEMO_* vars)
|
// Uses NOMOS_DEMO_* env vars (or legacy *_DEMO_* vars)
|
||||||
let mut plan = ScenarioBuilder::with_node_counts(3, 2)
|
let mut plan = ScenarioBuilder::with_node_counts(3, 2)
|
||||||
.with_run_duration(std::time::Duration::from_secs(120))
|
.with_run_duration(std::time::Duration::from_secs(120))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub async fn run_local_demo() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.with_run_duration(Duration::from_secs(60))
|
.with_run_duration(Duration::from_secs(60))
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
// Deploy and run
|
// Deploy and run
|
||||||
let deployer = LocalDeployer::default();
|
let deployer = LocalDeployer::default();
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use testing_framework_core::scenario::{Deployer, ScenarioBuilder};
|
|||||||
use testing_framework_runner_local::LocalDeployer;
|
use testing_framework_runner_local::LocalDeployer;
|
||||||
|
|
||||||
pub async fn step_6_deploy_and_execute() -> Result<()> {
|
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 deployer = LocalDeployer::default(); // Use local process deployer
|
||||||
let runner = deployer.deploy(&plan).await?; // Provision infrastructure
|
let runner = deployer.deploy(&plan).await?; // Provision infrastructure
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use testing_framework_runner_compose::ComposeDeployer;
|
|||||||
|
|
||||||
pub async fn run_with_compose_deployer() -> Result<()> {
|
pub async fn run_with_compose_deployer() -> Result<()> {
|
||||||
// ... same scenario definition ...
|
// ... 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 deployer = ComposeDeployer::default(); // Use Docker Compose
|
||||||
let runner = deployer.deploy(&plan).await?;
|
let runner = deployer.deploy(&plan).await?;
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
use testing_framework_workflows::ScenarioBuilderExt;
|
||||||
|
|
||||||
pub fn declarative_over_imperative() {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn declarative_over_imperative() -> SnippetResult<()> {
|
||||||
// Good: declarative
|
// Good: declarative
|
||||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||||
.transactions_with(|txs| {
|
.transactions_with(|txs| {
|
||||||
txs.rate(5) // 5 transactions per block
|
txs.rate(5) // 5 transactions per block
|
||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
// Bad: imperative (framework doesn't work this way)
|
// Bad: imperative (framework doesn't work this way)
|
||||||
// spawn_validator(); spawn_executor();
|
// spawn_validator(); spawn_executor();
|
||||||
// loop { submit_tx(); check_block(); }
|
// loop { submit_tx(); check_block(); }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,16 @@ use std::time::Duration;
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
use testing_framework_workflows::{ChaosBuilderExt, ScenarioBuilderExt};
|
||||||
|
|
||||||
pub fn determinism_first() {
|
use crate::SnippetResult;
|
||||||
|
|
||||||
|
pub fn determinism_first() -> SnippetResult<()> {
|
||||||
// Separate: functional test (deterministic)
|
// Separate: functional test (deterministic)
|
||||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||||
.transactions_with(|txs| {
|
.transactions_with(|txs| {
|
||||||
txs.rate(5) // 5 transactions per block
|
txs.rate(5) // 5 transactions per block
|
||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
// Separate: chaos test (introduces randomness)
|
// Separate: chaos test (introduces randomness)
|
||||||
let _chaos_plan =
|
let _chaos_plan =
|
||||||
@ -27,5 +29,6 @@ pub fn determinism_first() {
|
|||||||
txs.rate(5) // 5 transactions per block
|
txs.rate(5) // 5 transactions per block
|
||||||
})
|
})
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,20 @@ use std::time::Duration;
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
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)
|
// Bad: too short (~2 blocks with default 2s slots, 0.9 coeff)
|
||||||
let _too_short = ScenarioBuilder::with_node_counts(1, 0)
|
let _too_short = ScenarioBuilder::with_node_counts(1, 0)
|
||||||
.with_run_duration(Duration::from_secs(5))
|
.with_run_duration(Duration::from_secs(5))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
// Good: enough blocks for assertions (~27 blocks with default 2s slots, 0.9
|
// Good: enough blocks for assertions (~27 blocks with default 2s slots, 0.9
|
||||||
// coeff)
|
// coeff)
|
||||||
let _good = ScenarioBuilder::with_node_counts(1, 0)
|
let _good = ScenarioBuilder::with_node_counts(1, 0)
|
||||||
.with_run_duration(Duration::from_secs(60))
|
.with_run_duration(Duration::from_secs(60))
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use std::time::Duration;
|
|||||||
use testing_framework_core::scenario::ScenarioBuilder;
|
use testing_framework_core::scenario::ScenarioBuilder;
|
||||||
use testing_framework_workflows::ScenarioBuilderExt;
|
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
|
// Good: protocol-oriented thinking
|
||||||
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
let _plan = ScenarioBuilder::topology_with(|t| t.network_star().validators(2).executors(1))
|
||||||
.transactions_with(|txs| {
|
.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
|
.with_run_duration(Duration::from_secs(60)) // Let framework calculate expected blocks
|
||||||
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
.expect_consensus_liveness() // "Did we produce the expected blocks?"
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
// Bad: wall-clock assumptions
|
// Bad: wall-clock assumptions
|
||||||
// "I expect exactly 30 blocks in 60 seconds"
|
// "I expect exactly 30 blocks in 60 seconds"
|
||||||
// This breaks on slow CI where slot timing might drift
|
// This breaks on slow CI where slot timing might drift
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ async fn run_compose_case(
|
|||||||
.da_with(|da| da.channel_rate(DA_CHANNEL_RATE).blob_rate(DA_BLOB_RATE))
|
.da_with(|da| da.channel_rate(DA_CHANNEL_RATE).blob_rate(DA_BLOB_RATE))
|
||||||
.with_run_duration(run_duration)
|
.with_run_duration(run_duration)
|
||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let deployer = ComposeDeployer::new();
|
let deployer = ComposeDeployer::new();
|
||||||
info!("deploying compose stack");
|
info!("deploying compose stack");
|
||||||
|
|||||||
@ -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();
|
let deployer = K8sDeployer::new();
|
||||||
info!("deploying k8s stack");
|
info!("deploying k8s stack");
|
||||||
|
|||||||
@ -61,7 +61,7 @@ async fn run_local_case(validators: usize, executors: usize, run_duration: Durat
|
|||||||
.expect_consensus_liveness()
|
.expect_consensus_liveness()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut plan = scenario.build();
|
let mut plan = scenario.build()?;
|
||||||
|
|
||||||
let deployer = LocalDeployer::default().with_membership_check(true);
|
let deployer = LocalDeployer::default().with_membership_check(true);
|
||||||
info!("deploying local nodes");
|
info!("deploying local nodes");
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use std::{num::NonZeroUsize, sync::Arc, time::Duration};
|
use std::{num::NonZeroUsize, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -7,7 +8,7 @@ use super::{
|
|||||||
workload::Workload,
|
workload::Workload,
|
||||||
};
|
};
|
||||||
use crate::topology::{
|
use crate::topology::{
|
||||||
config::{TopologyBuilder, TopologyConfig},
|
config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
|
||||||
configs::{network::Libp2pNetworkLayout, wallet::WalletConfig},
|
configs::{network::Libp2pNetworkLayout, wallet::WalletConfig},
|
||||||
generation::GeneratedTopology,
|
generation::GeneratedTopology,
|
||||||
};
|
};
|
||||||
@ -16,6 +17,12 @@ const DEFAULT_FUNDS_PER_WALLET: u64 = 100;
|
|||||||
const MIN_EXPECTATION_BLOCKS: u32 = 2;
|
const MIN_EXPECTATION_BLOCKS: u32 = 2;
|
||||||
const MIN_EXPECTATION_FALLBACK_SECS: u64 = 10;
|
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
|
/// Immutable scenario definition shared between the runner, workloads, and
|
||||||
/// expectations.
|
/// expectations.
|
||||||
pub struct Scenario<Caps = ()> {
|
pub struct Scenario<Caps = ()> {
|
||||||
@ -214,7 +221,7 @@ impl<Caps> Builder<Caps> {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
/// Finalize the scenario, computing run metrics and initializing
|
/// Finalize the scenario, computing run metrics and initializing
|
||||||
/// components.
|
/// components.
|
||||||
pub fn build(self) -> Scenario<Caps> {
|
pub fn build(self) -> Result<Scenario<Caps>, ScenarioBuildError> {
|
||||||
let Self {
|
let Self {
|
||||||
topology,
|
topology,
|
||||||
mut workloads,
|
mut workloads,
|
||||||
@ -224,7 +231,7 @@ impl<Caps> Builder<Caps> {
|
|||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let generated = topology.build();
|
let generated = topology.build()?;
|
||||||
let duration = enforce_min_duration(&generated, duration);
|
let duration = enforce_min_duration(&generated, duration);
|
||||||
let run_metrics = RunMetrics::from_topology(&generated, duration);
|
let run_metrics = RunMetrics::from_topology(&generated, duration);
|
||||||
initialize_components(&generated, &run_metrics, &mut workloads, &mut expectations);
|
initialize_components(&generated, &run_metrics, &mut workloads, &mut expectations);
|
||||||
@ -238,7 +245,13 @@ impl<Caps> Builder<Caps> {
|
|||||||
"scenario built"
|
"scenario built"
|
||||||
);
|
);
|
||||||
|
|
||||||
Scenario::new(generated, workloads, expectations, duration, capabilities)
|
Ok(Scenario::new(
|
||||||
|
generated,
|
||||||
|
workloads,
|
||||||
|
expectations,
|
||||||
|
duration,
|
||||||
|
capabilities,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,9 @@ pub type DynError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
|||||||
pub use capabilities::{
|
pub use capabilities::{
|
||||||
NodeControlCapability, NodeControlHandle, ObservabilityCapability, RequiresNodeControl,
|
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 expectation::Expectation;
|
||||||
pub use observability::{ObservabilityCapabilityProvider, ObservabilityInputs};
|
pub use observability::{ObservabilityCapabilityProvider, ObservabilityInputs};
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
|
|||||||
@ -7,26 +7,54 @@ use nomos_core::{
|
|||||||
use nomos_da_network_core::swarm::DAConnectionPolicySettings;
|
use nomos_da_network_core::swarm::DAConnectionPolicySettings;
|
||||||
use testing_framework_config::topology::{
|
use testing_framework_config::topology::{
|
||||||
configs::{
|
configs::{
|
||||||
api::create_api_configs,
|
api::{ApiConfigError, create_api_configs},
|
||||||
base::{BaseConfigs, build_base_configs},
|
base::{BaseConfigError, BaseConfigs, build_base_configs},
|
||||||
consensus::{ConsensusParams, ProviderInfo, create_genesis_tx_with_declarations},
|
consensus::{
|
||||||
|
ConsensusConfigError, ConsensusParams, ProviderInfo,
|
||||||
|
create_genesis_tx_with_declarations,
|
||||||
|
},
|
||||||
da::DaParams,
|
da::DaParams,
|
||||||
network::{Libp2pNetworkLayout, NetworkParams},
|
network::{Libp2pNetworkLayout, NetworkParams},
|
||||||
tracing::create_tracing_configs,
|
tracing::create_tracing_configs,
|
||||||
wallet::WalletConfig,
|
wallet::WalletConfig,
|
||||||
},
|
},
|
||||||
invariants::validate_generated_vectors,
|
invariants::{TopologyInvariantError, validate_generated_vectors},
|
||||||
};
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::topology::{
|
use crate::topology::{
|
||||||
configs::{GeneralConfig, time::default_time_config},
|
configs::{GeneralConfig, time::default_time_config},
|
||||||
generation::{GeneratedNodeConfig, GeneratedTopology, NodeRole},
|
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 DEFAULT_DA_BALANCER_INTERVAL: Duration = Duration::from_secs(1);
|
||||||
const VALIDATOR_EXECUTOR_DA_BALANCER_INTERVAL: Duration = Duration::from_secs(5);
|
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.
|
/// High-level topology settings used to generate node configs for a scenario.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TopologyConfig {
|
pub struct TopologyConfig {
|
||||||
@ -96,7 +124,6 @@ impl TopologyConfig {
|
|||||||
/// Build a topology with explicit validator and executor counts.
|
/// Build a topology with explicit validator and executor counts.
|
||||||
pub fn with_node_numbers(validators: usize, executors: usize) -> Self {
|
pub fn with_node_numbers(validators: usize, executors: usize) -> Self {
|
||||||
let participants = validators + executors;
|
let participants = validators + executors;
|
||||||
assert!(participants > 0, "topology must include at least one node");
|
|
||||||
|
|
||||||
let mut da_params = DaParams::default();
|
let mut da_params = DaParams::default();
|
||||||
let da_nodes = participants;
|
let da_nodes = participants;
|
||||||
@ -240,9 +267,8 @@ impl TopologyBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
/// Finalize and generate topology and node descriptors.
|
/// Finalize and generate topology and node descriptors.
|
||||||
pub fn build(self) -> GeneratedTopology {
|
pub fn build(self) -> Result<GeneratedTopology, TopologyBuildError> {
|
||||||
let Self {
|
let Self {
|
||||||
config,
|
config,
|
||||||
ids,
|
ids,
|
||||||
@ -251,14 +277,15 @@ impl TopologyBuilder {
|
|||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let n_participants = config.n_validators + config.n_executors;
|
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 ids = resolve_ids(ids, n_participants)?;
|
||||||
let da_ports = resolve_ports(da_ports, n_participants, "DA");
|
let da_ports = resolve_ports(da_ports, n_participants, "DA")?;
|
||||||
let blend_ports = resolve_ports(blend_ports, n_participants, "Blend");
|
let blend_ports = resolve_ports(blend_ports, n_participants, "Blend")?;
|
||||||
|
|
||||||
validate_generated_vectors(n_participants, &ids, &da_ports, &blend_ports)
|
validate_generated_vectors(n_participants, &ids, &da_ports, &blend_ports)?;
|
||||||
.expect("invalid generated topology inputs");
|
|
||||||
|
|
||||||
let BaseConfigs {
|
let BaseConfigs {
|
||||||
mut consensus_configs,
|
mut consensus_configs,
|
||||||
@ -274,50 +301,56 @@ impl TopologyBuilder {
|
|||||||
&config.wallet_config,
|
&config.wallet_config,
|
||||||
&da_ports,
|
&da_ports,
|
||||||
&blend_ports,
|
&blend_ports,
|
||||||
)
|
)?;
|
||||||
.expect("failed to build base configs");
|
let api_configs = create_api_configs(&ids)?;
|
||||||
let api_configs = create_api_configs(&ids).expect("failed to create API configs");
|
|
||||||
let tracing_configs = create_tracing_configs(&ids);
|
let tracing_configs = create_tracing_configs(&ids);
|
||||||
let time_config = default_time_config();
|
let time_config = default_time_config();
|
||||||
|
|
||||||
let mut providers: Vec<_> = da_configs
|
let first_consensus = consensus_configs
|
||||||
.iter()
|
.first()
|
||||||
.enumerate()
|
.ok_or(TopologyBuildError::MissingConsensusConfig)?;
|
||||||
.map(|(i, da_conf)| ProviderInfo {
|
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,
|
service_type: ServiceType::DataAvailability,
|
||||||
provider_sk: da_conf.signer.clone(),
|
provider_sk: da_conf.signer.clone(),
|
||||||
zk_sk: da_conf.secret_zk_key.clone(),
|
zk_sk: da_conf.secret_zk_key.clone(),
|
||||||
locator: Locator(da_conf.listening_address.clone()),
|
locator: Locator(da_conf.listening_address.clone()),
|
||||||
note: consensus_configs[0].da_notes[i].clone(),
|
note,
|
||||||
})
|
});
|
||||||
.collect();
|
}
|
||||||
providers.extend(
|
for (i, blend_conf) in blend_configs.iter().enumerate() {
|
||||||
blend_configs
|
let note = first_consensus
|
||||||
.iter()
|
.blend_notes
|
||||||
.enumerate()
|
.get(i)
|
||||||
.map(|(i, blend_conf)| ProviderInfo {
|
.ok_or(TopologyBuildError::VectorLenMismatch {
|
||||||
service_type: ServiceType::BlendNetwork,
|
label: "blend_notes",
|
||||||
provider_sk: blend_conf.signer.clone(),
|
expected: blend_configs.len(),
|
||||||
zk_sk: blend_conf.secret_zk_key.clone(),
|
actual: first_consensus.blend_notes.len(),
|
||||||
locator: Locator(blend_conf.backend_core.listening_address.clone()),
|
})?
|
||||||
note: consensus_configs[0].blend_notes[i].clone(),
|
.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]
|
let ledger_tx = first_consensus.genesis_tx.mantle_tx().ledger_tx.clone();
|
||||||
.genesis_tx
|
let genesis_tx = create_genesis_tx_with_declarations(ledger_tx, providers)?;
|
||||||
.mantle_tx()
|
for c in &mut consensus_configs {
|
||||||
.ledger_tx
|
c.genesis_tx = genesis_tx.clone();
|
||||||
.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 =
|
let kms_configs =
|
||||||
@ -327,16 +360,100 @@ impl TopologyBuilder {
|
|||||||
let mut executors = Vec::with_capacity(config.n_executors);
|
let mut executors = Vec::with_capacity(config.n_executors);
|
||||||
|
|
||||||
for i in 0..n_participants {
|
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 {
|
let general = GeneralConfig {
|
||||||
consensus_config: consensus_configs[i].clone(),
|
consensus_config,
|
||||||
bootstrapping_config: bootstrapping_config[i].clone(),
|
bootstrapping_config,
|
||||||
da_config: da_configs[i].clone(),
|
da_config,
|
||||||
network_config: network_configs[i].clone(),
|
network_config,
|
||||||
blend_config: blend_configs[i].clone(),
|
blend_config,
|
||||||
api_config: api_configs[i].clone(),
|
api_config,
|
||||||
tracing_config: tracing_configs[i].clone(),
|
tracing_config,
|
||||||
time_config: time_config.clone(),
|
time_config: time_config.clone(),
|
||||||
kms_config: kms_configs[i].clone(),
|
kms_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
let role = if i < config.n_validators {
|
let role = if i < config.n_validators {
|
||||||
@ -352,10 +469,10 @@ impl TopologyBuilder {
|
|||||||
let descriptor = GeneratedNodeConfig {
|
let descriptor = GeneratedNodeConfig {
|
||||||
role,
|
role,
|
||||||
index,
|
index,
|
||||||
id: ids[i],
|
id,
|
||||||
general,
|
general,
|
||||||
da_port: da_ports[i],
|
da_port,
|
||||||
blend_port: blend_ports[i],
|
blend_port,
|
||||||
};
|
};
|
||||||
|
|
||||||
match role {
|
match role {
|
||||||
@ -364,11 +481,11 @@ impl TopologyBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneratedTopology {
|
Ok(GeneratedTopology {
|
||||||
config,
|
config,
|
||||||
validators,
|
validators,
|
||||||
executors,
|
executors,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
validator::{Validator, create_validator_config},
|
validator::{Validator, create_validator_config},
|
||||||
},
|
},
|
||||||
topology::{
|
topology::{
|
||||||
config::{TopologyBuilder, TopologyConfig},
|
config::{TopologyBuildError, TopologyBuilder, TopologyConfig},
|
||||||
configs::GeneralConfig,
|
configs::GeneralConfig,
|
||||||
generation::find_expected_peer_counts,
|
generation::find_expected_peer_counts,
|
||||||
readiness::{
|
readiness::{
|
||||||
@ -31,13 +31,15 @@ pub type DeployedNodes = (Vec<Validator>, Vec<Executor>);
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum SpawnTopologyError {
|
pub enum SpawnTopologyError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Build(#[from] TopologyBuildError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Node(#[from] SpawnNodeError),
|
Node(#[from] SpawnNodeError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Topology {
|
impl Topology {
|
||||||
pub async fn spawn(config: TopologyConfig) -> Result<Self, SpawnTopologyError> {
|
pub async fn spawn(config: TopologyConfig) -> Result<Self, SpawnTopologyError> {
|
||||||
let generated = TopologyBuilder::new(config.clone()).build();
|
let generated = TopologyBuilder::new(config.clone()).build()?;
|
||||||
let n_validators = config.n_validators;
|
let n_validators = config.n_validators;
|
||||||
let n_executors = config.n_executors;
|
let n_executors = config.n_executors;
|
||||||
let node_configs = generated
|
let node_configs = generated
|
||||||
@ -64,7 +66,7 @@ impl Topology {
|
|||||||
.with_ids(ids.to_vec())
|
.with_ids(ids.to_vec())
|
||||||
.with_da_ports(da_ports.to_vec())
|
.with_da_ports(da_ports.to_vec())
|
||||||
.with_blend_ports(blend_ports.to_vec())
|
.with_blend_ports(blend_ports.to_vec())
|
||||||
.build();
|
.build()?;
|
||||||
|
|
||||||
let node_configs = generated
|
let node_configs = generated
|
||||||
.nodes()
|
.nodes()
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use groth16::fr_to_bytes;
|
|||||||
use key_management_system_service::{backend::preload::PreloadKMSBackendSettings, keys::Key};
|
use key_management_system_service::{backend::preload::PreloadKMSBackendSettings, keys::Key};
|
||||||
use nomos_utils::net::get_available_udp_port;
|
use nomos_utils::net::get_available_udp_port;
|
||||||
use rand::{Rng, thread_rng};
|
use rand::{Rng, thread_rng};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::topology::configs::{
|
use crate::topology::configs::{
|
||||||
blend::GeneralBlendConfig, da::GeneralDaConfig, wallet::WalletAccount,
|
blend::GeneralBlendConfig, da::GeneralDaConfig, wallet::WalletAccount,
|
||||||
@ -52,40 +53,67 @@ pub fn create_kms_configs(
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_ids(ids: Option<Vec<[u8; 32]>>, count: usize) -> Vec<[u8; 32]> {
|
#[derive(Debug, Error)]
|
||||||
ids.map_or_else(
|
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<Vec<[u8; 32]>>,
|
||||||
|
count: usize,
|
||||||
|
) -> Result<Vec<[u8; 32]>, 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];
|
let mut generated = vec![[0; 32]; count];
|
||||||
for id in &mut generated {
|
for id in &mut generated {
|
||||||
thread_rng().fill(id);
|
thread_rng().fill(id);
|
||||||
}
|
}
|
||||||
generated
|
Ok(generated)
|
||||||
},
|
}
|
||||||
|ids| {
|
}
|
||||||
assert_eq!(
|
|
||||||
ids.len(),
|
|
||||||
count,
|
|
||||||
"expected {count} ids but got {}",
|
|
||||||
ids.len()
|
|
||||||
);
|
|
||||||
ids
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_ports(ports: Option<Vec<u16>>, count: usize, label: &str) -> Vec<u16> {
|
pub fn resolve_ports(
|
||||||
let resolved = ports.unwrap_or_else(|| {
|
ports: Option<Vec<u16>>,
|
||||||
iter::repeat_with(|| get_available_udp_port().unwrap())
|
count: usize,
|
||||||
.take(count)
|
label: &'static str,
|
||||||
.collect()
|
) -> Result<Vec<u16>, TopologyResolveError> {
|
||||||
});
|
let resolved = match ports {
|
||||||
assert_eq!(
|
Some(ports) => ports,
|
||||||
resolved.len(),
|
None => iter::repeat_with(|| {
|
||||||
count,
|
get_available_udp_port().ok_or(TopologyResolveError::PortAllocationFailed { label })
|
||||||
"expected {count} {label} ports but got {}",
|
})
|
||||||
resolved.len()
|
.take(count)
|
||||||
);
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
resolved
|
};
|
||||||
|
|
||||||
|
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<u16> {
|
pub fn multiaddr_port(addr: &nomos_libp2p::Multiaddr) -> Option<u16> {
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
use std::{env, path::PathBuf, time::Duration};
|
use std::{env, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use cucumber::World;
|
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 testing_framework_workflows::{ScenarioBuilderExt as _, expectations::ConsensusLiveness};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
@ -81,6 +83,11 @@ pub enum StepError {
|
|||||||
InvalidArgument { message: String },
|
InvalidArgument { message: String },
|
||||||
#[error("{message}")]
|
#[error("{message}")]
|
||||||
Preflight { message: String },
|
Preflight { message: String },
|
||||||
|
#[error("failed to build scenario: {source}")]
|
||||||
|
ScenarioBuild {
|
||||||
|
#[source]
|
||||||
|
source: ScenarioBuildError,
|
||||||
|
},
|
||||||
#[error("{message}")]
|
#[error("{message}")]
|
||||||
RunFailed { message: String },
|
RunFailed { message: String },
|
||||||
}
|
}
|
||||||
@ -195,14 +202,18 @@ impl TestingFrameworkWorld {
|
|||||||
pub fn build_local_scenario(&self) -> Result<Scenario<()>, StepError> {
|
pub fn build_local_scenario(&self) -> Result<Scenario<()>, StepError> {
|
||||||
self.preflight(DeployerKind::Local)?;
|
self.preflight(DeployerKind::Local)?;
|
||||||
let builder = self.make_builder_for_deployer::<()>(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<Scenario<NodeControlCapability>, StepError> {
|
pub fn build_compose_scenario(&self) -> Result<Scenario<NodeControlCapability>, StepError> {
|
||||||
self.preflight(DeployerKind::Compose)?;
|
self.preflight(DeployerKind::Compose)?;
|
||||||
let builder =
|
let builder =
|
||||||
self.make_builder_for_deployer::<NodeControlCapability>(DeployerKind::Compose)?;
|
self.make_builder_for_deployer::<NodeControlCapability>(DeployerKind::Compose)?;
|
||||||
Ok(builder.build())
|
builder
|
||||||
|
.build()
|
||||||
|
.map_err(|source| StepError::ScenarioBuild { source })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preflight(&self, expected: DeployerKind) -> Result<(), StepError> {
|
pub fn preflight(&self, expected: DeployerKind) -> Result<(), StepError> {
|
||||||
|
|||||||
@ -109,7 +109,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cfgsync_prebuilt_configs_preserve_genesis() {
|
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 topology = scenario.topology().clone();
|
||||||
let hosts = hosts_from_topology(&topology);
|
let hosts = hosts_from_topology(&topology);
|
||||||
let tracing_settings = tracing_settings(&topology);
|
let tracing_settings = tracing_settings(&topology);
|
||||||
@ -161,7 +163,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cfgsync_genesis_proofs_verify_against_ledger() {
|
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 topology = scenario.topology().clone();
|
||||||
let hosts = hosts_from_topology(&topology);
|
let hosts = hosts_from_topology(&topology);
|
||||||
let tracing_settings = tracing_settings(&topology);
|
let tracing_settings = tracing_settings(&topology);
|
||||||
@ -197,7 +201,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cfgsync_docker_overrides_produce_valid_genesis() {
|
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 topology = scenario.topology().clone();
|
||||||
let tracing_settings = tracing_settings(&topology);
|
let tracing_settings = tracing_settings(&topology);
|
||||||
let hosts = docker_style_hosts(&topology);
|
let hosts = docker_style_hosts(&topology);
|
||||||
@ -228,7 +234,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cfgsync_configs_match_topology_ports_and_genesis() {
|
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 topology = scenario.topology().clone();
|
||||||
let hosts = hosts_from_topology(&topology);
|
let hosts = hosts_from_topology(&topology);
|
||||||
let tracing_settings = tracing_settings(&topology);
|
let tracing_settings = tracing_settings(&topology);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user