mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-08-18 16:37:24 +02:00
Merge pull request #509 from sylvesterkaczmarek/fix/dispute-nonfinite-values
fix(infonet): reject non-finite dispute numerics
This commit is contained in:
@@ -28,6 +28,7 @@ Two effects:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from math import isfinite
|
||||||
from typing import Any, Iterable
|
from typing import Any, Iterable
|
||||||
|
|
||||||
from services.infonet.config import CONFIG
|
from services.infonet.config import CONFIG
|
||||||
@@ -38,6 +39,15 @@ def _payload(event: dict[str, Any]) -> dict[str, Any]:
|
|||||||
return p if isinstance(p, dict) else {}
|
return p if isinstance(p, dict) else {}
|
||||||
|
|
||||||
|
|
||||||
|
def _finite_float(value: Any) -> float | None:
|
||||||
|
"""Parse a chain numeric, rejecting malformed and non-finite values."""
|
||||||
|
try:
|
||||||
|
parsed = float(value)
|
||||||
|
except (TypeError, ValueError, OverflowError):
|
||||||
|
return None
|
||||||
|
return parsed if isfinite(parsed) else None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DisputeView:
|
class DisputeView:
|
||||||
"""Chain-derived view of a single dispute."""
|
"""Chain-derived view of a single dispute."""
|
||||||
@@ -87,18 +97,18 @@ def collect_disputes(
|
|||||||
if not open_events:
|
if not open_events:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Build by dispute_id keyed off the open event.
|
# Build by dispute_id keyed off the open event. A malformed
|
||||||
|
# authoritative open must not be normalized into active state.
|
||||||
disputes: dict[str, DisputeView] = {}
|
disputes: dict[str, DisputeView] = {}
|
||||||
open_id_by_market_event: dict[str, str] = {}
|
open_id_by_market_event: dict[str, str] = {}
|
||||||
for ev in open_events:
|
for ev in open_events:
|
||||||
p = _payload(ev)
|
p = _payload(ev)
|
||||||
|
cstake = _finite_float(p.get("challenger_stake"))
|
||||||
|
opened_at = _finite_float(ev.get("timestamp"))
|
||||||
|
if cstake is None or opened_at is None:
|
||||||
|
continue
|
||||||
did = _dispute_id(ev)
|
did = _dispute_id(ev)
|
||||||
challenger = ev.get("node_id") or ""
|
challenger = ev.get("node_id") or ""
|
||||||
try:
|
|
||||||
cstake = float(p.get("challenger_stake") or 0.0)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
cstake = 0.0
|
|
||||||
opened_at = float(ev.get("timestamp") or 0.0)
|
|
||||||
disputes[did] = DisputeView(
|
disputes[did] = DisputeView(
|
||||||
dispute_id=did, market_id=str(market_id),
|
dispute_id=did, market_id=str(market_id),
|
||||||
challenger_id=str(challenger), challenger_stake=cstake,
|
challenger_id=str(challenger), challenger_stake=cstake,
|
||||||
@@ -120,11 +130,8 @@ def collect_disputes(
|
|||||||
rep_type = p.get("rep_type")
|
rep_type = p.get("rep_type")
|
||||||
if rep_type not in ("oracle", "common"):
|
if rep_type not in ("oracle", "common"):
|
||||||
continue
|
continue
|
||||||
try:
|
amount = _finite_float(p.get("amount"))
|
||||||
amount = float(p.get("amount") or 0.0)
|
if amount is None or amount <= 0:
|
||||||
except (TypeError, ValueError):
|
|
||||||
continue
|
|
||||||
if amount <= 0:
|
|
||||||
continue
|
continue
|
||||||
record = {
|
record = {
|
||||||
"node_id": ev.get("node_id") or "",
|
"node_id": ev.get("node_id") or "",
|
||||||
@@ -134,7 +141,8 @@ def collect_disputes(
|
|||||||
target = disputes[did].confirm_stakes if side == "confirm" else disputes[did].reverse_stakes
|
target = disputes[did].confirm_stakes if side == "confirm" else disputes[did].reverse_stakes
|
||||||
target.append(record)
|
target.append(record)
|
||||||
|
|
||||||
# Resolution events.
|
# Resolution events. Invalid resolution timestamps fail closed:
|
||||||
|
# they cannot exert consensus authority through resolved_outcome.
|
||||||
for ev in chain_list:
|
for ev in chain_list:
|
||||||
if ev.get("event_type") != "dispute_resolve":
|
if ev.get("event_type") != "dispute_resolve":
|
||||||
continue
|
continue
|
||||||
@@ -145,8 +153,11 @@ def collect_disputes(
|
|||||||
outcome = p.get("outcome")
|
outcome = p.get("outcome")
|
||||||
if outcome not in ("upheld", "reversed", "tie"):
|
if outcome not in ("upheld", "reversed", "tie"):
|
||||||
continue
|
continue
|
||||||
|
resolved_at = _finite_float(ev.get("timestamp"))
|
||||||
|
if resolved_at is None:
|
||||||
|
continue
|
||||||
disputes[did].resolved_outcome = outcome
|
disputes[did].resolved_outcome = outcome
|
||||||
disputes[did].resolved_at = float(ev.get("timestamp") or 0.0)
|
disputes[did].resolved_at = resolved_at
|
||||||
|
|
||||||
return sorted(disputes.values(), key=lambda d: (d.opened_at, d.dispute_id))
|
return sorted(disputes.values(), key=lambda d: (d.opened_at, d.dispute_id))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"""Regression coverage for malformed and non-finite dispute numerics."""
|
||||||
|
|
||||||
|
from services.infonet.markets.dispute import collect_disputes
|
||||||
|
|
||||||
|
|
||||||
|
def _open_event(
|
||||||
|
*, dispute_id: str, market_id: str, timestamp: object, challenger_stake: object
|
||||||
|
) -> dict:
|
||||||
|
return {
|
||||||
|
"event_type": "dispute_open",
|
||||||
|
"event_id": dispute_id,
|
||||||
|
"node_id": "challenger",
|
||||||
|
"timestamp": timestamp,
|
||||||
|
"payload": {
|
||||||
|
"market_id": market_id,
|
||||||
|
"challenger_stake": challenger_stake,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_malformed_authoritative_open_events_are_excluded() -> None:
|
||||||
|
chain = [
|
||||||
|
_open_event(
|
||||||
|
dispute_id="bad-time",
|
||||||
|
market_id="market-1",
|
||||||
|
timestamp="not-a-timestamp",
|
||||||
|
challenger_stake=3.0,
|
||||||
|
),
|
||||||
|
_open_event(
|
||||||
|
dispute_id="bad-stake",
|
||||||
|
market_id="market-1",
|
||||||
|
timestamp=10.0,
|
||||||
|
challenger_stake=float("nan"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
assert collect_disputes("market-1", chain) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_economic_and_resolution_values_fail_closed() -> None:
|
||||||
|
chain = [
|
||||||
|
_open_event(
|
||||||
|
dispute_id="dispute-1",
|
||||||
|
market_id="market-1",
|
||||||
|
timestamp=10.0,
|
||||||
|
challenger_stake=3.0,
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"event_type": "dispute_stake",
|
||||||
|
"node_id": "oracle-1",
|
||||||
|
"timestamp": 11.0,
|
||||||
|
"payload": {
|
||||||
|
"dispute_id": "dispute-1",
|
||||||
|
"side": "confirm",
|
||||||
|
"rep_type": "oracle",
|
||||||
|
"amount": float("nan"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event_type": "dispute_resolve",
|
||||||
|
"timestamp": float("inf"),
|
||||||
|
"payload": {
|
||||||
|
"dispute_id": "dispute-1",
|
||||||
|
"outcome": "reversed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
dispute = collect_disputes("market-1", chain)[0]
|
||||||
|
|
||||||
|
assert dispute.confirm_stakes == []
|
||||||
|
assert dispute.resolved_outcome is None
|
||||||
|
assert dispute.resolved_at is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_collect_disputes_preserves_finite_numeric_strings() -> None:
|
||||||
|
chain = [
|
||||||
|
_open_event(
|
||||||
|
dispute_id="dispute-2",
|
||||||
|
market_id="market-2",
|
||||||
|
timestamp="12.5",
|
||||||
|
challenger_stake="3.5",
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"event_type": "dispute_stake",
|
||||||
|
"node_id": "oracle-2",
|
||||||
|
"payload": {
|
||||||
|
"dispute_id": "dispute-2",
|
||||||
|
"side": "reverse",
|
||||||
|
"rep_type": "oracle",
|
||||||
|
"amount": "2.25",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"event_type": "dispute_resolve",
|
||||||
|
"timestamp": "13.5",
|
||||||
|
"payload": {
|
||||||
|
"dispute_id": "dispute-2",
|
||||||
|
"outcome": "reversed",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
dispute = collect_disputes("market-2", chain)[0]
|
||||||
|
|
||||||
|
assert dispute.challenger_stake == 3.5
|
||||||
|
assert dispute.opened_at == 12.5
|
||||||
|
assert dispute.reverse_stakes[0]["amount"] == 2.25
|
||||||
|
assert dispute.resolved_outcome == "reversed"
|
||||||
|
assert dispute.resolved_at == 13.5
|
||||||
Reference in New Issue
Block a user