|
| 1 | +#!/bin/bash |
| 2 | +set -e |
| 3 | + |
| 4 | +# generate-shadow-yaml.sh — Generate shadow.yaml from validator-config.yaml |
| 5 | +# |
| 6 | +# Multi-client: reuses existing client-cmds/<client>-cmd.sh to get node_binary. |
| 7 | +# Works for zeam, ream, lantern, gean, or any client with a *-cmd.sh file. |
| 8 | +# |
| 9 | +# Usage: |
| 10 | +# ./generate-shadow-yaml.sh <genesis-dir> --project-root <path> [--stop-time 360s] [--output shadow.yaml] |
| 11 | + |
| 12 | +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" |
| 13 | + |
| 14 | +show_usage() { |
| 15 | + cat << EOF |
| 16 | +Usage: $0 <genesis-dir> --project-root <path> [--stop-time 360s] [--output shadow.yaml] |
| 17 | +
|
| 18 | +Generate a Shadow network simulator configuration (shadow.yaml) from validator-config.yaml. |
| 19 | +
|
| 20 | +Arguments: |
| 21 | + genesis-dir Path to genesis directory containing validator-config.yaml |
| 22 | +
|
| 23 | +Options: |
| 24 | + --project-root <path> Project root directory (parent of lean-quickstart). Required. |
| 25 | + --stop-time <time> Shadow simulation stop time (default: 360s) |
| 26 | + --output <path> Output shadow.yaml path (default: <project-root>/shadow.yaml) |
| 27 | +
|
| 28 | +This script is client-agnostic. It reads node names from validator-config.yaml, |
| 29 | +extracts the client name from the node prefix (e.g., zeam_0 → zeam), and sources |
| 30 | +the corresponding client-cmds/<client>-cmd.sh to generate per-node arguments. |
| 31 | +EOF |
| 32 | + exit 1 |
| 33 | +} |
| 34 | + |
| 35 | +# ======================================== |
| 36 | +# Parse arguments |
| 37 | +# ======================================== |
| 38 | +if [ -z "$1" ] || [ "${1:0:1}" == "-" ]; then |
| 39 | + show_usage |
| 40 | +fi |
| 41 | + |
| 42 | +GENESIS_DIR="$(cd "$1" && pwd)" |
| 43 | +shift |
| 44 | + |
| 45 | +PROJECT_ROOT="" |
| 46 | +STOP_TIME="360s" |
| 47 | +OUTPUT_FILE="" |
| 48 | + |
| 49 | +while [[ $# -gt 0 ]]; do |
| 50 | + case "$1" in |
| 51 | + --project-root) |
| 52 | + if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then |
| 53 | + PROJECT_ROOT="$(cd "$2" && pwd)" |
| 54 | + shift 2 |
| 55 | + else |
| 56 | + echo "❌ Error: --project-root requires a path" |
| 57 | + exit 1 |
| 58 | + fi |
| 59 | + ;; |
| 60 | + --stop-time) |
| 61 | + if [ -n "$2" ]; then |
| 62 | + STOP_TIME="$2" |
| 63 | + shift 2 |
| 64 | + else |
| 65 | + echo "❌ Error: --stop-time requires a value" |
| 66 | + exit 1 |
| 67 | + fi |
| 68 | + ;; |
| 69 | + --output) |
| 70 | + if [ -n "$2" ]; then |
| 71 | + OUTPUT_FILE="$2" |
| 72 | + shift 2 |
| 73 | + else |
| 74 | + echo "❌ Error: --output requires a path" |
| 75 | + exit 1 |
| 76 | + fi |
| 77 | + ;; |
| 78 | + *) |
| 79 | + echo "❌ Unknown option: $1" |
| 80 | + show_usage |
| 81 | + ;; |
| 82 | + esac |
| 83 | +done |
| 84 | + |
| 85 | +if [ -z "$PROJECT_ROOT" ]; then |
| 86 | + echo "❌ Error: --project-root is required" |
| 87 | + show_usage |
| 88 | +fi |
| 89 | + |
| 90 | +if [ -z "$OUTPUT_FILE" ]; then |
| 91 | + OUTPUT_FILE="$PROJECT_ROOT/shadow.yaml" |
| 92 | +fi |
| 93 | + |
| 94 | +VALIDATOR_CONFIG="$GENESIS_DIR/validator-config.yaml" |
| 95 | +if [ ! -f "$VALIDATOR_CONFIG" ]; then |
| 96 | + echo "❌ Error: validator-config.yaml not found at $VALIDATOR_CONFIG" |
| 97 | + exit 1 |
| 98 | +fi |
| 99 | + |
| 100 | +# ======================================== |
| 101 | +# Read nodes from validator-config.yaml |
| 102 | +# ======================================== |
| 103 | +node_names=($(yq eval '.validators[].name' "$VALIDATOR_CONFIG")) |
| 104 | +node_count=${#node_names[@]} |
| 105 | + |
| 106 | +if [ "$node_count" -eq 0 ]; then |
| 107 | + echo "❌ Error: No validators found in $VALIDATOR_CONFIG" |
| 108 | + exit 1 |
| 109 | +fi |
| 110 | + |
| 111 | +echo "🔧 Generating shadow.yaml for $node_count nodes..." |
| 112 | + |
| 113 | +# ======================================== |
| 114 | +# Write shadow.yaml preamble |
| 115 | +# ======================================== |
| 116 | +cat > "$OUTPUT_FILE" << EOF |
| 117 | +# Auto-generated Shadow network simulator configuration |
| 118 | +# Generated from: $VALIDATOR_CONFIG |
| 119 | +# Nodes: ${node_names[*]} |
| 120 | +
|
| 121 | +general: |
| 122 | + model_unblocked_syscall_latency: true |
| 123 | + stop_time: $STOP_TIME |
| 124 | +
|
| 125 | +experimental: |
| 126 | + native_preemption_enabled: true |
| 127 | +
|
| 128 | +network: |
| 129 | + graph: |
| 130 | + type: 1_gbit_switch |
| 131 | +
|
| 132 | +hosts: |
| 133 | +EOF |
| 134 | + |
| 135 | +# ======================================== |
| 136 | +# Generate per-node host entries |
| 137 | +# ======================================== |
| 138 | +for i in "${!node_names[@]}"; do |
| 139 | + item="${node_names[$i]}" |
| 140 | + |
| 141 | + # Extract client name from node prefix (zeam_0 → zeam, leanspec_0 → leanspec) |
| 142 | + IFS='_' read -r -a elements <<< "$item" |
| 143 | + client="${elements[0]}" |
| 144 | + |
| 145 | + # DNS-valid hostname: underscores → hyphens (Shadow requirement) |
| 146 | + hostname="${item//_/-}" |
| 147 | + |
| 148 | + # Extract IP from validator-config |
| 149 | + ip=$(yq eval ".validators[$i].enrFields.ip" "$VALIDATOR_CONFIG") |
| 150 | + |
| 151 | + # Set up environment for parse-vc.sh and client-cmd.sh |
| 152 | + # These scripts expect: $item, $configDir, $dataDir, $scriptDir, $validatorConfig |
| 153 | + export scriptDir="$SCRIPT_DIR" |
| 154 | + export configDir="$GENESIS_DIR" |
| 155 | + export dataDir="$PROJECT_ROOT/shadow.data/hosts/$hostname" |
| 156 | + export validatorConfig="$VALIDATOR_CONFIG" |
| 157 | + |
| 158 | + # Source parse-vc.sh to extract per-node config (quicPort, metricsPort, apiPort, etc.) |
| 159 | + # parse-vc.sh uses $item and $configDir |
| 160 | + source "$SCRIPT_DIR/parse-vc.sh" |
| 161 | + |
| 162 | + # Source client-cmd.sh to get node_binary |
| 163 | + node_setup="binary" |
| 164 | + client_cmd="$SCRIPT_DIR/client-cmds/${client}-cmd.sh" |
| 165 | + if [ ! -f "$client_cmd" ]; then |
| 166 | + echo "❌ Error: Client command script not found: $client_cmd" |
| 167 | + echo " Available clients:" |
| 168 | + ls "$SCRIPT_DIR/client-cmds/"*-cmd.sh 2>/dev/null | sed 's/.*\// /' | sed 's/-cmd.sh//' |
| 169 | + exit 1 |
| 170 | + fi |
| 171 | + source "$client_cmd" |
| 172 | + |
| 173 | + # node_binary is now set by the client-cmd.sh script |
| 174 | + # Convert relative paths to absolute paths for Shadow |
| 175 | + # Extract the binary path (first word) and args (rest) |
| 176 | + binary_path=$(echo "$node_binary" | awk '{print $1}') |
| 177 | + binary_args=$(echo "$node_binary" | sed "s|^[^ ]*||") |
| 178 | + |
| 179 | + # Make binary path absolute |
| 180 | + if [[ "$binary_path" != /* ]]; then |
| 181 | + binary_path="$(cd "$(dirname "$binary_path")" 2>/dev/null && pwd)/$(basename "$binary_path")" 2>/dev/null || binary_path="$PROJECT_ROOT/${binary_path#./}" |
| 182 | + fi |
| 183 | + |
| 184 | + # Make all path args absolute: replace $configDir, $dataDir references with absolute paths |
| 185 | + # The client-cmd.sh already uses $configDir and $dataDir which we set to absolute paths |
| 186 | + |
| 187 | + # Write host entry |
| 188 | + cat >> "$OUTPUT_FILE" << EOF |
| 189 | + $hostname: |
| 190 | + network_node_id: 0 |
| 191 | + ip_addr: $ip |
| 192 | + processes: |
| 193 | + - path: $binary_path |
| 194 | + args: >- |
| 195 | + $binary_args |
| 196 | + start_time: 1s |
| 197 | + expected_final_state: running |
| 198 | +
|
| 199 | +EOF |
| 200 | + |
| 201 | + echo " ✅ $item → $hostname ($ip) [$client]" |
| 202 | +done |
| 203 | + |
| 204 | +echo "" |
| 205 | +echo "📄 Shadow config written to: $OUTPUT_FILE" |
| 206 | +echo " Stop time: $STOP_TIME" |
| 207 | +echo " Nodes: $node_count" |
0 commit comments