T4 · What Is RPC?
Who is your DApp actually talking to?
- Capabilities practiced
- Builder
- Hands on
- Run the same query through two RPC providers and compare latency, rate limits, and historical-data availability.
- AI Lab
- Have AI write a provider wrapper with retries, timeouts, and multi-node failover. Add the idempotency handling it missed yourself.
A Real-World Problem
A DApp that worked perfectly on your computer begins behaving strangely three days after launch.
- A user sees a zero balance on page load, but the right value after refreshing several times.
- Another user sees missing transaction history that reappears the next day.
- Every test you run looks normal.
You inspect logs and find many 429 responses, then timeouts. Further down, one response reports a block height lower than a response from five minutes earlier.
Then you realize: **your code has never touched a blockchain directly.**It talks to an HTTP endpoint—a service you did not write, deploy, or monitor, whose logs you cannot inspect when it fails.
Who is behind that endpoint? Why should it have the data? When might it stop answering? How would you know if its answer were wrong?
Thought Experiment
Imagine building a reporting system on a read-only database replica. You are told five things about the replica:
- **It rate-limits.**Beyond some query rate it refuses requests, and queries have different "weights": fetching one row and scanning ten million rows may consume hundreds of times different quotas.
- **It may lag.**There is no guarantee when a primary write reaches the replica. Two consecutive requests can make the second look older if a load balancer sends you to another machine.
- It may lack history.To save disk, it retains only recent data. Asking for older data producesan opaque error, not an empty result.
- **It may return false data.**Nothing inherently prevents that. You receive JSON but cannot judge its truth from the JSON alone.
- **You do not operate it.**If it goes down, you wait.
Ask yourself: can you still make a reliable reporting system under these conditions?
Yes, but your architecture must handle all five explicitly: backoff and queuing for limits; monotonic checks for lag; known boundaries for missing history; cross-checks for false data; backup sources when the operator is unavailable.
Those five concerns are the whole chapter.
Replace "read-only replica" with "RPC endpoint" and every point still applies. The only difference is that the database replica might at least belong to your company; the endpoint may not.
Your Decision
You must choose an RPC setup for a product about to launch.
What Happens
The options differ on five dimensions:
| Dimension | Free public endpoint | Managed service | Self-hosted node | Multiple sources with your wrapper |
|---|---|---|---|---|
| Cost | Zero | Usage-based, grows with scale | Fixed and high upfront | Highest, but controllable |
| Rate limit | Strict and opaque | Stated quota, paid overages | Hardware-limited | Allocated by you |
| Historical depth | Usually shallow | Plan-dependent; archive costs extra | Your choice | Route by method |
| Trust | Trust the provider fully | Trust the provider fully | Trust your own validation | Cross-validation possible |
| During failure | Wait | Wait | Repair it yourself | Fail over automatically |
Of these five, trust is the only dimension that money alone cannot solve.
The rest are trade-offs: buy more quota, buy archive access, or spend staff time on your node. But no matter how much you pay, "how do I know this JSON is true?" has the same answer: you do not, unless you verify it.
The key fact about RPC is this: **using a third-party RPC means trusting the data it returns.**At that point, the endpoint puts a ceiling on how decentralized your product really is.
Build a Model
The path a request really takes:
- Your code
- Provider wrapper
- HTTPS to provider gateway
- Load balancer selects an instance
- A node process
- Its local database
Three dimensions that limit your product:
1. Rate limits.Think of them as computational quotas, not simply "requests per second." Reading one balance and scanning a million blocks of logs may consume very different amounts. Usage therefore depends less on user count than onwhich methods you call.
Optimize by narrowing log ranges, combining batch queries, and caching stable data. T15 covers the cache layer.
**2. Data depth.**Nodes vary: some retain only recent state; some retain all blocks but prune old state; archive nodes retain complete state at every historical height.
A balance or storage query at an old height fails on the first two rather than returning empty. The error message often looks nothing like "historical data unavailable", so first-time users mistake it for a bad parameter.
**3. Consistency.**Two instances may have different latest heights at the same time, and the chain tip can reorganize. Two consequences follow:
- Repeating the same logical request can return older state the second time.
- Data read at "latest" may disappear seconds later.
Handle this with explicit block tags rather than always asking for "latest":
| Tag | Meaning | Appropriate use |
|---|---|---|
| latest | This instance's current latest block | Displays that tolerate fluctuation |
| safe / finalized | Marked by the protocol as hard to reverse / irreversible | Decisions about funds |
| Specific height | One named block | Batch indexing, reconciliation, replayable work |
| pending | Includes mempool contents | Reading nonces only, not balances |
**Practical rule: any process whose result must match on a second run should pin a block height instead of using latest.**An indexer built against latest is not replayable; when it fails, you cannot even reproduce its input.
What your Provider wrapper should provide:
| Capability | Problem it solves |
|---|---|
| Timeout | A hung upstream does not exhaust your own connection pool |
| Retry with exponential backoff | Transient faults and 429 responses |
| Circuit breaker | Stop wasting time on a persistently failing source |
| Method-based routing | Expensive archive source for historical reads; cheaper source for ordinary reads |
| Monotonic height guard | Prevent a lagging fallback source from making height move backward in your business layer |
| Cross-validation | Ask two sources for critical data and alert if they disagree |
The last two turn "multiple backends" into actual resilience. With only the first four, you have load balancing; with all six, you address trust.
What It Is Called
A machine running a chain client, storing chain data, and performing validation.
Nodes differ in retained data: recent state only; all blocks but pruned old state; or complete state at every height in an archive node. The type determines how far back you can query, and its cost.
The external interface protocol of the chain. The request is JSON with a method name and parameter array; the response contains either a result or an error.
Its methods are standardized across clients. SDK APIs change frequently, but eth_getBalance has remained stable for years. That is why this course calls JSON-RPC directly.
The abstraction in your code responsible for making RPC calls.
It is the one place to add retry, timeout, cache, routing, and validation. If business code sends HTTP requests everywhere, you have nowhere to implement the features in this chapter.
The provider's restriction on usage, often weighted by each method's computational cost instead of raw request count.
It may fail with an explicit 429 or a returned error object. The latter is dangerous because code that ignores the error field may mistake it for an ordinary empty result.
A node retaining complete state at every historical height. It supports historical balance, storage-slot, and contract-call queries.
It costs far more storage than an ordinary node, and providers charge more for archive access. This choice sets the cost structure of many data products.
A query parameter identifying which block's state to use. latest, finalized, and a specific height serve different purposes.
**This parameter is often underestimated.**Use it wrongly and your code works 99% of the time, while the other 1% produces wrong results you cannot reproduce.
Hands-on
**Read-only queries throughout: no transactions, private keys, or real assets needed.**Try one free public endpoint and one managed provider's free tier to make differences visible.
Compare latency.
for i in $(seq 1 10); do
curl -s -o /dev/null -w '%{time_total}\n' -X POST <endpoint-A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
doneRun it for each endpoint and compare median and maximum. Pay attention to the maximum, not just the average; users experience the slow tail.
Compare reported heights.
Ask both providers for eth_blockNumber at the same time. Convert hexadecimal to decimal and compare the block counts.
Ask one provider ten times in a row. Does a later response ever show a lower height than an earlier one? If so, requests may reach different instances and you need a monotonic height guard.
Compare historical depth.
curl -s -X POST <endpoint-A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getBalance",
"params":["<any-active-address>","0x<very-old-block-height>"]}'Move backward from a recent height and binary-search for the height at which each provider begins returning an error. That is its history boundary.
Read the error carefully. It may describe a low-level state lookup failure, not "archive data unavailable." Remember its shape so you recognize it in production logs.
Compare maximum log ranges.
curl -s -X POST <endpoint-A> \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{
"fromBlock":"0x...","toBlock":"0x...",
"address":"<active-token-contract-address>"}]}'Start with 100 blocks, then double the range until rejected. Record each provider's limit and whether it limits block span or number of returned records. The distinction changes how an indexer must partition requests.
Compare rate limits.
Make 100 consecutive eth_blockNumber calls and count when 429 responses or error objects begin.
Repeat with a heavy method, such as a large-range eth_getLogs request. Compare how quickly you hit the limit to see what usage weighted by computation means.
Inspect the HTTP status and response body when limited.**Is there a Retry-After header? Is the failure at HTTP level or in the JSON error field?**Your retry logic must cover both.
Cross-validate once.
For the same address at the same specific block height—not latest—ask both providers for eth_getBalance and compare.
Repeat with an eth_call for a contract.
Results should match exactly. A mismatch means at least one source is wrong. Cross-checking is how your production system can notice that for itself.
Write your findings in a table.
For each provider, record median and P99 latency, historical boundary, log-range maximum, rate-limit threshold, and price.
Use the table to choose a provider and later to write routing rules. Measure again in three months; these numbers change.
AI Lab
Ask in two steps; the second is the point.
Step one:
Write an RPC Provider wrapper with multiple backend endpoints, timeouts,
failed-request retries with exponential backoff, method-based routing
(archive methods to a designated endpoint), and a circuit breaker.
Use my chosen language and no third-party libraries I did not request.
Step two:
Assume a replayable indexer uses this wrapper.
List every situation in which your implementation could return inconsistent results that look normal.
Propose a fix for each one.The second step should expose three omissions a first draft often has:
- Idempotency. Retrying
eth_sendRawTransactionwith the same signed bytes after a timeout is relatively safe; a node sees a known transaction. But reconstructing and signing during the retry can pay twice, as T3 showed. Models often conflate the two. - **Height regression.**When source A fails over to source B, B may be several blocks behind. An indexer may read state older than the position it has already processed and write incorrect data.
- **Timeout does not mean nonexecution.**A timeout says only that no response arrived in time. The upstream may have completed the request. For reads this is minor; for writes it is critical.
After you receive its fixes, reproduce one locally. Replace one source with a fake service that deliberately reports an old height and see whether the wrapper passes regressed data to the application.
**Many people can ask a model for a wrapper. Fewer know how to challenge it.**That is the point of this Lab.
Verify these points yourself after using AI
- The cited libraries, classes, and method signatures exist in official documentation
- It handles both HTTP 429 and an error field inside a JSON response as failures
- Retries distinguish idempotent methods from writes, including safe retry of transaction broadcast
- A fallback handles a source whose block height is lower than the previous source
- It does not assume a timed-out request was never executed upstream
- It converts hexadecimal heights and balances to decimal correctly
- It does not confuse a successful RPC response with correct data
Real-World Case
A major managed RPC provider suffers a regional outage. Many frontends cannot read any chain data at the same time.
The irony: the chain continues producing blocks normally. The chain did not stop; the products did.
This reveals a structural problem: many supposedly decentralized apps share one centralized dependency. Some teams built multi-source fallback only after the incident, though architecture reviews could have caught the risk earlier.
A trading interface reads pool state from a free public endpoint to calculate quotes. During peak demand, the endpoint falls several blocks behind.
Users see prices from tens of seconds earlier. Transactions submitted against those quotes may fail on-chain because slippage limits are exceeded—or worse, settle at a clearly unfavorable price.
The root cause is reading price-sensitive data at latest without checking its freshness, not simply that the endpoint was free. Read the returned block height too; compare it with your expected tip and refuse a quote if the gap is too large.
A data product needs to scan every event emitted by a contract since deployment. One huge log-range query is rejected by its endpoint.
The team partitions the range, fetches shards concurrently, records checkpoints, and retries failures. Only then does it notice it has built the outline of an indexer.
**Much on-chain data infrastructure begins when a log query is rejected for spanning too many blocks.**T17 completes the design with reorganization handling and checkpoint resumption.
A scammer persuades a user to add a "faster" custom RPC endpoint in their wallet.
That endpoint returns any number it wants for a balance query. The victim sees a large "received" balance in the wallet and hands over their goods, even though no transfer ever happened on-chain.
This is the clearest demonstration that "using a third-party RPC means trusting its returned data." Every number displayed in your interface is a claim made by some endpoint.
Change One Variable
You must verify independently. Three paths exist: run your own node, which is most thorough and expensive; cross-check several independent sources, which detects disagreement but cannot prove who is right; or use a light-client approach—trust block headers and require verifiable proofs for other data.
The third uses the Merkle structure from T2: receive a state value and a path to the state root, then recompute the root yourself.
Most products choose cross-checking for its cost-to-benefit ratio. At least know which path you chose.
Usage-based bills become a real cost line, and their growth may not match your revenue.
The real optimization is fewer calls, not merely a cheaper provider: cache read-heavy stable data, replace frequent polling with event subscriptions, aggregate per-user queries into batches, and store what you can locally. T15 and T16 cover these techniques.
This is often when a self-hosted node becomes economically attractive—not out of ideology, but because the numbers work.
Latency and call count fall, but a new problem appears: data during disconnection is lost.
On reconnect, backfill from the last processed height or you will silently miss blocks. Providers also handle reorganization notices differently; some push rollback events, while others do not.
Subscriptions are an optimization, not a replacement. A reliable system always needs a compensating path that replays by block height.
Methods differ—getAccountInfo reads an account and getBalance reads a balance—but all five dimensions still matter: rate limits, historical depth, consistency, trust, and single points of failure.
History is even more constrained: Solana normally retains only a limited span, while older data requires a dedicated historical service. A request for a transaction from six months ago therefore has a very different cost structure from EVM. T6 covers its account model, where even the query shape changes.
Takeaway Questions
If all your product data comes from one third-party endpoint and users have no way to verify it, its trust structure resembles a centralized API. The chain offers independent verification to anyone, but whether your users receive that benefit depends on your design at this layer.
You pay the RPC bill, not the user. An often missed unit-economics question is how much compute quota each active user consumes per day. That number determines whether the product scales. Calculating it with 100 users is cheaper than discovering it with 100,000.
Who bears losses if an endpoint returns incorrect data? Contractually, probably you. "We use a famous provider" does not control that risk; cross-validation and freshness checks do, especially for decisions about funds.
Chapter Check
RPC method names are standardized across clients and remain stable for years, while SDK APIs change more often.
There is a practical reason too: an SDK can hide how many requests you send. A one-line call might perform three RPC requests. Direct JSON-RPC makes usage explicit.
Production code may use an SDK, but you should be able to translate its actions back to RPC calls mentally.
latest means the newest block this node instance sees, and chain reorganizations can change it. finalized marks a block the protocol considers irreversible, but lags behind latest.
Reads that decide movements of funds should use finalized or a sufficiently deep specific height: crediting deposits, releasing limits, or triggering liquidations. Displays may use latest.
Replayable processes such as indexing and reconciliation need specific heights, rather than either moving tag.
Not necessarily. Load balancing may send the two requests to node instances at different synchronization points; a chain-tip reorganization can also move height backward.
Your Provider should remember the maximum height it has seen. When a response falls below it, retry, switch sources, or expose the condition to business logic—do not silently pass regressed data through.
When you need state at a historical moment: a year-old balance, reconstruction of a past transaction, replaying a contract call at a given block, or deep execution tracing.
Distinguish two needs: historical blocks and transactions do not necessarily require an archive node; historical state does. Confusing them can increase cost by an order of magnitude.
Confirm which data you need before choosing a service tier.
There is no single standard answer. Check these points:
- Does all business code call one Provider abstraction instead of sending HTTP independently?
- Are there at least two independent sources, with height regression handled on fallback?
- Does retry distinguish idempotent reads and writes, with defined timeout behavior?
- Are both HTTP 429 and a JSON error field handled?
- Are archive methods routed separately so every query does not pay the premium tier?
- Which block tag is used for decisions about funds?
- Do you monitor each source's latency, error rate, height difference, and quota use?
- Are critical values cross-validated, with alerts on disagreement?
Points 2, 3, and 8 show that you are building infrastructure rather than merely "connecting an API."
Key idea
RPC is the only gateway between you and a chain; its rate limits and data integrity set the ceiling for your product.