Integration FAQ
Common integration and protocol questions for Consensus Node, Block Node, and Mirror Node developers.
For term definitions, see the Glossary. For operator questions, see the Operator FAQ.
Publish stream protocol (CN → BN)
CN multi-BN routing: When a stream closes due to an
EndOfStreamresponse, the CN does not necessarily reconnect to the same Block Node. If other Block Nodes are configured and eligible, the CN may route subsequent blocks to a higher-priority BN. A Block Node that generates repeatedEndOfStreamresponses in a short window may be temporarily deprioritized by the CN before it is eligible to receive streams again.
How does a Consensus Node initiate a publish stream to a Block Node?
The CN opens a bidirectional gRPC stream to BlockStreamPublishService.publishBlockStream. The first message in the stream must be a BlockHeader for the next expected block. The BN inspects the block number and responds:
If the block number is the next expected block → streaming continues normally.
If the block number ≤ last verified block → BN responds with
DUPLICATE_BLOCK.If the block number > next expected → BN responds with
BehindPublisher.If the block number matches a block currently being streamed by another publisher → BN responds with
SKIP_BLOCK.
The CN is always the client (initiator). The BN never dials a CN.
See Publish Block Stream for more details.
What does the Block Node send back during streaming?
The BN can send five response types during an active stream:
BlockAcknowledgement
After block proof received and verified
Mark block secured and continue streaming the next block
SkipBlock
Another publisher is currently sending this block
Skip the current block and continue with the next
ResendBlock
BN needs the CN to resend from a specific block
Resend from the block number specified
BehindPublisher
BN is behind — CN is too far ahead
Start a new stream from the block number specified
EndOfStream
Terminal — stream is closed
See the status code and take the appropriate action
What does DUPLICATE_BLOCK mean and what should the CN do?
DUPLICATE_BLOCK means the block header the CN sent corresponds to a block that is already stored and verified by the BN. The CN closes the stream and resumes streaming — to this BN or a different available Block Node — beginning with the block after the last persisted and verified block.
What does PERSISTENCE_FAILED mean and is the block lost?
PERSISTENCE_FAILED (code 7 in EndOfStream) means the BN failed to durably store the block. The block is not necessarily lost — the CN still has it. The CN must:
Start a new stream and resend the block to this BN or a different reliable BN.
Not discard the block until it has been acknowledged as persisted and verified by at least one BN.
What does BAD_BLOCK_PROOF mean?
BAD_BLOCK_PROOF (code 6 in EndOfStream) means a BlockProof item could not be validated. The CN closes the stream and resumes streaming — to this BN or a different available Block Node — from before the failed block.
When does the Block Node send TIMEOUT to the Consensus Node?
TIMEOUT (code 4 in EndOfStream) is sent when the delay between stream items exceeds the BN's configured timeout — the CN did not deliver the next item within the allowed window. The CN closes the stream and resumes streaming — to this BN or a different available Block Node — from the timed-out block.
Does the Block Node reconnect to the Consensus Node if the stream drops?
No. The Block Node is the server; it does not initiate connections. When a stream closes (either orderly with RESET/SUCCESS or with an error code), the BN sends EndOfStream and waits. The CN is responsible for opening a new stream.
Subscribe stream protocol (MN → BN)
How does a Mirror Node subscribe to the block stream?
The MN opens a server-streaming gRPC call to BlockStreamSubscribeService.subscribeBlockStream with a SubscribeStreamRequest:
The BN responds with a stream of SubscribeStreamResponse messages containing block_items, end_of_block, and finally a terminal status code when the stream closes.
See Mirror Node Integration for more details.
What is the difference between INVALID_START_BLOCK_NUMBER and NOT_AVAILABLE?
INVALID_START_BLOCK_NUMBER (4)
start_block_number is structurally invalid — below first_available_block, or beyond the future-request window
Fix the request parameters; do not retry with the same value
NOT_AVAILABLE (6)
The block is not available on this BN at this time (e.g. pruned, or node is still backfilling)
Retry later with exponential backoff, or query a different Block Node
What should a subscriber do on NOT_AVAILABLE?
The subscriber may retry with exponential backoff against the same BN, or fall over to a lower-priority BN that covers the requested block range. Check serverStatusDetail on available BNs to find one whose available_ranges includes the needed block.
Clients may avoid this result by checking for gaps client-side. Gaps are reported clearly when calling serverStatusDetail, which returns available_ranges covering all blocks available on that node. If the node has all blocks it will return a single range from 0 through the latest persisted block.
The Subscribe API stream at the live edge, because it is live and unverified, may deliver blocks out of order, but should deliver the out-of-order block within a reasonable time.
See Mirror Node Integration for more details.
How does the Block Node handle slow subscribers?
The BN silently switches a slow subscriber session back to history and streams from the historical block store until the subscriber catches up to the live stream. Gaps are never introduced by the BN — they can only come from the upstream unverified CN→BN stream.
Blocks delivered from the live queue are unverified and may contain errors, be repeated, or be incomplete. Subscribers are responsible for verifying each block using its BlockProof.
See Mirror Node Integration for more details.
Block Access API
What is the difference between NOT_FOUND and NOT_AVAILABLE in getBlock?
NOT_FOUND (4)
The block should be available on this BN (within its managed range) but was not found — may be a transient storage issue
NOT_AVAILABLE (5)
The block is outside this BN's managed range — the BN has never held it or it has been pruned
For NOT_AVAILABLE, call serverStatusDetail to determine the BN's available_ranges, then query a BN that covers the needed range.
Is stateSnapshot implemented?
No. The StateService.stateSnapshot RPC is defined in the proto but not implemented. The proto itself contains a REVIEW NOTE: "This is TEMPORARY. We have not yet designed how state snapshots may be sent." There is no Java source plugin or service implementation in the Block Node codebase, and neither the Mirror Node nor the Consensus Node has any client code that calls this RPC.
Document any dependency on stateSnapshot as a future requirement.
CN → BN wiring
What is block-nodes.json and where does it live?
block-nodes.json is a JSON configuration file on the Consensus Node that identifies which Block Node(s) to stream to. Location:
Directory configured by
blockNode.blockNodeConnectionFileDirinapplication.properties(default:data/config), relative to the CN working directory.File name must be exactly
block-nodes.json.
See Configure Consensus Node Streaming for more details.
What values go in block-nodes.json?
address
✅
Hostname or IP of the Block Node
streamingPort
✅
Port for block stream publishing (default 40840)
servicePort
✅
Port for service APIs like serverStatus (defaults to streamingPort)
priority
✅
Lower value = higher priority; nodes with the same priority are selected randomly
messageSizeSoftLimitBytes
❌
Soft limit on per-request payload size; default 2,097,152 bytes (2 MB)
messageSizeHardLimitBytes
❌
Hard limit on per-item payload size; default 131,072,000 bytes (125 MB)
See Configure Consensus Node Streaming for more details.
What is writerMode and when do I need FILE_AND_GRPC vs GRPC?
blockStream.writerMode in CN application.properties controls where the CN writes block data:
FILE
Write to local disk only; Block Nodes receive nothing
FILE_AND_GRPC
Write to local disk and stream to configured Block Nodes (recommended before "cutover")
GRPC
Stream to Block Nodes only; no local files written (required after "cutover")
Use GRPC when the CN must stream to Block Nodes only and all history is owned by Block Nodes. Use FILE_AND_GRPC before "cutover" to send blocks while still directly uploading data to legacy cloud buckets.
See Configure Consensus Node Streaming for more details.
Does block-nodes.json require a CN restart to take effect?
No. The CN watches block-nodes.json for create, modify, and delete events and reloads it live. Changes take effect immediately — existing connections are shut down cleanly and new connections are established with the updated configuration. If the file is missing or fails to parse, the CN logs a warning and stops establishing new connections until a valid file is present.
See Configure Consensus Node Streaming for more details.
How do I use solo block node add-external instead of editing block-nodes.json?
For test and load-testing deployments, Solo can wire a temporary CN network to an external Block Node before the CNs start:
Run this command after the network is initialised but before starting the Consensus Nodes — this ensures the BN receives every block from block 0 onwards.
See Load Testing with Solo and NLG for more details.
On-chain registration (HIP-1137)
How do I generate an admin_key for Block Node registration?
The admin_key is a Hiero network key — not a generic EVM key or Hedera wallet key.
Key type: Ed25519 (standard recommendation). Generated via PrivateKey.generateED25519() in any of the seven official Hiero SDKs (Java, JavaScript, Go, Rust, Swift, C++, Python). Use an HSM or KMS if your operational policy requires hardware-backed key custody.
Critical rules:
The
admin_keymust not be tied to any network account. Entangling it with a treasury or transaction-signing account increases blast radius if the operator account is compromised.For production, use a
KeyListorThresholdKey(e.g.ThresholdKey(2-of-3, three operator-controlled Ed25519 keys)). Single-key control is acceptable for dev/test only.If the
admin_keyis lost, there is no recovery mechanism. Every create, update, and delete on a registered node must be signed by that node'sadmin_key. The most the network's governance structure can do is sign aDeletetransaction to remove the orphaned registration, after which the operator must create a new one with a freshregistered_node_id.
Example (JavaScript SDK):
See Block Node On-Chain Registration for the full registration workflow, key rotation procedure, and production custody recommendations.
WRB cutover and migration
What changes for Mirror Nodes when the network transitions from record streams to block streams?
Mirror Nodes transition from polling record files from cloud storage to subscribing to a long-lived gRPC stream from a Block Node:
Transport
Poll GCS / S3 buckets
Subscribe to Block Node via gRPC
Encoding
.rcd + sidecar + signature files
BlockItem messages in a single stream
Verification
Per-node RSA signatures, majority verification
Single BlockProof per block
Source discovery
Operator-configured bucket URL
On-chain registry (HIP-1137) or operator-supplied list
Switch trigger
Manual reconfiguration
Automatic on HAPI version ≥ 0.76.0 (Mirror Node v0.155+)
See Record Stream to Block Stream Migration for more details.
What preparation steps does a Block Node need before WRB cutover?
Run all common pre-upgrade checks (health, storage, provisioner version, artifact backup).
Clear the block store — reset removes preview blocks incompatible with the WRB format.
Enable the
roster-bootstrap-rsaplugin.Configure the RSA bootstrap roster (via Mirror Node auto-fetch, peer BN query, or manual JSON file).
Configure backfill sources pointing at the Record Block History (RBH) Block Node.
Enable greedy backfill (
BACKFILL_GREEDY=true) to pre-load WRB history before cutover.Verify all readiness checks pass and
lastAvailableBlockis advancing.
See Preparing for WRB Cutover for more details.
Versioning and compatibility
Which Block Node version supports which Consensus Node version?
The Block Node is designed with long-term forward compatibility as a core goal — a given BN version is intended to remain compatible with future CN versions for as long as practical. In practice this means the minimum compatible BN version for a given CN release is expected to change very slowly (for example, BN 1.0.0 might remain the minimum compatible version across many CN releases).
That said, always run the latest available Block Node release — the minimum compatible version is a floor, not a recommendation to stay pinned.
The current BN release is built and tested against the CN version pinned in hiero-dependency-versions/build.gradle.kts:
The E2E test suite (solo-e2e-test.yml) runs against the latest available version of each component dynamically rather than pinned pairs. For production compatibility guidance, consult your Hashgraph PoC.
Last updated