From efa9da58b55876923fbca41f8037a5c592dae9e8 Mon Sep 17 00:00:00 2001 From: Sylvester Kaczmarek <16242628+sylvesterkaczmarek@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:11:04 +0100 Subject: [PATCH] fix(infonet): ignore invalid timestamps in majority time --- backend/services/infonet/time_validity.py | 45 +++++++++++++++-------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/backend/services/infonet/time_validity.py b/backend/services/infonet/time_validity.py index 725c657..76f5efb 100644 --- a/backend/services/infonet/time_validity.py +++ b/backend/services/infonet/time_validity.py @@ -33,6 +33,7 @@ responsibility. from __future__ import annotations +import math import statistics from typing import Any, Iterable @@ -47,6 +48,17 @@ from services.infonet.config import CONFIG _DEFAULT_MEDIAN_N = 11 +def _finite_timestamp(value: Any) -> float | None: + """Return a finite numeric timestamp, or ``None`` when unusable.""" + try: + timestamp = float(value) + except (TypeError, ValueError, OverflowError): + return None + if not math.isfinite(timestamp): + return None + return timestamp + + def chain_majority_time( chain: Iterable[dict[str, Any]], *, @@ -64,22 +76,27 @@ def chain_majority_time( """ if n <= 0: raise ValueError("n must be positive") - events = [e for e in chain if isinstance(e, dict)] - events.sort(key=lambda e: float(e.get("timestamp") or 0.0), reverse=True) + + events: list[tuple[float, dict[str, Any]]] = [] + for event in chain: + if not isinstance(event, dict): + continue + timestamp = _finite_timestamp(event.get("timestamp")) + if timestamp is None: + continue + events.append((timestamp, event)) + events.sort(key=lambda item: item[0], reverse=True) + seen_nodes: set[str] = set() timestamps: list[float] = [] - for ev in events: - node = ev.get("node_id") + for timestamp, event in events: + node = event.get("node_id") if not isinstance(node, str) or not node: continue if node in seen_nodes: continue seen_nodes.add(node) - ts = ev.get("timestamp") - try: - timestamps.append(float(ts)) - except (TypeError, ValueError): - continue + timestamps.append(timestamp) if len(timestamps) >= n: break if not timestamps: @@ -104,12 +121,10 @@ def is_event_too_future( if chain is None: raise ValueError("Pass chain or chain_time") chain_time = chain_majority_time(chain) - try: - ts = float(event.get("timestamp")) - except (TypeError, ValueError): - # Non-numeric timestamp is its own validation failure — let the - # schema-level check catch that. Drift check itself returns - # False here (we cannot meaningfully compare). + ts = _finite_timestamp(event.get("timestamp")) + if ts is None: + # Non-numeric or non-finite timestamps are their own validation + # failure. Drift checking cannot meaningfully compare them. return False drift = float(CONFIG["max_future_event_drift_sec"]) return ts > chain_time + drift