Logo
Nuevos usuarios de RPC: 35% de descuento el primer mesVer oferta
OnFinality Learn
Solución de problemas de RPC12 min de lectura

Objeto de error JSON-RPC: decodificar code, message, data

Aprende el contrato exacto del objeto de error JSON-RPC 2.0 y cómo clasificar cualquier fallo de RPC por code, message y data.

TL;DR

El objeto de error JSON-RPC 2.0 tiene exactamente tres miembros: code (entero, obligatorio), message (cadena, obligatorio) y data (opcional, sin restricciones). La especificación predefine cinco códigos desde -32700 hasta -32603 y reserva -32000 a -32768 para errores de servidor definidos por la implementación. Un cliente correcto distingue una respuesta de error de un resultado null exitoso y de un fallo de transporte, clasifica los códigos en acciones de reintentar, corregir o abortar, y nunca analiza el mensaje legible por humanos. Este artículo muestra el contrato, un decodificador ejecutable en Node.js, el mapeo de id en lotes y una tabla de resultados que completas contra tu propio endpoint.

El contrato del objeto de error JSON-RPC 2.0

La especificación JSON-RPC 2.0 define el objeto de error en la Sección 5.1 con tres miembros: code, message y data. Solo code y message son obligatorios; data es opcional y la especificación deja deliberadamente su tipo y significado sin restricciones. Un objeto de respuesta debe contener result o error, nunca ambos, y el miembro id debe coincidir con la solicitud.

Dado que result y error son mutuamente excluyentes, la presencia de la clave error es la señal normativa de fallo. El miembro code es un entero y es el único discriminador estable sobre el que un cliente puede ramificar. El miembro message es una cadena corta legible por humanos, y la especificación señala explícitamente que está destinada a desarrolladores en lugar de usuarios finales.

Este contrato es independiente del transporte. Ya sea que llames a un nodo de Ethereum a través del endpoint de la red Ethereum de OnFinality o cualquier otro servicio JSON-RPC, la misma forma de tres miembros aplica. El comportamiento específico del proveedor más allá de los códigos predefinidos se documenta por proveedor y varía según el proveedor.

  • code: entero, obligatorio, el discriminador estable legible por máquina.
  • message: cadena, obligatorio, legible por humanos y no normativo para la lógica.
  • data: opcional, sin restricciones, la vía de escape para detalles estructurados.
  • result y error son mutuamente excluyentes en un único objeto de respuesta.

Códigos predefinidos y el rango de servidor reservado

La Sección 5.1.1 de la especificación JSON-RPC 2.0 predefine cinco códigos de error. -32700 es Parse error, lo que significa que se recibió JSON inválido. -32600 es Invalid Request, lo que significa que el JSON era válido pero no un objeto Request válido. -32601 es Method not found. -32602 es Invalid params. -32603 es Internal error.

La especificación también reserva el rango -32000 a -32768 para errores de servidor definidos por la implementación. Esta es la advertencia crítica: los códigos dentro de ese rango no tienen significado entre proveedores. Un nodo puede reutilizar -32000 para muchas condiciones distintas, y los proveedores pueden agregar sus propios códigos fuera del conjunto predefinido. Por lo tanto, las comparaciones de códigos entre proveedores solo son confiables para los cinco códigos predefinidos.

La especificación JSON-RPC de Ethereum se basa en esta base y documenta el comportamiento de error específico de Ethereum, incluida la ubicación de los datos de revert. Trata la especificación de Ethereum como autoritativa para la semántica de Ethereum y la especificación JSON-RPC 2.0 como autoritativa para el sobre.

  • -32700 Parse error: JSON inválido.
  • -32600 Invalid Request: JSON válido, objeto Request inválido.
  • -32601 Method not found.
  • -32602 Invalid params.
  • -32603 Internal error.
  • -32000 a -32768: reservado para errores de servidor definidos por la implementación.

Distinguir respuestas de error de resultados null y fallos de transporte

El error de cliente más común es tratar result: null y error: {...} como el mismo fallo. No lo son. Una llamada exitosa puede devolver legítimamente null — por ejemplo, una búsqueda de bloque para una altura que aún no existe — y eso es un éxito, no un error. Un fallo de transporte es una capa completamente diferente: la solicitud HTTP puede fallar, agotar el tiempo de espera o devolver un cuerpo no JSON antes de que exista cualquier sobre JSON-RPC.

Un type guard correcto verifica tres cosas en orden: si el transporte tuvo éxito, si el cuerpo se analizó como JSON y si el objeto analizado contiene un miembro error. Solo la tercera condición es un error JSON-RPC. Este orden evita que clasifiques erróneamente una interrupción de red como un error de protocolo o un resultado null como un fallo.

La misma disciplina se aplica cuando lees razones de revert y errores personalizados: el detalle del revert vive dentro del data del objeto de error, pero solo después de que hayas confirmado que estás viendo un objeto de error.

function classifyResponse(httpOk, bodyText) {
  if (!httpOk) return { kind: 'transport', retryable: true };
  let parsed;
  try {
    parsed = JSON.parse(bodyText);
  } catch (e) {
    return { kind: 'transport', retryable: true, reason: 'non-json body' };
  }
  if (parsed && typeof parsed === 'object' && 'error' in parsed) {
    return { kind: 'jsonrpc-error', error: parsed.error, id: parsed.id };
  }
  if (parsed && typeof parsed === 'object' && 'result' in parsed) {
    return { kind: 'success', result: parsed.result, id: parsed.id };
  }
  return { kind: 'malformed', retryable: false };
}

Mapear rangos de código a acciones de reintentar, corregir o abortar

Una vez que hayas confirmado un objeto de error, el code determina la acción. Los errores de análisis (-32700) son errores del cliente: tu serializador produjo JSON inválido, y reintentar la misma carga útil fallará idénticamente. Invalid Request (-32600) también es un error del cliente en el sobre de la solicitud. Ninguno es reintentable.

-32601 Method not found y -32602 Invalid params casi siempre son un nombre de método incorrecto o un array de params mal formado. Estos se corrigen con un cambio de código, no reintentando. -32603 Internal error es el ambiguo: puede ser transitorio, así que reintenta una vez y luego expón el error. Los códigos en el rango -32000 son específicos del nodo o de la cadena y deben manejarse por cadena, porque el mismo código numérico puede significar cosas diferentes en distintos proveedores.

Esta clasificación es la capa genérica que subyace a los decodificadores específicos de cadena como decodificación de errores de simulateTransaction en Solana. La capa genérica decide si reintentar; la capa específica de cadena decide qué significa el fallo.

  • -32700, -32600: error del cliente, nunca reintentar, corregir la carga útil.
  • -32601, -32602: método o params incorrectos, corregir en el código, no reintentar.
  • -32603: reintentar una vez, luego exponer al llamador.
  • rango -32000: específico de cadena o nodo, manejar por cadena.
  • códigos desconocidos: exponer con contexto completo en lugar de adivinar.

Por qué message nunca debe analizarse y code es la única clave estable

El miembro message es texto humano. Los proveedores lo localizan, lo envuelven o lo reescriben, y la especificación no restringe su redacción. Cualquier cliente que haga coincidencia de cadenas en message queda acoplado a la redacción de un proveedor y se romperá cuando esa redacción cambie. El miembro code es el único discriminador estable.

La advertencia es que los proveedores reutilizan -32000 para muchas condiciones distintas y pueden agregar sus propios códigos fuera de la especificación. Eso significa que code es estable dentro del conjunto documentado de un proveedor, pero no necesariamente comparable entre proveedores. Registra el message para humanos, ramifica según el code para la lógica y mantén una tabla de mapeo por proveedor para el rango -32000.

Si necesitas comparar el comportamiento entre proveedores, restringe tu lógica automatizada a los cinco códigos predefinidos y trata todo lo demás como específico del proveedor. Esta es la misma separación de responsabilidades que aplicas al razonar sobre idempotencia JSON-RPC y seguridad ante solicitudes duplicadas: el protocolo garantiza el sobre, no la semántica del proveedor.

El campo data como vía de escape para detalles estructurados

El miembro data es donde las implementaciones colocan detalles estructurados que no caben en code o message. Para Ethereum, una cadena de revert o un error personalizado codificado en ABI se coloca comúnmente allí. La especificación no requiere que data esté presente, por lo que un cliente nunca debe asumir que existe.

La consecuencia práctica es que tu decodificador debe tratar data como opcional y validar su forma antes de usarlo. Si esperas un error personalizado codificado en ABI, verifica que data sea una cadena hexadecimal de longitud suficiente antes de intentar decodificarla. Si está ausente, recurre a code y message para la clasificación.

Cuando simulas llamadas con sobrescrituras de estado de eth_call, el mismo campo data transporta el detalle del revert, por lo que la ruta de decodificación se comparte entre llamadas en vivo y simulaciones.

function extractRevertData(error) {
  if (!error || typeof error !== 'object') return null;
  const d = error.data;
  if (typeof d === 'string' && /^0x[0-9a-fA-F]*$/.test(d)) return d;
  if (d && typeof d === 'object' && typeof d.data === 'string') return d.data;
  return null;
}

Ejemplo ejecutable en Node.js: fetch sin procesar versus error de biblioteca envuelto

Las bibliotecas ocultan el objeto de error sin procesar. El ejemplo a continuación realiza una llamada fetch sin procesar para que puedas ver el sobre de error JSON-RPC exacto, luego muestra cómo un error de biblioteca envuelto típicamente anida los mismos campos. Ejecútalo contra tu propio endpoint para observar la forma real que devuelve tu proveedor.

Reemplaza la URL del endpoint con la tuya. El objetivo no es producir un resultado específico sino revelar la diferencia entre la respuesta de transporte y la abstracción de la biblioteca, para que puedas decidir en qué capa debe vivir tu manejo de errores.

const endpoint = process.env.RPC_URL || 'https://your-endpoint.example';

async function rawCall() {
  const res = await fetch(endpoint, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'eth_getBalance',
      params: ['0x0000000000000000000000000000000000000000', 'latest']
    })
  });
  const text = await res.text();
  console.log('http status:', res.status);
  console.log('raw body:', text);
  try {
    const parsed = JSON.parse(text);
    if (parsed.error) {
      console.log('code:', parsed.error.code);
      console.log('message:', parsed.error.message);
      console.log('data present:', 'data' in parsed.error);
    }
  } catch (e) {
    console.log('body was not JSON');
  }
}

rawCall().catch((e) => console.error('transport failure:', e.message));

Respuestas por lotes y mapeo de errores a solicitudes por id

Una solicitud por lotes envía un array de objetos de solicitud y recibe un array de objetos de respuesta. Algunas entradas pueden llevar result y otras pueden llevar error, y cada entrada lleva su propio id. No se garantiza que el orden del array coincida con el orden de la solicitud, por lo que debes mapear por id en lugar de por posición.

Construye un mapa de id a solicitud antes de enviar, luego itera el array de respuesta y adjunta cada resultado o error a su solicitud de origen. Esta es la única forma confiable de saber qué llamada falló cuando un lote tiene éxito parcialmente.

Si estás agrupando escrituras, revisa idempotencia JSON-RPC y seguridad ante solicitudes duplicadas antes de reintentar entradas fallidas, porque un reintento de un lote parcialmente aplicado puede duplicar efectos.

function mapBatch(requests, responses) {
  const byId = new Map(requests.map((r) => [r.id, r]));
  return responses.map((resp) => {
    const req = byId.get(resp.id);
    if (resp.error) {
      return { id: resp.id, method: req && req.method, status: 'error', error: resp.error };
    }
    return { id: resp.id, method: req && req.method, status: 'ok', result: resp.result };
  });
}

Tabla de resultados: medir el comportamiento de errores contra tu propio endpoint

La tabla a continuación es una plantilla de medición reproducible. Complétala enviando cada solicitud a tu propio endpoint y registrando lo que devuelve. No confíes en los números de este artículo; el punto es observar el comportamiento real de tu proveedor.

Ejecuta cada solicitud al menos dos veces para distinguir errores deterministas de transitorios. Registra si data está presente, porque eso determina si puedes decodificar detalles estructurados. Luego asigna una clasificación y una acción usando los rangos anteriores.

  • Solicitud: el método JSON-RPC exacto y los params que enviaste.
  • Code: el entero de error.code.
  • Message: la cadena de error.message, registrada textualmente.
  • ¿Data presente?: sí o no, y su tipo si está presente.
  • Clasificación: parse, invalid request, method, params, internal o rango de servidor.
  • Acción: reintentar, corregir o abortar, con la razón.

Limitaciones y compensaciones de la decodificación genérica de errores

La especificación deja deliberadamente el rango -32000 a las implementaciones, por lo que las comparaciones de códigos entre proveedores solo son confiables para el conjunto predefinido. Cualquier lógica que asuma un significado específico de -32000 estará acoplada al proveedor y puede romperse cuando cambies de endpoint o cuando un proveedor cambie su mapeo.

Nunca se debe asumir que data estructurado está presente. Un decodificador que requiera data fallará en proveedores que lo omitan. El patrón seguro es intentar la decodificación estructurada cuando data existe y recurrir a code más message cuando no.

También hay una compensación entre el manejo genérico y el específico de cadena. La clasificación genérica decide reintentar versus corregir; los decodificadores específicos de cadena deciden el significado. Mantener estas capas separadas hace que ambas sean más fáciles de probar, pero significa que mantienes dos mapeos en lugar de uno.

Lista de verificación de solución de problemas para fallos de RPC recurrentes

Cuando los fallos se repiten, recorre las capas en orden. Primero confirma que el transporte tuvo éxito y que el cuerpo se analizó como JSON. Luego confirma que estás viendo un miembro error en lugar de un resultado null. Después lee el code y clasifícalo. Solo después de eso debes inspeccionar data.

Si el código es -32601 o -32602, verifica el nombre del método y el array de params contra la especificación JSON-RPC de Ethereum antes de cambiar cualquier otra cosa. Si el código está en el rango -32000, consulta la documentación de tu proveedor, porque el significado es específico del proveedor.

Para la selección de endpoints y problemas de conectividad que no son errores de protocolo, la guía de endpoints RPC cubre cómo elegir y verificar un endpoint. Para la planificación de capacidad en torno a reintentos, consulta precios de RPC y la descripción general del servicio API.

  • ¿Transporte ok? ¿Cuerpo analizado como JSON?
  • ¿Hay un miembro error, o es un resultado null?
  • ¿Cuál es el code y en qué rango cae?
  • ¿Está data presente y su forma coincide con tu expectativa?
  • ¿El código es específico del proveedor, lo que requiere manejo por proveedor?

Próximos pasos: construir un decodificador de errores reutilizable

Convierte las piezas anteriores en un pequeño módulo: un type guard que separe transporte, éxito y error; un clasificador que mapee códigos a reintentar, corregir o abortar; y un extractor opcional de data para decodificación específica de cadena. Mantén el mapeo de -32000 específico del proveedor en configuración en lugar de en código para que puedas actualizarlo sin un lanzamiento.

Luego conecta el módulo en tus sitios de llamada y registra el objeto de error completo, incluidos code, message y si data estaba presente. Ese registro es lo que hace que la tabla de resultados sea reproducible con el tiempo.

Para un contexto más amplio sobre patrones de uso de RPC, comienza desde el centro de aprendizaje de OnFinality y la página de la red Ethereum. El objetivo es un decodificador que sobreviva a un cambio de proveedor porque ramifica según el contrato del protocolo, no según la redacción de un proveedor.

Nunca te preocupes por la infraestructura nuevamente

OnFinality elimina la carga pesada de DevOps para que puedas construir de forma más inteligente y rápida.

Comenzar