# Quickstart Get a working example running quickly. ## From Scratch (Complete Setup) If you're starting from zero, here's everything you need: ```bash # 1. Install Rust nightly curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh rustup default nightly # 2. Clone the repository git clone https://github.com/logos-blockchain/logos-blockchain-testing.git cd logos-blockchain-testing # 3. Run your first scenario (downloads dependencies automatically) scripts/run/run-examples.sh -t 60 -n 1 host ``` **First run takes 5-10 minutes** (downloads ~120MB circuit assets, builds binaries). **Windows users:** Use WSL2 (Windows Subsystem for Linux). Native Windows is not supported. --- ## Prerequisites If you already have the repository cloned: - Rust toolchain (nightly) - Unix-like system (tested on Linux and macOS) - For Docker Compose examples: Docker daemon running - For Docker Desktop on Apple silicon (compose/k8s): set `LOGOS_BLOCKCHAIN_BUNDLE_DOCKER_PLATFORM=linux/arm64` to avoid slow/fragile amd64 emulation builds - **`versions.env` file** at repository root (defines VERSION, LOGOS_BLOCKCHAIN_NODE_REV, LOGOS_BLOCKCHAIN_BUNDLE_VERSION) **Note:** `logos-blockchain-node` binaries are built automatically on demand or can be provided via prebuilt bundles. **Important:** The `versions.env` file is required by helper scripts. If missing, the scripts will fail with an error. The file should already exist in the repository root. ## Your First Test The framework ships with runnable example binaries in `examples/src/bin/`. **Recommended:** Use the convenience script: ```bash # From the logos-blockchain-testing directory scripts/run/run-examples.sh -t 60 -n 1 host ``` This handles circuit setup, binary building, and runs a complete scenario: 1 node, transaction workload (5 tx/block), 60s duration. **Alternative:** Direct cargo run (requires manual setup): ```bash # Requires circuits in place and LOGOS_BLOCKCHAIN_NODE_BIN set cargo run -p runner-examples --bin local_runner ``` **Core API Pattern** (simplified example): ```rust,ignore use std::time::Duration; use anyhow::Result; use testing_framework_core::scenario::{Deployer, ScenarioBuilder}; use testing_framework_runner_local::LocalDeployer; use testing_framework_workflows::ScenarioBuilderExt; pub async fn run_local_demo() -> Result<()> { // Define the scenario (1 node, tx workload) let mut plan = ScenarioBuilder::topology_with(|t| t.network_star().nodes(1)) .wallets(1_000) .transactions_with(|txs| { txs.rate(5) // 5 transactions per block .users(500) // use 500 of the seeded wallets }) .expect_consensus_liveness() .with_run_duration(Duration::from_secs(60)) .build(); // Deploy and run let deployer = LocalDeployer::default(); let runner = deployer.deploy(&plan).await?; let _handle = runner.run(&mut plan).await?; Ok(()) } ``` **Note:** The examples are binaries with `#[tokio::main]`, not test functions. If you want to write integration tests, wrap this pattern in `#[tokio::test]` functions in your own test suite. **What you should see:** - Nodes spawn as local processes - Consensus starts producing blocks - Scenario runs for the configured duration - Node state/logs written under a temporary per-run directory in the current working directory (removed after the run unless `LOGOS_BLOCKCHAIN_TESTS_KEEP_LOGS=1`) - To write per-node log files to a stable location: set `LOGOS_BLOCKCHAIN_LOG_DIR=/path/to/logs` (files will have prefix like `logos-blockchain-node-0*`, may include timestamps) ## What Just Happened? Let's unpack the code: ### 1. Topology Configuration ```rust,ignore use testing_framework_core::scenario::ScenarioBuilder; pub fn step_1_topology() -> testing_framework_core::scenario::Builder<()> { ScenarioBuilder::topology_with(|t| { t.network_star() // Star topology: all nodes connect to seed .nodes(1) // 1 node }) } ``` This defines **what** your test network looks like. ### 2. Wallet Seeding ```rust,ignore use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; pub fn step_2_wallets() -> testing_framework_core::scenario::Builder<()> { ScenarioBuilder::with_node_counts(1).wallets(1_000) // Seed 1,000 funded wallet accounts } ``` Provides funded accounts for transaction submission. ### 3. Workloads ```rust,ignore use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; pub fn step_3_workloads() -> testing_framework_core::scenario::Builder<()> { ScenarioBuilder::with_node_counts(1) .wallets(1_000) .transactions_with(|txs| { txs.rate(5) // 5 transactions per block .users(500) // Use 500 of the 1,000 wallets }) } ``` Generates transaction traffic to stress the inclusion pipeline. ### 4. Expectation ```rust,ignore use testing_framework_core::scenario::ScenarioBuilder; use testing_framework_workflows::ScenarioBuilderExt; pub fn step_4_expectation() -> testing_framework_core::scenario::Builder<()> { ScenarioBuilder::with_node_counts(1).expect_consensus_liveness() // This says what success means: blocks must be produced continuously. } ``` This says **what success means**: blocks must be produced continuously. ### 5. Run Duration ```rust,ignore use std::time::Duration; use testing_framework_core::scenario::ScenarioBuilder; pub fn step_5_run_duration() -> testing_framework_core::scenario::Builder<()> { ScenarioBuilder::with_node_counts(1).with_run_duration(Duration::from_secs(60)) } ``` Run for 60 seconds (~27 blocks with default 2s slots, 0.9 coefficient). Framework ensures this is at least 2× the consensus slot duration. Adjust consensus timing via `CONSENSUS_SLOT_TIME` and `CONSENSUS_ACTIVE_SLOT_COEFF`. ### 6. Deploy and Execute ```rust,ignore use anyhow::Result; 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).build(); let deployer = LocalDeployer::default(); // Use local process deployer let runner = deployer.deploy(&plan).await?; // Provision infrastructure let _handle = runner.run(&mut plan).await?; // Execute workloads & expectations Ok(()) } ``` **Deployer** provisions the infrastructure. **Runner** orchestrates execution. ## Adjust the Topology **With run-examples.sh** (recommended): ```bash # Scale up to 3 nodes, run for 2 minutes scripts/run/run-examples.sh -t 120 -n 3 host ``` **With direct cargo run:** ```bash # Uses LOGOS_BLOCKCHAIN_DEMO_* env vars (or legacy *_DEMO_* vars) LOGOS_BLOCKCHAIN_DEMO_NODES=3 \ LOGOS_BLOCKCHAIN_DEMO_RUN_SECS=120 \ cargo run -p runner-examples --bin local_runner ``` ## Try Docker Compose Use the same API with a different deployer for reproducible containerized environment. **Recommended:** Use the convenience script (handles everything): ```bash scripts/run/run-examples.sh -t 60 -n 1 compose ``` This automatically: - Fetches circuit assets (to `~/.logos-blockchain-circuits` by default) - Builds/uses prebuilt binaries (via `LOGOS_BLOCKCHAIN_BINARIES_TAR` if available) - Builds the Docker image - Runs the compose scenario **Alternative:** Direct cargo run with manual setup: ```bash # Option 1: Use prebuilt bundle (recommended for compose/k8s) scripts/build/build-bundle.sh --platform linux # Creates .tmp/nomos-binaries-linux-v0.3.1.tar.gz export LOGOS_BLOCKCHAIN_BINARIES_TAR=.tmp/nomos-binaries-linux-v0.3.1.tar.gz # Option 2: Manual circuit/image setup (rebuilds during image build) scripts/setup/setup-logos-blockchain-circuits.sh v0.3.1 /tmp/logos-blockchain-circuits scripts/build/build_test_image.sh # Run with Compose LOGOS_BLOCKCHAIN_TESTNET_IMAGE=logos-blockchain-testing:local \ cargo run -p runner-examples --bin compose_runner ``` **Benefit:** Reproducible containerized environment (Dockerized nodes, repeatable deployments). **Optional: Prometheus + Grafana** The runner can integrate with external observability endpoints. For a ready-to-run local stack: ```bash scripts/setup/setup-observability.sh compose up eval "$(scripts/setup/setup-observability.sh compose env)" ``` Then run your compose scenario as usual (the environment variables enable PromQL querying and node OTLP metrics export). **Note:** Compose expects circuits at `/opt/circuits` inside containers (set by the image build). **In code:** Just swap the deployer: ```rust,ignore use anyhow::Result; use testing_framework_core::scenario::{Deployer, ScenarioBuilder}; 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).build(); let deployer = ComposeDeployer::default(); // Use Docker Compose let runner = deployer.deploy(&plan).await?; let _handle = runner.run(&mut plan).await?; Ok(()) } ``` ## Next Steps Now that you have a working test: - **Understand the philosophy**: [Testing Philosophy](testing-philosophy.md) - **Learn the architecture**: [Architecture Overview](architecture-overview.md) - **See more examples**: [Examples](examples.md) - **API reference**: [Builder API Quick Reference](dsl-cheat-sheet.md) - **Debug failures**: [Troubleshooting](troubleshooting.md)