- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains - Omit embedded publish git dirs and empty placeholders from index Made-with: Cursor
312 lines
12 KiB
Bash
312 lines
12 KiB
Bash
#!/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
|