Skip to content

Commit 91e2186

Browse files
chore: sync block-node docs from hiero-block-node@54c1cf9
Signed-off-by: swirlds-automation <eng-automation@hashgraph.com>
1 parent 319010b commit 91e2186

3 files changed

Lines changed: 207 additions & 31 deletions

File tree

block-node/block-node/block-node-on-chain-registration.md

Lines changed: 140 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ Three operator-visible steps. All three transactions also support deferred execu
8787

8888
Submit a `RegisteredNodeCreateTransactionBody`, signed by the new `admin_key`. On success, the transaction receipt carries the assigned `registered_node_id` - **record this value safely**; it is your handle for every subsequent update or delete. If lost, recover it by listing all Block Node registrations on the network and filtering by your `admin_key`, endpoint host, or description:
8989

90+
**Before you begin, gather:**
91+
92+
- **Externally reachable endpoint** - the hostname or public IP your Block Node is reachable at from outside your network, such as the address your load balancer or ingress exposes. Do not register internal cluster addresses such as `ClusterIP` services or pod IPs.
93+
- **Admin key pair** - a fresh ED25519 key pair used to authorize all future updates and deletions. The SDK examples in [Submit the transaction](#submit-the-transaction) show how to generate one; for production, use an HSM or KMS and store the private key immediately after generating it.
94+
- **Funded payer account** - a Hiero account with enough HBAR to cover the transaction fee (approximately $0.09 at the pegged schedule rate; see [Fees and throttles](#fees-and-throttles)).
95+
9096
> **Mainnet only:** On Hedera mainnet, `RegisteredNodeCreate` is a privileged transaction. It can only be submitted by a payer account in the range `0.0.2``0.0.55`.
9197
9298
```bash
@@ -125,12 +131,104 @@ Splitting publish, subscribe, and status across separate endpoints lets each pat
125131

126132
#### Submit the transaction
127133

128-
Two paths:
134+
Two paths to submit the create transaction:
129135

130136
- **`yahcli`** - the DevOps CLI bundled with consensus-node. The `registerednodes create / update / delete` subcommands wrap the three transactions directly. Source at [`hedera-node/yahcli/.../commands/registerednodes/`](https://github.com/hiero-ledger/hiero-consensus-node/tree/main/hedera-node/yahcli/src/main/java/com/hedera/services/yahcli/commands/registerednodes); usage in [`hedera-node/yahcli/README.md`](https://github.com/hiero-ledger/hiero-consensus-node/blob/main/hedera-node/yahcli/README.md).
131-
- **Any official Hiero SDK** - all seven expose `RegisteredNodeCreateTransaction` (and Update / Delete equivalents): [Java](https://github.com/hiero-ledger/hiero-sdk-java), [JavaScript](https://github.com/hiero-ledger/hiero-sdk-js), [Go](https://github.com/hiero-ledger/hiero-sdk-go), [Rust](https://github.com/hiero-ledger/hiero-sdk-rust), [Swift](https://github.com/hiero-ledger/hiero-sdk-swift), [C++](https://github.com/hiero-ledger/hiero-sdk-cpp), [Python](https://github.com/hiero-ledger/hiero-sdk-python). All SDKs also expose `PrivateKey.generateED25519()` (or the language-equivalent) for `admin_key` generation; an external keygen tool is only needed if your operational policy requires one (HSM, KMS, etc.). The [Hiero SDKs index](https://docs.hiero.org/sdks) is the entry point for SDK-specific guides.
137+
- **Any official Hiero SDK** - all seven expose `RegisteredNodeCreateTransaction` (and the Update / Delete equivalents): [Java](https://github.com/hiero-ledger/hiero-sdk-java), [JavaScript](https://github.com/hiero-ledger/hiero-sdk-js), [Go](https://github.com/hiero-ledger/hiero-sdk-go), [Rust](https://github.com/hiero-ledger/hiero-sdk-rust), [Swift](https://github.com/hiero-ledger/hiero-sdk-swift), [C++](https://github.com/hiero-ledger/hiero-sdk-cpp), [Python](https://github.com/hiero-ledger/hiero-sdk-python).
138+
139+
The Java and JavaScript examples below implement the endpoint set from the [worked example](#worked-example) above. Initialize your `client` with your operator account ID and key before running either example; see the [Hiero SDKs index](https://docs.hiero.org/sdks) for per-language setup guides.
140+
141+
**Java** (`com.hedera.hashgraph:sdk`):
142+
143+
```java
144+
import com.hedera.hashgraph.sdk.*;
145+
import java.util.List;
146+
147+
// Generate the admin key. Store the private key in secure storage before proceeding.
148+
PrivateKey adminKey = PrivateKey.generateED25519();
149+
150+
BlockNodeServiceEndpoint publishEndpoint = new BlockNodeServiceEndpoint()
151+
.setDomainName("bn.example.com")
152+
.setPort(40984)
153+
.setRequiresTls(true)
154+
.setEndpointApis(List.of(BlockNodeApi.PUBLISH));
155+
156+
BlockNodeServiceEndpoint subscribeEndpoint = new BlockNodeServiceEndpoint()
157+
.setDomainName("bn.example.com")
158+
.setPort(40980)
159+
.setRequiresTls(true)
160+
.setEndpointApis(List.of(BlockNodeApi.SUBSCRIBE_STREAM));
161+
162+
BlockNodeServiceEndpoint statusEndpoint = new BlockNodeServiceEndpoint()
163+
.setDomainName("bn.example.com")
164+
.setPort(40982)
165+
.setRequiresTls(true)
166+
.setEndpointApis(List.of(BlockNodeApi.STATUS));
167+
168+
TransactionReceipt receipt = new RegisteredNodeCreateTransaction()
169+
.setAdminKey(adminKey)
170+
.setDescription("acme-mainnet-1")
171+
.addServiceEndpoint(publishEndpoint)
172+
.addServiceEndpoint(subscribeEndpoint)
173+
.addServiceEndpoint(statusEndpoint)
174+
.freezeWith(client)
175+
.sign(adminKey)
176+
.execute(client)
177+
.getReceipt(client);
178+
179+
// Record this value. It is required for every subsequent update or deletion.
180+
long registeredNodeId = receipt.registeredNodeId;
181+
System.out.println("registered_node_id: " + registeredNodeId);
182+
```
132183

133-
The flow is the same regardless of path: build the transaction body using the fields from the [Worked example](#worked-example), sign with the `admin_key`, execute against the target network's gRPC endpoint, and read the `TransactionReceipt` to capture the assigned `registered_node_id`.
184+
**JavaScript** (`@hiero-ledger/sdk`):
185+
186+
```javascript
187+
import {
188+
BlockNodeApi,
189+
BlockNodeServiceEndpoint,
190+
PrivateKey,
191+
RegisteredNodeCreateTransaction,
192+
} from "@hiero-ledger/sdk";
193+
194+
// Generate the admin key. Store the private key in secure storage before proceeding.
195+
const adminKey = PrivateKey.generateED25519();
196+
197+
const publishEndpoint = new BlockNodeServiceEndpoint()
198+
.setDomainName("bn.example.com")
199+
.setPort(40984)
200+
.setRequiresTls(true)
201+
.setEndpointApis([BlockNodeApi.Publish]);
202+
203+
const subscribeEndpoint = new BlockNodeServiceEndpoint()
204+
.setDomainName("bn.example.com")
205+
.setPort(40980)
206+
.setRequiresTls(true)
207+
.setEndpointApis([BlockNodeApi.SubscribeStream]);
208+
209+
const statusEndpoint = new BlockNodeServiceEndpoint()
210+
.setDomainName("bn.example.com")
211+
.setPort(40982)
212+
.setRequiresTls(true)
213+
.setEndpointApis([BlockNodeApi.Status]);
214+
215+
const createTx = await new RegisteredNodeCreateTransaction()
216+
.setAdminKey(adminKey.publicKey)
217+
.setDescription("acme-mainnet-1")
218+
.addServiceEndpoint(publishEndpoint)
219+
.addServiceEndpoint(subscribeEndpoint)
220+
.addServiceEndpoint(statusEndpoint)
221+
.freezeWith(client)
222+
.sign(adminKey);
223+
224+
const receipt = await (await createTx.execute(client)).getReceipt(client);
225+
226+
// Record this value. It is required for every subsequent update or deletion.
227+
const registeredNodeId = receipt.registeredNodeId;
228+
console.log("registered_node_id:", registeredNodeId.toString());
229+
```
230+
231+
For other SDKs (Go, Rust, Swift, C++, Python), the pattern is the same: build the transaction, sign with the `admin_key`, execute against the target network, and read `registered_node_id` from the receipt. The full lifecycle example for each SDK is linked from the [Hiero SDKs index](https://docs.hiero.org/sdks).
134232

135233
#### Verify the registration
136234

@@ -246,6 +344,45 @@ Three surfaces are exposed by the existing Hiero infrastructure once you are reg
246344
- **Automatic Mirror Node pickup.** Mirror Nodes running with `hiero.mirror.importer.block.autoDiscoveryEnabled = true` pick up your registration from the registry without any per-Mirror-Node configuration change - see [hiero-mirror-node#13013](https://github.com/hiero-ledger/hiero-mirror-node/issues/13013) and the [Mirror Node Integration](./mirror-node-integration.md) guide for the consumer side.
247345
- **Consensus node address book.** The existing `/api/v1/network/nodes` endpoint now includes an `associated_registered_nodes` field on each consensus-node entry, listing the registered nodes operated by the same entity.
248346

347+
## Troubleshooting
348+
349+
### Transaction returns `INVALID_ADMIN_KEY`
350+
351+
The transaction was not signed by the `admin_key` declared in the transaction body, or the key does not satisfy the `KeyList` / `ThresholdKey` threshold. Verify that:
352+
353+
1. The key passed to `.setAdminKey(...)` and the key used to `.sign(...)` are the same key pair.
354+
2. For a multi-sig `admin_key`, enough members have signed to meet the threshold before you call `execute`.
355+
3. You have not confused the operator key (the account paying the fee) with the `admin_key` (the key that controls the registration). Both sign the transaction, but they serve different roles.
356+
357+
### gRPC returns UNIMPLEMENTED (code 12)
358+
359+
The target network is running a Consensus Node version that pre-dates HIP-1137. The gRPC methods `AddressBookService/createRegisteredNode`, `updateRegisteredNode`, and `deleteRegisteredNode` are available from Consensus Node `v0.75` onward. Check the [Consensus Node release page](https://github.com/hiero-ledger/hiero-consensus-node/releases) and the [Availability across networks](#availability-across-networks) table above.
360+
361+
### Transaction is throttled (`BUSY` or `THROTTLED_AT_CONSENSUS`)
362+
363+
`RegisteredNodeCreate` shares a throttle bucket with `CryptoCreate` and `NodeCreate`, capped at approximately 2 ops/sec network-wide. If you are onboarding multiple Block Nodes, space create submissions by at least 1 second and implement exponential back-off on throttle rejections. `RegisteredNodeUpdate` and `RegisteredNodeDelete` are not separately rate-limited.
364+
365+
### `RegisteredNodeCreate` rejected on mainnet
366+
367+
On Hedera mainnet, `RegisteredNodeCreate` is a privileged transaction - only accounts in the range `0.0.2``0.0.55` may be the transaction payer. Confirm the account you set as the operator (the fee-payer) is in that range. On previewnet and testnet, any funded account may be the payer.
368+
369+
### Registration is not visible in the Mirror Node REST API
370+
371+
Mirror Nodes index the registry from block-stream data. Allow up to 30 seconds after a `SUCCESS` receipt before treating a missing entry as a failure. If the entry does not appear after a minute, confirm the Mirror Node version is `v0.156` or later (the release that adds `/api/v1/network/registered-nodes`).
372+
373+
### `RegisteredNodeDelete` returns `REGISTERED_NODE_STILL_ASSOCIATED`
374+
375+
If the `registered_node_id` you are deleting is still listed in a consensus node's `associated_registered_nodes`, the delete returns `REGISTERED_NODE_STILL_ASSOCIATED`. Remove the association first: submit `NodeUpdate` with the `registered_node_id` removed from the list (or with an empty list to clear all associations), wait for `SUCCESS`, then resubmit the delete.
376+
377+
### `registered_node_id` is unknown
378+
379+
If you lost the assigned ID, recover it by querying the Mirror Node for your endpoint hostname or admin key:
380+
381+
```bash
382+
curl -s "https://{MIRROR_NODE_HOST}/api/v1/network/registered-nodes?type=BLOCK_NODE" \
383+
| jq '.registered_nodes[] | select(.service_endpoints[]?.domain_name == "{YOUR_ENDPOINT_HOST}")'
384+
```
385+
249386
## Backwards compatibility
250387

251388
The on-chain registry is entirely net-new functionality. Existing transactions, message types, and APIs are unaffected. A Block Node that does not register continues to function exactly as before - clients that already know its address will still connect. Only discoverability via the registry is gated on registration.

block-node/design/block-stream-forward-compatibility.md

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,38 @@ stays as it is.
211211
**None of this changes.** The point of the design is only what happens for item
212212
types that do not exist yet.
213213

214+
### The Single Field Invariant
215+
216+
A `BlockItem` is a protobuf `oneof`, so a valid item carries **exactly one
217+
field**. The wire format cannot enforce this: any combination of fields parses
218+
successfully, with fields unknown to the compiled schema preserved as unknown
219+
fields. The hashing step therefore checks the invariant itself, before any
220+
category placement:
221+
222+
- An item with a known type and **no** unknown fields is processed normally.
223+
- An item with **no type set and exactly one unknown field** is a future item,
224+
and the numbering rule below applies to it.
225+
- An item with a known type **and** one or more unknown fields is a valid
226+
encoding but not a processable stream, and the block is refused as an
227+
unsupported stream format.
228+
- An item with no type set and **more than one** unknown field is likewise
229+
refused as an unsupported stream format.
230+
- An item with **no field at all** carries nothing valid to process and is
231+
refused as an unknown error.
232+
233+
None of these refusals are parse failures: the bytes are well formed protobuf
234+
and parsing succeeds. They are structural violations detected after parsing.
235+
214236
### The Numbering Rule for Future Item Types
215237

216238
To maximize forward compatibility, and to minimize the need to coordinate
217239
deployments of different systems creating and processing block streams in the
218240
future, the block stream format requires the following rule for field numbering.
241+
242+
An unknown field numbered **below 20** is a first release field, reserved for
243+
item types that require specific handling. A version that does not know such a
244+
field cannot process it and refuses the block as an unsupported item type.
245+
219246
Fields numbered **20 and above** MUST be numbered so that:
220247

221248
```text
@@ -391,15 +418,15 @@ existing verification configuration is unchanged.
391418

392419
## Metrics
393420

394-
No new metrics are required, but a few would make forward compatibility events
395-
visible to operators and give early warning of a needed upgrade:
421+
Three counters make forward compatibility events visible to operators and give
422+
early warning of a needed upgrade:
396423

397424
- a count of future item types placed into a subtree by the rule, including the
398425
extension subtrees,
399426
- a count of future items safely ignored as `not hashed`,
400-
- a count of blocks refused because an item fell into a reserved category
401-
(16 to 18), or needed specific handling (1 or 2) that this version does not
402-
know.
427+
- a count of future items refused because they fell into a reserved category
428+
(16 to 18), needed specific handling (1 or 2) that this version does not
429+
know, or carried an unknown first release field (below 20).
403430

404431
## Exceptions
405432

@@ -413,7 +440,14 @@ failure** for the block instead.
413440
specific handling" category (1 or 2) whose processing this version does not
414441
know, causes the block to be refused, with a clear failure showing that an
415442
upgrade is required. It is never silently dropped or placed elsewhere.
416-
- A malformed or unexpected item that does not fit the rule at all is treated as a
443+
- A future item carrying an unknown first release field (below 20) is refused
444+
the same way, as an unsupported item type.
445+
- An item that violates the single field invariant (a known type together with
446+
unknown fields, or several unknown fields at once) parses successfully but is
447+
refused as an **unsupported stream format**; an item with no field at all is
448+
refused as an **unknown error**. These are structural violations, not parse
449+
failures.
450+
- A malformed item whose bytes cannot be decoded at all is treated as a
417451
**parse failure**, as it is today.
418452

419453
## Acceptance Tests

0 commit comments

Comments
 (0)