Saltar al contenido principal

Manejo de errores

Es importante anticipar los errores que tus usuarios pueden encontrar mientras desarrollas en Stellar. En muchos tutoriales a lo largo de nuestra documentación para desarrolladores, dejamos fuera el código de manejo de errores para centrarnos en el ejemplo. En esta sección, haremos lo contrario y hablaremos específicamente sobre los errores. Al final de esta sección, deberías poder categorizar errores y entender la mejor manera de manejarlos en tu aplicación.

Muchas acciones interactúan con la red Stellar a través de la API Horizon, y estas acciones posibles se dividen en dos categorías principales:

  1. Consultas (cualquier solicitud GET, como a /accounts)
  2. Envíos de transacciones (un POST /transactions, POST /transactions_async).

Existen muchos códigos de error posibles al ejecutar estas acciones, y típicamente puedes manejar estos códigos de error utilizando las siguientes estrategias:

  • Ajustes de solicitud: ajustar la solicitud para resolver errores estructurales con consultas o envíos de transacciones. Supón que has incluido un parámetro incorrecto, has formado incorrectamente tu XDR, o de alguna manera no seguiste la especificación del endpoint. En estos casos, resuelve el error consultando los detalles o códigos de resultado de la respuesta de error.
  • Consulta y reintenta: esta es la forma recomendada de sortear problemas de latencia o congestión encontrados a lo largo de la tubería entre tu ordenador y la red Stellar, que pueden suceder a veces debido a la naturaleza de los sistemas distribuidos.

Manejo de errores para consultas

Muchas solicitudes GET tienen requisitos específicos de parámetros, y aunque los SDK pueden ayudar a hacerlos cumplir, aún puedes pasar argumentos inválidos (por ejemplo, una cadena de activo que no es compatible con SEP-11) que fallan cada vez. En este escenario, no hay nada que puedas hacer aparte de seguir la especificación de la API. El campo extras de la respuesta de error a menudo te indicará dónde buscar y qué buscar.

curl -s https://horizon-testnet.stellar.org/claimable_balances/0000 | jq '.extras'
{
"invalid_field": "id",
"reason": "Invalid claimable balance ID"
}

Ten en cuenta que los SDK se esfuerzan por distinguir una solicitud inválida (como la anterior) de un recurso faltante (un 404 Not Found) (por ejemplo, el genérico NetworkError frente a un NotFoundError en el SDK de JavaScript), donde este último puede no considerarse un error dependiendo de tu situación.

Manejo de errores para envíos de transacciones

Horizon actualmente admite dos tipos de endpoints para envío de transacciones:

  1. /transactions_async: Horizon envía una transacción a Stellar-Core de manera asíncrona y pasa la respuesta relevante de Stellar-Core de vuelta inmediatamente al cliente. Es responsabilidad del cliente consultar el estado de esa transacción.
  2. /transactions: Horizon envía una transacción a Stellar-Core y luego espera a que sea ingresada en su base de datos. Ya sea que devuelva un éxito (200), falla (400) o una respuesta de tiempo de espera (504), después de lo cual es responsabilidad del cliente consultar el estado de la transacción.

Ten en cuenta que consultar el hash de la transacción devolverá un 404 hasta que se incluya en el ledger, o no lo logre hacer. En ambos casos, recibirás una respuesta cuando Horizon la haya ingresado en la base de datos.

Existen algunas estrategias de resolución que son comunes entre los 2 endpoints, mientras que otras estrategias son más específicas del endpoint.

Ajustes de solicitud

Ciertos fallos en el envío de transacciones también necesitan ajustes para tener éxito.

  • Si el XDR está mal formado, o la transacción es de alguna manera inválida, te encontrarás con un 400 Bad Request (por ejemplo, una cuenta de origen inválida). Tanto las transacciones como sus operaciones pueden estar fácilmente mal formadas o ser inválidas: consulta el campo extras.result_codes para obtener detalles y crúzalo con la documentación de códigos de resultado apropiados para determinar especificidades.
  • Las tarifas de transacción también son un ajuste seguro modificando las tarifas a través de una transacción de ajuste de tarifas si recibes un error tx_insufficient_fee. Consulta la sección Tarifas insuficientes y precios de auge más adelante en este documento para obtener más información sobre cómo gestionar tarifas y las estrategias relacionadas.

Consulta y reintento de transacciones

Envío de transacciones asíncronas

Los envíos utilizando el endpoint /transactions_async devuelven una respuesta inmediata de Stellar-Core. Existen diferentes acciones que los clientes pueden tomar basándose en el estado específico tx_status devuelto:

  1. PENDING: El envío es exitoso, pero la transacción aún está esperando ser incluida en un ledger. Deberías usar el endpoint GET /transactions/:transaction_hash para consultar la transacción enviada y verificar si ha sido incluida en un ledger. Ten en cuenta que, aunque el envío fue exitoso, aún puede fallar al ser incluido en el ledger.
  2. DUPLICATE: El envío fue un duplicado de una transacción previamente enviada. Esto podría ocurrir si el cliente volvió a enviar la misma transacción varias veces.
  3. ERROR: El envío no se completó debido a un error en Stellar-Core. Consulta el mensaje de error adjunto para obtener más detalles, modifica tu transacción si es necesario y vuelve a enviar.
  4. TRY_AGAIN_LATER: Esto indica que la instancia de Stellar-Core actualmente no puede procesar el envío de esta transacción particular. Los clientes deberían esperar un tiempo antes de reenviar la transacción. Esto podría ocurrir por diferentes razones:
    • Hay otra transacción desde la misma cuenta de origen en memoria
    • Ha sido rechazada debido a una tarifa de inclusión demasiado baja y ha sido reenviada demasiado pronto
let server = sdk.Server("https://horizon-testnet.stellar.org");
let contractId = "CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE";
let contract = new StellarSdk.Contract(contractId);

// Right now, this is just the default fee for this example.
const fee = StellarSdk.BASE_FEE;

let transaction = new StellarSdk.TransactionBuilder(account, { fee })
.setNetworkPassphrase(StellarSdk.Networks.TESTNET)
.setTimeout(30) // valid for the next 30s
// Add an operation to call increment() on the contract
.addOperation(contract.call("increment"))
.build();

// Sign this transaction with the secret key
// NOTE: signing is transaction is network specific. Test network transactions
// won't work in the public network. To switch networks, use the Network object
// as explained above (look for StellarSdk.Network).
let sourceKeypair = StellarSdk.Keypair.fromSecret(sourceSecretKey);
transaction.sign(sourceKeypair);

server.submitAsyncTransaction(transaction).then((result) => {
console.log("hash:", result.hash);
console.log("status:", result.tx_status);
console.log("errorResultXdr:", result.error_result_xdr);
});

// Add a small sleep duration before polling the transaction.
time.sleep(5 * time.Second);
server
.transactions()
.transaction(result.hash)
.call()
.then((txResult) => {
console.log("Transaction status:", txResult);
});

Envío de transacciones síncronas

Debido a la naturaleza bloqueante de este endpoint, las cosas son un poco diferentes en comparación con la estrategia asíncrona. Existen 3 escenarios posibles que los clientes pueden encontrar:

  1. El envío es exitoso y Horizon devuelve la respuesta de la transacción. Este es el camino feliz y los clientes no necesitan hacer nada más que esperar la respuesta de Horizon.
  2. Stellar-Core devuelve una respuesta ERROR del envío. Los clientes deben consultar el mensaje de error adjunto y reintentar el envío nuevamente.
  3. Tiempos de espera: Horizon puede responder con un código HTTP 504. Esta respuesta no es un error, sino una advertencia de que tu transacción aún no ha sido aceptada por la red. Podría haber muchas razones posibles para el tiempo de espera, la más común de las cuales es la congestión de la red, pero también podría deberse a otros problemas transitorios.
    1. Consultar el hash de la transacción: Usa el hash de la transacción en la respuesta de tiempo de espera y consulta el endpoint GET /transactions/:transaction_hash para ver si se ha incluido exitosamente en un ledger.
    2. Reenviar la transacción: Antes de intentar cualquier reenvío, debes asegurarte de que tu transacción haya expirado según los límites de tiempo que especificaste. Después de que tu transacción haya caducado, puedes confirmarlo consultando nuevamente la transacción y obteniendo una respuesta tx_too_late de Horizon. Reconstituye la transacción actualizando los límites de tiempo y reenvía la transacción.
advertencia

Ten en cuenta que reenviar una transacción es seguro solo cuando no ha cambiado - mismas operaciones, firmas, número de secuencia, etc... Ten cuidado al trabajar con un error que requiera cambios en la transacción. Esto puede causar transacciones duplicadas, lo que puede generar problemas: pagos dobles, líneas de confianza incorrectas, y más. Si continúas enfrentando tiempos de espera en los reintentos, considera usar una transacción de ajuste de tarifas para entrar en el ledger (después de que expire el tiempo de la transacción inicial) o aumentar la tarifa máxima que estás dispuesto a pagar. Lee sobre Precios de Auge y Estrategias de Tarifas para más detalles.

Ejemplo: Uso de Límites de Tiempo

Los límites de tiempo son opcionales, pero altamente recomendados ya que ponen un límite de tiempo definitivo en la finalización de la transacción - después de que expire, sabrás con certeza si fue incluida en un ledger. Por ejemplo, envías una transacción y esta entra en la cola de la red Stellar, pero Horizon falla mientras te da una respuesta. Incierto sobre el estado de la transacción, vuelves a enviar la transacción (¡sin cambios!) hasta que (a) Horizon vuelva a funcionar para darte una respuesta o (b) tus límites de tiempo se excedan.

Solo hay dos resultados posibles para este escenario: o la transacción es incluida en el ledger (exactamente una vez) y Horizon te da la respuesta, o la transacción nunca sale de la cola, y recibes la correspondiente respuesta tx_too_late.

Implementación de ejemplo:

import { Horizon } from "@stellar/stellar-sdk";

let server = Horizon.Server("https://horizon-testnet.stellar.org");

function submitTransaction(tx, timeout) {
if (!tx.timeBounds || tx.timeBounds.maxTime === 0) {
throw new Error("Always set a reasonable timebound!");
}
const expiration = parseInt(tx.timeBounds.maxTime);

return server.submitTransaction(tx).catch(function (error) {
if (isNonRetryErrorCase(error)) {
// ...do other error handling...
return;
}

// the tx no longer has a chance of making it into a ledger
if (Date.now() >= expiration) {
return new Error("The transaction timed out.");
}

timeout = timeout || 1; // start the (linear) back-off process
return sleep(timeout).then(function () {
return submitTransaction(tx, timeout + 5);
});
});
}

Asumimos la existencia de una implementación de espera similar a la que se encuentra aquí. Asegúrate de integrar un retroceso en tu mecanismo de reintento. En nuestro código de manejo de errores de ejemplo anterior, implementamos un retroceso lineal simple, pero hay muchas recomendaciones para varias otras estrategias. El retroceso es importante tanto para mantener el rendimiento como para evitar problemas de limitación de tasa.

Ejemplo: Números de Secuencia Inválidos

Estos errores suelen ocurrir cuando tienes una vista desactualizada de una cuenta. Esto podría ser porque múltiples dispositivos están usando esta cuenta, tienes envíos concurrentes, o por otras razones. La solución es relativamente simple: recupera los detalles de la cuenta y vuelve a intentarlo con un número de secuencia actualizado.

// suppose `account` is an outdated `AccountResponse` object
let tx = sdk.TransactionBuilder(account, ...)/* etc */.build();
server.submitTransaction(tx).catch(function (error) {
if (error.response && error.status == 400 && error.extras &&
error.extras.result_codes.transaction == sdk.TX_BAD_SEQ) {
return server.loadAccount(account.accountId())
.then(function (response) {
let tx = sdk.TransactionBuilder(response, ...)/* etc */.build()
return server.submitTransaction(tx);
});
}
// ...other error conditions...
})

A pesar de la simplicidad de la solución, las cosas pueden salir mal rápidamente si no entiendes por qué ocurrió el error.

Supón que envías transacciones desde múltiples lugares en tu aplicación simultáneamente, y tu usuario presionó el botón Enviar Pago varias veces por su impaciencia. Si envías la misma transacción de pago exacta por cada toque, naturalmente, solo una tendrá éxito. Las demás fallarán con un número de secuencia inválido (tx_bad_seq), y si vuelves a enviar ciegamente con un número de secuencia actualizado (como hicimos anteriormente), estos pagos también tendrán éxito, resultando en más de un pago cuando solo uno era la intención. Así que ten mucho cuidado al reenviar transacciones que han sido modificadas para sortear un error.

Manejo de Errores Específicos

Aquí, cubriremos errores específicos comúnmente encontrados durante el envío de transacciones y te dirigiremos a la resolución apropiada.

ResultadoCódigoDescripción
FAILED-1Una de las operaciones falló (consulta Lista de Operaciones para errores)
TOO_EARLY-2El closeTime del ledger antes del valor minTime en la transacción
TOO_LATE-3El closeTime del ledger después del valor maxTime en la transacción
MISSING_OPERATION-4No se especificó ninguna operación
BAD_SEQ-5El número de secuencia no coincide con la cuenta de origen
BAD_AUTH-6Demasiadas pocas firmas válidas / red equivocada
INSUFFICIENT_BALANCE-7La tarifa haría que la cuenta quedara por debajo del saldo mínimo; consulta nuestra sección sobre Lumens para más información
NO_ACCOUNT-8Cuenta de origen no encontrada
INSUFFICIENT_FEE-9La tarifa es demasiado pequeña; consulta nuestra sección sobre Tarifas para más información
BAD_AUTH_EXTRA-10Firmas no utilizadas adjuntas a la transacción
INTERNAL_ERROR-11Ocurrió un error desconocido
NOT_SUPPORTED-12El tipo de transacción no es compatible
FEE_BUMP_INNER_FAILED-13La transacción interna de una transacción de ajuste de tarifas falló
BAD_SPONSORSHIP-14La patrocinación no está confirmada

Tarifas insuficientes y precios de auge

Consulta la sección de Tarifas

Limitación de tasa

Horizon puede limitar la tasa de solicitudes y devolver el error 429 Too Many Requests al superar los límites de tasa. Si utilizas tu propia instancia, quizá quieras aumentar o desactivar la limitación de tasa. Si utilizas una instancia de Horizon de terceros, puede que quieras desplegar la tuya propia para tener más control sobre esta configuración o enviar solicitudes con menos frecuencia.

Saldo XLM insuficiente

Cualquier transacción que reduzca el saldo de una cuenta a menos que lo mínimo será rechazada con un error INSUFFICIENT_BALANCE. De igual manera, las obligaciones de venta de lúmenes que reduzcan el saldo de una cuenta a menos que el mínimo más las obligaciones de venta de lúmenes serán rechazadas con un error INSUFFICIENT_BALANCE.

Para más sobre los saldos mínimos, consulta nuestra sección de Lumens.