Skip to content

/health tip_age_slots always returns 0 and never measures tip age #8057

Description

@Scottcjn

Summary

/health reports "tip_age_slots": 0 on a node whose newest block header is 234 days old. The field is not measuring anything.

The code

rustchain_v2_integrated_v2.2.1_rip200.py, _tip_age_slots():

def _tip_age_slots():
    """Check tip freshness - query DB directly to avoid Response object"""
    try:
        with sqlite3.connect(DB_PATH, timeout=3) as db:
            row = db.execute("SELECT slot FROM headers ORDER BY slot DESC LIMIT 1").fetchone()
        return 0 if row else None
    except Exception:
        return None

It selects the newest header and then discards it, returning a hardcoded 0 if any row exists at all. The function name says age, the docstring says freshness, and neither is computed. An empty headers table returns None; a table whose newest entry is from last year returns 0, identical to a table updated one second ago.

Observed on node 1 (50.28.86.131)

GET /health       -> {"tip_age_slots": 0, "ok": true, ...}
GET /headers/tip  -> {"slot": 2941199, "tip_age": 20280879, "miner": "sophia-nas-c4130"}

tip_age of 20,280,879 seconds is about 234 days. The two endpoints disagree because only one of them measures anything.

Database state: SELECT COUNT(*), MIN(slot), MAX(slot) FROM headers returns 224 | 35 | 2941199. 224 headers total.

Note the max slot is on the pre-fix raw-timestamp convention (time.time() // 600) rather than the genesis-relative current_slot() the node uses now, which is consistent with the newest header dating to around the production chain launch.

Impact

Low severity in isolation, because ok is not gated on this value:

ok = ok_db and (age_h is None or age_h < 36)

The harm is that the field is load-bearing for humans and dashboards. Anyone reading /health to answer "is the chain advancing" gets a confident 0 that means "a headers row exists." This is the same failure shape as #8055, where the health CLI printed ALL SYSTEMS OPERATIONAL and exited 0 while the tip check failed. A health surface that cannot report unhealthy is worse than no health surface, because it suppresses the question.

Suggested fix

Compute the real age and return it, keeping it out of the ok boolean so monitoring semantics do not change without a deliberate decision:

  • Select ts alongside slot and derive age from wall clock, or derive slot age against current_slot(). Pick one and make the field name match the unit it returns.
  • If the name stays tip_age_slots, return slots. If it returns seconds, rename it.
  • Decide separately, and explicitly, whether a stale tip should fail ok. That is a policy call about whether header production is expected to be continuous on this network, and it should not be smuggled in as part of a bug fix.

Deliberately not patched on the running node

Changing this value from a constant 0 to a real number will change what any external monitoring sees, potentially turning dashboards red. That is a maintainer decision, not a drive-by fix.

Related: #8055 and PR #8056, same class of defect in the health CLI.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions