Skip to content

Migrate commonware-avs-node into this repo #49

Migrate commonware-avs-node into this repo

Migrate commonware-avs-node into this repo #49

name: Integration Test
on:
push:
branches:
- main
- dev
- staging
pull_request:
branches:
- main
- dev
- staging
jobs:
docker-compose-test:
name: Test with Docker Compose
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Create .env file from example
run: |
cp example.env .env
# Set to LOCAL mode
sed -i 's|^ENVIRONMENT=.*|ENVIRONMENT=LOCAL|' .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: Create CI override file
run: |
cat > docker-compose.ci.yml << 'EOF'
# Override file for CI testing - uses locally built image
services:
router:
image: commonware-avs-router:ci-test
build:
context: .
dockerfile: router.Dockerfile
EOF
- name: Build router image for CI testing
run: |
docker build -t commonware-avs-router:ci-test .
- name: Pull required images
run: |
docker compose -f docker-compose.yml -f docker-compose.ci.yml pull ethereum eigenlayer signer node-1 node-2 node-3
- name: Start services with docker-compose
run: |
# Use the CI override file to use locally built image
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d
- 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 -f docker-compose.yml -f docker-compose.ci.yml 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 -f docker-compose.yml -f docker-compose.ci.yml 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 -f docker-compose.yml -f docker-compose.ci.yml logs eigenlayer --tail=50
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"
cat config/.nodes/avs_deploy.json
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_COUNT=$(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
}' | jq -r '.result' | xargs printf "%d\n")
echo "Initial counter value: $INITIAL_COUNT"
echo "INITIAL_COUNT=$INITIAL_COUNT" >> $GITHUB_ENV
# Wait for 5 aggregation cycles (30 seconds each by default = 150 seconds)
echo "Waiting for 5 aggregation cycles (150 seconds)..."
sleep 150
# Read the final counter value from the smart contract
FINAL_COUNT=$(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
}' | jq -r '.result' | xargs printf "%d\n")
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"
# Save the current count for next step
echo "LAST_COUNT=$FINAL_COUNT" >> $GITHUB_ENV
echo "COUNTER_ADDRESS=$COUNTER_ADDRESS" >> $GITHUB_ENV
else
echo "✗ Counter did not increment (still at $FINAL_COUNT)"
echo "=== Recent router logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs router --tail 50
exit 1
fi
- name: Test with fast aggregation frequency
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
run: |
echo "Testing with fast aggregation frequency (0.5 seconds)..."
# Update the .env file to set fast aggregation
echo "AGGREGATION_FREQUENCY=0.5" >> .env
# Recreate only the router container to pick up new environment variables
# Note: We stop and remove the router, then recreate it with --no-deps
# to avoid starting its dependencies (eigenlayer would try to redeploy)
docker compose -f docker-compose.yml -f docker-compose.ci.yml stop router
docker compose -f docker-compose.yml -f docker-compose.ci.yml rm -f router
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d router --no-deps
# Wait for router to be ready
sleep 10
# Get the starting counter value from previous step
START_COUNT=${{ env.LAST_COUNT }}
DEFAULT_COUNT=${{ env.LAST_COUNT }}
echo "Starting counter value: $START_COUNT"
echo "DEFAULT_COUNT=$DEFAULT_COUNT" >> $GITHUB_ENV
# Wait for 1 minute total with fast aggregation (0.5 seconds each cycle)
echo "Waiting for 1 minute with fast aggregation..."
sleep 60
# Read the counter value after fast aggregation
FAST_COUNT=$(curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"'${{ env.COUNTER_ADDRESS }}'",
"data":"0x8381f58a"
}, "latest"],
"id":1
}' | jq -r '.result' | xargs printf "%d\n")
echo "Counter value after fast aggregation: $FAST_COUNT"
# Verify fast aggregation worked (should have multiple increments)
if [ "$FAST_COUNT" -gt "$START_COUNT" ]; then
INCREMENTS=$((FAST_COUNT - START_COUNT))
echo "✓ Fast aggregation successful: $INCREMENTS increments in ~10 seconds"
echo "LAST_COUNT=$FAST_COUNT" >> $GITHUB_ENV
else
echo "✗ Fast aggregation failed (counter still at $FAST_COUNT)"
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs router --tail 50
exit 1
fi
- name: Test with ingress enabled
if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
run: |
echo "Testing with ingress enabled..."
# Update the .env file to enable ingress
sed -i 's/^INGRESS=false/INGRESS=true/' .env
# Also ensure it's set (in case it wasn't in the file)
echo "INGRESS=true" >> .env
# Recreate only the router container to pick up new environment variables
# Note: We stop and remove the router, then recreate it with --no-deps
# to avoid starting its dependencies (eigenlayer would try to redeploy)
docker compose -f docker-compose.yml -f docker-compose.ci.yml stop router
docker compose -f docker-compose.yml -f docker-compose.ci.yml rm -f router
docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d router --no-deps
# Wait for router to be ready and ingress server to start
sleep 15
# Get the starting counter value from previous step
START_COUNT=${{ env.LAST_COUNT }}
echo "Starting counter value: $START_COUNT"
# Send ingress requests to trigger increments
echo "Sending ingress requests to /trigger endpoint..."
for i in {1..5}; do
echo "=== Sending ingress request $i ==="
# The ingress server runs on port 8080 with /trigger endpoint
# Expected payload format: {"body": {"metadata": {"key": "value"}}}
RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X POST http://localhost:8080/trigger \
-H "Content-Type: application/json" \
-d '{"body": {"metadata": {"request_id": "'$i'", "action": "increment"}}}')
HTTP_STATUS=$(echo "$RESPONSE" | tail -n 1 | cut -d: -f2)
BODY=$(echo "$RESPONSE" | head -n -1)
echo "Response: $BODY"
echo "HTTP Status: $HTTP_STATUS"
if [ "$HTTP_STATUS" != "200" ]; then
echo "Warning: HTTP request failed with status $HTTP_STATUS"
fi
sleep 1
done
# Wait for aggregation to process the ingress requests
echo "Waiting for aggregation to process ingress requests..."
sleep 15
# Read the counter value after ingress
COUNTER_ADDR="${{ env.COUNTER_ADDRESS }}"
COUNTER_RESPONSE=$(curl -s -X POST http://localhost:8545 \
-H "Content-Type: application/json" \
-d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"'$COUNTER_ADDR'",
"data":"0x8381f58a"
}, "latest"],
"id":1
}')
COUNTER_HEX=$(echo "$COUNTER_RESPONSE" | jq -r '.result')
# Handle empty or invalid response
if [ -z "$COUNTER_HEX" ] || [ "$COUNTER_HEX" = "null" ] || [ "$COUNTER_HEX" = "0x" ]; then
echo "Warning: Invalid counter response, defaulting to 0"
INGRESS_COUNT=0
else
INGRESS_COUNT=$(printf "%d\n" "$COUNTER_HEX" 2>/dev/null || echo "0")
fi
echo "Counter value after ingress: $INGRESS_COUNT"
# Verify ingress increments worked
if [ "$INGRESS_COUNT" -gt "$START_COUNT" ]; then
INCREMENTS=$((INGRESS_COUNT - START_COUNT))
echo "✓ Ingress test successful: $INCREMENTS increments after ingress requests"
else
echo "✗ Ingress test failed (counter still at $INGRESS_COUNT)"
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs router --tail 50
exit 1
fi
# Final summary
echo "=== Test Summary ==="
echo "Initial count: ${{ env.INITIAL_COUNT }}"
echo "After default aggregation (5 cycles @ 30s): ${{ env.DEFAULT_COUNT }}"
echo "After fast aggregation (10 cycles @ 0.5s): $FAST_COUNT"
echo "After ingress requests (5 requests): $INGRESS_COUNT"
echo "Total increments: $((INGRESS_COUNT - ${{ env.INITIAL_COUNT }}))"
- name: Collect logs on failure
if: failure()
run: |
echo "=== Docker Compose Status ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml ps -a
echo "=== Ethereum Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs ethereum --tail 50
echo "=== EigenLayer Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs eigenlayer --tail 100
echo "=== Router Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs router --tail 100
echo "=== Node-1 Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs node-1 --tail 100
echo "=== Node-2 Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs node-2 --tail 100
echo "=== Node-3 Logs ==="
docker compose -f docker-compose.yml -f docker-compose.ci.yml logs node-3 --tail 100
- name: Cleanup
if: always()
run: |
docker compose -f docker-compose.yml -f docker-compose.ci.yml down --volumes --remove-orphans