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

Meta de getTransaction de Solana: decodificar innerInstructions y err

Decodifica el objeto meta de una transacción confirmada de Solana, pliega innerInstructions sobre las instrucciones externas y atribuye los fallos al programa correcto.

TL;DR

Después de enviar una transacción, getTransaction devuelve un objeto meta que contiene el resultado autoritativo posterior a la ejecución: err, logMessages, innerInstructions, deltas de saldo y uso de cómputo. El campo err puede ser una cadena simple o un objeto como { InstructionError: [index, reason] }, donde index apunta a la instrucción externa en el mensaje, no a una instrucción interna. innerInstructions[].index usa la misma indexación de instrucciones externas, por lo que debes plegar cada lista interna sobre su instrucción externa padre antes de poder nombrar el programa que falló. Este artículo recorre los campos de meta, la regla de correlación de índices, las formas de err y un decodificador ejecutable con @solana/web3.js que imprime un árbol de instrucciones correlacionado. También cubre los modos de fallo que producen resultados nulos o errores engañosos, y cómo medir el comportamiento contra tu propio endpoint.

Por qué getTransaction es el método de decodificación posterior al envío

getTransaction es el método JSON-RPC de Solana que devuelve una transacción confirmada por firma, incluido su objeto meta. Es la herramienta correcta una vez que se ha enviado una transacción y necesitas el resultado de ejecución autoritativo. El artículo hermano sobre Decodificación de errores de simulateTransaction en Solana cubre la simulación previa al envío, donde el nodo ejecuta contra el estado actual sin confirmar; getTransaction, en cambio, informa lo que realmente ocurrió en la cadena.

La forma de la solicitud importa. Una llamada mínima pasa la firma y un objeto de configuración con encoding, commitment y maxSupportedTransactionVersion. El encoding controla cómo se devuelven las instrucciones y las claves de cuenta; jsonParsed es cómodo para programas estándar, mientras que json o base64 preservan los datos compilados sin procesar. El nivel de commitment determina de qué estado del ledger lee el nodo, y la semántica de processed, confirmed y finalized se cubre en Niveles de commitment de Solana: processed vs confirmed vs finalized.

Omitir maxSupportedTransactionVersion es una causa común de fallo en transacciones versionadas (v0). La documentación RPC de Solana para getTransaction indica que el campo es obligatorio cuando la transacción usa un mensaje versionado; sin él, el nodo no puede saber qué formato de mensaje decodificar y devuelve un error en lugar de un resultado. Configúralo siempre en 0 a menos que tengas una razón específica para solicitar una versión compatible diferente.

  • Usa getTransaction después del envío; usa simulateTransaction antes del envío.
  • Configura encoding en jsonParsed para instrucciones legibles, o base64 para fidelidad sin procesar.
  • Configura commitment explícitamente para saber qué estado del ledger estás leyendo.
  • Configura maxSupportedTransactionVersion en 0 para transacciones v0.
curl -s https://api.mainnet-beta.solana.com -X POST -H 'Content-Type: application/json' -d '{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransaction",
  "params": [
    "YOUR_SIGNATURE_HERE",
    { "encoding": "jsonParsed", "commitment": "confirmed", "maxSupportedTransactionVersion": 0 }
  ]
}'

El objeto meta campo por campo

El objeto meta es el resumen posterior a la ejecución. La documentación RPC de Solana para getTransaction define sus campos, y cada uno responde a una pregunta diferente. err te dice si la ejecución falló y, de ser así, cómo. status es un resumen de confirmación más reciente que puede contener Ok o Err. fee informa los lamports cobrados. preBalances y postBalances dan el delta de lamports por índice de cuenta, mientras que preTokenBalances y postTokenBalances hacen lo mismo para cuentas de tokens SPL.

logMessages es el flujo ordenado de logs del programa, incluidas líneas como 'Program log:' y 'Program ... failed'. loadedAddresses enumera las cuentas de la tabla de direcciones cargadas para transacciones versionadas, lo cual importa porque los índices de cuentas de instrucción pueden referirse a ellas. computeUnitsConsumed informa el presupuesto de cómputo utilizado. innerInstructions contiene las invocaciones entre programas (CPI) desencadenadas por instrucciones externas. rewards enumera recompensas de staking o votación cuando la transacción toca esas rutas.

Ningún campo por sí solo es suficiente. err nombra el fallo pero no siempre el programa; logMessages da contexto narrativo pero puede truncarse; innerInstructions da estructura pero requiere correlación de índices. Trata meta como un conjunto de señales corroborantes y concílialas antes de presentar una causa al usuario.

  • err: indicador de fallo, en forma de cadena u objeto.
  • status: resumen de confirmación, Ok o Err.
  • fee: lamports cobrados por la transacción.
  • preBalances / postBalances: deltas de lamports por índice de cuenta.
  • preTokenBalances / postTokenBalances: deltas de tokens por cuenta de token.
  • logMessages: líneas de log del programa en orden.
  • loadedAddresses: cuentas de la tabla de direcciones para transacciones versionadas.
  • computeUnitsConsumed: presupuesto de cómputo utilizado.
  • innerInstructions: CPI agrupadas por índice de instrucción externa.
  • rewards: recompensas de staking o votación cuando corresponda.

La regla de correlación de índices para innerInstructions

La regla de decodificación más importante es que innerInstructions[].index se refiere a la posición de la instrucción externa en el mensaje de la transacción, no a una instrucción interna. Cada entrada agrupa las instrucciones internas que se ejecutaron debido a esa instrucción externa. La documentación RPC de Solana para InnerInstruction y CompiledInstruction establece esta indexación. Para atribuir un fallo, debes plegar cada lista interna sobre su instrucción externa padre.

Una forma práctica de pensarlo: la instrucción externa en el índice i del mensaje posee las instrucciones internas listadas en innerInstructions donde index es igual a i. Si un programa de wallet aparece como instrucción interna, fue invocado por la instrucción externa en ese índice, no por la primera instrucción de la transacción por defecto. Por eso los analizadores ingenuos que leen innerInstructions en orden y asumen que pertenecen a la primera instrucción atribuyen mal los fallos.

Cuando construyas un árbol correlacionado, recorre las instrucciones del mensaje en orden, adjunta la lista interna correspondiente a cada una y luego busca en el conjunto combinado el id del programa que falló. El programa que falló es el nombrado en la línea de log 'Program ... failed' y, cuando está presente, en el índice de InstructionError. Verifica ambos antes de decirle a un usuario qué programa rechazó la transacción.

  • innerInstructions[].index es un índice de instrucción externa en el mensaje.
  • Las instrucciones internas se ejecutan en el rango [index, siguiente index).
  • Pliega las listas internas sobre su instrucción externa padre antes de atribuir.
  • Concilia el id del programa que falló a partir de los logs y del índice de InstructionError.

Formas del objeto err y cómo presentarlas

El campo err tiene dos formas amplias. La forma de cadena aparece para fallos a nivel de transacción como 'AccountInUse' o 'BlockhashNotFound'. Estos no están ligados a una instrucción específica y normalmente indican una condición de envío o de estado más que un rechazo de programa. La forma de objeto aparece para fallos a nivel de instrucción, más comúnmente { InstructionError: [index, reason] }, donde index es la posición de la instrucción externa y reason describe el fallo.

El reason puede estar anidado a su vez. Un fallo definido por el programa suele aparecer como { Custom: n }, donde n es un código de error específico del programa. La documentación RPC de Solana para TransactionError enumera las variantes estándar, y la documentación del propio programa mapea los códigos Custom a significados. Cuando presentes esto a un usuario, traduce el índice externo al id del programa de la instrucción usando el mensaje, y luego traduce el código Custom usando la tabla de errores del programa.

No confundas los errores JSON-RPC del momento del envío con meta.err. Los errores devueltos al enviar, como -32002 Transaction simulation failed, -32003 Transaction signature verification failure y -32005 node is behind, siguen el sobre de objeto de error de JSON-RPC 2.0 con code, message y data, según lo definido en la especificación JSON-RPC 2.0. Esos son errores de nivel de transporte o previos a la aceptación; meta.err es el resultado en cadena.

  • Forma de cadena: condición a nivel de transacción, no específica de instrucción.
  • Forma de objeto: { InstructionError: [index, reason] } para fallos de instrucción.
  • Forma anidada: { Custom: n } para códigos de error definidos por el programa.
  • Los errores de envío usan el sobre code/message/data de JSON-RPC 2.0.

Decodificador ejecutable en Node.js con @solana/web3.js

El siguiente ejemplo obtiene una transacción por firma, lee meta.err, recorre meta.logMessages en busca de líneas de fallo e imprime un árbol de instrucciones correlacionado que nombra el programa que falló. Usa @solana/web3.js y asume un endpoint RPC configurado. Reemplaza el endpoint y la firma con tus propios valores.

El decodificador pliega innerInstructions sobre su instrucción externa padre usando la regla de índices, y luego busca en el conjunto combinado el id del programa nombrado en el log de fallo. Imprime el índice de la instrucción externa, el id del programa y cualquier instrucción interna debajo de él. Esto te da un fallo atribuible en lugar de una cadena de error sin procesar.

const { Connection, PublicKey } = require('@solana/web3.js');

async function decodeTransaction(endpoint, signature) {
  const connection = new Connection(endpoint, 'confirmed');
  const tx = await connection.getTransaction(signature, {
    commitment: 'confirmed',
    maxSupportedTransactionVersion: 0,
  });

  if (!tx) {
    console.log('No transaction found for signature:', signature);
    return;
  }

  const meta = tx.meta;
  console.log('err:', JSON.stringify(meta.err));
  console.log('computeUnitsConsumed:', meta.computeUnitsConsumed);

  const message = tx.transaction.message;
  const accountKeys = message.staticAccountKeys || message.accountKeys;
  const outerInstructions = message.compiledInstructions || message.instructions;

  const innerByIndex = new Map();
  for (const group of meta.innerInstructions || []) {
    innerByIndex.set(group.index, group.instructions);
  }

  const failingPrograms = new Set();
  for (const line of meta.logMessages || []) {
    const failed = line.match(/Program (\S+) failed/);
    if (failed) failingPrograms.add(failed[1]);
  }

  outerInstructions.forEach((ix, outerIndex) => {
    const programId = accountKeys[ix.programIdIndex].toString();
    const inner = innerByIndex.get(outerIndex) || [];
    const isFailing = failingPrograms.has(programId);
    console.log(
      `outer[${outerIndex}] program=${programId}${isFailing ? ' <-- FAILED' : ''}`
    );
    inner.forEach((innerIx, innerIndex) => {
      const innerProgramId = accountKeys[innerIx.programIdIndex].toString();
      const innerFailing = failingPrograms.has(innerProgramId);
      console.log(
        `  inner[${outerIndex}.${innerIndex}] program=${innerProgramId}${innerFailing ? ' <-- FAILED' : ''}`
      );
    });
  });

  if (meta.err && meta.err.InstructionError) {
    const [index, reason] = meta.err.InstructionError;
    const programId = accountKeys[outerInstructions[index].programIdIndex].toString();
    console.log(`InstructionError at outer[${index}] program=${programId} reason=${JSON.stringify(reason)}`);
  }
}

decodeTransaction('YOUR_RPC_ENDPOINT', 'YOUR_SIGNATURE');

Correlacionar logs, instrucciones internas e ids de programa

La correlación de logs es el puente entre el objeto err sin procesar y una explicación humana. Las líneas 'Program log:' muestran lo que imprimió un programa, y la línea 'Program ... failed' nombra el programa que abortó. Como las instrucciones internas se agrupan por índice externo, puedes recorrer el flujo de logs y el árbol de instrucciones juntos para ver qué instrucción externa desencadenó la CPI que falló.

Un procedimiento fiable es localizar primero el id del programa que falló a partir de la línea de log, luego encontrar cada aparición de ese id de programa en el árbol correlacionado y después comprobar si el índice de InstructionError apunta a la instrucción externa que lo posee. Si el programa que falló aparece solo como instrucción interna, la instrucción externa en ese índice es el punto de entrada que lo invocó. Esta distinción importa cuando un programa de wallet o agregador llama a un programa de tokens que rechaza la transferencia.

El Solana Cookbook y el código fuente de @solana/web3.js documentan la ruta de decodificación del lado del cliente para instrucciones internas, mensajes de log e ids de programa por índice. Úsalos como referencia para los nombres de campos y para el diseño de instrucciones compiladas, especialmente cuando alternes entre codificaciones jsonParsed y base64.

  • Encuentra el id del programa que falló a partir de la línea de log 'Program ... failed'.
  • Localiza ese id de programa en el árbol de instrucciones correlacionado.
  • Comprueba si el índice de InstructionError apunta a la instrucción externa propietaria.
  • Distingue un punto de entrada externo de una CPI interna al atribuir la culpa.

Tabla de resultados: mide contra tu propio endpoint

El comportamiento del proveedor varía, así que mide contra tu propio endpoint en lugar de confiar en afirmaciones generales. La siguiente tabla es una plantilla: rellena cada fila con el resultado observado de tu proveedor RPC. No trates ninguna fila como una expectativa fija, porque el comportamiento documentado varía según el proveedor y el nivel de commitment.

Ejecuta la misma firma a través de cada endpoint que uses y registra el resultado. Esto revela diferencias en truncamiento de logs, manejo de nulos y soporte de transacciones versionadas antes de que afecten a producción.

  • Endpoint: la URL RPC que probaste.
  • Commitment: el nivel que solicitaste.
  • Result: objeto de transacción o null.
  • meta.err: el valor de err observado.
  • logMessages count: número de líneas de log devueltas.
  • innerInstructions groups: número de grupos devueltos.
  • maxSupportedTransactionVersion: si la llamada tuvo éxito sin él.
  • Notes: cualquier truncamiento o comportamiento específico del proveedor observado.

Modos de fallo: resultados nulos, commitment y truncamiento

Un resultado nulo es la sorpresa más común. getTransaction devuelve null cuando la firma no se encuentra en el nivel de commitment solicitado, lo que a menudo significa que la transacción aún no está confirmada o que el nodo no se ha puesto al día. Si solicitaste finalized pero la transacción solo está confirmed, puedes ver null hasta la finalización. Reintenta con un commitment más bajo o espera, y considera la guía de reintentos en Tiempos de espera y estrategia de reintentos de RPC de Solana.

Omitir maxSupportedTransactionVersion falla en transacciones v0, como se indicó antes. Un desajuste de commitment produce null o datos obsoletos en lugar de un error explícito, así que registra siempre el commitment que usaste. Algunos proveedores truncan logMessages, lo que puede ocultar la línea 'Program ... failed'; si los logs parecen cortos, verifica con un segundo endpoint o con un explorador de bloques.

La búsqueda de firmas en sí puede paginarse cuando estás escaneando el historial en lugar de obtener una sola transacción. Si primero estás enumerando firmas, consulta Paginación de getSignaturesForAddress de Solana para el patrón de cursor, y luego introduce cada firma en getTransaction.

  • Resultado nulo: no encontrado en el commitment solicitado, o retraso del nodo.
  • Desajuste de commitment: null o datos obsoletos sin un error explícito.
  • Falta maxSupportedTransactionVersion: fallo en transacciones v0.
  • Logs truncados: específico del proveedor, puede ocultar la línea del programa que falló.

Limitaciones y compensaciones

getTransaction te da el resultado en cadena, pero no explica la intención. Un código de error Custom solo tiene sentido con la tabla de errores del programa, y un flujo de logs truncado puede dejar el fallo ambiguo. El objeto meta también refleja el estado en el commitment que solicitaste, por lo que una lectura processed puede diferir de una lectura finalized. No hay un único campo que nombre el programa que falló en todos los casos; la atribución requiere conciliar err, logs e instrucciones internas.

Las compensaciones de rendimiento y disponibilidad dependen del proveedor. Los niveles de commitment más altos son más seguros pero tardan más en devolver, y algunos proveedores limitan las búsquedas históricas o la retención de logs. Para sistemas en producción, cachea los resultados decodificados por firma y almacena el nivel de commitment junto a ellos para que puedas reproducir la vista exacta más tarde. La Guía de la API RPC de Solana (RPC Assistant) cubre la selección de endpoints y la cobertura de métodos, y Precios de RPC describe las diferencias a nivel de plan.

  • Los códigos Custom requieren la tabla de errores del propio programa.
  • Los logs truncados pueden hacer ambigua la atribución.
  • El nivel de commitment cambia lo que refleja el objeto meta.
  • Cachea los resultados decodificados con el nivel de commitment para reproducibilidad.

Lista de verificación para transacciones problemáticas

Cuando una transacción no se decodifica limpiamente, recorre la lista en orden. Primero confirma que la firma es correcta y que la transacción está confirmada en el commitment solicitado. Segundo, añade maxSupportedTransactionVersion si falta. Tercero, compara la longitud de logMessages entre dos endpoints para detectar truncamiento. Cuarto, verifica que tu plegado de instrucciones internas use el índice externo, no el orden interno.

Si el objeto err es una cadena como 'BlockhashNotFound', es probable que la transacción nunca haya llegado a la cadena; trátalo como un problema de envío en lugar de un fallo de programa. Si el err es { InstructionError: [index, reason] } con un { Custom: n } anidado, resuelve el id del programa en ese índice externo y busca n en la documentación del programa. Para transacciones versionadas, recuerda que los índices de cuenta pueden referirse a loadedAddresses, lo cual se cubre en Transacciones versionadas de Solana y análisis de getBlock.

  • Confirma la firma y el commitment antes de decodificar.
  • Añade maxSupportedTransactionVersion para transacciones v0.
  • Compara las longitudes de log entre endpoints para detectar truncamiento.
  • Pliega las instrucciones internas por índice externo, no por orden interno.
  • Resuelve los códigos Custom contra la tabla de errores del programa.

Próximos pasos y lecturas relacionadas

Para profundizar, empieza por el centro de aprendizaje de OnFinality para la pista completa de solución de problemas de RPC, y revisa la página de la red Solana para contexto de endpoints y red. La Guía de la API RPC de Solana (RPC Assistant) cubre detalles a nivel de método, y la página del servicio de API explica cómo conectar endpoints gestionados a tu aplicación.

Para temas de decodificación adyacentes, lee Decodificación de errores de simulateTransaction en Solana para la ruta previa al envío, Niveles de commitment de Solana: processed vs confirmed vs finalized para la semántica de confirmación, y Paginación de getSignaturesForAddress de Solana para el escaneo de historial. En conjunto, cubren el ciclo de vida completo desde el envío hasta la atribución posterior al envío.

  • Centro de aprendizaje: /en/learn
  • Red Solana: /en/networks/solana
  • Guía de RPC Assistant: /en/rpc-assistant/solana-api-guide
  • Servicio de API: /en/api-service
  • Precios de RPC: /en/pricing/rpc

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