Cuando una transacción de Solana falla en la simulación de preflight, el mensaje de error JSON-RPC 'Transaction simulation failed' contiene un array de registros que señala el fallo. La clave es leer la línea 'Program <PROGRAM_ID> failed: custom program error: 0x<N>' o 'Error processing Instruction N: Program failed to complete', y luego mapear el índice de instrucción a las instrucciones compiladas de la transacción y el código numérico al enum de errores del programa. Este artículo explica el mecanismo, proporciona un script reproducible en Node.js para analizar el error y ofrece una lista de verificación para corregir causas comunes.
Respuesta directa: Cómo leer un error de simulación de Solana
Cuando llamas a simulateTransaction (o sendTransaction con preflight habilitado) en un endpoint RPC de Solana, el nodo ejecuta la transacción contra el estado actual del banco. Si falla, la respuesta JSON-RPC es un objeto de error con message: "Transaction simulation failed" y un array data.logs. La última línea de registro significativa suele indicar exactamente qué salió mal. Para un error de programa, verás Program <PROGRAM_ID> failed: custom program error: 0x<N>. Para un fallo a nivel de runtime (por ejemplo, una instrucción invocó un programa que no pudo completarse), verás Error processing Instruction N: Program failed to complete. El índice de instrucción N se refiere a la posición en las instrucciones compiladas de la transacción (después de cualquier búsqueda en tablas de direcciones para transacciones versionadas). El código hexadecimal 0x<N> es el índice de la variante de error en el enum de errores personalizados del programa que falló, comenzando en 0x0 para la primera variante. Para corregir el problema, debes mapear ese índice de vuelta al código fuente del programa y verificar el estado de las cuentas involucradas.
Este artículo es parte del centro de aprendizaje de OnFinality y se centra en el diagnóstico de transacciones fallidas, complementando nuestras guías sobre transacciones versionadas de Solana y su análisis y tiempos de espera y reintentos de RPC en Solana. Si eres nuevo en los endpoints RPC de Solana, consulta nuestros métodos JSON-RPC de Solana (Asistente RPC) y la descripción general de la red Solana.
Mecanismo: Qué sucede durante la simulación de preflight
El runtime de Solana procesa una transacción en una secuencia determinista. Primero, verifica el saldo del pagador de tarifas y el blockhash. Luego carga cada cuenta referenciada por la transacción. Para cada instrucción, invoca el programa especificado con las cuentas y los datos de instrucción dados. Si algún paso falla, el runtime aborta y devuelve un error. El nodo RPC captura los registros emitidos durante este proceso y los adjunta a la respuesta de error JSON-RPC.
El método RPC simulateTransaction (documentado en la documentación RPC de Solana) acepta un indicador sigVerify y una configuración opcional de accounts. Por defecto, no requiere un blockhash reciente a menos que establezcas replaceRecentBlockhash. La respuesta incluye un objeto value con err, logs y accounts. Cuando err no es nulo, la transacción falló. El array logs contiene mensajes del runtime y de los propios programas. La línea de error final suele tener una de dos formas:
Program <PROGRAM_ID> failed: custom program error: 0x<N>– el programa devolvió un error personalizado. El número hexadecimal es el índice de la variante de error en el enum de errores del programa. Por ejemplo, si un programa defineenum MyError { InsufficientFunds, InvalidOwner }, entonces0x0significaInsufficientFundsy0x1significaInvalidOwner.
Error processing Instruction N: Program failed to complete– el programa no devolvió un error limpio (por ejemplo, entró en pánico, agotó las unidades de cómputo o encontró una falla inesperada de syscall). El índice de instrucciónNte indica qué instrucción causó el problema.
Otros errores comunes del runtime incluyen BlockhashNotFound (el blockhash está obsoleto o es inválido) y AccountInUse (un conflicto con otra transacción). Estos no son específicos de un programa y generalmente indican un problema del lado del cliente más que un error de lógica.
- La simulación de preflight es opcional:
sendTransactionaceptaskipPreflight: truepara omitirla, pero la transacción fallará al enviarse si es inválida. - El array
logspuede contener registros parciales antes de la línea de error; estos pueden ayudar a entender hasta dónde llegó la transacción. - Para transacciones versionadas (v0), el índice de instrucción en el error se refiere al orden en el array de instrucciones compiladas, que puede incluir búsquedas en tablas de direcciones. El objeto
Transactiondel SDK exponeinstructionsen el mismo orden.
Decodificación del índice de instrucción y el ID de programa
El índice de instrucción N en Error processing Instruction N es de base cero. Para encontrar la instrucción correspondiente, mira el array instructions de la transacción (para legacy) o message.compiledInstructions (para versionadas). Cada instrucción tiene un programIdIndex que apunta al array de claves de cuenta. El ID de programa es la clave de cuenta en ese índice. Si usas @solana/web3.js, el objeto Transaction tiene un método compileMessage() que devuelve las instrucciones compiladas. Para transacciones versionadas, la clase TransactionMessage puede descompilar el mensaje de vuelta a objetos TransactionInstruction.
Una vez que tengas el ID de programa, puedes identificar qué programa falló. Si es un programa conocido (por ejemplo, el System Program, Token Program o Associated Token Account Program), los códigos de error están documentados en el código fuente de Solana. Para programas personalizados, necesitas mirar el código fuente del programa para mapear el código de error. La convención es que el índice de la variante del enum de errores coincide con el código hexadecimal. Por ejemplo, si el registro dice custom program error: 0x2, el programa devolvió la tercera variante (índice 2) de su enum de errores.
Es importante distinguir entre un error de programa y un error de runtime. Un error de programa es devuelto por el propio programa mediante ProgramError::Custom(u32). Un error de runtime como ProgramFailedToComplete significa que la ejecución del programa fue abortada debido a una condición inesperada, como un pánico o exceder el presupuesto de cómputo. En ese caso, los registros pueden mostrar una línea Program log: Panicked o un mensaje de agotamiento de unidades de cómputo.
- Verifica el
programIdIndexde la instrucción para obtener el ID de programa de las claves de cuenta. - Para transacciones versionadas, recuerda que las instrucciones compiladas pueden referenciar cuentas de tablas de búsqueda de direcciones; el SDK maneja esto de forma transparente cuando usas
TransactionMessage.decompile(). - Si el error es
Program failed to complete, busca una línea precedenteProgram log: PanickedoProgram log: Error:para obtener más detalles.
Ejemplo reproducible: Análisis de un error de simulación con Node.js
El siguiente script usa @solana/web3.js para construir una transacción simple, simularla contra una URL RPC proporcionada por el usuario e imprimir el error JSON-RPC crudo junto con un desglose analizado. Reemplaza YOUR_RPC_URL con tu endpoint (por ejemplo, de RPC de Solana de OnFinality). El script crea intencionalmente una transacción que fallará (por ejemplo, transferir lamports desde una cuenta sin saldo) para demostrar el formato de error.
Ejecútalo con Node.js 18+ y npm install @solana/web3.js. El script imprime el objeto de error crudo y luego extrae el índice de instrucción, el ID de programa y el código de error usando una expresión regular simple en los registros.
- Salida esperada: El resultado de la simulación contendrá
errcon un mensaje como"Transaction simulation failed: Error processing Instruction 0: custom program error: 0x0"y registros que muestranProgram 11111111111111111111111111111111 failed: custom program error: 0x0(el errorInsufficientFundsdel System Program). - El script imprime el error JSON-RPC crudo y los campos analizados. Úsalo como plantilla para tus propias transacciones.
const { Connection, Keypair, SystemProgram, Transaction, LAMPORTS_PER_SOL } = require('@solana/web3.js');
const RPC_URL = process.env.RPC_URL || 'YOUR_RPC_URL';
const connection = new Connection(RPC_URL, 'confirmed');
async function main() {
// Create a fee payer with no lamports (will cause simulation to fail)
const feePayer = Keypair.generate();
const recipient = Keypair.generate();
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: feePayer.publicKey,
toPubkey: recipient.publicKey,
lamports: LAMPORTS_PER_SOL,
})
);
tx.feePayer = feePayer.publicKey;
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
try {
const result = await connection.simulateTransaction(tx);
console.log('Simulation result:', JSON.stringify(result, null, 2));
if (result.value.err) {
const logs = result.value.logs || [];
const errorLine = logs.find(l => l.includes('failed:') || l.includes('Error processing'));
console.log('\nParsed error line:', errorLine);
// Extract instruction index and program ID
const match = errorLine.match(/Error processing Instruction (\d+): Program (\S+) failed/);
if (match) {
console.log('Instruction index:', match[1]);
console.log('Program ID:', match[2]);
}
const customMatch = errorLine.match(/custom program error: (0x[0-9a-fA-F]+)/);
if (customMatch) {
console.log('Custom error code:', customMatch[1]);
}
}
} catch (e) {
console.error('RPC error:', e.message);
if (e.data && e.data.logs) {
console.log('Logs:', e.data.logs);
}
}
}
main().catch(console.error);Tabla de resultados: Completa para tu propia transacción
Cuando ejecutes el script contra tu propia transacción fallida, registra los siguientes campos. Esta tabla te ayuda a diagnosticar sistemáticamente el problema.
Campo Valor (completar) Interpretación URL RPC El endpoint utilizado Tipo de transacción Legacy / Versionada Afecta la indexación de instrucciones Mensaje de error p. ej., 'Transaction simulation failed' Índice de instrucción Qué instrucción falló (base 0) ID de programa El programa que devolvió el error Código de error personalizado Código hexadecimal como 0x0 Variante del enum de error Mapea el código al enum de errores del programa Saldo del pagador de tarifas Verifica si es suficiente Blockhash Verifica si es reciente Registros antes del error Registros parciales para contexto
Lista de verificación de fallos y correcciones
Usa esta lista de verificación para resolver fallos de simulación comunes. El primer paso es siempre identificar si el error proviene del runtime (por ejemplo, saldo insuficiente del pagador de tarifas) o de un programa (error personalizado).
Problemas con el pagador de tarifas: Si el error es 0x0 del System Program, generalmente significa que el pagador de tarifas no tiene lamports para cubrir la tarifa de transacción o el monto de la transferencia. Verifica el saldo del pagador de tarifas con getBalance. También asegúrate de que el blockhash sea reciente; un blockhash obsoleto causa BlockhashNotFound.
Cuenta no inicializada: Si un programa espera que una cuenta esté inicializada (por ejemplo, una cuenta de token), la simulación puede fallar con un error personalizado como UninitializedAccount. Verifica que todas las cuentas requeridas existan y sean propiedad del programa correcto.
Error personalizado del programa: Mapea el código hexadecimal al enum de errores del programa. Por ejemplo, si el programa tiene enum MyError { InvalidOwner, InsufficientFunds }, entonces 0x0 es InvalidOwner y 0x1 es InsufficientFunds. Mira el código fuente del programa o su ABI para entender la condición.
Orden de instrucciones: Para transacciones versionadas, asegúrate de usar el índice de instrucción correcto. El objeto Transaction del SDK maneja esto, pero si estás construyendo el mensaje manualmente, verifica dos veces el orden de las instrucciones compiladas.
Agotamiento de unidades de cómputo: Si los registros muestran Program failed to complete y un mensaje de unidades de cómputo, aumenta el presupuesto de cómputo agregando una instrucción ComputeBudgetProgram.setComputeUnitLimit.
Configuración de preflight específica del proveedor: Algunos proveedores de RPC pueden tener comportamientos de preflight diferentes (por ejemplo, pueden omitir el preflight por defecto o usar un nivel de compromiso diferente). Consulta la documentación de tu proveedor. Las páginas de precios de RPC y servicio de API de OnFinality describen nuestra configuración estándar, pero siempre verifica con tu endpoint.
Limitaciones y compensaciones
La simulación no garantiza que la transacción tendrá éxito cuando se envíe. El estado puede cambiar entre la simulación y la confirmación, lo que lleva a un resultado diferente. Además, la simulación no ejecuta la transacción contra el estado más reciente si usas un nivel de compromiso más bajo; usa commitment: 'confirmed' o 'finalized' para obtener resultados más precisos.
El array logs puede truncarse para transacciones muy grandes o cuando el programa registra demasiado. En tales casos, la línea de error podría faltar. Puedes aumentar el límite de registros estableciendo el parámetro encoding, pero esto no siempre es compatible.
Para transacciones versionadas, el índice de instrucción en el error se refiere al orden de instrucciones compiladas, que puede diferir del orden en que construiste si hay búsquedas en tablas de direcciones. Siempre descompila el mensaje para verificar.
La configuración de preflight específica del proveedor puede afectar si ves el error o no. Algunos proveedores pueden devolver un error genérico sin registros. Si encuentras esto, intenta usar un endpoint RPC diferente o el método simulateTransaction directamente.
Próximos pasos y lecturas adicionales
Ahora que puedes decodificar errores de simulación, puedes aplicar este conocimiento para depurar tus aplicaciones de Solana. Para temas más avanzados, explora nuestras otras guías:
- Transacciones versionadas de Solana y su análisis – comprende cómo analizar transacciones versionadas, lo cual es esencial para una indexación correcta de instrucciones.
- Tiempos de espera y reintentos de RPC en Solana – maneja problemas de red al enviar transacciones.
- Límites de tasa y errores 429 en Solana – evita alcanzar límites de tasa durante operaciones de alto rendimiento.
- Consulta de datos históricos de Solana a través de RPC – recupera datos de transacciones pasadas para análisis.
- Métodos JSON-RPC de Solana (Asistente RPC) – referencia para todos los métodos RPC de Solana.
Para detalles oficiales del protocolo, consulta la documentación de Solana sobre simulateTransaction y la referencia de códigos de error de Solana. El Solana Cookbook también tiene explicaciones de errores contribuidas por la comunidad.