Skip to content

Commit

Permalink
Refactor error handling page (#649)
Browse files Browse the repository at this point in the history
* Add getTransactions documentation - 3

* Add getTransactions documentation - 5

* Refactor error-handling page - 1

* Move error-handling to admin-guide

* Format table

* Fix docs path

* Fix docs path - 2

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com>

* Move error-handling to Errors section

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Molly Karcher <mdarkar@gmail.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Molly Karcher <mdarkar@gmail.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Molly Karcher <mdarkar@gmail.com>

* Update network/horizon/admin-guide/error-handling.mdx

Co-authored-by: Molly Karcher <mdarkar@gmail.com>

* Change "core" to "Stellar-Core"

* Add more information for `TRY_AGAIN_LATER`

* Add link for GET /transactions/:hash

* Make the subsections bold

* Change txn -> transaction

* Refactor - 1

* Refactor - 2

* Refactor - 3

* Refactor - 4

* Update network/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update network/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Change error-handling page link url

* Update index.js

* Fix URL links

* Fix URL links - 2

* Fix URL links - 3

* Add an example to use async method

* Resolve review comments

* Resolve review comments - 2

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Update docs/data/horizon/api-reference/errors/error-handling.mdx

Co-authored-by: George <Shaptic@users.noreply.github.com>

* Final changes - 1

* Fix broken link

* Add polling code

* Fix caution section

---------

Co-authored-by: Jake Urban <10968980+JakeUrban@users.noreply.github.com>
Co-authored-by: Molly Karcher <mdarkar@gmail.com>
Co-authored-by: George <Shaptic@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 8, 2024
1 parent 6912dd4 commit c8ad0db
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 194 deletions.
2 changes: 1 addition & 1 deletion docs/build/apps/moneygram-access-integration-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ print(
[trustline]: ../../data/horizon/api-reference/resources/operations/object/change-trust.mdx
[sell offer]: ../../data/horizon/api-reference/resources/operations/object/sell-offer.mdx
[postmessage]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
[submitting transactions and handling errors gracefully]: ../../learn/encyclopedia/errors-and-debugging/error-handling.mdx
[submitting transactions and handling errors gracefully]: /data/horizon/api-reference/errors/error-handling.mdx
[path payments]: ../../data/horizon/api-reference/resources/operations/object/path-payment-strict-receive.mdx
[claimable balances]: ../../data/horizon/api-reference/resources/operations/object/create-claimable-balance.mdx
[moneygram screening questionnaire]: https://stellarquestionnaire.typeform.com/to/RD1a71wQ
Expand Down
2 changes: 1 addition & 1 deletion docs/build/guides/basics/create-account.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,6 @@ Now that you’ve got an account, you can [start sending and receiving payments]

<Alert>

In the above code samples, proper error checking is omitted for brevity. However, you should _always_ validate your results, as there are many ways that requests can fail. You should refer to the guide on [Error Handling](../../../learn/encyclopedia/errors-and-debugging/error-handling.mdx) for tips on error management strategies.
In the above code samples, proper error checking is omitted for brevity. However, you should _always_ validate your results, as there are many ways that requests can fail. You should refer to the guide on [Error Handling](/data/horizon/api-reference/errors/error-handling.mdx) for tips on error management strategies.

</Alert>
2 changes: 1 addition & 1 deletion docs/build/guides/basics/send-and-receive-payments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Every transaction also incurs a small fee. Like the minimum balance on accounts,

<Alert>

In the following code samples, proper error checking is omitted for brevity. However, you should _always_ validate your results, as there are many ways that requests can fail. You should refer to the guide on [Error Handling](../../../learn/encyclopedia/errors-and-debugging/error-handling.mdx) for tips on error management strategies.
In the following code samples, proper error checking is omitted for brevity. However, you should _always_ validate your results, as there are many ways that requests can fail. You should refer to the guide on [Error Handling](/data/horizon/api-reference/errors/error-handling.mdx) for tips on error management strategies.

</Alert>

Expand Down
232 changes: 232 additions & 0 deletions docs/data/horizon/api-reference/errors/error-handling.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
title: Error Handling
---

import { CodeExample } from "@site/src/components/CodeExample";

It’s important to anticipate errors your users may encounter as you develop on Stellar. In many tutorials throughout our developer documentation, we leave out error handling code to focus on the example. In this section, we will do the opposite and talk specifically about the errors. By the end of this section, you should be able to categorize errors and understand the best way to handle them in your application.

Many actions interact with the Stellar network through the Horizon API, and these possible actions fall into two main categories:

1. Queries (any `GET` request, like to `/accounts`)
2. Transaction submissions (a `POST /transactions`, `POST /transactions_async`).

There are many possible error codes when executing these actions, and you can typically handle these error codes using the following strategies:

- Request adjustments: adjusting the request to resolve structural errors with queries or transaction submissions. Suppose you’ve included a bad parameter, malformed your XDR, or otherwise didn’t follow the endpoint’s specification. In these cases, resolve the error by referencing the details or result codes of the error response.
- Polling and retrying: this is the recommended way to work around latency or congestion issues encountered along the pipeline between your computer and the Stellar network, which can sometimes happen due to the nature of distributed systems.

## Error Handling for Queries

Many `GET` requests have specific parameter requirements, and while the SDKs can help enforce them, you can still pass invalid arguments (for example, an asset string that isn’t [SEP-11](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0011.md#asset-trustlineasset) compatible) that error out every time. In this scenario, there’s nothing you can do aside from following the API specification. The `extras` field of the error response will often clue you in on where to look and what to look for.

<CodeExample>

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

</CodeExample>

Note that the SDKs make it a point to distinguish an invalid request (as above) versus a missing resource (a `404 Not Found`) (for example, the generic `NetworkError` versus a `NotFoundError` in the JavaScript SDK), where the latter might not be considered an error depending on your situation.

## Error Handling for Transaction Submissions

Horizon currently supports two types of transaction submission endpoints:

1. `/transactions_async`: Horizon submits a transaction to Stellar-Core in an asynchronous manner and passes the relevant Stellar-Core response back immediately to the client. It is then the client's responsibility to poll for the status of that transaction.
2. `/transactions`: Horizon submits a transaction to Stellar-Core and then waits for it to be ingested into its database. It will either return a success (200), failure (400) or a timeout response (504), after which it is the client's responsibility to poll for the status of the transaction.

Note that polling the transaction hash will return a 404 until it gets included in the ledger, or it fails to do so. In both cases, you will get a response when Horizon has ingested it into the database.

There are some resolution strategies that are common between the 2 endpoints while others strategies are more endpoint specific.

### Request Adjustments

Certain transaction submission failures also need adjustments to succeed.

- If the XDR is malformed, or the transaction is otherwise invalid, you’ll encounter a `400 Bad Request` (for example, an invalid source account). Both transactions and their operations can be easily malformed or invalid: look at the `extras.result_codes` field for details and cross-reference them with the appropriate result codes documentation to determine specifics.
- Transaction fees are also a safe adjustment by modifying the fees via a [fee-bump transaction](docs/learn/encyclopedia/transactions-specialized/fee-bump-transactions) if you get a `tx_insufficient_fee` error. Refer to the [Insufficient Fees and Surge Pricing](#insufficient-fees-and-surge-pricing) section later in this document for more information on managing fees and strategies around it.

### Polling and Retrying Transactions

#### Async Transaction Submission

Submissions using the `/transactions_async` endpoint return an immediate response back from Stellar-Core. There are different actions that clients can take based on the specific `tx_status` returned:

1. `PENDING`: The submission is successful but the transaction is still waiting to be included in a ledger. You should use the [GET /transactions/:transaction_hash](/docs/data/horizon/api-reference/resources/retrieve-a-transaction) endpoint to poll for the submitted transaction and check if it makes into a ledger. Note that even though the submission was successful, it can still fail to get included in the ledger.
2. `ERROR`: The submission did not go through due to an error in Stellar-Core. Take a look at the attached error message for more details, modify your transaction if necessary, and resubmit.
3. `TRY_AGAIN_LATER`: This indicates that the Stellar-Core instance is currently unable to process submission of this particular transaction. Clients should wait for sometime before resubmitting the transaction. This could happen due to different reasons:
- There is another transaction from same source account in memory
- It has been rejected due to too low fee and has been resubmitted too soon
- The transaction has resource consumption higher than the network limits allow: clients should wait for sometime before retrying it again.

<CodeExample>

```js
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);
});
```

</CodeExample>

#### Synchronous Transaction Submission

Due to the blocking nature of this endpoint, things are a little different compared to the asynchronous strategy. There are 3 possible scenarios that clients can encounter:

1. The submission is successful and Horizon returns the transaction response back. This is the happy path and clients do not need to do anything but wait for Horizon's response.
2. Stellar-Core sends back an `ERROR` response from the submission. Clients should consult the attached error message and retry the submission again.
3. Timeouts: Horizon may respond with a `504` HTTP code. This response is not an error but a warning that your transaction hasn't been accepted by the network yet. There could be many possible reasons for the timeout, the most common of which is network congestion, but it could also be due to other transient issues.
1. **Polling the transaction hash**: Use the transaction hash in the timeout response and poll the [GET /transactions/:transaction_hash](/docs/data/horizon/api-reference/resources/retrieve-a-transaction) endpoint to see if it successfully makes it into a ledger.
2. **Resubmitting the transaction**: Before attempting any resubmissions, you need to make sure your transaction has timed out based on the time bounds you specified. After your transaction has expired, you can confirm it by polling the transaction again and getting a `tx_too_late` response from Horizon. Rebuild the transaction by updating the timebounds and resubmit the transaction.

:::caution

Do note that resubmitting a transaction is only safe when it is unchanged - same operations, signatures, sequence number, etc... Be careful when working around an error that does require changes to the transaction. It can cause duplicate transactions, which can cause problems - double payments, incorrect trustlines, and more. If you continue to face timeouts on retries, consider using a fee-bump transaction to get into the ledger (after the initial transaction's timebound expires) or increasing the maximum fee you’re willing to pay. Read up on [Surge Pricing and Fee Strategies](/docs/learn/fundamentals/fees-resource-limits-metering) for more details.

:::

### Example: Using Time Bounds

Timebounds are optional but **highly** recommended as they put a definitive time limit on the transaction's finality - after it times out, you will know for sure whether it made it into a ledger. For example, you submit a transaction, and it enters the queue of the Stellar network, but Horizon crashes while giving you a response. Uncertain about the transaction status, you resubmit the transaction (with no changes!) until either (a) Horizon comes back up to give you a reply or (b) your time bounds are exceeded.

There are only two possible results to this scenario: either the transaction makes it into the ledger (exactly once) and Horizon gives you the response, or the transaction never makes it out of the queue, and you receive the corresponding `tx_too_late` response.

Example implementation:

<CodeExample>

```js
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);
});
});
}
```

</CodeExample>

We assume the existence of a sleep implementation similar to the one [here](https://stackoverflow.com/a/39914235). Be sure to integrate backoff into your retry mechanism. In our example error-handling code above, we implement a simple linear backoff, but there are [plenty of recommendations](https://backoff-utils.readthedocs.io/en/latest/strategies.html#why-are-backoff-strategies-useful) for various other strategies. Backoff is important both for maintaining performance and avoiding rate-limiting issues.

### Example: Invalid Sequence Numbers

These errors typically occur when you have an outdated view of an account. This could be because multiple devices are using this account, you have concurrent submissions happening, or other reasons. The solution is relatively simple: retrieve the account details and try again with an updated sequence number.

<CodeExample>

```js
// 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...
}
```
</CodeExample>
Despite the solution’s simplicity, things can go wrong fast if you don’t understand why the error occurred.
Suppose you submit transactions from multiple places in your application simultaneously, and your user spammed a _Send Payment_ button a few times in their impatience. If you send the exact same payment transaction for each tap, naturally, only one will succeed. The others will fail with an invalid sequence number (`tx_bad_seq`), and if you resubmit blindly with an updated sequence number (as we do above), these payments will also succeed, resulting in more than one payment being made when only one was intended. So **be very careful when resubmitting transactions** that have been modified to work around an error.
## Managing specific Errors
Here, we will cover specific errors commonly encountered during transaction submission and direct you to the appropriate resolution.
| Result | Code | Description |
| --- | --- | --- |
| `FAILED` | -1 | One of the operations failed (see [List of Operations](/docs/learn/fundamentals/transactions/list-of-operations) for errors) |
| `TOO_EARLY` | -2 | Ledger `closeTime` before `minTime` value in the transaction |
| `TOO_LATE` | -3 | Ledger `closeTime` after `maxTime` value in the transaction |
| `MISSING_OPERATION` | -4 | No operation was specified |
| `BAD_SEQ` | -5 | Sequence number does not match source account |
| `BAD_AUTH` | -6 | Too few valid signatures / wrong network |
| `INSUFFICIENT_BALANCE` | -7 | Fee would bring account below minimum balance; see our section on [Lumens](/docs/learn/fundamentals/lumens#minimum-balance) for more info |
| `NO_ACCOUNT` | -8 | Source account not found |
| `INSUFFICIENT_FEE` | -9 | Fee is too small; see our section on [Fees](/docs/learn/fundamentals/fees-resource-limits-metering) for more info |
| `BAD_AUTH_EXTRA` | -10 | Unused signatures attached to transaction |
| `INTERNAL_ERROR` | -11 | An unknown error occurred |
| `NOT_SUPPORTED` | -12 | The transaction type is not supported |
| `FEE_BUMP_INNER_FAILED` | -13 | The inner transaction of a fee-bump transaction failed |
| `BAD_SPONSORSHIP` | -14 | The sponsorship is not confirmed |
### Insufficient fees and surge pricing
See the [Fees section](/docs/learn/fundamentals/fees-resource-limits-metering)
### Rate limiting
Horizon may rate limit requests and return `429 Too Many Requests` error when exceeding the rate limits. If using your own instance, you may want to either increase or disable rate limiting. If you're using a third-party Horizon instance, you may want to deploy your own to have more control over this configuration or send requests less frequently.
### Insufficient XLM balance
Any transaction that would reduce an account’s balance to less than the minimum will be rejected with an `INSUFFICIENT_BALANCE` error. Likewise, lumen selling liabilities that would reduce an account’s balance to less than the minimum plus lumen selling liabilities will be rejected with an `INSUFFICIENT_BALANCE` error.
For more on minimum balances, see our [Lumens section](/docs/learn/fundamentals/lumens#minimum-balance).
Loading

0 comments on commit c8ad0db

Please sign in to comment.