Cube Runner
Plan differences, block sources, the long-term storage queue and history depth for getBlock requests to constant-k Solana RPC endpoints.
Cube Runner answers every getBlock request sent to a constant-k Solana RPC endpoint, in each region. It serves blocks from the region's cache, the region's ledger and the caches of other regions. On Blade Runner and Nexus tier plans, it also retrieves blocks from long-term storage, which holds every block since genesis.
There is no separate endpoint and no client change. Parameters and responses are those of standard getBlock, and the response is byte-identical whichever source serves it.
Plan comparison
| Free, Operator | Blade Runner | Nexus | |
|---|---|---|---|
getFirstAvailableBlock returns | The oldest block from which the region holds every later block, in its cache or its ledger. Older blocks are still returned when a cache in any region holds them. | 0, the genesis block | 0, the genesis block |
| Retrieval rate from long-term storage, per region | Not included | 1 block per 15 seconds | 1 block per 3 seconds |
| Monthly allowance per API key, per region | Not applicable | 10,000 blocks | 50,000 blocks |
| Blocks queued per API key | Not applicable | 200 | 500 |
| Blocks queued per region | Not applicable | 1,000 | 2,000 |
| Longest allowed queue time (no guaranteed delivery; request again to requeue) | Not applicable | 1 hour | 2 hours |
- The monthly allowance counts only blocks retrieved from long-term storage for that API key. Blocks served by a cache, a ledger or another region never count.
- Each region keeps its own count, so an API key has the full allowance in each region.
- The allowance resets at 00:00 UTC on the first day of each month.
- The
getBlockrate limit in Plans & Limits applies to everygetBlockrequest, whichever source serves it.
Lookup order
Each request is answered by the first source that holds the block.
| Order | Source | Holds | Plans |
|---|---|---|---|
| 1 | Region cache, memory then disk | Recent blocks, and older blocks that are requested often. The depth depends on stored data, so it varies with block size and between regions. | All |
| 2 | Region ledger | The ledger window | All |
| 3 | Cache of another region | Blocks in that region's cache | All |
| 4 | Long-term storage | Every block since genesis | Blade Runner, Nexus |
A region asks another region only when that region holds the block. Each region also copies, in the background, the blocks it is missing from the caches of the other regions, as far back as its own cache can hold.
Free and Operator tier plans
On Free and Operator tier plans, a getBlock request for a block that no cache, ledger or other region holds returns code -32001 with HTTP 200. The message and data.firstAvailableBlock carry the value getFirstAvailableBlock returns: every block from that slot onward can be retrieved.
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Block 250000000 not available on this tier. First available block: 452746590",
"data": { "slot": 250000000, "firstAvailableBlock": 452746590, "reason": "tier" }
},
"id": 1
}Long-term storage
On Blade Runner and Nexus tier plans, Cube Runner retrieves a block from long-term storage when no cache, ledger or other region holds it. Each plan has its own queue in each region, shared by every API key on that plan in that region. The retrieval rate and queue limits for each plan are in Plan comparison.
- A block requested by several API keys at once is retrieved once.
- A retrieved block is held in the region for at least 15 minutes and served from there to any API key on any plan. More recent blocks are held longer.
- Blocks that are requested often are kept longer. When space is needed, the oldest blocks leave first, but a block requested several times in the last few hours stays until requests for it stop.
Queued requests
| Block | Response |
|---|---|
| Retrieved within 25 seconds of the request | The block, HTTP 200 |
| Not retrieved within 25 seconds | HTTP 429 with a Retry-After header and code -32019. The block stays queued. |
{
"jsonrpc": "2.0",
"error": {
"code": -32019,
"message": "Block 250000000 is being retrieved from long-term storage; retry in 30 seconds",
"data": { "slot": 250000000, "retryAfterSecs": 30, "queuePosition": 2 }
},
"id": 1
}Retry-Afteris between 1 and 60 seconds.- Repeating the request after
Retry-Afterreturns the block once it has been retrieved. - A queued block is dropped when no request for it arrives for 150 seconds. The next request queues it again.
Responses that are not queued
When a block was not queued, was dropped from the queue, or was not returned by long-term storage, the response also carries HTTP 429, a Retry-After header and code -32019. data.reason names the cause.
data.reason | Cause |
|---|---|
queue_full | The region's queue for the plan is full. The block was not queued. |
key_full | The API key already has the plan's number of blocks queued: 200 on Blade Runner, 500 on Nexus. The block was not queued. |
key_monthly | The API key has used its monthly allowance in the region. Retry-After is 60. The allowance resets at the start of the next month. |
dropped | The queued block reached the plan's longest allowed queue time before it was retrieved. Delivery is not guaranteed; the next request queues the block again. |
failed | Long-term storage did not return the block. |
Client handling
- On HTTP
429with code-32019, wait the number of seconds inRetry-After, then send the same request again. - Repeat each queued request within 150 seconds, or the block is dropped from the queue.
- Treat only
-32007and-32009as skipped slots. Code-32019and HTTP429mean the block has not been served yet.
| Client | Behaviour on HTTP 429 |
|---|---|
solana-client RpcClient (Rust) | Retries up to 5 times and waits for Retry-After when it is under 120 seconds. The default request timeout is 30 seconds. |
@solana/web3.js 1.x Connection | Retries 4 times with its own backoff of 0.5 to 4 seconds. Ignores Retry-After. |
@solana/kit | Does not retry. The error carries the response headers. |
Retrieval order
- API keys on the plan take turns.
- On an API key's turn, Cube Runner retrieves the key's queued block that the most API keys are waiting for. Repeated requests from one API key do not add to that count.
- Among blocks with the same count, the newest block is retrieved first.
An API key gets fewer turns as its repeat count grows.
Repeat requests
Cube Runner records which blocks each API key received in the last 24 hours. Requesting a block again costs nothing while a cache holds it. An API key that makes Cube Runner retrieve a block again from long-term storage, after it already received that block, builds up a repeat count, which reduces its turns in the long-term storage queue.
| Rule | Value |
|---|---|
| Requests that never count | Every request served by a cache, a ledger or another region, however often the same block is requested |
| A request that counts | One that retrieves again from long-term storage a block the API key received in the last 24 hours: adds 2 to the repeat count |
| Decay | The repeat count halves every 6 hours |
| Repeat count | Turns in the long-term storage queue, compared with an API key whose repeat count is 0 |
|---|---|
| 50 | Half as many |
| 150 | A quarter as many |
The repeat count affects only these turns. Requests served by a cache, a ledger or another region are never delayed by it.
History depth
| Method | Behaviour |
|---|---|
getFirstAvailableBlock | Returns the oldest block from which every later block can be retrieved with getBlock. The value for each plan is in Plan comparison. |
minimumLedgerSlot | Returns the oldest slot the region's ledger holds. That slot is the history depth for getTransaction and getSignaturesForAddress. |
getTransaction, getSignaturesForAddress | Not served by Cube Runner. Their history ends at the region's ledger window. |