fix(infonet): reject non-finite stalemate burn inputs (#514)

* fix(infonet): reject non-finite stalemate burn inputs

* test(infonet): cover non-finite stalemate burn inputs
This commit is contained in:
Sylvester Kaczmarek authored and GitHub committed 2026-08-14 18:30:22 -06:00
1 parent 072bd29445
commit e9e33ca637
2 files changed
+63 -14

No files matched your search

@@ -30,28 +30,43 @@ specific branches in ``resolution.py`` that match the spec's
from __future__ import annotations from __future__ import annotations
from typing import Iterable import math
from typing import Any, Iterable
from services.infonet.config import CONFIG from services.infonet.config import CONFIG
def _finite_float(value: Any) -> float | None:
try:
parsed = float(value)
except (TypeError, ValueError):
return None
return parsed if math.isfinite(parsed) else None
def stalemate_burn_pct() -> float: def stalemate_burn_pct() -> float:
"""Current burn percentage from CONFIG. Helper so callers don't """Current burn percentage from CONFIG. Helper so callers don't
need to remember the key name.""" need to remember the key name."""
return float(CONFIG["resolution_stalemate_burn_pct"]) pct = _finite_float(CONFIG["resolution_stalemate_burn_pct"])
if pct is None:
raise ValueError("resolution_stalemate_burn_pct must be finite")
return pct
def split_burn_and_return(amount: float, burn_pct: float | None = None) -> tuple[float, float]: def split_burn_and_return(amount: float, burn_pct: float | None = None) -> tuple[float, float]:
"""Compute (burn_amount, returned_amount) for a single stake.""" """Compute (burn_amount, returned_amount) for a single stake."""
if amount <= 0: amt = _finite_float(amount)
if amt is None or amt <= 0:
return 0.0, 0.0 return 0.0, 0.0
pct = float(stalemate_burn_pct() if burn_pct is None else burn_pct) pct = _finite_float(stalemate_burn_pct() if burn_pct is None else burn_pct)
if pct is None:
raise ValueError("burn_pct must be finite")
if pct <= 0: if pct <= 0:
return 0.0, float(amount) return 0.0, amt
if pct >= 1: if pct >= 1:
return float(amount), 0.0 return amt, 0.0
burn = float(amount) * pct burn = amt * pct
returned = float(amount) - burn returned = amt - burn
return burn, returned return burn, returned
@@ -67,18 +82,17 @@ def apply_to_stakes(
folds these into the larger ``ResolutionResult`` rather than folds these into the larger ``ResolutionResult`` rather than
mutating any state directly. mutating any state directly.
""" """
pct = float(stalemate_burn_pct() if burn_pct is None else burn_pct) pct = _finite_float(stalemate_burn_pct() if burn_pct is None else burn_pct)
if pct is None:
raise ValueError("burn_pct must be finite")
returns: dict[tuple[str, str], float] = {} returns: dict[tuple[str, str], float] = {}
total_burned = 0.0 total_burned = 0.0
for s in stakes: for s in stakes:
node_id = s.get("node_id") if isinstance(s, dict) else getattr(s, "node_id", None) node_id = s.get("node_id") if isinstance(s, dict) else getattr(s, "node_id", None)
rep_type = s.get("rep_type") if isinstance(s, dict) else getattr(s, "rep_type", None) rep_type = s.get("rep_type") if isinstance(s, dict) else getattr(s, "rep_type", None)
amount = s.get("amount") if isinstance(s, dict) else getattr(s, "amount", None) amount = s.get("amount") if isinstance(s, dict) else getattr(s, "amount", None)
try: amt = _finite_float(amount)
amt = float(amount) if amount is not None else 0.0 if amt is None or amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
except (TypeError, ValueError):
amt = 0.0
if amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
continue continue
burn, ret = split_burn_and_return(amt, pct) burn, ret = split_burn_and_return(amt, pct)
if ret > 0: if ret > 0:
@@ -0,0 +1,35 @@
"""Regression coverage for non-finite stalemate burn inputs."""
import math
import pytest
from services.infonet.markets.stalemate_burn import apply_to_stakes, split_burn_and_return
def test_split_rejects_nonfinite_amounts():
assert split_burn_and_return(float("nan"), 0.1) == (0.0, 0.0)
assert split_burn_and_return(float("inf"), 0.1) == (0.0, 0.0)
def test_apply_skips_nonfinite_stakes_without_poisoning_totals():
returns, burned = apply_to_stakes(
[
{"node_id": "alice", "rep_type": "oracle", "amount": 10.0},
{"node_id": "mallory", "rep_type": "oracle", "amount": float("nan")},
{"node_id": "eve", "rep_type": "common", "amount": float("inf")},
],
burn_pct=0.1,
)
assert returns == {("alice", "oracle"): 9.0}
assert burned == 1.0
assert math.isfinite(burned)
def test_nonfinite_burn_percentage_is_rejected():
with pytest.raises(ValueError, match="burn_pct must be finite"):
split_burn_and_return(10.0, float("nan"))
with pytest.raises(ValueError, match="burn_pct must be finite"):
apply_to_stakes([], burn_pct=float("inf"))