RPC Consistency and Client Correctness
Publish and operate clear commitment, transaction, and WebSocket semantics for a Solana RPC service.
RPC consistency is the guarantee a service makes about the ledger state, commitment level, and delivery behavior behind each response or notification. An RPC service can be available, authenticated, and fast while still producing incorrect client behavior if it does not make its consistency and retry semantics explicit. This page is for operators and service owners defining a public RPC contract.
The upstream RPC documentation defines method-specific parameters and return structures. The service layer must decide which of those options it supports, how it routes requests, and how clients should act when backends disagree or a connection drops.
Commitment is part of every read contract
Many HTTP and WebSocket methods accept a commitment of processed, confirmed, or finalized; method defaults are not a safe substitute for an explicit product decision. The account subscription reference, for example, lists a default commitment of finalized, while applications may deliberately select another level for their latency/certainty trade-off.
Publish a policy like this for every product endpoint:
| Use case | Commitment policy to document | Client consequence |
|---|---|---|
| UI that can show tentative state | Whether processed is permitted and how reorg-like changes are represented. | Treat updates as provisional until the chosen durable condition is met. |
| Application workflow | The exact requested commitment and whether a minimum context slot is used. | Do not mix results from arbitrary slots without handling the context. |
| Accounting/settlement/export | Finality requirement and data-retention behavior. | Do not mark a business action final from a lower commitment response. |
| Health/readiness | Reference-slot comparison and lag tolerance. | A backend can be removed even when its HTTP listener works. |
Return or preserve the response context slot where the API provides it. It gives callers evidence of the ledger view that produced a response. For requests that support minContextSlot, explain whether clients should use it to prevent a lagging backend from serving a response below a previously observed state.
Load balancing and slot lag
Round-robin routing alone can send sequential calls to backends at different slots. The risk becomes visible when a client reads account state, obtains a blockhash, and submits or confirms through another backend. Mitigate it with a documented combination of:
- Slot-aware backend readiness: exclude nodes that exceed the allowed reference-slot lag.
- Client affinity where it materially improves a multi-step workflow, with a bounded lifetime so an unhealthy backend cannot pin traffic forever.
- Explicit commitment and
minContextSlotuse where supported by the RPC method. - Clear retry guidance that treats a timeout as an unknown outcome, not proof of failure.
Do not claim that a load balancer creates strong consistency. The correctness contract remains method-, commitment-, and workflow-specific.
Transaction submission lifecycle
Document the whole lifecycle, not only sendTransaction:
- Obtain a recent blockhash with
getLatestBlockhash; retain both the blockhash and itslastValidBlockHeight. - Construct/sign/send the transaction using the service’s documented preflight and commitment settings.
- Track the signature until the application’s required status is reached, rather than treating an accepted HTTP request as execution.
- If the transaction is still unresolved, compare
getBlockHeightat the relevant commitment to the savedlastValidBlockHeightbefore deciding that the blockhash has expired. - Reconcile by signature before sending a replacement transaction. A proxy timeout or connection loss does not prove that the original was not accepted.
The official confirmation guide recommends retaining lastValidBlockHeight and polling getBlockHeight at confirmed until it exceeds that value when determining expiry. It also notes that RPC nodes can lag cluster propagation; this is why slot-aware backend readiness matters.
Failure classes to distinguish
| Observation | What it means | Safe next action |
|---|---|---|
HTTP/JSON-RPC success from sendTransaction | The RPC accepted the submission request; it is not a final business outcome. | Track the returned signature. |
| Proxy timeout / dropped connection | Submission outcome may be unknown. | Query/reconcile the signature; do not immediately duplicate-submit. |
| Preflight/simulation error | The RPC rejected or failed preflight under its configured behavior. | Surface structured error data, then repair/rebuild only when understood. |
getTransaction returns null | It is not found at the requested commitment or is unavailable at that backend/retention state. | Check commitment, retention, lag, and expiry before declaring failure. |
Block height exceeded saved lastValidBlockHeight | The recent blockhash is expired for that lifecycle. | Build and sign a new transaction after application-level reconciliation. |
If an application needs a transaction that can be prepared well before submission, evaluate durable-nonce workflows from the official guidance separately. Do not silently substitute them for normal blockhash-based flows; they have distinct setup and lifecycle requirements.
WebSocket subscriptions are streams, not durable queues
Subscriptions such as accountSubscribe, programSubscribe, signatureSubscribe, slotSubscribe, and logsSubscribe are valuable for low-latency notification, but a WebSocket disconnection creates a gap. The service should publish:
- Supported subscription methods, encodings, commitment options, filters, and per-connection/per-key limits.
- Maximum connection age, idle timeout, ping/heartbeat expectation, and behavior during deploys or backend failover.
- Whether subscriptions are sticky to one backend and what happens to them when that backend is removed.
- A reconnect procedure: resubscribe, then reconcile missed state with HTTP queries from a recorded cursor/slot/signature where the application supports it.
- Payload-size and rate controls, plus a clear reason when a subscription is rejected or terminated.
Treat a notification as a signal to reconcile state, particularly for workflows with financial consequences. Do not promise exactly-once delivery or durability unless the service actually implements a separate persisted streaming product.
blockSubscribe deserves special caution: the official RPC reference labels it unstable and says it requires both --rpc-pubsub-enable-block-subscription and --enable-rpc-transaction-history. If the service exposes it, put its availability, cost, limits, and version dependency in the API contract rather than presenting it as a baseline feature.
Versioned transactions and response compatibility
Some RPC methods accept maxSupportedTransactionVersion. A client that omits a necessary value can receive a response it cannot safely decode, while a provider that changes its backend version can expose a new transaction form before a downstream integration is ready. Version every client example, test against the declared transaction-version policy, and announce compatibility changes before deployment.
Likewise, treat jsonParsed as a convenience representation with method/program-specific behavior, not an immutable database schema. For long-lived indexing, preserve the raw or canonical fields required by the product and pin parser expectations in tests.
Operator test cases
Before publishing an RPC tier, automate these scenarios:
- A multi-step client workflow crosses a healthy but slot-lagged backend; the readiness policy prevents a stale response from violating the documented contract.
- A transaction submission connection is cut after the edge forwards it; the client can reconcile without duplicate business effects.
- A blockhash reaches
lastValidBlockHeight; the client reports expiry based on block height rather than a guessed wall-clock timeout. - A WebSocket connection drops during activity; the client reconnects and reconciles the missed range.
- A subscription is rejected because it exceeds method/filter/connection limits; the error is actionable and does not impact other tenants.
- A history/index-dependent method reaches its retention boundary; the response is deterministic and documented.
These tests belong beside RPC security and capacity testing. They prove the service’s client contract, whereas host benchmarks prove only one part of its infrastructure behavior.