Proviroll

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 caseCommitment policy to documentClient consequence
UI that can show tentative stateWhether processed is permitted and how reorg-like changes are represented.Treat updates as provisional until the chosen durable condition is met.
Application workflowThe exact requested commitment and whether a minimum context slot is used.Do not mix results from arbitrary slots without handling the context.
Accounting/settlement/exportFinality requirement and data-retention behavior.Do not mark a business action final from a lower commitment response.
Health/readinessReference-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 minContextSlot use 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:

  1. Obtain a recent blockhash with getLatestBlockhash; retain both the blockhash and its lastValidBlockHeight.
  2. Construct/sign/send the transaction using the service’s documented preflight and commitment settings.
  3. Track the signature until the application’s required status is reached, rather than treating an accepted HTTP request as execution.
  4. If the transaction is still unresolved, compare getBlockHeight at the relevant commitment to the saved lastValidBlockHeight before deciding that the blockhash has expired.
  5. 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

ObservationWhat it meansSafe next action
HTTP/JSON-RPC success from sendTransactionThe RPC accepted the submission request; it is not a final business outcome.Track the returned signature.
Proxy timeout / dropped connectionSubmission outcome may be unknown.Query/reconcile the signature; do not immediately duplicate-submit.
Preflight/simulation errorThe RPC rejected or failed preflight under its configured behavior.Surface structured error data, then repair/rebuild only when understood.
getTransaction returns nullIt 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 lastValidBlockHeightThe 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.

On this page