fix: query Horizon inside the container and flag lost core connection

- Fetch Horizon via curl inside the container (127.0.0.1:8000) instead of
  the host port 31401; the host->bridge forwarding hung until the 5 s
  timeout and made each cycle take ~6 s instead of ~1 s
- Report an error when Horizon's core_latest_ledger is 0 (no connection
  to stellar-core) instead of a false "Synced" with a negative lag
- Compute the ingest lag against stellar-core's own ledger
- "History Oldest" now shows history_elder_ledger; the latest ledger is
  listed separately as "History Latest"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 08:05:10 +00:00
parent 4e27efc3b9
commit 6bca2c62e6
2 changed files with 25 additions and 12 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ Single self-contained script (`pi-stat`). Logic is split into helper functions a
- **Docker SDK** — container CPU/RAM stats; all commands run inside the auto-detected container (`mainnet` → `testnet2` → `testnet`, first running one) via `exec_run`
- **Stellar Core HTTP API** — `exec_json()` runs `stellar-core http-command <cmd>` inside the container
- **Horizon API** — `http://localhost:31401`
- **Horizon API** — `curl http://127.0.0.1:8000/` run inside the container via `exec_json()`, not via the host port `31401` (host→bridge forwarding can hang until timeout if Docker's host firewall rules are broken)
- **PostgreSQL** — `psql` inside the container; used for peer DB counts and pubkey→IP mapping
- **ZFS CLI** — `zpool`/`zfs` commands on the host for disk health (`z01pool`); if `zpool` is not found (`HAS_ZFS = False`), falls back to `shutil.disk_usage()` — works on Linux without ZFS, WSL2, and Windows
- **ip-api.com** — batch geolocation (Country, City), cached in `_geo_cache`; only fetched with `-g`
+24 -11
View File
@@ -38,7 +38,7 @@ except ImportError:
# --- Configuration ---
HAS_ZFS = shutil.which("zpool") is not None
CANDIDATE_CONTAINERS = ["mainnet", "testnet2", "testnet"]
HORIZON_URL = "http://localhost:31401"
HORIZON_URL = "http://127.0.0.1:8000" # queried from inside the container, not via the host port
CFG_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stellar-core.cfg")
DEFAULT_REFRESH = 5
REFRESH_WARN_RATIO = 0.8 # warn if a cycle takes >= 80% of the refresh interval
@@ -262,12 +262,12 @@ def run_cycle(client, peers_extended, geo_enabled, show_ip, interactive=False, e
if raw:
core_info = raw.get("info")
# Query Horizon via curl inside the container: independent of the host's
# port forwarding (31401), which can hang until timeout if Docker's
# firewall rules on the host are broken.
hz_data = None
try:
with urlopen(HORIZON_URL + "/", timeout=5) as resp:
hz_data = json.loads(resp.read())
except Exception:
pass
if container:
hz_data = exec_json(container, ["curl", "-s", "-m", "5", HORIZON_URL + "/"])
# =============================================================
# 1. CONTAINER STATUS
@@ -373,23 +373,36 @@ def run_cycle(client, peers_extended, geo_enabled, show_ip, interactive=False, e
hdr("🌅 API Status (Horizon)")
if hz_data is None:
err(f"Horizon API not reachable ({HORIZON_URL})")
err(f"Horizon API not reachable ({HORIZON_URL} in container '{container_name}')")
else:
core_blk = hz_data.get("core_latest_ledger", 0)
hist_blk = hz_data.get("history_latest_ledger", 0)
elder_blk = hz_data.get("history_elder_ledger", 0)
ings_blk = hz_data.get("ingest_latest_ledger", 0)
lag = core_blk - ings_blk
closed_at = hz_data.get("history_latest_ledger_closed_at", "?")
if lag <= 2:
# Reference ledger for the ingest lag: stellar-core's own ledger if we
# have it, otherwise what Horizon reports for core. core_latest_ledger
# is 0 when Horizon has lost its connection to stellar-core; computing
# the lag against 0 would give a negative value and a false "Synced".
ref_blk = block if core_info is not None else core_blk
lag = (ref_blk - ings_blk) if ref_blk else None
if not core_blk:
err("Status: Horizon has no connection to stellar-core (core_latest_ledger = 0)")
if lag is not None and lag > 2:
warn(f"Ingest {lag} ledger behind core")
elif lag <= 2:
ok(f"Status: Synced (Ingest lag: {lag} ledger)")
else:
warn(f"Status: Lagging (Ingest {lag} ledger behind core)")
core_diff = (block - core_blk) if (core_info is not None and core_blk) else None
core_diff_str = f" (Δ {core_diff:+d} vs Core)" if core_diff is not None else ""
print(f" Core Latest Ledger: {core_blk}{core_diff_str}")
core_blk_str = str(core_blk) if core_blk else "— (not connected)"
print(f" Core Latest Ledger: {core_blk_str}{core_diff_str}")
print(f" Ingest Latest Ledger: {ings_blk} (last closed: {closed_at})")
print(f" History Oldest: {hist_blk}")
print(f" History Latest: {hist_blk}")
print(f" History Oldest: {elder_blk}")
# =============================================================
# 5. NODE METRICS (stellar-core)