← Back to Blog

Caching Blockchain State: Nodes, RPCs, and 50KB Receipts

April 18, 2026 | 12 min read | Engineering

If you run a blockchain RPC provider -- Helius, Alchemy, Infura, QuickNode, or your own full node behind an API gateway -- the cache layer is not a performance optimization. It is the product. Your customers do not care how many archive nodes you operate or how fast your Erigon instance syncs. They care about one number: how many milliseconds elapse between their eth_getTransactionReceipt call and the JSON response. The cache determines that number for 80-95% of requests.

This is a different caching problem from web application sessions or feature flags. The payloads are large. Ethereum transaction receipts with event logs range from 10KB to 80KB. Block data is 1-2MB. Solana account data for programs can exceed 10MB. And the request volume is extreme: major RPC providers serve tens of millions of requests per second across all methods. Every cache miss means re-querying an archive node, which takes 50-500ms depending on the method and how deep into history the request reaches.

Most RPC providers use Redis as their primary cache. This post explains why that architecture breaks at blockchain payload sizes, what the alternatives look like, and how the post-quantum transition will make the problem significantly worse.

What Gets Cached: RPC Methods and Their Payloads

Not all RPC methods are equal. They differ in response size, access pattern, and cacheability. Understanding this taxonomy is essential before choosing a caching strategy.

RPC MethodChainTypical Response SizeAccess PatternCacheability
eth_getTransactionReceiptEthereum10-80 KBHot for recent txs, then coldImmutable after confirmation
eth_getBlockByNumberEthereum1-2 MB (full block)Hot for last 128 blocksImmutable after finality
eth_callEthereum32 B - 100 KBDepends on contractValid until next block
eth_getLogsEthereum1 KB - 5 MBBursty (indexers, dashboards)Immutable for finalized ranges
eth_getBalanceEthereum32 BHigh frequency, hot walletsValid until next block
getAccountInfoSolana100 B - 10 MBHot for active programsValid until slot update
getTransactionSolana5-20 KBHot for recent, then coldImmutable after confirmation
getBlockSolana100 KB - 1 MBHot for recent slotsImmutable after finality
getMultipleAccountsSolana10 KB - 50 MBDeFi aggregators, batch readsValid until slot update
eth_getTransactionByHashEthereum1-5 KBModerate, lookup by hashImmutable after confirmation

Two patterns emerge from this table. First, the most frequently accessed methods produce large responses. Transaction receipts, the single most common read operation on Ethereum infrastructure, average 30-50KB when the transaction interacted with a DeFi protocol that emits multiple events. A Uniswap V3 swap receipt with price oracle updates, pool state changes, and transfer events routinely exceeds 40KB. Second, most blockchain data is immutable once confirmed. A transaction receipt for a finalized transaction will never change. This makes it an ideal cache candidate -- there is no invalidation problem for confirmed data.

The challenge is the combination of large payloads and high request rates. A popular DeFi dashboard that displays recent swaps might call eth_getTransactionReceipt 50 times per page load, once for each displayed transaction. Multiply that by 10,000 concurrent users and you have 500,000 receipt requests per second, each returning 30-50KB of JSON.

Redis at Blockchain Scale: The Math Does Not Work

Consider a mid-tier Ethereum RPC provider handling 100,000 eth_getTransactionReceipt requests per second. Average receipt size is 50KB. Using Redis as the cache layer, every cache hit requires serializing 50KB through the RESP protocol, transmitting it over TCP, and deserializing it on the client side.

At 50KB per value, Redis GET latency is approximately 1.4ms at P50 in the same availability zone. That number comes from the linear scaling of Redis latency with value size -- at 50KB, the TCP transfer and serialization costs dominate the base network round-trip.

1.4ms
Redis GET at 50KB
100K
Requests per second
140s
Cumulative Redis latency/sec

The cumulative math is straightforward: 100,000 requests/sec multiplied by 1.4ms per request equals 140 seconds of Redis processing time consumed every second. You need at minimum 140 Redis connections operating in parallel just to keep up, and that assumes zero queueing, zero P99 spikes, and zero cross-shard overhead. In practice, RPC providers run 10 to 50 Redis shards to distribute this load, each shard handling a subset of keys. This adds significant operational complexity: consistent hashing, shard rebalancing, cross-shard lookups when related data (a block and its receipts and their logs) spans multiple shards.

The problem compounds for eth_getBlockByNumber. A full Ethereum block with transactions is 1-2MB. At that size, Redis GET latency exceeds 12ms at P50. Caching the latest block -- which every light client, every indexer, and every dashboard requests -- means every cache hit costs 12ms of Redis time. At 10,000 block requests per second, you consume 120 seconds of Redis time per second on a single key.

This is why large RPC providers do not serve block data from Redis. They use custom in-memory stores, memory-mapped files, or in-process caches. But the receipts, the logs, the account data -- those still flow through Redis at most providers, and they pay the latency tax on every request.

The Cross-Shard Receipt Problem

An API call like eth_getBlockReceipts returns all receipts for a block. A typical Ethereum block contains 150-300 transactions. If receipts are sharded across Redis by transaction hash, fetching all receipts for one block requires 150-300 Redis GETs across multiple shards. At 50KB per receipt and 1.4ms per GET, the sequential cost is 210-420ms -- slower than re-querying the archive node. Pipelining helps, but cross-shard pipelines still require multiple network round-trips and coordinated reassembly.

The Solana PnL Story: 19,050x Faster

We measured this directly. The Solana PnL use case computes profit-and-loss for a JUP treasury wallet using the Helius RPC API. The cold path -- no cache, full RPC call chain through Cloudflare to Helius to a Solana validator -- takes 2,286ms. That is 2.3 seconds to answer "what is this wallet's PnL?"

With Cachee serving the response from in-process memory on a warm hit, the same query returns in 0.12ms. That is a 19,050x speedup.

2,286ms
Cold (Helius RPC)
0.12ms
Warm (Cachee)
19,050x
Speedup

The breakdown of the cold path is instructive. Of the 2,286ms total, approximately 44% is Cloudflare infrastructure overhead -- TLS termination, WAF rules, DDoS filtering, geographic routing, and HTTP/2 connection management. The actual Helius RPC processing and Solana validator query account for the remaining 56%. An in-process cache eliminates all of it on repeat queries. There is no Cloudflare hop. There is no TLS handshake. There is no TCP connection. The response is a pointer dereference in local memory.

If Redis were the cache layer instead, the warm path would be approximately 1.5-3ms depending on the response size (Solana PnL responses range from 20-100KB depending on transaction history depth). That is 12,500x better than cold, but still 12,500x worse than in-process. For an RPC provider serving millions of requests, that difference between 0.12ms and 3ms is the difference between a 200-server fleet and a 20-server fleet.

Block Data Tiering: Hot, Warm, and Cold

Blockchain data has a natural temperature gradient that maps directly to cache tiering. The access pattern is remarkably predictable compared to general web application caching.

L0: The Hot Window (Last 128 Blocks)

On Ethereum, the last 128 blocks receive the overwhelming majority of read traffic. Block explorers show recent transactions. DeFi dashboards track recent swaps. Wallets check recent confirmations. MEV searchers scan the latest blocks for opportunities. This window represents approximately 25 minutes of history (at 12-second block times) and accounts for 70-85% of all block and receipt reads.

These 128 blocks, with all their receipts and logs, fit comfortably in L0 in-process memory. At 2MB per block and 150 receipts averaging 50KB each, the total is approximately 1.2GB. That is trivially small for a modern server. CacheeLFU naturally promotes these entries because their access frequency is orders of magnitude higher than historical data.

L1: The Warm Band (Last 24 Hours)

Blocks from 128 to approximately 7,200 blocks back (24 hours) receive moderate traffic. Analytics dashboards, 24-hour volume trackers, and periodic indexer sweeps hit this range. These can live in a shared cache or be fetched from a local SSD-backed store. CacheeLFU handles the transition automatically -- as a block's access frequency drops below the admission threshold, it evicts from L0 and falls through to L1 on the next access.

L2: The Cold Archive (Everything Else)

Historical data older than 24 hours is accessed infrequently and unpredictably. Someone investigating a year-old transaction, an auditor reviewing contract history, a researcher analyzing historical gas prices. These requests go to archive nodes and take 50-500ms. There is no value in caching this data proactively because the access pattern is too sparse. Cache on demand with short TTLs: if someone looks up a specific old receipt, cache it for 10 minutes in case they look up related receipts from the same block.

The key insight is that CacheeLFU handles this tiering without explicit configuration. You do not need to write rules that say "keep the last 128 blocks in L0." The access frequency of recent blocks is so much higher than historical blocks that frequency-based admission produces the correct tiering automatically. A block that was hot 10 minutes ago but has not been accessed since evicts naturally as newer, more frequently accessed blocks take its place.

Mempool and Pending Transaction Caching

Mempool data is the most cache-hostile data in blockchain infrastructure. On Ethereum, the mempool churns completely every 12 seconds (one block time). A pending transaction is either included in the next block or replaced, repriced, or dropped. The effective TTL for mempool data is 12 seconds at most, and often less.

Despite this, eth_getTransactionByHash for pending transactions is one of the highest-volume RPC methods. Wallets poll for transaction status. MEV bots monitor pending transactions for sandwich opportunities. Frontends display "pending" states. Every one of these callers is hammering the same pending transaction hashes repeatedly within that 12-second window.

Redis is particularly bad for this workload. The 12-second TTL means constant churn in the keyspace. Redis must handle both the SET (on transaction arrival) and the DEL or expiry (at block confirmation) for every pending transaction, in addition to the GETs. On a busy day, Ethereum's mempool contains 100,000+ pending transactions. At 5-20KB per transaction response, setting and evicting 100,000 keys every 12 seconds generates significant Redis CPU load purely on expiry processing.

In-process caching with short TTLs avoids all of this. The SET is a hash table insert (nanoseconds). The GET is a hash table lookup (nanoseconds). The eviction is a TTL check on read (nanoseconds). No network. No serialization. No expiry processing thread. The entire mempool cache lifecycle -- insert, read 50 times, expire -- completes in under a microsecond of total CPU time. In Redis, the same lifecycle consumes 50+ milliseconds of cumulative network and serialization time.

The 12-Second Window

At a 1.4ms Redis round-trip per GET, a pending transaction can be read from Redis at most 8,571 times within its 12-second lifespan. At 31ns per in-process read, the same entry can be read 387 million times. For high-frequency consumers like MEV bots that poll transaction status hundreds of times per second, the Redis round-trip consumes a meaningful fraction of the decision window. The bot that reads from in-process cache has 12 full seconds to analyze the transaction. The bot that reads from Redis loses 1.4ms per read -- and at 100 reads per second, that is 140ms of latency budget consumed by the cache layer alone.

The Post-Quantum Problem: 50KB Receipts Become 200KB Receipts

Everything discussed so far is the current state. The future is worse.

Ethereum's research community is actively evaluating post-quantum signature schemes for account abstraction (EIP-4337) and future protocol upgrades. The leading candidates are ML-DSA (formerly Dilithium) and hash-based schemes like SLH-DSA. The signature size implications are dramatic.

Signature SchemeSignature SizePublic Key SizeTotal per Tx
secp256k1 ECDSA (current)65 bytes33 bytes98 bytes
ML-DSA-65 (NIST PQ)3,309 bytes1,952 bytes5,261 bytes
SLH-DSA-SHA2-128f17,088 bytes32 bytes17,120 bytes
FALCON-512690 bytes897 bytes1,587 bytes

A transaction receipt includes the transaction itself, which includes the signature. With secp256k1, the signature contributes 65 bytes to a receipt that is already 30-50KB -- negligible. With ML-DSA-65, each transaction's signature contribution grows by 3,244 bytes. A receipt that was 50KB becomes approximately 53KB. That is a modest increase for a single receipt.

But a full block contains 150-300 transactions. The aggregate signature data in a block grows from approximately 10KB (300 transactions * 33 bytes) to approximately 1.6MB (300 transactions * 5,261 bytes). A full block response that was 1-2MB becomes 2.6-3.6MB. And eth_getBlockReceipts, which returns all receipts for a block, grows from approximately 7.5MB (150 receipts * 50KB) to approximately 8.3MB.

If a provider adopts SLH-DSA for maximum post-quantum security, the numbers are far worse. Each transaction's signature payload grows by 17,023 bytes. A block of 300 transactions adds 5.1MB of pure signature data. The block response approaches 7MB. Individual receipts that included multiple internal transactions or contract interactions -- where the receipt references multiple signers -- could reach 100-200KB.

The cache layer that barely handles 50KB receipts in Redis today will be completely unable to handle 100-200KB receipts at the same request rates. Every 2x increase in average receipt size doubles the Redis latency per GET, doubles the bandwidth consumed, and doubles the number of Redis shards required. The infrastructure that costs $50,000/month in Redis capacity today will cost $100,000-200,000/month with post-quantum signatures, with no improvement in cache hit rates or feature capability.

An in-process cache is immune to this growth. The GET latency is 31 nanoseconds whether the receipt is 50KB or 200KB, because the operation returns a pointer, not a copy of the bytes. The only cost of larger receipts is memory capacity -- you fit fewer of them in L0. But with CacheeLFU admission, the hot receipts still stay in L0, and the additional memory cost of post-quantum signatures is linear and predictable.

Building for the PQ Transition

The cache architecture you deploy today needs to handle post-quantum payload sizes within 3-5 years. Ethereum's EIP-4337 already allows arbitrary signature schemes via smart contract wallets. The first PQ-signed transactions on mainnet will appear via account abstraction long before the protocol itself mandates PQ signatures. Your cache layer will encounter 200KB receipts gradually, then suddenly. An in-process L0 cache handles the transition without architectural changes -- the same 31ns pointer dereference works at any payload size.

Architecture for RPC Providers

The optimal caching architecture for a blockchain RPC provider is not a single layer. It is a tiered system that matches the natural temperature gradient of blockchain data.

L0: In-Process (Cachee)

Each RPC server process maintains an in-process cache for the hottest data: recent blocks, recent receipts, frequently queried accounts, and pending transactions. CacheeLFU handles admission and eviction. GET latency: 31ns. No serialization, no network, no cross-process coordination needed for read-heavy workloads.

# Install Cachee
brew tap h33ai-postquantum/tap
brew install cachee

# Initialize with blockchain-optimized settings
cachee init --mode rpc-provider --l0-memory 8GB
cachee start

L1: Shared Warm Store

For data that is warm but not hot enough for every process to hold in L0, a shared store (Redis, if you must, or a Cachee federation) serves as L1. L0 misses fall through to L1. L1 hits promote to L0. This layer handles the 24-hour warm band and any data that is too large for per-process L0 budgets.

L2: Archive Nodes

Cache misses at both L0 and L1 go to archive nodes. These are the source of truth. Historical queries that miss both cache layers take 50-500ms. The result is inserted into L1 (and promoted to L0 if accessed frequently) for subsequent requests.

Invalidation Strategy

Blockchain simplifies invalidation dramatically. Confirmed transaction data is immutable -- it never needs invalidation. The only data that requires invalidation is block-dependent state: eth_call results, account balances, and pending transactions. These invalidate on every new block (every 12 seconds on Ethereum, every 400ms on Solana). A simple block-number check on cached entries handles this: if the cached entry's block number is less than the current block number, it is stale.

The Cache Is the Product

For blockchain RPC providers, the cache layer is not infrastructure. It is the product itself. The difference between a 50ms response (archive node) and a 0.12ms response (in-process cache) is the difference between a usable API and an unusable one. DeFi applications that need to display swap quotes in real time cannot wait 50ms per receipt lookup. MEV bots that need to analyze pending transactions cannot spend 1.4ms per Redis GET when the entire decision window is 12 seconds.

The payload sizes in blockchain -- 50KB receipts today, 200KB receipts in the post-quantum future -- push Redis past its performance envelope. The linear scaling of Redis latency with value size, which is invisible for 64-byte session tokens, becomes the dominant cost at blockchain payload sizes. An in-process cache eliminates serialization, network transfer, and deserialization entirely, delivering constant 31ns reads regardless of payload size.

Build the cache layer for the payloads you will have, not the payloads you have today.

31ns reads for 50KB receipts. Same latency at 200KB when PQ signatures arrive.

Install Cachee Solana PnL: 19,050x Faster