diff --git a/CLAUDE.md b/CLAUDE.md index 05f050f..d9b7e35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ` 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` diff --git a/pi-stat b/pi-stat index d0a3a5a..2d4c5de 100755 --- a/pi-stat +++ b/pi-stat @@ -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)