Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
251 lines
9.6 KiB
Bash
Executable File
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
|