Connecting a Mirror Node to a Block Node
This guide walks a Mirror Node operator through the steps required to subscribe a Mirror Node to one or more Block Nodes, verify the connection is healthy, and handle common failure modes.
For the rationale, subscription model, status codes, and the gap / reconnection behaviour Mirror Node operators must understand, see the companion Mirror Node Integration concept doc.
Overview
A Mirror Node opens a long-lived gRPC server-streaming call to each Block Node's BlockStreamSubscribeService.subscribeBlockStream RPC. The Block Node streams SubscribeStreamResponse messages containing block items, terminates each block with an end_of_block marker, and closes the stream with a single terminal status message.
The Mirror Node ships with built-in multi-Block-Node support: configure one or more Block Nodes under hiero.mirror.importer.block.nodes[] and the Mirror Node selects between them by priority and measured latency, failing over automatically if a node becomes inactive.
This guide shows how to:
Confirm each target Block Node is reachable and serving the desired block range.
Configure the Mirror Node to subscribe to one or more Block Nodes.
Verify the connection through logs, metrics, and the Block Node's
serverStatusendpoint.Diagnose the most common failure modes.
Most Mirror Node operators consume the block stream from a public Block Node and do not run their own. The exception is an operator running many Mirror Nodes, who may run a Tier 2 Block Node as a local buffer that pre-validates and redistributes the stream to multiple Mirror Nodes from a single connection upstream. This guide covers both cases; sections specific to running your own Block Node are marked accordingly.
Prerequisites
Before you begin, ensure you have:
One or more Block Nodes to connect to, using one of the following sources:
Block Nodes published on the network - for production use against production networks (the typical case).
Bare Metal Single Node Kubernetes Deployment - if you are running your own Block Node.
Virtual Machine Single Node Kubernetes Deployment - if you are running your own Block Node.
Block Node Dev Quickstart (Docker) - for local development and testing.
A running Mirror Node deployment ready to be reconfigured to consume from a Block Node:
Mirror Node Installation Guide - local or Docker Compose install paths.
Mirror Node Configuration Reference - full property reference for the Mirror Node services.
Network connectivity between the Mirror Node host and each Block Node host on the gRPC port (default
40840).A gRPC client capable of HTTP/2 server-streaming calls:
The Mirror Node's built-in gRPC stack (for production integration), or
grpcurl- for ad-hoc verification from a shell (optional but recommended). Install viabrew install grpcurlon macOS, following the official guide on Windows, or your distribution's package manager on Linux.
The table below summarises the Mirror-Node-side technical requirements. For Block Node version, port, transport, and block-availability requirements when running your own Block Node, see the Block Node Configuration reference and the deployment guides linked above.
On the Mirror Node host
Network access
TCP connectivity from the Mirror Node host to each Block Node host on its gRPC port (default 40840).
Proto definitions
block_stream_subscribe_service.proto, node_service.proto, and shared_message_types.proto from protobuf-sources/src/main/proto/block-node/api/. For ad-hoc grpcurl use, the matching versioned bundle from the Block Node releases page is the easiest source — see Step 1.
gRPC reflection
The Block Node does not enable gRPC server reflection on the public port. Clients must supply protobuf descriptors explicitly.
Configuration
If you operate your own Block Node: tuning the subscriber-facing settings (
subscriber.*) and server-level limits (server.*,server.http2.*) is covered in Block Node Configuration. Mirror Node operators consuming a public Block Node do not need to touch these.
Mirror Node properties
Configure the Mirror Node via application.yml (or equivalent Spring property source). All keys live under the hiero.mirror.importer.block.* namespace. See the Mirror Node Configuration Reference for the full table.
Required
hiero.mirror.importer.block.enabled
false
true — master switch for the block-stream source.
hiero.mirror.importer.block.sourceType
AUTO
BLOCK_NODE to subscribe exclusively, or AUTO to try Block Node first and fall back to record-file ingestion if unavailable.
Block Node endpoints
Declare one entry under hiero.mirror.importer.block.nodes[] per target Block Node:
hiero.mirror.importer.block.nodes[].host
—
Host or IP of the Block Node gRPC service. Required.
hiero.mirror.importer.block.nodes[].port
40840
gRPC port of the Block Node.
hiero.mirror.importer.block.nodes[].priority
0
Selection priority. Lower value is higher priority. Highest-priority reachable node is preferred.
hiero.mirror.importer.block.nodes[].requiresTls
false
Set to true if the Block Node endpoint is fronted by TLS termination.
Example YAML for two Block Nodes with the second as failover:
Selection and readmit behaviour
The Mirror Node will not block on a single unhealthy Block Node. Tune the readmit logic with:
hiero.mirror.importer.block.scheduler.type
PRIORITY_THEN_LATENCY
Selection strategy: LATENCY, PRIORITY, or PRIORITY_THEN_LATENCY. The default picks by priority and uses measured latency as a tiebreaker.
hiero.mirror.importer.block.stream.maxSubscribeAttempts
3
Consecutive failed subscribe attempts before a Block Node is marked inactive.
hiero.mirror.importer.block.stream.readmitDelay
1m
How long an inactive Block Node stays out before being retried.
hiero.mirror.importer.block.stream.responseTimeout
400ms
serverStatus request timeout.
Migration cutover
During the records-to-block-streams cutover (HIP-1193), the Mirror Node can switch automatically from record-file ingestion to block-stream subscription as the network rolls forward:
hiero.mirror.importer.block.cutover.enabled
unset; network default is true for mainnet/testnet, false for others. When set, overrides the network default.
Enables the auto-switch.
hiero.mirror.importer.block.cutover.hapiVersion
0.76.0
The HAPI version following which the final cutover will happen.
Cutover detection and handling are automatic in Mirror Node v0.155 and later; operators are not expected to set these properties under normal conditions.
Step-by-Step Guide
Step 1: Confirm each Block Node is reachable and serving blocks
For each Block Node entry you plan to configure, run the checks below.
Reachability
Expected output:
Connection to <BLOCK_NODE_HOST> port 40840 [tcp/*] succeeded!If this fails: investigate firewall rules, security groups, and that the Block Node process is running.
Available block range
Because the Block Node does not enable gRPC reflection, the protobuf descriptors must be supplied explicitly. Download the matching protobuf release bundle once and reuse it for all checks:
Note: The download uses
curl -LOrather thanwgetbecausewgetis not installed on macOS by default. On Linux either tool works. The extracted tarball lays outblock/,block-node/,platform/,services/, andstreams/directly in the current directory — there is no version-prefixed top-level folder.
Expected output (active node with blocks ingested):
Expected output (freshly started node, no blocks yet):
When both values are
uint64_max, the Block Node has not yet ingested any blocks. The Mirror Node will receiveNOT_AVAILABLE (6)if it tries to subscribe now.Without
-emit-defaults,grpcurlelides"onlyLatestState": falsefrom the output; both forms are semantically equivalent.
Step 2: Configure the Mirror Node and restart
Edit the Mirror Node importer's
application.yml(or equivalent override).Set the required properties and add a
nodes[]entry per Block Node, as shown in the example above.Restart the importer.
The Mirror Node selects an active Block Node by scheduler.type (default PRIORITY_THEN_LATENCY) and opens a subscribeBlockStream gRPC call against it. On failure, it tries the next eligible Block Node, marking failed nodes inactive after maxSubscribeAttempts consecutive failures and readmitting them after readmitDelay.
Step 3: Smoke-test the subscribe call from the shell (optional)
If you want to confirm the Block Node will accept a subscribe call before the Mirror Node restarts, use the same protobuf bundle from Step 1:
Expected behaviour on a Block Node that has ingested blocks:
grpcurlprints a continuous stream ofSubscribeStreamResponsemessages alternating betweenblock_items(batched block data) andend_of_block(one per completed block). The stream remains open until you cancel withCtrl-Cor the Block Node returns a terminalstatus.Expected behaviour on a freshly started Block Node with no blocks: a single terminal status, then the stream closes:
Step 4: Handle disconnects and gaps
The Block Node closes the stream when the finite range is fully served, when the connection reaches the Block Node's connection lifetime limit, when an internal error occurs, or when the client disconnects. The Mirror Node handles reconnection automatically: it will retry against the highest-priority reachable Block Node, governed by maxSubscribeAttempts and readmitDelay.
Two operator-visible patterns are worth knowing:
Gap in
end_of_block.block_number: the flow of data from Consensus Node to Block Node arrived out-of-order or required a resend due to verification or persistence failure on the upstream stream. The Mirror Node may reconnect to backfill the missing range from history. The Mirror Node detects this and re-subscribes withstart_block_number = (last_committed_block + 1). If the gap is not available on the current Block Node (terminalNOT_AVAILABLE), failover to a higher-priority Tier-1 archive node is required - configure such a node as a low-priority entry undernodes[]so the Mirror Node can fall over automatically.Repeated
ERROR (3)from one Block Node: the Block Node is failing internally. The Mirror Node will mark it inactive aftermaxSubscribeAttemptsand try the next configured node.
See Gaps and out-of-order blocks come from the unverified stream in the concept doc for the rationale.
Verification
Verify on the Mirror Node side
The Mirror Node's last-committed block number advances monotonically.
Importer logs show subscribe activity against the configured
nodes[]entries; no Block Node remains continuously marked inactive.Block-processing latency (time from
end_of_blockreceived to block committed) stays below the block interval.
Optional: verify on the Block Node side (if you operate it)
The checks in this section run against the Block Node and are only relevant if you are operating one yourself. Mirror Node operators consuming a public Block Node should rely on the Mirror-Node-side checks above.
Logs
When a session ends in error, the Block Node logs at INFO level (the %(,d format specifier expands to the numeric client identifier):
When a session ends with SUCCESS, it logs at TRACE level:
Enable TRACE for the org.hiero.block.node.stream.subscriber logger if you want positive confirmation per session.
Metrics
The Block Node exposes Prometheus-format metrics on the default metrics endpoint:
Expected output with one Mirror Node connected:
The counter is exposed with the Prometheus-conventional
_totalsuffix even though the underlying metric is registered assubscriber_errors. The_open_connectionsgauge is updated lazily: a closed session's decrement is processed only when the next subscriber attempts to connect. In low-traffic windows the gauge can appear stuck on the previous value. Treat the gauge as approximate; use the Mirror Node side (last committed block, reconnect rate) and infrastructure-level connection counts (load balancer, ingress) for precise observation.
Status
Re-run serverStatus while the Mirror Node is connected and confirm lastAvailableBlock advances as Consensus Nodes publish new blocks:
Troubleshooting
grpcurl returns Failed to dial: connection refused
Block Node is not listening on the expected port.
Verify server.port in the Block Node configuration and that the process is running. On Linux: ss -tlnp | grep 40840. On macOS: lsof -nP -iTCP:40840 -sTCP:LISTEN.
grpcurl returns Failed to list services: ... malformed header: missing HTTP content-type
The Block Node does not enable gRPC server reflection on the public port; grpcurl cannot self-discover services.
Supply protobuf descriptors explicitly with -import-path and -proto, as shown in Step 1.
nc -vz succeeds but subscribeBlockStream immediately closes with NOT_AVAILABLE (6)
start_block_number is below first_available_block, or the Block Node has not ingested any blocks yet.
Call serverStatus; if both first_available_block and last_available_block equal uint64_max, wait for ingest. Otherwise set start_block_number >= first_available_block.
Immediate close with INVALID_START_BLOCK_NUMBER (4)
start_block_number exceeds last_available_block + subscriber.maximumFutureRequest.
Wait for the Block Node to advance, or reduce start_block_number.
Immediate close with INVALID_END_BLOCK_NUMBER (5)
end_block_number < start_block_number.
Set end_block_number >= start_block_number, or use 18446744073709551615 for an indefinite stream.
Repeated terminal ERROR (3) from a single Block Node
Block Node internal failure.
Check that Block Node's logs at INFO for failed due to ...; review its health (CPU, memory, disk). The Mirror Node will mark this node inactive after maxSubscribeAttempts.
All configured Block Nodes marked inactive
Network reachability problem, or all Block Nodes simultaneously unhealthy.
Verify host/port for each nodes[] entry; check that requiresTls matches the actual termination setup; inspect each Block Node's metrics endpoint.
blocknode_subscriber_open_connections does not increment after Mirror Node connects
Connection is not reaching the Block Node.
Re-check firewall rules; verify the Mirror Node is connecting to the correct host and port.
Gap in end_of_block.block_number after going live
The live block stream experienced interruptions or errors and blocks were received out of order or were resent.
The Mirror Node will reconnect to backfill from history.
Stream stalls with no new block_items after going live
Block Node has not received new blocks from Consensus Nodes.
Check blocknode_publisher_open_connections and Consensus Node logs; this is a publisher-side issue, not a subscriber one. See Block Node Troubleshooting.
High latency between block production and Mirror Node receipt
Live queue is polled at up to MAX_LIVE_POLL_DELAY = 500 ms.
This is the worst-case poll latency in the current implementation.
Session fails shortly after reconnect with NOT_AVAILABLE (6)
Mirror Node reconnected before the Block Node re-indexed the requested range after a restart.
Add a short delay and re-query serverStatus before each reconnect (already handled by the Mirror Node's readmit logic via readmitDelay).
Cutover does not switch to block-stream source
hiero.mirror.importer.block.cutover.enabled is false, or the network has not reached cutover.hapiVersion.
Set cutover.enabled=true; confirm the network HAPI version meets cutover.hapiVersion.
Related documentation
Mirror Node Integration - concept doc explaining the rationale, subscription model, status codes, and the gap / reconnection behaviour Mirror Node operators must understand.
Mirror Node Configuration Reference - full property reference including the
hiero.mirror.importer.block.*namespace.
Last updated