mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-26 23:19:07 +00:00
fix(telemetry): Reject a JSON reply or a weights argument that is not an object
Both workload scripts decoded JSON and went straight to .get() or .items(). An array or a scalar decodes fine and then raises AttributeError, which is not a ValueError, so the handler around the weights parsing could not catch it and the operator saw a traceback naming neither the command nor what arrived. Four sites across the two files, all four guarded, and the weights handler now also catches TypeError for a non-numeric weight value. Negative weights are left alone: they do not raise, the type is dropped from the mix silently, and changing that changes accepted input.
This commit is contained in:
@@ -411,6 +411,14 @@ async def _recv_matching_reply(
|
||||
raise asyncio.TimeoutError(f"{command} (id {request_id}) got no reply")
|
||||
raw = await asyncio.wait_for(conn.ws.recv(), timeout=remaining)
|
||||
reply = json.loads(raw)
|
||||
# A reply that decodes to an array or a scalar has no .get(), and the
|
||||
# AttributeError that follows names neither the command nor what
|
||||
# arrived. Reject it here so the message carries both.
|
||||
if not isinstance(reply, dict):
|
||||
raise TypeError(
|
||||
f"{command} (id {request_id}) got a JSON "
|
||||
f"{type(reply).__name__} reply, expected an object"
|
||||
)
|
||||
# A reply with no id counts as this request's: a few xrpld error paths
|
||||
# answer before the id is parsed, and treating those as stale would
|
||||
# turn a reported error into a timeout.
|
||||
@@ -769,6 +777,19 @@ def main() -> None:
|
||||
if args.weights:
|
||||
try:
|
||||
custom = json.loads(args.weights)
|
||||
# A JSON array or scalar decodes fine and then has no .items().
|
||||
# AttributeError is not a ValueError, so the handler below would not
|
||||
# catch it: report the type that arrived instead of a traceback.
|
||||
if not isinstance(custom, dict):
|
||||
logger.error(
|
||||
"Invalid --weights: expected a JSON object of command to "
|
||||
"weight, got a JSON %s: %s",
|
||||
type(custom).__name__,
|
||||
args.weights,
|
||||
)
|
||||
sys.exit(1)
|
||||
# TypeError covers a non-numeric weight value (null, a list, an
|
||||
# object), which int() rejects with TypeError rather than ValueError.
|
||||
weights = {k: int(v) for k, v in custom.items()}
|
||||
if not weights or sum(weights.values()) <= 0:
|
||||
logger.error(
|
||||
@@ -777,7 +798,7 @@ def main() -> None:
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Using custom weights: %s", weights)
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
||||
logger.error("Invalid --weights JSON: %s", exc)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ async def ws_request(
|
||||
Raises:
|
||||
TimeoutError: If no reply carrying this request's ``id`` arrived within
|
||||
RECV_TIMEOUT_S.
|
||||
TypeError: If a reply decodes to something other than a JSON object.
|
||||
"""
|
||||
request_id = next(_request_ids)
|
||||
request: dict[str, Any] = {"command": command}
|
||||
@@ -311,6 +312,14 @@ async def ws_request(
|
||||
raise TimeoutError(f"{command} (id {request_id}) got no reply")
|
||||
raw = await asyncio.wait_for(ws.recv(), timeout=remaining)
|
||||
resp = json.loads(raw)
|
||||
# A reply that decodes to an array or a scalar has no .get(), and the
|
||||
# AttributeError that follows names neither the command nor what
|
||||
# arrived. Reject it here so the message carries both.
|
||||
if not isinstance(resp, dict):
|
||||
raise TypeError(
|
||||
f"{command} (id {request_id}) got a JSON "
|
||||
f"{type(resp).__name__} reply, expected an object"
|
||||
)
|
||||
reply_id = resp.get("id")
|
||||
if reply_id is None or reply_id == request_id:
|
||||
break
|
||||
@@ -1211,6 +1220,19 @@ def main() -> None:
|
||||
if args.weights:
|
||||
try:
|
||||
custom = json.loads(args.weights)
|
||||
# A JSON array or scalar decodes fine and then has no .items().
|
||||
# AttributeError is not a ValueError, so the handler below would not
|
||||
# catch it: report the type that arrived instead of a traceback.
|
||||
if not isinstance(custom, dict):
|
||||
logger.error(
|
||||
"Invalid --weights: expected a JSON object of transaction "
|
||||
"type to weight, got a JSON %s: %s",
|
||||
type(custom).__name__,
|
||||
args.weights,
|
||||
)
|
||||
sys.exit(1)
|
||||
# TypeError covers a non-numeric weight value (null, a list, an
|
||||
# object), which int() rejects with TypeError rather than ValueError.
|
||||
weights = {k: int(v) for k, v in custom.items()}
|
||||
if not weights or sum(weights.values()) <= 0:
|
||||
logger.error(
|
||||
@@ -1219,7 +1241,7 @@ def main() -> None:
|
||||
)
|
||||
sys.exit(1)
|
||||
logger.info("Using custom weights: %s", weights)
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
except (json.JSONDecodeError, TypeError, ValueError) as exc:
|
||||
logger.error("Invalid --weights JSON: %s", exc)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user