#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" if [[ -f "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" ]]; then # shellcheck source=/dev/null source "${PROJECT_ROOT}/scripts/lib/load-project-env.sh" fi BASE_URL="${1:-https://blockscout.defi-oracle.io}" BASE_URL="${BASE_URL%/}" EDGE_SSH_HOST="${EXPLORER_EDGE_SSH_HOST:-root@192.168.11.12}" EDGE_VMID="${EXPLORER_EDGE_VMID:-5000}" BLOCKSCOUT_IP="${IP_BLOCKSCOUT:-192.168.11.140}" REQUIRE_PUBLIC_EDGE="${REQUIRE_PUBLIC_EDGE:-0}" failures=0 notes=() public_edge_ready=1 require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "[fail] missing required command: $1" >&2 exit 1 } } require_cmd curl require_cmd jq require_cmd ssh ok() { printf '[ok] %s\n' "$*" } info() { printf '[info] %s\n' "$*" } warn() { printf '[warn] %s\n' "$*" } fail() { printf '[fail] %s\n' "$*" failures=$((failures + 1)) } count_header_lines() { local headers="$1" local name="$2" printf '%s\n' "$headers" | grep -iEc "^${name}:" || true } first_header_value() { local headers="$1" local name="$2" { printf '%s\n' "$headers" | grep -iE "^${name}:" || true } \ | head -n1 \ | cut -d: -f2- \ | tr -d '\r' \ | sed 's/^[[:space:]]*//' } assert_single_header_equals() { local label="$1" local headers="$2" local name="$3" local expected="$4" local count local value count="$(count_header_lines "$headers" "$name")" if [[ "$count" -ne 1 ]]; then fail "$label has ${count} ${name} header(s); expected exactly 1" return fi value="$(first_header_value "$headers" "$name")" if [[ "$value" == "$expected" ]]; then ok "$label ${name} matches expected policy" else fail "$label ${name} mismatch. Expected: $expected. Actual: $value" fi } assert_single_header_contains() { local label="$1" local headers="$2" local name="$3" local expected_fragment="$4" local count local value count="$(count_header_lines "$headers" "$name")" if [[ "$count" -ne 1 ]]; then fail "$label has ${count} ${name} header(s); expected exactly 1" return fi value="$(first_header_value "$headers" "$name")" if [[ "$value" == *"$expected_fragment"* ]]; then ok "$label ${name} contains expected policy fragment" else fail "$label ${name} is missing expected fragment: $expected_fragment" fi } check_explorer_header_policy() { local label="$1" local headers="$2" local csp_value assert_single_header_equals "$label" "$headers" "Cache-Control" "no-store, no-cache, must-revalidate" assert_single_header_contains "$label" "$headers" "Content-Security-Policy" "script-src 'self' 'unsafe-inline' 'unsafe-eval'" csp_value="$(first_header_value "$headers" "Content-Security-Policy")" if [[ "$csp_value" == *"connect-src 'self'"* ]] && [[ "$csp_value" == *"https://blockscout.defi-oracle.io"* || "$csp_value" == *"https://explorer.d-bis.org"* ]]; then ok "$label Content-Security-Policy contains an allowed explorer origin" else fail "$label Content-Security-Policy is missing an allowed explorer origin" fi assert_single_header_equals "$label" "$headers" "X-Frame-Options" "SAMEORIGIN" assert_single_header_equals "$label" "$headers" "X-Content-Type-Options" "nosniff" assert_single_header_contains "$label" "$headers" "Strict-Transport-Security" "max-age=" assert_single_header_equals "$label" "$headers" "Referrer-Policy" "strict-origin-when-cross-origin" assert_single_header_equals "$label" "$headers" "X-XSS-Protection" "0" } http_headers_have_status() { local headers="$1" local status="$2" printf '%s\n' "$headers" | grep -Eq "^HTTP/[0-9.]+ ${status}([[:space:]]|$)" } json_check() { local label="$1" local body="$2" local expr="$3" local desc="$4" if printf '%s' "$body" | jq -e "$expr" >/dev/null 2>&1; then ok "$label ($desc)" else local sample sample="$(printf '%s' "$body" | head -c 240 | tr '\n' ' ')" fail "$label returned unexpected payload. Expected $desc. Sample: $sample" fi } run_edge_get() { local url="$1" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -ksS -L --connect-timeout 15 --max-time 30 \"$url\"'" } run_edge_head() { local url="$1" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -ksSI --connect-timeout 15 --max-time 30 \"$url\"'" } run_edge_internal_get() { local path="$1" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -sS -L --connect-timeout 15 --max-time 30 \"http://127.0.0.1${path}\"'" } run_edge_internal_head() { local path="$1" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -sSI --connect-timeout 15 --max-time 30 \"http://127.0.0.1${path}\"'" } run_edge_internal_post() { local path="$1" local payload="$2" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -sS -L --connect-timeout 15 --max-time 30 -H \"content-type: application/json\" -X POST --data '\''$payload'\'' \"http://127.0.0.1${path}\"'" } run_edge_public_post() { local path="$1" local payload="$2" ssh -o BatchMode=yes -o ConnectTimeout=10 "$EDGE_SSH_HOST" \ "pct exec ${EDGE_VMID} -- bash -lc 'curl -ksS -L --connect-timeout 15 --max-time 30 -H \"content-type: application/json\" -X POST --data '\''$payload'\'' \"${BASE_URL}${path}\"'" } echo "== Explorer end-to-end verification ==" echo "Base URL: ${BASE_URL}" echo "Edge host: ${EDGE_SSH_HOST} (VMID ${EDGE_VMID})" echo "Expected explorer VM IP: ${BLOCKSCOUT_IP}" echo info "Local workstation reachability" if curl -ksS -I --connect-timeout 10 --max-time 15 "${BASE_URL}/" >/dev/null 2>&1; then ok "Current workstation can reach ${BASE_URL}" else warn "Current workstation cannot reach ${BASE_URL} directly; continuing from edge host" notes+=("Current workstation has no direct route to ${BASE_URL}; public checks were validated from VMID ${EDGE_VMID}.") fi echo info "Edge host public path" if edge_home_headers="$(run_edge_head "${BASE_URL}/" 2>/dev/null)"; then if http_headers_have_status "$edge_home_headers" 200; then ok "Public explorer homepage reachable from edge host" check_explorer_header_policy "Public explorer homepage" "$edge_home_headers" else if [[ "$REQUIRE_PUBLIC_EDGE" == "1" ]]; then fail "Public explorer homepage returned unexpected status from edge host" else warn "Public explorer homepage returned unexpected status from edge host; continuing with internal explorer checks" public_edge_ready=0 notes+=("Public self-curl from the LAN edge host did not return 200. This environment appears to have unreliable hairpin access to the public edge.") fi fi else if [[ "$REQUIRE_PUBLIC_EDGE" == "1" ]]; then fail "Public explorer homepage not reachable from edge host" else warn "Public explorer homepage not reachable from edge host; continuing with internal explorer checks" public_edge_ready=0 notes+=("Public self-curl from the LAN edge host failed. This environment appears to have unreliable hairpin access to the public edge.") fi fi if [[ "$REQUIRE_PUBLIC_EDGE" == "1" || "$public_edge_ready" == "1" ]]; then if edge_blocks_headers="$(run_edge_head "${BASE_URL}/blocks" 2>/dev/null)"; then if http_headers_have_status "$edge_blocks_headers" 200; then ok "Public explorer blocks page reachable from edge host" check_explorer_header_policy "Public explorer blocks page" "$edge_blocks_headers" else fail "Public explorer blocks page returned unexpected status from edge host" fi else fail "Public explorer blocks page not reachable from edge host" fi fi planner_payload='{"sourceChainId":138,"tokenIn":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","tokenOut":"0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1","amountIn":"100000000000000000"}' if [[ "$REQUIRE_PUBLIC_EDGE" == "1" || "$public_edge_ready" == "1" ]]; then edge_stats="$(run_edge_get "${BASE_URL}/api/v2/stats")" json_check "Public Blockscout stats" "$edge_stats" 'type == "object" and (.total_blocks | tostring | length > 0) and (.total_transactions | tostring | length > 0)' 'stats with total_blocks and total_transactions' edge_networks="$(run_edge_get "${BASE_URL}/api/v1/networks")" json_check "Public token-aggregation networks" "$edge_networks" 'type == "object" and (.networks | type == "array") and (.source | type == "string")' 'object with .networks[] and .source' edge_token_list="$(run_edge_get "${BASE_URL}/api/v1/report/token-list?chainId=138")" json_check "Public report token-list" "$edge_token_list" 'type == "object" and (.tokens | type == "array") and (.tokens | length > 0)' 'object with non-empty .tokens[]' edge_capabilities="$(run_edge_get "${BASE_URL}/token-aggregation/api/v2/providers/capabilities?chainId=138")" json_check "Public planner-v2 capabilities" "$edge_capabilities" 'type == "object" and (.providers | type == "array") and (.providers | length > 0)' 'object with non-empty .providers[]' if printf '%s' "$edge_capabilities" | jq -e 'any(.providers[]; .provider == "uniswap_v3" and .live == true)' >/dev/null 2>&1; then ok "Public planner-v2 exposes live uniswap_v3" else fail "Public planner-v2 does not expose live uniswap_v3" fi edge_plan="$(run_edge_public_post "/token-aggregation/api/v2/routes/internal-execution-plan" "$planner_payload")" json_check "Public internal execution plan" "$edge_plan" 'type == "object" and (.plannerResponse.decision == "direct-pool") and (.execution.contractAddress | type == "string")' 'direct-pool planner response with execution contract' echo else warn "Skipping public self-curl API probes after edge-host reachability failure" echo fi info "Explorer VM internal path" internal_home="$(run_edge_internal_get "/")" if [[ "$internal_home" == *"SolaceScan"* || "$internal_home" == *"Chain 138 Explorer by DBIS"* || "$internal_home" == *" 0)' 'stats with total_blocks' internal_networks="$(run_edge_internal_get "/api/v1/networks")" json_check "Internal token-aggregation networks" "$internal_networks" 'type == "object" and (.networks | type == "array") and (.source | type == "string")' 'object with .networks[] and .source' internal_token_list="$(run_edge_internal_get "/api/v1/report/token-list?chainId=138")" json_check "Internal report token-list" "$internal_token_list" 'type == "object" and (.tokens | type == "array") and (.tokens | length > 0)' 'object with non-empty .tokens[]' internal_cors_headers="$(run_edge_internal_head "/api/v1/networks")" if printf '%s' "$internal_cors_headers" | grep -qi 'Access-Control-Allow-Origin'; then ok "Internal token-aggregation responses include CORS headers" else fail "Internal token-aggregation responses are missing CORS headers" fi internal_plan="$(run_edge_internal_post "/token-aggregation/api/v2/routes/internal-execution-plan" "$planner_payload")" json_check "Internal planner execution plan" "$internal_plan" 'type == "object" and (.plannerResponse.decision == "direct-pool") and (.plannerResponse.legs[0].provider == "uniswap_v3")' 'direct-pool planner response via uniswap_v3' echo info "Summary" if (( failures > 0 )); then printf '[fail] explorer E2E verification found %d issue(s)\n' "$failures" if (( ${#notes[@]} > 0 )); then printf '[note] %s\n' "${notes[@]}" fi exit 1 fi ok "Explorer public edge, internal nginx, Blockscout API, token-aggregation v1, and planner-v2 all verified" if (( ${#notes[@]} > 0 )); then printf '[note] %s\n' "${notes[@]}" fi