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.
Summary
/healthreports"tip_age_slots": 0on 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():It selects the newest header and then discards it, returning a hardcoded
0if any row exists at all. The function name says age, the docstring says freshness, and neither is computed. An empty headers table returnsNone; a table whose newest entry is from last year returns0, identical to a table updated one second ago.Observed on node 1 (50.28.86.131)
tip_ageof 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 headersreturns224 | 35 | 2941199. 224 headers total.Note the max slot is on the pre-fix raw-timestamp convention (
time.time() // 600) rather than the genesis-relativecurrent_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
okis not gated on this value:The harm is that the field is load-bearing for humans and dashboards. Anyone reading
/healthto answer "is the chain advancing" gets a confident0that 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
okboolean so monitoring semantics do not change without a deliberate decision:tsalongsideslotand derive age from wall clock, or derive slot age againstcurrent_slot(). Pick one and make the field name match the unit it returns.tip_age_slots, return slots. If it returns seconds, rename it.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
0to 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.