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
2026-08-14 18:30:22 -06:00
committed by GitHub
parent 072bd29445
commit e9e33ca637
2 changed files with 63 additions and 14 deletions
@@ -30,28 +30,43 @@ specific branches in ``resolution.py`` that match the spec's
from __future__ import annotations
from typing import Iterable
import math
from typing import Any, Iterable
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:
"""Current burn percentage from CONFIG. Helper so callers don't
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]:
"""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
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:
return 0.0, float(amount)
return 0.0, amt
if pct >= 1:
return float(amount), 0.0
burn = float(amount) * pct
returned = float(amount) - burn
return amt, 0.0
burn = amt * pct
returned = amt - burn
return burn, returned
@@ -67,18 +82,17 @@ def apply_to_stakes(
folds these into the larger ``ResolutionResult`` rather than
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] = {}
total_burned = 0.0
for s in stakes:
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)
amount = s.get("amount") if isinstance(s, dict) else getattr(s, "amount", None)
try:
amt = float(amount) if amount is not None else 0.0
except (TypeError, ValueError):
amt = 0.0
if amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
amt = _finite_float(amount)
if amt is None or amt <= 0 or not isinstance(node_id, str) or rep_type not in ("oracle", "common"):
continue
burn, ret = split_burn_and_return(amt, pct)
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"))