mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-18 08:27:18 +02:00
Merge pull request #510 from sylvesterkaczmarek/fix/chain-majority-time-invalid-timestamps
fix(infonet): ignore invalid timestamps in majority time
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
"""Regression coverage for malformed chain timestamps."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from services.infonet.time_validity import chain_majority_time, is_event_too_future
|
||||
|
||||
|
||||
def _event(node_id: str, timestamp: Any) -> dict[str, Any]:
|
||||
return {"node_id": node_id, "timestamp": timestamp}
|
||||
|
||||
|
||||
def test_chain_majority_time_ignores_malformed_and_nonfinite_timestamps() -> None:
|
||||
chain = [
|
||||
_event("valid-a", 10.0),
|
||||
_event("bad-text", "not-a-timestamp"),
|
||||
_event("bad-nan", float("nan")),
|
||||
_event("bad-inf", float("inf")),
|
||||
_event("valid-b", 20.0),
|
||||
]
|
||||
|
||||
assert chain_majority_time(chain) == 15.0
|
||||
|
||||
|
||||
def test_chain_majority_time_accepts_finite_numeric_strings() -> None:
|
||||
chain = [_event("a", "10.5"), _event("b", "20.5")]
|
||||
|
||||
assert chain_majority_time(chain) == 15.5
|
||||
|
||||
|
||||
def test_future_check_fails_closed_for_invalid_timestamps() -> None:
|
||||
assert is_event_too_future(_event("bad-text", "not-a-timestamp"), chain_time=100.0)
|
||||
assert is_event_too_future(_event("bad-nan", float("nan")), chain_time=100.0)
|
||||
assert is_event_too_future(_event("bad-inf", float("inf")), chain_time=100.0)
|
||||
|
||||
|
||||
def test_future_check_preserves_valid_timestamp_behavior() -> None:
|
||||
assert not is_event_too_future(_event("near", 110.0), chain_time=100.0)
|
||||
assert is_event_too_future(_event("far", 10_000.0), chain_time=100.0)
|
||||
@@ -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:
|
||||
@@ -93,24 +110,23 @@ def is_event_too_future(
|
||||
*,
|
||||
chain_time: float | None = None,
|
||||
) -> bool:
|
||||
"""Is ``event.timestamp`` more than ``max_future_event_drift_sec``
|
||||
ahead of ``chain_majority_time``?
|
||||
"""Is ``event.timestamp`` invalid or beyond allowed future drift?
|
||||
|
||||
Pass ``chain_time`` when the caller has already computed it (e.g.
|
||||
bulk validation of a batch — avoids recomputing the median per
|
||||
event). Otherwise pass ``chain``.
|
||||
|
||||
Invalid/non-finite event timestamps fail closed: they return
|
||||
``True`` so callers reject or re-queue them rather than allowing
|
||||
malformed events through the drift gate.
|
||||
"""
|
||||
if chain_time is None:
|
||||
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).
|
||||
return False
|
||||
ts = _finite_timestamp(event.get("timestamp"))
|
||||
if ts is None:
|
||||
return True
|
||||
drift = float(CONFIG["max_future_event_drift_sec"])
|
||||
return ts > chain_time + drift
|
||||
|
||||
|
||||
Reference in New Issue
Block a user