Migrate commonware-avs-node into this repo
#51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |