-
Notifications
You must be signed in to change notification settings - Fork 158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor error handling page #649
Merged
Merged
Changes from all commits
Commits
Show all changes
61 commits
Select commit
Hold shift + click to select a range
96f60fa
Add getTransactions documentation - 3
aditya1702 7c9df3b
Merge branch 'refs/heads/main' into error-handling
aditya1702 9ce5a78
Add getTransactions documentation - 5
aditya1702 1d3e6c3
Refactor error-handling page - 1
aditya1702 adf9968
Move error-handling to admin-guide
aditya1702 d8184e7
Format table
aditya1702 13cd1ff
Fix docs path
aditya1702 8abee6a
Fix docs path - 2
aditya1702 6ceebd9
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 1f4e1b2
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 d9ddda5
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 9e4aab1
Merge branch 'refs/heads/main' into error-handling
aditya1702 964734e
Move error-handling to Errors section
aditya1702 59cf2a5
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 bfa97e7
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 e2475d8
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 7000cf3
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 7290131
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 e761a7a
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 9858ff9
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 b198acf
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 4ef7be7
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 8360a6b
Update network/horizon/admin-guide/error-handling.mdx
aditya1702 fc424e9
Merge remote-tracking branch 'origin/error-handling' into error-handling
aditya1702 9de0ed6
Change "core" to "Stellar-Core"
aditya1702 64ff828
Add more information for `TRY_AGAIN_LATER`
aditya1702 ade1fb4
Add link for GET /transactions/:hash
aditya1702 1bdf5f7
Make the subsections bold
aditya1702 39b5d33
Change txn -> transaction
aditya1702 222c438
Refactor - 1
aditya1702 db0d1cf
Refactor - 2
aditya1702 a346dd7
Refactor - 3
aditya1702 7463cd2
Refactor - 4
aditya1702 de71b63
Update network/horizon/api-reference/errors/error-handling.mdx
aditya1702 7f223bc
Update network/horizon/api-reference/errors/error-handling.mdx
aditya1702 65a2e0d
Update network/horizon/api-reference/errors/error-handling.mdx
aditya1702 d004fc5
Merge
aditya1702 0e2fefa
Change error-handling page link url
aditya1702 18ca97f
Update index.js
aditya1702 2a7cf0a
Fix URL links
aditya1702 54cebed
Fix URL links - 2
aditya1702 cd3d3e7
Merge branch 'refs/heads/main' into error-handling
aditya1702 bce63aa
Fix URL links - 3
aditya1702 1c97944
Add an example to use async method
aditya1702 e243dc7
Resolve review comments
aditya1702 38354b0
Resolve review comments - 2
aditya1702 5f7a4a5
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 b7e1abe
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 d55b478
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 5e7feb1
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 24fa327
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 07aff89
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 43dc887
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 572da8a
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 3c08c61
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 d1f3e36
Update docs/data/horizon/api-reference/errors/error-handling.mdx
aditya1702 49cfe97
Merge branch 'refs/heads/main' into error-handling
aditya1702 e1ad6a8
Final changes - 1
aditya1702 4d4bb1d
Fix broken link
aditya1702 f32c1e2
Add polling code
aditya1702 8c5e14e
Fix caution section
aditya1702 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
232 changes: 232 additions & 0 deletions
232
docs/data/horizon/api-reference/errors/error-handling.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | ||||||
aditya1702 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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). |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that we don't have documentation for the new endpoint in the Horizon API (unless I'm blind)! We can add this cross-reference after we've added that in (after this PR).