Skip to content
248 changes: 248 additions & 0 deletions .github/workflows/rpc_healthcheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
name: RPC Healthcheck

permissions:
contents: read
pull-requests: write

on:
schedule:
- cron: '41 2 * * *'
- cron: '41 10 * * *'
- cron: '41 18 * * *'
workflow_dispatch:


jobs:
check-networkinfo:
name: Check ${{ matrix.network }} networkInfo
if: ${{ github.repository == 'QuarkChain/goquarkchain' }}
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
fail-fast: false
matrix:
include:
- network: mainnet
rpc_url: https://rpc.indexer.mainnet.quarkchain.io:38391
expected_network_id: "0x1"
- network: devnet
rpc_url: https://rpc.indexer.devnet.quarkchain.io:38392
expected_network_id: "0xff"
- network: mainnetHost
rpc_url: http://65.108.121.223:38391
expected_network_id: "0x1"
- network: devnetHost
rpc_url: http://50.112.62.65:38391
expected_network_id: "0xff"

steps:
- name: Check endpoint JSON field
shell: bash
env:
RPC_URL: ${{ matrix.rpc_url }}
EXPECTED_NETWORK_ID: ${{ matrix.expected_network_id }}
NETWORK_NAME: ${{ matrix.network }}
run: |
set -euo pipefail

payload='{"jsonrpc":"2.0","id":"id","method":"networkInfo","params":[]}'
resp_file="${RUNNER_TEMP}/networkinfo_${NETWORK_NAME}.json"
report_file="${RUNNER_TEMP}/networkinfo_report_${NETWORK_NAME}.json"
: > "$resp_file"

code="$(
curl --silent --show-error --location \
--retry 3 --retry-delay 5 --retry-all-errors \
--connect-timeout 10 --max-time 30 \
-X POST \
-H 'Content-Type: application/json' \
--data "$payload" \
--output "$resp_file" --write-out '%{http_code}' \
"$RPC_URL" \
|| echo '000'
)"

result="success"
error_msg=""

if [[ "$code" != "200" ]]; then
result="failure"
error_msg="HTTP status not 200: $code"
elif ! jq -e --arg expected "$EXPECTED_NETWORK_ID" '.result.networkId == $expected' "$resp_file" > /dev/null; then
result="failure"
error_msg="JSON check failed: .result.networkId == $EXPECTED_NETWORK_ID"
fi

jq -n \
--arg network "$NETWORK_NAME" \
--arg rpc_url "$RPC_URL" \
--arg result "$result" \
--arg error "$error_msg" \
'{network: $network, rpc_url: $rpc_url, result: $result, error: $error}' > "$report_file"

if [[ "$result" != "success" ]]; then
echo "[$NETWORK_NAME] $error_msg"
cat "$resp_file"
exit 1
fi

echo "[$NETWORK_NAME] JSON check passed: .result.networkId == $EXPECTED_NETWORK_ID"

- name: Upload endpoint report
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: rpc-healthcheck-${{ matrix.network }}
path: ${{ runner.temp }}/networkinfo_report_${{ matrix.network }}.json
if-no-files-found: error

notify:
name: Notify (email)
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
- check-networkinfo
if: ${{ always() && github.repository == 'QuarkChain/goquarkchain' }}

steps:
- name: Download endpoint reports
uses: actions/download-artifact@v4
with:
pattern: rpc-healthcheck-*
merge-multiple: true
path: ${{ runner.temp }}/rpc-healthcheck

- name: Compose email
id: compose
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_SCHEDULE: ${{ github.event.schedule }}
CHECK_RESULT: ${{ needs.check-networkinfo.result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
send=false
overall="OK"
report_dir="${RUNNER_TEMP}/rpc-healthcheck"

table_rows_with_error=''
table_rows_no_error=''
html_rows_with_error=''
html_rows_no_error=''

shopt -s nullglob
report_files=("$report_dir"/*.json)
if [[ ${#report_files[@]} -eq 0 ]]; then
overall="FAILED"
send=true
table_rows_with_error='| unknown | unknown | failure | missing endpoint report artifact |'
html_rows_with_error='<tr><td>unknown</td><td>unknown</td><td>failure</td><td>missing endpoint report artifact</td></tr>'
else
for f in "${report_files[@]}"; do
network="$(jq -r '.network' "$f")"
rpc_url="$(jq -r '.rpc_url' "$f")"
result="$(jq -r '.result' "$f")"
error="$(jq -r '.error' "$f")"

if [[ "$result" != "success" ]]; then
overall="FAILED"
send=true
if [[ -z "$error" ]]; then
error="unknown error"
fi
else
if [[ -z "$error" ]]; then
error="-"
fi
fi

# Escape markdown table special chars
rpc_url="${rpc_url//|/\\|}"
error="${error//|/\\|}"
table_rows_with_error+="| ${network} | ${rpc_url} | ${result} | ${error} |\n"
table_rows_no_error+="| ${network} | ${rpc_url} | ${result} |\n"

network_html="${network//&/&amp;}"
network_html="${network_html//</&lt;}"
network_html="${network_html//>/&gt;}"
rpc_url_html="${rpc_url//&/&amp;}"
rpc_url_html="${rpc_url_html//</&lt;}"
rpc_url_html="${rpc_url_html//>/&gt;}"
result_html="${result//&/&amp;}"
result_html="${result_html//</&lt;}"
result_html="${result_html//>/&gt;}"
error_html="${error//&/&amp;}"
error_html="${error_html//</&lt;}"
error_html="${error_html//>/&gt;}"
html_rows_with_error+="<tr><td>${network_html}</td><td>${rpc_url_html}</td><td>${result_html}</td><td>${error_html}</td></tr>"
html_rows_no_error+="<tr><td>${network_html}</td><td>${rpc_url_html}</td><td>${result_html}</td></tr>"
done
fi

if [[ "$CHECK_RESULT" != "success" ]]; then
overall="FAILED"
send=true
else
# Success: send only on manual trigger, or once per day for scheduled runs.
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
send=true
elif [[ "$EVENT_NAME" == "schedule" ]]; then
if [[ "${EVENT_SCHEDULE:-}" == "41 18 * * *" ]]; then
send=true
fi
fi
fi
if [[ "$overall" == "OK" ]]; then
subject="✅ QuarkChain RPC Healthcheck OK"
else
subject="❌ QuarkChain RPC Healthcheck FAILED"
fi

echo "send=$send" >> "$GITHUB_OUTPUT"
echo "subject=$subject" >> "$GITHUB_OUTPUT"
echo "body<<EOF" >> "$GITHUB_OUTPUT"
echo "QuarkChain RPC Healthcheck: $overall" >> "$GITHUB_OUTPUT"
echo >> "$GITHUB_OUTPUT"
echo "Event: $EVENT_NAME" >> "$GITHUB_OUTPUT"
echo "Schedule: ${EVENT_SCHEDULE:-N/A}" >> "$GITHUB_OUTPUT"
echo "Run: $RUN_URL" >> "$GITHUB_OUTPUT"
echo >> "$GITHUB_OUTPUT"
if [[ "$overall" == "OK" ]]; then
echo "| network | rpc_url | result |" >> "$GITHUB_OUTPUT"
echo "|---|---|---|" >> "$GITHUB_OUTPUT"
printf "%b" "$table_rows_no_error" >> "$GITHUB_OUTPUT"
else
echo "| network | rpc_url | result | error |" >> "$GITHUB_OUTPUT"
echo "|---|---|---|---|" >> "$GITHUB_OUTPUT"
printf "%b" "$table_rows_with_error" >> "$GITHUB_OUTPUT"
fi
echo "EOF" >> "$GITHUB_OUTPUT"

echo "html_body<<EOF" >> "$GITHUB_OUTPUT"
echo "<p>QuarkChain RPC Healthcheck: <b>${overall}</b></p>" >> "$GITHUB_OUTPUT"
echo "<p>Event: ${EVENT_NAME}<br/>Schedule: ${EVENT_SCHEDULE:-N/A}<br/>Run: <a href=\"${RUN_URL}\">${RUN_URL}</a></p>" >> "$GITHUB_OUTPUT"
echo "<table border=\"1\" cellpadding=\"6\" cellspacing=\"0\" style=\"border-collapse: collapse;\">" >> "$GITHUB_OUTPUT"
if [[ "$overall" == "OK" ]]; then
echo "<thead><tr><th>network</th><th>rpc_url</th><th>result</th></tr></thead>" >> "$GITHUB_OUTPUT"
echo "<tbody>${html_rows_no_error}</tbody>" >> "$GITHUB_OUTPUT"
else
echo "<thead><tr><th>network</th><th>rpc_url</th><th>result</th><th>error</th></tr></thead>" >> "$GITHUB_OUTPUT"
echo "<tbody>${html_rows_with_error}</tbody>" >> "$GITHUB_OUTPUT"
fi
echo "</table>" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"

- name: Send email
if: ${{ steps.compose.outputs.send == 'true' }}
uses: dawidd6/action-send-mail@v6
with:
server_address: smtp.gmail.com
server_port: 465
username: ${{ secrets.RPC_HEALTHCHECK_SMTP_USERNAME }}
password: ${{ secrets.RPC_HEALTHCHECK_SMTP_PASSWORD }}
from: QuarkChainRPC
to: ${{ secrets.RPC_HEALTHCHECK_EMAIL_TO }}
subject: ${{ steps.compose.outputs.subject }}
body: ${{ steps.compose.outputs.body }}
html_body: ${{ steps.compose.outputs.html_body }}
Loading