Skip to content

Migrate commonware-avs-node into this repo #14

Migrate commonware-avs-node into this repo

Migrate commonware-avs-node into this repo #14

name: Local Integration Test
on:
pull_request:
branches:
- main
- dev
- staging
jobs:
local-integration-test:
name: Local Integration Test (No Docker Build)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo builds
uses: Swatinem/rust-cache@v2
- name: Create .env file from example
run: |
cp example.env .env
# Set to LOCAL mode
sed -i 's|^ENVIRONMENT=.*|ENVIRONMENT=LOCAL|' .env
# Comment out TESTNET RPC URLs
sed -i 's|^HTTP_RPC=https://|# HTTP_RPC=https://|' .env
sed -i 's|^WS_RPC=wss://|# WS_RPC=wss://|' .env
sed -i 's|^RPC_URL=https://|# RPC_URL=https://|' .env
# Uncomment local RPC URLs
sed -i '/^# HTTP_RPC=http:\/\/localhost:8545/s/^# //' .env
sed -i '/^# WS_RPC=ws:\/\/localhost:8545/s/^# //' .env
sed -i '/^# RPC_URL=http:\/\/ethereum:8545/s/^# //' .env
# Set FORK_URL for local forking from repository secret or default to Holesky
if [ -n "${{ secrets.RPC_URL }}" ]; then
sed -i "s|^# FORK_URL=.*|FORK_URL=${{ secrets.RPC_URL }}|" .env
else
sed -i 's|^# FORK_URL=.*|FORK_URL=https://ethereum-holesky.publicnode.com|' .env
fi
# Uncomment Holesky testnet contract addresses (needed for LOCAL mode with fork)
sed -i 's/^#DELEGATION_MANAGER_ADDRESS=/DELEGATION_MANAGER_ADDRESS=/' .env
sed -i 's/^#STRATEGY_MANAGER_ADDRESS=/STRATEGY_MANAGER_ADDRESS=/' .env
sed -i 's/^#LST_CONTRACT_ADDRESS=/LST_CONTRACT_ADDRESS=/' .env
sed -i 's/^#LST_STRATEGY_ADDRESS=/LST_STRATEGY_ADDRESS=/' .env
sed -i 's/^#BLS_SIGNATURE_CHECKER_ADDRESS=/BLS_SIGNATURE_CHECKER_ADDRESS=/' .env
sed -i 's/^#OPERATOR_STATE_RETRIEVER_ADDRESS=/OPERATOR_STATE_RETRIEVER_ADDRESS=/' .env
sed -i 's/^#ALLOCATION_MANAGER_ADDRESS=/ALLOCATION_MANAGER_ADDRESS=/' .env
# Use default Anvil private key for testing
DEFAULT_ANVIL_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
sed -i "s|^PRIVATE_KEY=.*|PRIVATE_KEY=$DEFAULT_ANVIL_KEY|" .env
sed -i "s|^FUNDED_KEY=.*|FUNDED_KEY=$DEFAULT_ANVIL_KEY|" .env
- name: Update config for host-based router
run: |
# Update node addresses in config.example.json to use localhost
# instead of Docker DNS names, since the router runs on the host.
# EigenLayer will use this file to register operators on-chain.
sed -i 's|"node-1:3001"|"localhost:4001"|g' config/config.example.json
sed -i 's|"node-2:3002"|"localhost:4002"|g' config/config.example.json
sed -i 's|"node-3:3003"|"localhost:4003"|g' config/config.example.json
# Also create config.json for consistency
cp config/config.example.json config/config.json
- name: Pull required Docker images
run: |
docker compose pull ethereum eigenlayer signer node-1 node-2 node-3
- name: Start services with docker-compose (no router build)
run: |
# Start all services except router
docker compose up -d ethereum eigenlayer signer node-1 node-2 node-3
- name: Wait for EigenLayer setup to complete
run: |
echo "Waiting for EigenLayer to complete setup..."
timeout=300
elapsed=0
while [ $elapsed -lt $timeout ]; do
# Check if the keys have been generated by EigenLayer
if [ -f "config/.nodes/operator_keys/testacc1.private.bls.key.json" ] &&
[ -f "config/.nodes/operator_keys/testacc2.private.bls.key.json" ] &&
[ -f "config/.nodes/operator_keys/testacc3.private.bls.key.json" ]; then
echo "✓ BLS keys generated by EigenLayer"
# Check if AVS deployment file exists
if [ -f "config/.nodes/avs_deploy.json" ]; then
echo "✓ AVS deployed"
echo "=== EigenLayer setup complete ==="
ls -la config/.nodes/
ls -la config/.nodes/operator_keys/
break
fi
fi
# Check if container exited with error
container_id=$(docker compose ps -q eigenlayer)
if [ -n "$container_id" ] && [ "$(docker inspect -f '{{.State.Status}}' $container_id 2>/dev/null)" = "exited" ]; then
exit_code=$(docker inspect -f '{{.State.ExitCode}}' $container_id)
if [ "$exit_code" != "0" ]; then
echo "EigenLayer container exited with error code: $exit_code"
docker compose logs eigenlayer --tail=50
exit 1
fi
fi
echo "Waiting for EigenLayer setup... ($elapsed/$timeout seconds)"
sleep 10
elapsed=$((elapsed + 10))
done
if [ $elapsed -ge $timeout ]; then
echo "Timeout waiting for EigenLayer setup"
docker compose logs eigenlayer --tail=50
exit 1
fi
- name: Build and run router with cargo
run: |
echo "Building router with cargo..."
cargo build -p commonware-avs-router --release
echo "Starting router in background..."
# Source environment and run router in background
nohup cargo run -p commonware-avs-router --release -- --key-file config/router_orchestrator.json --port 3000 > router.log 2>&1 &
echo $! > router.pid
# Wait for router to start
sleep 10
# Check if router is running
if kill -0 $(cat router.pid) 2>/dev/null; then
echo "✓ Router started successfully (PID: $(cat router.pid))"
else
echo "✗ Router failed to start"
cat router.log
exit 1
fi
- name: Test counter increment functionality
run: |
echo "Testing counter increment..."
# Wait for services to be fully ready
sleep 20
# Get the counter contract address from the deployment file
if [ ! -f "config/.nodes/avs_deploy.json" ]; then
echo "AVS deployment file not found"
exit 1
fi
COUNTER_ADDRESS=$(cat config/.nodes/avs_deploy.json | jq -r '.addresses.counter' || echo "")
if [ -z "$COUNTER_ADDRESS" ]; then
echo "Counter contract address not found in deployment file"
exit 1
fi
echo "Counter contract address: $COUNTER_ADDRESS"
# Read the initial counter value from the smart contract
# The function signature for "number()" is 0x8381f58a
INITIAL_RESPONSE=$(curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"'$COUNTER_ADDRESS'",
"data":"0x8381f58a"
}, "latest"],
"id":1
}')
INITIAL_HEX=$(echo "$INITIAL_RESPONSE" | jq -r '.result')
# Handle empty or invalid response
if [ -z "$INITIAL_HEX" ] || [ "$INITIAL_HEX" = "null" ] || [ "$INITIAL_HEX" = "0x" ]; then
echo "Warning: Invalid initial counter response, defaulting to 0"
INITIAL_COUNT=0
else
INITIAL_COUNT=$(printf "%d\n" "$INITIAL_HEX" 2>/dev/null || echo "0")
fi
echo "Initial counter value: $INITIAL_COUNT"
# Wait for increments (router aggregates periodically)
echo "Waiting 150 seconds for counter increments..."
sleep 150
# Read the final counter value from the smart contract
FINAL_RESPONSE=$(curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"'$COUNTER_ADDRESS'",
"data":"0x8381f58a"
}, "latest"],
"id":1
}')
FINAL_HEX=$(echo "$FINAL_RESPONSE" | jq -r '.result')
# Handle empty or invalid response
if [ -z "$FINAL_HEX" ] || [ "$FINAL_HEX" = "null" ] || [ "$FINAL_HEX" = "0x" ]; then
echo "Warning: Invalid final counter response, defaulting to 0"
FINAL_COUNT=0
else
FINAL_COUNT=$(printf "%d\n" "$FINAL_HEX" 2>/dev/null || echo "0")
fi
echo "Final counter value: $FINAL_COUNT"
# Verify increment
if [ "$FINAL_COUNT" -gt "$INITIAL_COUNT" ]; then
echo "✓ Counter successfully incremented from $INITIAL_COUNT to $FINAL_COUNT"
else
echo "✗ Counter did not increment (still at $FINAL_COUNT)"
echo "=== Router logs ==="
cat router.log
exit 1
fi
- name: Collect logs on failure
if: failure()
run: |
echo "=== Docker Compose Status ==="
docker compose ps -a
echo "=== Ethereum Logs ==="
docker compose logs ethereum --tail 50
echo "=== EigenLayer Logs ==="
docker compose logs eigenlayer --tail 100
echo "=== Router Logs ==="
if [ -f router.log ]; then
cat router.log
else
echo "Router log file not found"
fi
echo "=== Node-1 Logs ==="
docker compose logs node-1 --tail 50
echo "=== Node-2 Logs ==="
docker compose logs node-2 --tail 50
echo "=== Node-3 Logs ==="
docker compose logs node-3 --tail 50
- name: Cleanup
if: always()
run: |
# Kill router if still running
if [ -f router.pid ] && kill -0 $(cat router.pid) 2>/dev/null; then
kill $(cat router.pid)
fi
# Stop docker compose services
docker compose down --volumes --remove-orphans