Conversation
There was a problem hiding this comment.
Pull request overview
Adds automation to run a multi-node devnet inside the Shadow network simulator, including fixed-time genesis generation and client-agnostic Shadow config generation based on validator-config.yaml.
Changes:
- Added
run-shadow.shto orchestrate genesis generation, Shadow config generation, and Shadow execution. - Added
generate-shadow-yaml.shto emitshadow.yamlby sourcingclient-cmds/<client>-cmd.shand parsingvalidator-config.yaml. - Extended
generate-genesis.shwith--genesis-time <timestamp>to support fixed genesis timestamps (used by Shadow), plus added a Shadow-specific 4-node config and a newleanspecclient cmd.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| shadow-devnet/genesis/validator-config.yaml | Adds a 4-node Shadow validator config with virtual IPs and ports. |
| run-shadow.sh | New top-level runner script to generate genesis + shadow.yaml and run Shadow. |
| generate-shadow-yaml.sh | New generator that converts validator config + client command scripts into shadow.yaml. |
| generate-genesis.sh | Adds support for exact genesis timestamps via --genesis-time. |
| client-cmds/leanspec-cmd.sh | Adds leanspec client command definitions for quickstart automation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # ======================================== | ||
| # Read nodes from validator-config.yaml | ||
| # ======================================== | ||
| node_names=($(yq eval '.validators[].name' "$VALIDATOR_CONFIG")) | ||
| node_count=${#node_names[@]} |
There was a problem hiding this comment.
yq is used to read validator names before any dependency check, so if yq is missing this script will fail with a generic "command not found". Add an explicit command -v yq check (similar to parse-vc.sh) near the top and exit with a clear install hint.
| # Extract client name from node prefix (zeam_0 → zeam, leanspec_0 → leanspec) | ||
| IFS='_' read -r -a elements <<< "$item" | ||
| client="${elements[0]}" | ||
|
|
There was a problem hiding this comment.
The client value is derived from validator-config.yaml and then interpolated into a path that is sourced. If a user points --genesis-dir at an untrusted config, a crafted validator name could trigger path traversal (e.g., ../../...) and execute arbitrary code. Restrict client to a safe allowlist pattern (e.g., [a-z0-9-]+), reject values containing / or .., and fail fast with an error before sourcing.
| # Validate client name to prevent path traversal and restrict characters | |
| # Allowed: lowercase letters, digits, and hyphens. Disallow '/' and '..'. | |
| if [[ "$client" == *"/"* || "$client" == *".."* || ! "$client" =~ ^[a-z0-9-]+$ ]]; then | |
| echo "❌ Error: Invalid client name '$client'. Allowed pattern: [a-z0-9-]+ and no '/' or '..'." | |
| exit 1 | |
| fi |
| # Make binary path absolute | ||
| if [[ "$binary_path" != /* ]]; then | ||
| binary_path="$(cd "$(dirname "$binary_path")" 2>/dev/null && pwd)/$(basename "$binary_path")" 2>/dev/null || binary_path="$PROJECT_ROOT/${binary_path#./}" |
There was a problem hiding this comment.
The logic that makes binary_path absolute treats any command that doesn't start with / as a filesystem path. This breaks PATH-resolved commands like uv (used by leanspec-cmd.sh), turning it into <cwd>/uv which likely doesn't exist. Only absolutize when the command contains a / (i.e., is a path), or resolve bare commands via command -v and keep them unchanged if found on PATH.
| # Make binary path absolute | |
| if [[ "$binary_path" != /* ]]; then | |
| binary_path="$(cd "$(dirname "$binary_path")" 2>/dev/null && pwd)/$(basename "$binary_path")" 2>/dev/null || binary_path="$PROJECT_ROOT/${binary_path#./}" | |
| # Make binary path absolute when it is a filesystem path. | |
| # - If binary_path starts with '/', it is already absolute. | |
| # - If binary_path contains '/', treat it as a path and absolutize it. | |
| # - If binary_path has no '/', treat it as a bare command and leave it for PATH resolution. | |
| if [[ "$binary_path" != /* ]]; then | |
| if [[ "$binary_path" == */* ]]; then | |
| binary_path="$(cd "$(dirname "$binary_path")" 2>/dev/null && pwd)/$(basename "$binary_path")" 2>/dev/null || binary_path="$PROJECT_ROOT/${binary_path#./}" | |
| else | |
| # Bare command: verify it exists on PATH but do not rewrite it into a filesystem path. | |
| if ! command -v "$binary_path" >/dev/null 2>&1; then | |
| echo "⚠️ Warning: binary '$binary_path' not found on PATH; Shadow may fail to start this process." >&2 | |
| fi | |
| fi |
| STOP_TIME="$2" | ||
| shift 2 | ||
| ;; | ||
| --genesis-dir) |
There was a problem hiding this comment.
--genesis-dir is resolved via cd "$2" && pwd under set -e. If the path is missing/invalid, the script exits with a raw cd error rather than a user-friendly message. Add an explicit directory existence check and print a clear error (including the provided value) before exiting.
| --genesis-dir) | |
| --genesis-dir) | |
| if [ -z "$2" ]; then | |
| echo "❌ Error: --genesis-dir requires a path argument." >&2 | |
| exit 1 | |
| fi | |
| if [ ! -d "$2" ]; then | |
| echo "❌ Error: Genesis directory '$2' does not exist or is not a directory." >&2 | |
| exit 1 | |
| fi |
| # Source client-cmd.sh to get node_binary | ||
| node_setup="binary" | ||
| client_cmd="$SCRIPT_DIR/client-cmds/${client}-cmd.sh" | ||
| if [ ! -f "$client_cmd" ]; then | ||
| echo "❌ Error: Client command script not found: $client_cmd" |
There was a problem hiding this comment.
This script sources each client-cmds/*-cmd.sh, which (per existing quickstart contract) sets node_setup and provides both node_binary and node_docker. However, the generated Shadow config always uses node_binary later, even when node_setup="docker" (e.g., leanspec-cmd.sh explicitly selects docker). Either honor node_setup (and build an executable command accordingly), or fail fast with a clear error if a client is configured for docker-only so Shadow runs don’t silently generate a non-working config.
7c8fcf1 to
d5b2769
Compare
d5b2769 to
2c695dd
Compare
- run-shadow.sh: orchestrator (genesis → shadow.yaml → shadow) - generate-shadow-yaml.sh: generates shadow.yaml from validator-config.yaml using each client's client-cmds/<client>-cmd.sh - shadow-devnet/genesis/validator-config.yaml: 4 zeam nodes with Shadow IPs - generate-genesis.sh: add --genesis-time flag for fixed timestamps - README: add Shadow Network Simulator section
2c695dd to
a7c3b5b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - path: $binary_path | ||
| args: >- | ||
| $binary_args | ||
| start_time: 1s | ||
| expected_final_state: running |
There was a problem hiding this comment.
The generated YAML under processes: is incorrectly indented (- path is aligned with processes:). This will produce invalid YAML (or an unexpected structure) for Shadow. Indent the process list items under processes: (and their nested keys accordingly).
| - path: $binary_path | |
| args: >- | |
| $binary_args | |
| start_time: 1s | |
| expected_final_state: running | |
| - path: $binary_path | |
| args: >- | |
| $binary_args | |
| start_time: 1s | |
| expected_final_state: running |
Summary
run-shadow.shorchestrates genesis generation, shadow.yaml creation, and shadow executiongenerate-shadow-yaml.shreadsvalidator-config.yamland sources each client'sclient-cmds/<client>-cmd.shto emitshadow.yamlshadow-devnet/genesis/validator-config.yamlprovides a 4-node Shadow config with virtual IPs (100.0.0.x)client-cmds/leanspec-cmd.shadds leanSpec client supportgenerate-genesis.shgains--genesis-time <timestamp>flag for fixed timestamps (Shadow uses epoch946684860)Usage
The scripts auto-detect which clients are configured in
shadow-devnet/genesis/validator-config.yamland source matchingclient-cmds/<name>-cmd.shfiles.Test plan
./run-shadow.shon a Codespace with Shadow installedgenerate-genesis.sh --genesis-time 946684860produces correct fixed-time genesis