Skip to content

feat: add Shadow network simulator automation#151

Open
GrapeBaBa wants to merge 1 commit intomainfrom
feat/shadow-automation
Open

feat: add Shadow network simulator automation#151
GrapeBaBa wants to merge 1 commit intomainfrom
feat/shadow-automation

Conversation

@GrapeBaBa
Copy link
Copy Markdown
Member

Summary

  • Add client-agnostic Shadow network simulator automation scripts
  • run-shadow.sh orchestrates genesis generation, shadow.yaml creation, and shadow execution
  • generate-shadow-yaml.sh reads validator-config.yaml and sources each client's client-cmds/<client>-cmd.sh to emit shadow.yaml
  • shadow-devnet/genesis/validator-config.yaml provides a 4-node Shadow config with virtual IPs (100.0.0.x)
  • client-cmds/leanspec-cmd.sh adds leanSpec client support
  • generate-genesis.sh gains --genesis-time <timestamp> flag for fixed timestamps (Shadow uses epoch 946684860)

Usage

# From any client repo with lean-quickstart as submodule:
cd lean-quickstart
./run-shadow.sh

The scripts auto-detect which clients are configured in shadow-devnet/genesis/validator-config.yaml and source matching client-cmds/<name>-cmd.sh files.

Test plan

  • Run ./run-shadow.sh on a Codespace with Shadow installed
  • Verify 4 nodes reach consensus (finalized slot > 0)
  • Verify generate-genesis.sh --genesis-time 946684860 produces correct fixed-time genesis

Copilot AI review requested due to automatic review settings April 2, 2026 09:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh to orchestrate genesis generation, Shadow config generation, and Shadow execution.
  • Added generate-shadow-yaml.sh to emit shadow.yaml by sourcing client-cmds/<client>-cmd.sh and parsing validator-config.yaml.
  • Extended generate-genesis.sh with --genesis-time <timestamp> to support fixed genesis timestamps (used by Shadow), plus added a Shadow-specific 4-node config and a new leanspec client 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.

Comment on lines +100 to +104
# ========================================
# Read nodes from validator-config.yaml
# ========================================
node_names=($(yq eval '.validators[].name' "$VALIDATOR_CONFIG"))
node_count=${#node_names[@]}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
# Extract client name from node prefix (zeam_0 → zeam, leanspec_0 → leanspec)
IFS='_' read -r -a elements <<< "$item"
client="${elements[0]}"

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
Comment on lines +179 to +181
# 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#./}"
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
STOP_TIME="$2"
shift 2
;;
--genesis-dir)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--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.

Suggested change
--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

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +166
# 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"
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 2, 2026 14:14
@GrapeBaBa GrapeBaBa force-pushed the feat/shadow-automation branch from 7c8fcf1 to d5b2769 Compare April 2, 2026 14:14
@GrapeBaBa GrapeBaBa force-pushed the feat/shadow-automation branch from d5b2769 to 2c695dd Compare April 2, 2026 14:15
- 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
@GrapeBaBa GrapeBaBa force-pushed the feat/shadow-automation branch from 2c695dd to a7c3b5b Compare April 2, 2026 14:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +193 to +197
- path: $binary_path
args: >-
$binary_args
start_time: 1s
expected_final_state: running
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
- 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

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants