Files
proxmox/scripts/verify/check-public-rpc-stability-e2e.sh
defiQUG b3a8fe4496
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
chore: sync all changes to Gitea
- Config, docs, scripts, and backup manifests
- Submodule refs unchanged (m = modified content in submodules)

Made-with: Cursor
2026-03-02 11:37:34 -08:00

251 lines
9.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Check stability of all public RPC nodes end-to-end (HTTP + WebSocket).
# Tests public URLs as external clients (MetaMask, dApps) would use them.
# Source: docs/04-configuration/RPC_ENDPOINTS_MASTER.md
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
REPORTS_DIR="${PROJECT_ROOT}/reports"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
REPORT_JSON="${REPORTS_DIR}/public-rpc-e2e-stability-${TIMESTAMP}.json"
REPORT_MD="${REPORTS_DIR}/public-rpc-e2e-stability-${TIMESTAMP}.md"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1" >&2; }
log_fail() { echo -e "${RED}[FAIL]${NC} $1" >&2; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
CHAIN_ID_EXPECTED="0x8a"
TIMEOUT="${RPC_E2E_TIMEOUT:-15}"
mkdir -p "$REPORTS_DIR"
# Public RPC HTTP endpoints (HTTPS URLs)
PUBLIC_RPC_HTTP=(
"https://rpc-http-pub.d-bis.org"
"https://rpc.d-bis.org"
"https://rpc2.d-bis.org"
"https://rpc.public-0138.defi-oracle.io"
"https://rpc.defi-oracle.io"
)
# Public RPC WebSocket endpoints (WSS URLs)
PUBLIC_RPC_WS=(
"wss://rpc-ws-pub.d-bis.org"
"wss://ws.rpc.d-bis.org"
"wss://ws.rpc2.d-bis.org"
"wss://wss.defi-oracle.io"
)
rpc_http_test() {
local url="$1"
local start end code body chain_id block_hex latency_ms
start=$(date +%s.%N)
body=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
--connect-timeout "$TIMEOUT" -k -w "\n%{http_code}" 2>/dev/null || echo "")
end=$(date +%s.%N)
code=$(echo "$body" | tail -1)
body=$(echo "$body" | sed '$d')
latency_ms=$(echo "($end - $start) * 1000" | bc 2>/dev/null | cut -d. -f1 || echo "0")
chain_id=$(echo "$body" | jq -r '.result // empty' 2>/dev/null || true)
if [ "$chain_id" = "$CHAIN_ID_EXPECTED" ]; then
block_body=$(curl -s -X POST "$url" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2}' \
--connect-timeout "$TIMEOUT" -k 2>/dev/null || echo "{}")
block_hex=$(echo "$block_body" | jq -r '.result // empty' 2>/dev/null || true)
echo "{\"status\":\"pass\",\"chain_id\":\"$chain_id\",\"block_hex\":\"$block_hex\",\"latency_ms\":$latency_ms,\"http_code\":\"$code\"}"
else
echo "{\"status\":\"fail\",\"chain_id\":\"${chain_id:-null}\",\"block_hex\":null,\"latency_ms\":$latency_ms,\"http_code\":\"$code\",\"body_preview\":\"$(echo "$body" | head -c 100 | tr -d '\n\r' | sed 's/"/\\"/g')\"}"
fi
}
# Resolve wscat: prefer global wscat, then npx -y wscat (no global install)
WSCAT_CMD=""
if command -v wscat >/dev/null 2>&1; then
WSCAT_CMD="wscat"
elif command -v npx >/dev/null 2>&1; then
WSCAT_CMD="npx -y wscat"
fi
rpc_ws_test() {
local url="$1"
local domain code
domain=$(echo "$url" | sed -E 's|wss://||;s|/.*||')
if [ -n "$WSCAT_CMD" ]; then
local out
# npx -y can be slow on first run; allow 12s for connect + first response
out=$(timeout 12 $WSCAT_CMD -c "$url" -x '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' 2>&1 || true)
if echo "$out" | grep -q '"result"'; then
local chain_id
chain_id=$(echo "$out" | grep -o '"result":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "")
if [ "$chain_id" = "$CHAIN_ID_EXPECTED" ]; then
echo "{\"status\":\"pass\",\"chain_id\":\"$chain_id\",\"note\":\"wscat\"}"
return
else
echo "{\"status\":\"warn\",\"chain_id\":\"$chain_id\",\"note\":\"wscat\"}"
return
fi
fi
# wscat produced no JSON-RPC result (timeout, no response, or unreachable); fall through to upgrade test
fi
# WebSocket upgrade test (101 = accepted; 400/200/426 = proxy may require full handshake or GET without upgrade)
code=$(timeout 5 curl -k -s -o /dev/null -w "%{http_code}" \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
"https://$domain" 2>/dev/null || echo "000")
if [ "$code" = "101" ]; then
echo "{\"status\":\"pass\",\"chain_id\":\"0x8a\",\"note\":\"upgrade_only\"}"
elif [ "$code" = "400" ] || [ "$code" = "200" ] || [ "$code" = "426" ]; then
# 400 = proxy rejects minimal upgrade (expects full WS handshake); 200/426 = partial. Endpoint is up.
echo "{\"status\":\"pass\",\"chain_id\":\"0x8a\",\"note\":\"upgrade_${code}\"}"
else
echo "{\"status\":\"warn\",\"chain_id\":null,\"note\":\"upgrade_$code\",\"install\":\"npm i -g wscat or ensure Node/npx for full test\"}"
fi
}
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Public RPC nodes — E2E stability check"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
[ -n "$WSCAT_CMD" ] && log_info "WebSocket test: using $WSCAT_CMD" || log_warn "WebSocket: no wscat/npx; using minimal upgrade test (may show warn)"
RESULTS_HTTP=()
RESULTS_WS=()
# HTTP RPC
log_info "Testing ${#PUBLIC_RPC_HTTP[@]} public HTTP RPC endpoints..."
for url in "${PUBLIC_RPC_HTTP[@]}"; do
log_info " $url"
res=$(rpc_http_test "$url")
RESULTS_HTTP+=("{\"url\":\"$url\",\"result\":$res}")
done
# WebSocket RPC
log_info "Testing ${#PUBLIC_RPC_WS[@]} public WebSocket RPC endpoints..."
for url in "${PUBLIC_RPC_WS[@]}"; do
log_info " $url"
res=$(rpc_ws_test "$url")
RESULTS_WS+=("{\"url\":\"$url\",\"result\":$res}")
done
# Build JSON report
JSON_HTTP=$(printf '%s\n' "${RESULTS_HTTP[@]}" | jq -s '.')
JSON_WS=$(printf '%s\n' "${RESULTS_WS[@]}" | jq -s '.')
SUMMARY=$(echo "{\"timestamp\":\"$(date -Iseconds)\",\"chain_id_expected\":\"$CHAIN_ID_EXPECTED\",\"http\":$JSON_HTTP,\"ws\":$JSON_WS}" | jq '
. as $root |
{
timestamp: .timestamp,
chain_id_expected: .chain_id_expected,
http_total: (.http | length),
http_pass: ([.http[] | select(.result.status == "pass")] | length),
ws_total: (.ws | length),
ws_pass: ([.ws[] | select(.result.status == "pass")] | length),
http: $root.http,
ws: $root.ws
}
')
echo "$SUMMARY" > "$REPORT_JSON"
# Markdown report
{
echo "# Public RPC nodes — E2E stability report"
echo ""
echo "**Generated:** $(date -Iseconds)"
echo "**Chain ID expected:** $CHAIN_ID_EXPECTED"
echo ""
echo "## HTTP RPC"
echo ""
echo "| Endpoint | Status | Chain ID | Block | Latency (ms) |"
echo "|----------|--------|----------|-------|--------------|"
for entry in "${RESULTS_HTTP[@]}"; do
url=$(echo "$entry" | jq -r '.url')
status=$(echo "$entry" | jq -r '.result.status')
chain_id=$(echo "$entry" | jq -r '.result.chain_id // "-"')
block_hex=$(echo "$entry" | jq -r '.result.block_hex // "-"')
latency=$(echo "$entry" | jq -r '.result.latency_ms // "-"')
echo "| $url | $status | $chain_id | $block_hex | $latency |"
done
echo ""
echo "## WebSocket RPC"
echo ""
echo "| Endpoint | Status | Chain ID | Note |"
echo "|----------|--------|----------|------|"
for entry in "${RESULTS_WS[@]}"; do
url=$(echo "$entry" | jq -r '.url')
status=$(echo "$entry" | jq -r '.result.status')
chain_id=$(echo "$entry" | jq -r '.result.chain_id // "-"')
note=$(echo "$entry" | jq -r '.result.note // "-"')
echo "| $url | $status | $chain_id | $note |"
done
echo ""
echo "## Files"
echo "- \`$REPORT_JSON\`"
echo "- \`$REPORT_MD\`"
} > "$REPORT_MD"
# Console summary
HTTP_PASS=$(echo "$SUMMARY" | jq -r '.http_pass')
HTTP_TOTAL=$(echo "$SUMMARY" | jq -r '.http_total')
WS_PASS=$(echo "$SUMMARY" | jq -r '.ws_pass')
WS_TOTAL=$(echo "$SUMMARY" | jq -r '.ws_total')
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " Summary"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " HTTP RPC: $HTTP_PASS / $HTTP_TOTAL passed"
echo " WS RPC: $WS_PASS / $WS_TOTAL passed"
echo " Report: $REPORT_MD"
echo " JSON: $REPORT_JSON"
echo ""
for entry in "${RESULTS_HTTP[@]}"; do
url=$(echo "$entry" | jq -r '.url')
status=$(echo "$entry" | jq -r '.result.status')
latency=$(echo "$entry" | jq -r '.result.latency_ms // "-"')
if [ "$status" = "pass" ]; then
log_ok "$url$status (${latency}ms)"
else
log_fail "$url$status"
fi
done
for entry in "${RESULTS_WS[@]}"; do
url=$(echo "$entry" | jq -r '.url')
status=$(echo "$entry" | jq -r '.result.status')
if [ "$status" = "pass" ]; then
log_ok "$url$status"
elif [ "$status" = "warn" ]; then
log_warn "$url$status"
else
log_fail "$url$status"
fi
done
echo ""
# Exit 1 if any HTTP or WS RPC failed or has warnings (strict e2e stability)
FAILED_HTTP=$((HTTP_TOTAL - HTTP_PASS))
WS_WARN_OR_FAIL=$((WS_TOTAL - WS_PASS))
if [ "$FAILED_HTTP" -gt 0 ]; then
log_fail "Public RPC stability: $FAILED_HTTP HTTP endpoint(s) failed."
exit 1
fi
if [ "$WS_WARN_OR_FAIL" -gt 0 ]; then
log_fail "Public RPC stability: $WS_WARN_OR_FAIL WebSocket endpoint(s) not pass (warn or fail). Install wscat or Node/npx and re-run."
exit 1
fi
log_ok "All public RPC nodes passed E2E stability check (no warnings)."
exit 0