Files
Jude AllredandGitHub 5268819234 Use structured RPC errors in full_node_rpc_api (#20477)
* Use structured RPC errors

* Handle an empty error message edge case

* Add a missing structured error in a location introduced during rebase

* Test repair

* PR iterations -- Tidy structured error type.

-- rename structuredError.structuredMessage to message
-- remove the legacy 'message' field from structuredError.data, allow the top level 'error' string to preserve that for us.
-- remove the 'originalError' field from structuredError
-- rename structured_errors to rpc_errors

* Update a test funnction name

* Tidy extraneous changes

* Add tests entirely for the purpose of increasing code coverage

* Use RpcError.simple everywhere we can

* Bug Fix: We must check that self.service is defined _before_ we access its properties.

* Add code coverage for all RPC errors

* Dead code elimination

* Test coverage touchups

* Narrow PR to only provide structured logs for full_node_rpc_api

* Condense tests

* Tidy rpc_errors.py and remove the (as-yet) unused error codes

* Strengthen rpc_errors type

* Improve fidelity in converting other Chia error classes into RPC errors

- Extract the underlying error code when possible
- Check the instance type of the error and map that to an RPC error of the same type.
2026-02-23 09:44:06 -06:00

100 lines
3.6 KiB
Python

from __future__ import annotations
import logging
import traceback
from collections.abc import Awaitable, Callable
from typing import TYPE_CHECKING, Any, get_type_hints
import aiohttp
from chia.rpc.rpc_errors import structured_error_from_exception
from chia.util.json_util import obj_to_response
from chia.util.streamable import Streamable
from chia.wallet.util.blind_signer_tl import BLIND_SIGNER_TRANSLATION
from chia.wallet.util.clvm_streamable import (
TranslationLayer,
json_deserialize_with_clvm_streamable,
json_serialize_with_clvm_streamable,
)
log = logging.getLogger(__name__)
# TODO: consolidate this with chia.rpc.rpc_server.Endpoint
# Not all endpoints only take a dictionary so that definition is imperfect
# This definition is weaker than that one however because the arguments can be anything
RpcEndpoint = Callable[..., Awaitable[dict[str, Any]]]
MarshallableRpcEndpoint = Callable[..., Awaitable[Streamable]]
if TYPE_CHECKING:
from chia.rpc.rpc_server import EndpointResult
ALL_TRANSLATION_LAYERS: dict[str, TranslationLayer] = {"CHIP-0028": BLIND_SIGNER_TRANSLATION}
def marshal(func: MarshallableRpcEndpoint) -> RpcEndpoint:
hints = get_type_hints(func)
request_hint = hints["request"]
assert issubclass(request_hint, Streamable)
request_class = request_hint
async def rpc_endpoint(self: object, request: dict[str, Any], *args: object, **kwargs: object) -> EndpointResult:
response_obj: Streamable = await func(
self,
(
request_class.from_json_dict(request)
if not request.get("CHIP-0029", False)
else json_deserialize_with_clvm_streamable(
request,
request_hint,
translation_layer=(
ALL_TRANSLATION_LAYERS[request["translation"]] if "translation" in request else None
),
)
),
*args,
**kwargs,
)
if not request.get("CHIP-0029", False):
return response_obj.to_json_dict()
else:
response_dict = json_serialize_with_clvm_streamable(
response_obj,
translation_layer=(
ALL_TRANSLATION_LAYERS[request["translation"]] if "translation" in request else None
),
)
if isinstance(response_dict, str): # pragma: no cover
raise ValueError("Internal Error. Marshalled endpoint was made with clvm_streamable.")
return response_dict
rpc_endpoint.__name__ = func.__name__
return rpc_endpoint
def wrap_http_handler(
f: Callable[[dict[str, Any]], Awaitable[EndpointResult]],
route: str,
) -> Callable[[aiohttp.web.Request], Awaitable[aiohttp.web.StreamResponse]]:
async def inner(request: aiohttp.web.Request) -> aiohttp.web.StreamResponse:
request_data = await request.json()
try:
res_object = await f(request_data)
if res_object is None:
res_object = {}
if "success" not in res_object:
res_object["success"] = True
except Exception as e:
tb = traceback.format_exc()
log.warning(f"Error while handling message for {route}: {tb}")
error_message, structured = structured_error_from_exception(e)
res_object = {
"success": False,
"error": error_message,
"traceback": tb,
"structuredError": structured,
}
return obj_to_response(res_object)
return inner