9.5 KiB
Morphit beta-test incident-response runbook
This is a one-page triage guide for the operator (you) during paid beta testing. When a tester reports something broken, work top-to-bottom.
The mental model: every layer is a gate. Find which gate is saying "no," fix it, signups resume.
0. First, is the relay even running?
sudo systemctl status morphit-relay
sudo journalctl -u morphit-relay -n 20
If not running: see §6.
1. Did the request reach the relay?
Every request now produces a single access-log line. Grep for the time window the tester reported:
sudo journalctl -u morphit-relay --since "5 minutes ago" \
| grep '\[access\] request'
Look for the tester's request. Each line shows:
method=POST path=/v1/account/create status=503 dur_ms=4 code=signups_disabled
-
No matching line? The request never reached the relay. Either: (a) DNS/firewall problem (check the public origin resolves and accepts connections), (b) the tester's frontend is talking to the wrong relay, (c) the request hit your reverse proxy but didn't proxy through (check nginx logs).
-
Line present, status 4xx or 5xx? Read the
codefield and consult §2 below. -
Line present, status 200, but the tester says it failed? The relay accepted, but something downstream (chain, dust transfer, frontend rendering) failed. See §4.
2. Response code lookup
code |
What it means | What to check |
|---|---|---|
signups_disabled |
Kill-switch is on | §3 |
daily_ceiling_reached |
Today's quota is full | §3 |
rate_limited |
This IP exceeded the burst cap (5/hour default) | Tester needs to wait 1h, OR they're behind a NAT/CGNAT shared with other testers — see §5 |
rate_limited_daily |
This IP hit the daily cap (2/day default) | Tester waits until UTC midnight, OR shared NAT — §5 |
spacing_cooldown |
This IP signed up recently; next allowed in retry_after_minutes |
Working as intended; tell the tester to wait |
relay_out_of_funds |
Relay's BLURT balance can't cover the next signup | §6 — refill the relay account |
chain_unavailable |
Blurt RPC isn't responding | §7 |
invalid_pubkey |
Frontend sent a malformed BLT key | Real bug; collect their browser console + relay logs and send to me |
malformed_operation |
Body shape doesn't match the schema | Real bug, same as above |
name_not_allowed |
Account name failed validation (reserved, bad chars, etc.) | Tester picks a different name |
already_registered |
Name is taken | Tester picks a different name |
invite_expired / invite_already_used / invite_ip_mismatch |
Invite token problem | Tester refreshes the page (gets a fresh invite) |
altcha_required |
PoW puzzle delivered; tester's frontend should solve it | If the frontend doesn't solve it, that's a frontend bug — escalate |
altcha_bad_solution |
Tester's frontend solved the puzzle wrong | Frontend bug, escalate |
origin_required / origin_not_allowed |
Tester's frontend Origin header isn't in the allowlist | Add their frontend's origin to MORPHIT_RELAY_ALLOWED_ORIGINS and restart |
fee_higher_than_configured |
Chain fee jumped above your configured budget | Update MORPHIT_INDEXER_ACCOUNT_CREATION_FEE_BLURT and restart |
broadcast_failed |
Chain rejected the tx for an unmapped reason | Real bug or chain weirdness; check journalctl for the upstream error |
chunked_unsupported |
Tester's frontend used chunked encoding | Frontend bug, escalate |
request_too_large |
Body > 64KB | Almost certainly a malformed/malicious request; ignore |
3. Signups paused — is it me or the system?
# Is the kill-switch file present?
ls -la /var/lib/morphit/relay/SIGNUPS_DISABLED 2>/dev/null
# Is the env-var disable on?
grep MORPHIT_RELAY_SIGNUP_ENABLED /etc/morphit/relay.env
# What does today's ceiling status look like?
# Note: the relay's /v1/health is on port 8080 (the indexer
# is 8081; don't mix them up). Both expose /v1/health with
# different field shapes.
curl -s http://localhost:8080/v1/health?verbose=1 | jq .signup_stats
Three ways signups get paused:
-
Kill-switch file exists → if you put it there during an incident, removing it resumes signups within ~1 second.
sudo rm /var/lib/morphit/relay/SIGNUPS_DISABLED -
Env-var is
false→ edit relay.env, setMORPHIT_RELAY_SIGNUP_ENABLED=true, restart:sudo systemctl restart morphit-relay. Note: env-var change requires restart; the kill-switch file does NOT. -
Daily ceiling reached → from
signup_stats, ifsuccessful_today >= daily_ceiling, the cap is hit until UTC midnight. Realsignup_statsshape:{enabled, daily_ceiling, successful_today, current_hour_count, peak_hour_count, peak_other_hours, resets_at}. This is normal during high beta volume. Either wait, or raiseMORPHIT_RELAY_SIGNUP_DAILY_CEILINGand restart (think about whether the higher number is covered by your wallet — see §6).
4. Request succeeded but tester says it failed
Order of suspects:
-
Frontend rendering bug. The trx hit the chain (you can verify: paste the
trx_idfrom the access log into a blurt explorer). If the chain has the account, the relay did its job; the frontend must be misrendering. Get the tester's browser console and screenshot. -
Slow chain confirmation. Sometimes blurt witnesses are slow; the trx is in mempool but the tester's frontend timed out waiting. Check journalctl for the broadcast confirmation; if it's there but a minute late, this is normal under chain congestion.
-
Frontend caching. The tester may be hitting a cached version of the page. Ask them to hard-refresh (cmd-shift-R).
5. Multiple testers behind a shared IP
Several paid betas in the same office / on the same VPN / behind the same CGNAT will all share an IP from the relay's perspective. The per-IP rate limits (5/hour, 2/day) and spacing cooldown will gate them collectively.
Fixes:
- Tell the testers to spread out. An hour between signups is plenty.
- Whitelist their VPN's IP in nginx's geo block, but ONLY if you trust the testers — a shared whitelist removes the attack defense too.
- Raise the per-IP daily cap (
MORPHIT_RELAY_CREATE_RATE_PER_DAY, default 2; orMORPHIT_RELAY_CREATE_RATE_PER_HOUR, default 5) during the beta, then lower it back at launch. Trade-off: raises the drain ceiling per attacker IP.
6. Relay out of funds
# What's the current balance?
# (relay's /v1/health is on port 8080)
curl -s http://localhost:8080/v1/health?verbose=1 | jq .blurt_balance
# How many more signups can we afford?
# Compute as daily_ceiling - successful_today:
curl -s http://localhost:8080/v1/health?verbose=1 \
| jq '.signup_stats | (.daily_ceiling - .successful_today)'
If the headroom is low or zero, the relay account needs more BLURT. Transfer in BLURT from your operator wallet to the relay account. Within 30 seconds the HealthService background poll picks up the new balance and signups resume.
If you've been draining unexpectedly fast, check
signup_stats.successful_today and peak_hour_count — a
sudden spike with the kill-switch off is the drain pattern;
flip the kill-switch on (§3) and investigate before
refilling.
7. Chain RPC unavailable
The relay talks to Blurt via HTTP RPC. If the configured endpoint is down or slow:
# The env var is a comma-separated list of HTTPS endpoints
grep MORPHIT_RELAY_BLURT_RPC /etc/morphit/relay.env
# Pick the first one, test it directly
endpoint=$(grep MORPHIT_RELAY_BLURT_RPC /etc/morphit/relay.env | cut -d= -f2- | tr -d "'\"" | cut -d, -f1)
curl -m 5 -X POST -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"condenser_api.get_dynamic_global_properties","params":[],"id":1}' \
"$endpoint"
If the curl fails or times out, that specific RPC node is down. The relay rotates through all endpoints in the comma-separated list, so as long as one of them works the relay keeps going. Add a fallback endpoint to the list and restart — or rotate out a dead one — pick from the Blurt witness directory or community channels.
8. Information to collect when escalating to me
When you have a real bug to report (codes that the runbook calls "Real bug, escalate"), bundle these:
- The access-log line(s) for the failing request.
- The full journalctl output from the relay for the same
minute (
sudo journalctl -u morphit-relay --since "1 minute ago"). - The tester's browser console output (Settings → Developer tools → Console, screenshot or copy-paste).
- The exact error message they saw on screen.
- (If applicable) the
trx_idif they got one but the trx "didn't work."
Drop these into a chat with me and I'll start triage.
9. Quick-reference cheat sheet
# Pause signups RIGHT NOW
sudo touch /var/lib/morphit/relay/SIGNUPS_DISABLED
# Resume signups
sudo rm /var/lib/morphit/relay/SIGNUPS_DISABLED
# How many signups today? (relay's /v1/health on port 8080)
curl -s localhost:8080/v1/health?verbose=1 | jq .signup_stats.successful_today
# Wallet balance (port 8080 = relay)
curl -s localhost:8080/v1/health?verbose=1 | jq .blurt_balance
# Recent access log
sudo journalctl -u morphit-relay --since "5m ago" | grep '\[access\]'
# Recent errors only
sudo journalctl -u morphit-relay --since "5m ago" -p err
# Who's hammering me right now?
sudo journalctl -u morphit-relay --since "5m ago" \
| grep '\[access\]' | awk '{print $NF}' | sort | uniq -c | sort -rn | head