Files
proxmox/scripts/verify/check-explorer-e2e.sh

312 lines
12 KiB
Bash
Raw Normal View History

#!/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" == *"<!DOCTYPE html"* ]]; then
ok "Internal nginx root serves explorer frontend"
else
fail "Internal nginx root does not look like explorer frontend HTML"
fi
internal_stats="$(run_edge_internal_get "/api/v2/stats")"
json_check "Internal Blockscout stats" "$internal_stats" 'type == "object" and (.total_blocks | tostring | length > 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