How to Deal with Blockhash Errors on Solana
What is a Blockhash?
To understand what a blockhash is, you must understand what slots and blocks are.
- A slot is a period of time in which a validator can produce a block
- A block is a collection of transactions + metadata that a validator processes. The metadata in each block links it to the previous block, creating a chain.
It is important to understand that slots last between 400 and 600ms and in each slot, a validator can propose a new block. If a block is not created, the slot is incremented, and another validator will try to create a new block. This means that not all slots will have associated blocks, but all blocks have associated slots in which they were proposed.
Okay, so what is a blockhash? A blockhash is a unique hash value of all the blockchain's ledger entries created during a slot. This is calculated from the last entry id of the block. Each produced block result is a unique blockhash being created. These blockhashes are used as timestamps.
What are Solana's Commitment Levels?
Another important concept to understand is commitment levels. These commitment levels measure the network confirmation for a specific block. The three options are processed, confirmed, and finalized.
When a validator submits a block to the chain the block will be in the processed state. Once the required number of validators (66% of validators) have voted to include the block, it is added to the chain and the commitment level changes to confirmed. Once an additional 31 blocks have been built on top of this block, the commitment level changes to finalized.
Why Do Blockhash Errors Occur?
All transactions include a recent blockhash that acts as a time stamp, and that transaction expires when the blockhash is no longer considered recent enough. Validators that process transactions will check the “BlockhashQueue” (a list of the last 300 blockhashes) to see if the transactions blockhash is recent enough. If the blockhash is not in the list then the transaction will be rejected. Since slots usually last between 400ms and 600ms, a blockhash remains valid for 60-90 seconds.
Blockhash not found (Transaction simulation failed: Blockhash not found)
“Blockhash not found" errors occur when the blockhash included in a transaction is not deemed to be valid when a validator processes a transaction. This can either be because it is too old or, in some cases, too new.
The most common cause of this error is when the blockhash included in a transaction is not present in the queue of the last 300 blockhashes that the validator will compare against. This will cause the blockhash not found error. Expired transactions can happen when a transaction is created and processed within the required time period. This can be because a user takes a long time to sign a transaction. There can also be situations in which a valid transaction is submitted but not included in the current block, and when it can be included in a later block, the transaction’s block hash is too old.
In situations like this, where the transaction has effectively expired, it is common to see Block height exceeded errors (TransactionExpiredBlock heightExceededError). To better understand this error, one should understand what block height means. Block height refers to the number of blocks beneath the current block. If the current block is block 1000 then the block height is 1000. When a transaction is created, the maximum block height for that transaction will be valid is noted. If it is observed while this transaction is being processed that the current block height is higher than the transaction's maximum valid block height, then the error will be thrown.
Another situation that could cause a “blockhash not found error” is when a transaction’s blockhash is newer than the blockhash used to check the expiration for that transaction. This is a bit confusing so here is an example: You create a transaction and include a blockhash for a specific block, let's say block 1000, and immediately send it to an RPC, the RPC may get the blockhash for the previous block, which in this case could be block 999 (due to the RPC using a higher commitment level or if the RPC node is lagging behind the network). As a result, the blockhash in the transaction will not be found. This is an odd situation, but it can happen in two scenarios:
- When creating the transaction, the confirmed commitment level is used, but when the RPC calculates its validity, it defaults to using the finalized commitment. This can cause the blockhash to be older than the transaction's blockhash as finalized blocks are 31 blocks “behind” the latest block.
- If the processed commitment level is used to fetch the blockhash for a transaction on a minority fork that is eventually dropped, the blockhash will be invalid and won't be found during processing.
- If two different RPCs are used for getting the blockhash and sending the transaction, any lag experienced by the RPC sending the transaction could cause the block it uses to check validity to be older than the block used when creating the transaction.
How to Deal with Blockhash Errors
There are a few steps to take to deal with each of the above-mentioned situations.
- Ensure a transaction is submitted with a blockhash that is not too old:
- Use the "confirmed" commitment level when fetching the blockhash for a transaction, as this ensures a newer blockhash is included compared to the "finalized" commitment level.
- One can also use the “processed” commitment level for a slightly more recent blockhash, but roughly 5% of processed blocks aren’t finalized by the cluster. This would mean that the transaction’s blockhash will belong to a dropped fork and would no longer be valid.
- Ensure a transaction’s blockhash is not newer than the blockhash used to check the validity of the transaction:
- Always set the preflightCommitment (even when using skipPreflight) to the same commitment level used to fetch the transactions blockhash. This will help you avoid the transaction’s blockhash being newer than the blockhash used to check the transaction's validity.
- To deal with lagging RPC nodes when sending transactions, one should keep resending transitions to the RPC. This can be done on a set interval so that if an RPC is lagging, it will eventually catch up and detect the transaction's expiry.
- If the simulateTransaction request is used, then the replaceRecentBlockhash parameter should be set. This flag instructs the RPC to replace the simulated transactions blockhash with a blockhash that will always be valid for simulation.
- Continue to retry the transaction while the blockhash is valid:
- When a transaction is created and the latest blockhash is fetched, you should note the lastValidBlockHeight for that blockhash. You can then continue to retry the transaction with that blockhash until the current block height exceeds the transaction’s valid block height. The getBlockHeight RPC call can be used to continuously check if the transaction is still valid. Once the current block height is higher than the transaction’s lastValidBlock height, a new blockhash should be fetched and used.
I hope this blog post helps you gain a better understanding of blockhash errors and how to reduce the number of times these errors occur. If you have any further questions, please don't hesitate to join our Discord community or send us a direct message on X.
Additional Resources:
Helius Blog - Slots, blocks and Epochs