Expert · LESSON 03 OF 07
3. Transaction reliability and recovery
By the end, you can reconcile solana transactions across confirmation, expiration, retries, and interrupted clients.
Look up Solana terms in the glossary
Optional reading history is saved only in this browser. It is not a scored checkpoint or exam result.
In this lesson9 field notes · practice · sources · checkpoint
Read a text version of this diagram
An eligible operation is prepared and submitted with a bounded blockhash lifetime, then reconciled against chain state. Confirmation is one terminal path; expiry or execution failure requires a distinct recovery path. Retried requests must reuse stable identifiers and must inspect prior outcomes so a timeout cannot create duplicate or unintended actions.
- ELIGIBLE
- PREPARED
- SUBMITTED
- CONFIRMED
- EXPIRED
- FAILED
Read a text version: Solana transaction formats: legacy, v0, and v1
Legacy and v0 transactions are limited to 1,232 bytes and request resource limits with ComputeBudget instructions. V0 adds address lookup tables to reference non-signer accounts compactly; required signers remain inline. V1 raises the size limit to 4,096 bytes, keeps account addresses inline without lookup tables, and stores resource limits in the signed message configuration. Its priority fee is a total lamport amount. V1 is active on Mainnet, Devnet, and Testnet. Applications may keep sending legacy or v0, but a history reader must opt in with maxSupportedTransactionVersion set to 1 to read v1 transactions.
- LEGACY
- V0 WITH ADDRESS LOOKUP TABLES
- V1 WITH MESSAGE CONFIG
FREE INTERACTIVE CASEBOOK · ONLINE OR OFFLINE
Same signature or new authorization?
Work through six fictional wallet, RPC, expiration, execution, and duplicate-intent incidents. Choose the next action from the evidence available; feedback distinguishes delivery retries from a fresh business action.
Open the Transaction Recovery Tabletop Download the offline tabletopThe practice uses no wallet, RPC, blockchain, network request, or saved answers.
Separate user intent from network evidence
A button click expresses intent. A wallet prompt shows a message awaiting approval. A returned signature identifies a signed transaction, and an RPC acknowledgement only says a provider accepted a submission request. These are different states. Track them separately so the interface never reports “complete” from a click, signature string, or optimistic balance change. Record the cluster and transaction signature alongside the user action; never treat an explorer page without its cluster context as sufficient evidence.
Freeze the message before collecting signatures
A transaction message contains its fee payer, recent blockhash or durable nonce, account addresses, signer and writable permissions, instruction order, and instruction data. Every signer approves those bytes. Changing the fee payer, account list, instruction, or blockhash creates a different message and invalidates prior signatures. A coordinator should check that returned signatures match the required public keys and that the user-reviewed message did not change before submission. Keep the original serialized message or a digest when several parties sign asynchronously.
Treat blockhash lifetime as transaction state
A recent blockhash gives a transaction a limited processing window. Fetch and retain `lastValidBlockHeight` with it; after the block height passes that limit, the old transaction cannot be made live again by retrying the same bytes. Build a fresh message and collect signatures again. Durable nonce transactions support a different lifetime model, but the nonce must be valid and advanced correctly. Before rebuilding, query the original signature because a timeout does not prove the transaction was never processed.
Understand execution, fees, and commitment
Solana transactions execute their instructions atomically: state changes are committed together or rolled back if execution fails. The fee payer can still be charged when execution fails. An RPC response can report processed, confirmed, or finalized observations according to commitment and cluster migration state. On TowerBFT clusters, confirmed and finalized are distinct; after Alpenglow migration, Solana documents them as converging and plans to remove confirmed. Check the same cluster with a compatible getAgGenesisCert RPC, and use finalized in new code. Store the returned slot and status, and wait for the commitment required by the product before updating durable business records. Do not describe a submitted transaction as finalized without the required chain evidence.
Make application retries idempotent
The runtime rejects a transaction message that has already been processed, but a client can build a new message for the same logical action. Prevent duplicate deposits, orders, claims, or withdrawals at the application and program layers. Use a business idempotency key or canonical receipt account, reserve the action atomically, and make repeated requests return the existing result. The uniqueness rule must cover the actual protocol intent, not only a wallet button or a particular transaction signature.
Protect clients from stale and reordered results
Two requests can finish out of order: a slow balance read for wallet A may arrive after the user has switched to wallet B. Tag asynchronous work with the original cluster, address, and request generation. Apply a response only if those values still match current state. Treat provider timeouts and rate limits as retryable observations, not proof of failure. Use bounded backoff and avoid sending a new value-moving transaction automatically when the earlier attempt is uncertain.
Reconcile instead of guessing
Define a state machine such as draft, awaiting signature, signed, submitted, confirmed, finalized, expired, and failed. Each transition needs evidence: wallet response, signature, status query, transaction metadata, block height, or account state. On restart, look up the same signature and inspect the intended account changes before creating another action. If providers disagree, record the uncertainty and query an independent endpoint. A durable receipt should let support explain exactly what happened without asking for a recovery phrase or private key.
Treat transaction version as part of recovery evidence
Transaction v1 is live on Mainnet, Devnet, and Testnet. The Solana Foundation records Mainnet activation at the start of epoch 1035 on September 15, 2026, at approximately 01:00 UTC. Keep transaction writing and reading separate: applications may continue to send legacy or v0 transactions, but history readers that encounter v1 must request the integer `maxSupportedTransactionVersion: 1`. A `getTransaction` request without that support can fail even when the transaction landed; a `getBlock` request can fail as a whole when its block contains a v1 transaction. `blockSubscribe` also takes the version option; without support, its stream can stop advancing at a v1 slot and report `block: null` with an error. Treat that as a failed read, not an empty block. SDK support is directional: the current migration guide says `@solana/web3.js` v1.99.0 and later can read and deserialize v1 with that opt-in, but the web3.js v1 line cannot build, sign, or send v1 transactions; earlier v1 releases are limited to legacy and v0. Use a current, pinned `@solana/kit` 8.x client for v1 construction and sending, and verify the exact wallet path too. For v1 construction, set the compute-unit limit and loaded-account-data-size limit explicitly; unlike older formats, v1 carries resource configuration in the message and compute-budget instructions are no-ops. This means a client that copies a legacy/v0 fee calculation can report the wrong priority fee: older formats derive it from requested compute units times the micro-lamport price per unit, while v1 records a total priority fee in lamports. Before retrying an uncertain submission, preserve the original signature and blockhash lifetime, query with a decoder that supports the transaction format, and reconcile state. If the lookup fails because the reader is too old, that is a compatibility failure, not evidence that the transaction never landed. Test the migration against pinned SDK versions and the wallet path used by the application.
Retry the same signed message before rebuilding
A successful `sendTransaction` response returns the first transaction signature; it does not confirm execution. Keep that signature, the exact serialized signed bytes, the cluster, and the `lastValidBlockHeight` returned with the blockhash. If the client loses the acknowledgement or times out while the recent blockhash is still valid, it may rebroadcast the same fully signed bytes: the transaction message and signature stay identical, so the runtime duplicate check prevents a second execution. Continue checking that same signature and report the result as pending until the required commitment is observed. Do not ask the wallet to sign a new message merely to repeat network delivery. After the recorded block-height limit passes, the old message cannot be refreshed without invalidating its signatures. Search transaction history when the signature may have left the recent cache, query a compatible transaction reader at the needed commitment, and reconcile the relevant account or receipt state. A `null` from one provider is not proof of absence. If evidence supports a genuinely unprocessed action, check the business idempotency key or on-chain receipt before building a new message and collecting fresh signatures. A processed instruction error can still charge the fee payer even though instruction state rolls back; distinguish that chain failure from a wallet cancellation or an RPC timeout.
A detail worth keeping.
For a checkout that transfers a token and records an order, inject a dropped response after submission and again after finalization but before the database write. Reconcile the same signature, confirm the order exists once, and show that a retry cannot charge the buyer twice.
Work through an example
For a multi-party signing flow, let one signer respond after the recent blockhash expires. The old signatures cannot authorize a rebuilt message. Explain why the client must fetch a fresh blockhash, freeze the new message, and collect all required signatures again.
Create a transaction-recovery table for an escrow purchase. Include user cancellation, RPC timeout, expired blockhash, failed execution with fees, delayed finalization, duplicate browser requests, and a lost database acknowledgement.
GUIDED PRACTICE · EXPERT
Do the work, one step at a time.
Use fictional addresses and disposable test data. Do not enter a recovery phrase or private key. Most exercises work on paper or with a local test; only a lesson that clearly says “read-only Devnet” makes a public network request.
- Build a state diagram from wallet intent to finalized account state, including expired and failed branches.
- Use a mocked RPC to return delayed, null, duplicate, and out-of-order status responses.
- Bind every response to its original cluster, signature, blockhash lifetime, and request generation.
- Retry one logical purchase from two browser tabs and prove it creates only one business result.
- Write the recovery procedure for an uncertain submission and identify which observations remain non-final.
- Rebroadcast identical signed bytes while the recorded blockhash remains valid, then simulate expiry and a history miss; keep the business action unresolved until signature history and receipt state justify the next message.
Before you move on, check your reasoning
- Draw the transaction and business-state lifecycles side by side.
- Inject interruptions at signing, submission, confirmation, and persistence boundaries.
- For every retry, name the evidence that prevents duplicate execution.
OPEN SELF-CHECK · UNGRADED
Pause and test your reasoning
Practice 1 of 3
The RPC request timed out immediately after transaction submission. What is the safest next step?
Reveal the answer and explanation
One best answer: Treat the outcome as unknown, reconcile the original signature and relevant state, and retry only under an explicit duplicate-safe policy.
A client timeout does not reveal whether an RPC received, forwarded, or confirmed the signed transaction. Query the original signature and application state on the correct cluster; only retry after expiry and duplicate behavior are understood.
Sources: Transaction Confirmation and Expiration · getSignatureStatuses
Practice 2 of 3
A recent-status query is empty after a blockhash expires. What must happen before retrying the user’s action?
Reveal the answer and explanation
One best answer: Reconcile history and state before retrying.
Blockhash expiry limits transaction validity, but a timeout or empty recent cache is not complete history. Reconcile the original signature and durable business state before a new authorization.
Sources: getSignatureStatuses · getTransaction · Transaction Confirmation and Expiration
Practice 3 of 3
Two workers receive the same confirmed claim request. Which control prevents two business actions?
Reveal the answer and explanation
One best answer: Enforce a durable business idempotency key with an atomic reservation; reconcile signature and receipt evidence before retrying.
Different signed transactions can represent the same business intent. A durable application-level idempotency key and atomic state change prevent duplicate actions; reconcile persisted receipts and signature history when workers race or recover.
Sources: Transactions · getSignatureStatuses
This practice is not scored, saved, or part of a certificate. Account-based lesson checkpoints and level exams remain separate.
Never enter a recovery phrase or private key into a learning site or lesson exercise.
READ THE SOURCE
Go deeper in the official docs
Reviewed on 2026-10-02. Protocol documentation can change; check the linked page’s current version as you use this material.
YOUR PROGRESS, SAVED
Ready for the checkpoint?
Make a free account to save your progress and unlock the quizzes.
Create free account