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:
- Consultas (cualquier solicitud
GET
, como a/accounts
) - 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.
- bash
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:
/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./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 campoextras.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:
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.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.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.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
- JavaScript
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:
- 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.
- Stellar-Core devuelve una respuesta
ERROR
del envío. Los clientes deben consultar el mensaje de error adjunto y reintentar el envío nuevamente. - 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.- 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.
- 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.
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:
- JavaScript
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.
- JavaScript
// 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.
Resultado | Código | Descripción |
---|---|---|
FAILED | -1 | Una de las operaciones falló (consulta Lista de Operaciones para errores) |
TOO_EARLY | -2 | El closeTime del ledger antes del valor minTime en la transacción |
TOO_LATE | -3 | El closeTime del ledger después del valor maxTime en la transacción |
MISSING_OPERATION | -4 | No se especificó ninguna operación |
BAD_SEQ | -5 | El número de secuencia no coincide con la cuenta de origen |
BAD_AUTH | -6 | Demasiadas pocas firmas válidas / red equivocada |
INSUFFICIENT_BALANCE | -7 | La 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 | -8 | Cuenta de origen no encontrada |
INSUFFICIENT_FEE | -9 | La tarifa es demasiado pequeña; consulta nuestra sección sobre Tarifas para más información |
BAD_AUTH_EXTRA | -10 | Firmas no utilizadas adjuntas a la transacción |
INTERNAL_ERROR | -11 | Ocurrió un error desconocido |
NOT_SUPPORTED | -12 | El tipo de transacción no es compatible |
FEE_BUMP_INNER_FAILED | -13 | La transacción interna de una transacción de ajuste de tarifas falló |
BAD_SPONSORSHIP | -14 | La 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.