Choose a topology
Components and service count
You do not need one HotblocksDB per network. A single HotblocksDB configuration can declare several datasets and consume one or more EVM data services for each dataset.
You normally run at least one EVM data service per network. Run additional services when the same network uses multiple RPC providers for redundancy.
Prerequisites
- Docker and Docker Compose
- A persistent volume for every HotblocksDB instance
- An RPC endpoint with enough capacity for every real-time EVM dataset
debug_traceBlockByHashsupport when traces or state diffs are required- A load balancer and multiple replicas when one instance is not an acceptable failure domain
- at least 1,000,000 locked
SQD(see Token requirements and compute units for sizing); - an Arbitrum RPC endpoint and an Ethereum L1 RPC endpoint;
- a registered Portal peer ID;
- Arbitrum ETH for registration and lock transactions.
The real-time ingestion example below uses the EVM data service. Other data
kinds require a compatible service that can feed HotblocksDB.
Archival-only setup
Use this topology for high-throughput historical queries when a delay near the chain head is acceptable.1
Clone the Portal repository
2
Generate and register the Portal identity
3
Review the network configuration
The repository’s Review the RPC endpoints and listen address in
mainnet.config.yml serves every Network dataset:mainnet.config.yml
.env before starting the service.4
Start and verify
Real-time-only setup for a custom EVM network
HotblocksDB already serves the Portal streaming API. If clients only need real-time stream and head endpoints, they can connect to HotblocksDB directly without running the Portal service.Configure retention
Create a HotblocksDB dataset configuration:hotblocks.yaml
Head: 2000keeps a sliding window of the latest 2,000 blocks.FromBlock: { number: 0 }keeps everything from block zero.FromBlock: { number: 123456 }keeps everything from a chosen start block.
FromBlock only when the retained range fits on disk. Keeping the whole chain makes local reindexing fast, but storage continues to grow.
Run the services
The minimal Compose shape is:compose.yaml
KROWN_RPC_URL, then start the stack:
Verify the custom dataset
--traces, --diffs, and --use-debug-api-for-statediffs to the EVM data service only when clients request those fields and the RPC supports the required debug methods.
Archival plus real-time setup
Use this topology when clients need fast historical backfills and current blocks through the same dataset URL. The data flow is:Configure HotblocksDB
UseApi retention for datasets whose older blocks are available from the Network:
hotblocks.yaml
Api retention, HotblocksDB waits for a retention update before it starts ingesting the dataset.
Configure Hotblocks-retain
Api retention means HotblocksDB does not decide on its own when a block may be
dropped. Hotblocks-retain makes that decision from outside: it reads each dataset’s
last indexed archival block from the Network scheduler status and posts that height
to HotblocksDB as the new retention lower bound. It moves a number, never block data.
Two consequences follow:
- an
Apidataset does not ingest anything until the first retention update arrives; - if the lower bound overtakes what Portal believes the archive holds, the blocks in between are in neither source. See How real-time data is served for where this sits in the architecture.
List the datasets to track
One instance can track several datasets. The config is a mapping keyed by the HotblocksDB dataset name:retained-datasets.yaml
name when the HotblocksDB
dataset name differs from the Network dataset it tracks:
retained-datasets.yaml
id when the dataset is absent from the manifest at --datasets-url. Private
datasets are not published there, so a name never resolves to a dataset ID — even
when the HotblocksDB name and the Network name are identical. Give the ID instead:
retained-datasets.yaml
id never
consults the manifest, but --datasets-url stays a required flag; point it at the
public manifest as usual.
Run the service
On the testnet, use the
tethys scheduler status and datasets-testnet.yml.
As a Compose service alongside the rest of the stack:
compose.yaml
Api retention. Replicas
ingest independently onto their own disks, so each needs its own bound pushed to it.
Choose a retain delay
--retain-delay-secs defaults to 0, which leaves no margin. Set it explicitly.
Hotblocks-retain reads the archive head directly from the scheduler status. A Portal
reaches the same head indirectly, through the assignment it refreshes on its own
interval, so every Portal’s view of the archive trails Hotblocks-retain’s. The delay
is the margin that covers that skew. Below it, the retention bound can pass a
Portal’s archival ceiling and open a gap between the two sources while both services
are healthy.
A second reason not to run a tight margin: the scheduler height means a chunk has
been indexed for assignment, not that every assigned worker has finished downloading
it.
600 seconds is a reasonable starting point. It clears a Portal’s assignment refresh
several times over while adding little to the retained window. Raise it if you see
gaps; the cost of a generous delay is disk, and the ceiling below bounds that anyway.
Bound the window from above
The lower bound only advances when the archive advances. If archival ingestion stalls, the bound stops moving and the window grows for as long as the stall lasts. Cap it:hotblocks.yaml
max(lower bound, head - max_blocks) .. head, so
max_blocks bounds disk regardless of what the archive does. It is a soft cap:
trimming drops whole chunks, so part of the first chunk can survive it.
Size it from the dataset’s block rate — roughly a day of blocks is a common starting
point — then check it against how often the archive publishes new chunks for that
dataset. A window shorter than the interval between chunk publications trims below
the archival head between them and opens a gap on a healthy system. Chains with slow
or large chunks need a window wider than a day.
Verify
Hotblocks-retain applies a bound by posting to HotblocksDB, which turns anApi
dataset’s runtime strategy from None into FromBlock. Read it back:
data is populated and firstBlock advances on later
polls:
x-sqd-data-source response header: network for archival workers, real_time for
HotblocksDB. A request starting just above the archival head should return blocks
under real_time — substitute your own fromBlock:
Configure Portal routing
Declare the HotblocksDB URL inside every dataset’sreal_time section:
mainnet.config.yml
real_time.dataset only when the names differ.
Portal resolves each dataset key against the Network manifest at sqd_network.datasets. A private dataset is not published in that manifest, so declare its ID:
mainnet.config.yml
dataset_name instead when the dataset is in the manifest under a name other than the config key.
Verify routing
real_time: true. Run historical and near-head stream queries to verify that both sources are reachable.
Put a custom network behind Portal
A network does not need an SQD Network archive to appear in Portal. Declare only a real-time source:mainnet.config.yml
krown-network dataset must exist in HotblocksDB.
Choose the client endpoint based on the required API:
Squid SDK and Pipes SDK clients can use either endpoint for supported streaming operations.
Token requirements and compute units
The minimum token locking requirement for a Portal instance is 1,000,000SQD. This should be enough for most use cases. If you want to know the exact capabilities this gives you, read on.
The rate limiting mechanism of SQD Network relies on the concept of a compute unit, or CU for short. CUs do not map directly to the amount of data fetched by a Portal instance; instead, they (roughly) represent the amount of work that the network does internally while serving the Portal instance’s requests.
Understanding compute units (CU)
Understanding compute units (CU)
Network datasets are partitioned by block number. Dataset chunks are randomly distributed among worker nodes.When a Portal instance receives a data request, the following happens:
- The Portal instance forwards the request to several workers that hold the chunks of the relevant dataset
- The workers execute the request separately on each chunk
- Workers send the results back to the Portal instance. For lightweight queries, they send one response for each dataset chunk; however, if any response exceeds 100 Mbytes, it’s split into several parts
- The Portal instance concatenates the workers’ replies and serves a continuous stream of data to the user
SQD you lock and the greater the lockup period is, the more CUs you get. Currently, each locked SQD generates 1-3 CUs at the beginning of each epoch, depending on the lockup period.
In principle, any valid amount of locked SQD generates an infinite amount of CUs. However, if the rate at which your queries consume CUs exceeds the rate at which they are produced, your app will be throttled. To avoid that, you may want to understand how many CUs your queries spend.
There is currently no tool for estimating the number of CUs a query needs before running it. However, on EVM you can use the following formula to get an order of magnitude estimate:
- This assumes lightweight queries - that is, queries that fetch much less data than the total dataset size. For heavyweight queries multiply the estimate by a factor of 2-5.
- This is a rough estimate. Multiply it by ten to get a somewhat safe figure. If you want to minimize your
SQDlockup, start at that safe figure, then measure the actual amount of CUs you spend and reduce the lockup accordingly. - If your network has an Etherscan-style explorer, you can estimate the
transactions_per_blockby visiting its front page, reading the “Transactions” stat and dividing it by the “Last finalized block” height.
How the estimate is calculated
How the estimate is calculated
For a lightweight query, the amount of CUs spent is determined by how many dataset chunks the network needs to examine to process it. The ingester creates chunks of roughly the same size within each dataset. Since the amount of data per block is roughly proportional to the number of transactions in that block, we can assume that the number of chunks in any given range is proportional to the number of transactions per block.Extrapolating from the Ethereum dataset:Where:
eth_chunks= 3.1e4eth_height= 2.1e7eth_txs_per_block= 1.2e2
1e-5 coefficient of the final formula.Important assumption: This assumes all EVM datasets have the same chunk size as Ethereum. In reality, chunk sizes vary between 50-1000 Mbytes. Ethereum’s chunk size is roughly 500 Mbytes, so expect the estimate to be off by a factor of 0.5-10, which is within the “order of magnitude” definition.Heavyweight queries: Scale the same way but may spend more than one CU per chunk. The heaviest possible queries (fetching the whole dataset) on Ethereum consume roughly 5 CUs per chunk.X CUs each and you run them once per Y epochs, you need to lock up at least this much SQD:
boost_factor is a multiplier ranging from 1 to 3 depending on the lockup length.
Redundancy and capacity
- Run one EVM data service per RPC provider and list all providers under the dataset’s
data_sources. - Run multiple Portal replicas behind a load balancer. Portal is stateless. Replicas can share a single wallet and a single
SQDlock: register one peer ID per replica from the same wallet. - Give every HotblocksDB replica its own persistent volume. Replicas ingest independently and do not share RocksDB files.
- Run Hotblocks-retain alongside every HotblocksDB replica that uses
Apiretention. - Pin tested container versions or digests for production instead of tracking
latestautomatically. - Protect Portal peer keys, RPC credentials, and internal service ports.
- Monitor
/ready,/metrics, dataset heads, disk usage, and RPC error rates.
Troubleshooting
What are the consequences of losing my key file / getting it stolen?
If you lose your key file, you won’t be able to run your Portal instance until you generate and register a new one. If your key file is stolen, the perpetrator can cause connectivity issues, effectively creating downtime for your Portal instance. To recover:- Unregister your Portal instance on the portals page.
- Generate a new key file.
- Register the new portal peer ID.
Portal ignores the real-time source
Check startup logs for:hotblocksDB field and set datasets.<name>.real_time.url.
HotblocksDB does not ingest an Api dataset
Api retention does not start until the first retention update arrives. Check
/datasets/<name>/status on HotblocksDB: "retentionStrategy": "None" with
"data": null means no update has landed yet.
Confirm Hotblocks-retain is running, that the dataset key in its config exactly
matches the HotblocksDB dataset name, and that it can reach the HotblocksDB, status,
and datasets URLs. It logs and retries on every failed fetch, so a repeating failed to fetch status or failed to refresh datasets manifest in its logs is the answer.
dataset not found in manifest, skipping means the name does not resolve to a
Network dataset. For a private dataset this is expected — it is not published in the
manifest, so set id for that entry. Otherwise set name.
Clients get no data near the chain head
The requested start is above the archival head but below HotblocksDB’s first retained block, so neither source covers it. Portal reports this as no data rather than skipping ahead to the first retained block. See Reliability and monitoring. ComparefirstBlock from /datasets/<name>/status against the archival head Portal
is working from. If the former is higher, the retention bound has overtaken Portal:
raise --retain-delay-secs, and check that max_blocks is wider than the dataset’s
chunk publication interval.
Traces or state diffs are missing
Confirm the EVM data service was started with the matching flags and the RPC supportsdebug_traceBlockByHash. Do not enable expensive fields that clients do not query.
Disk grows continuously
Inspectretention_strategy. FromBlock retains every block from the configured height. Use a bounded Head window for real-time-only access or Api retention when the Network supplies history.
One RPC failure stops current data
Run a data service for a second provider and add both service URLs to the HotblocksDB dataset. Test provider failure before relying on the setup in production.The portal is slower than expected
Portal performance metrics are exposed at the/metrics endpoint. Check the throttling statistics:
portal_stream_throttled_ratio_sum indicate better performance. If throttling is high, consider locking more SQD tokens to increase your compute unit allocation.