Skip to content

Commit dd5a08f

Browse files
authored
HIP-1137: Block Node discoverability via on-chain registry (#1659)
Signed-off-by: dosi <dosi.kolev@limechain.tech>
1 parent d384b29 commit dd5a08f

35 files changed

Lines changed: 4578 additions & 82 deletions

examples/registered_node/main.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"os"
7+
"time"
8+
9+
hiero "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
10+
)
11+
12+
// Registered Node Lifecycle
13+
//
14+
// Walks through the full lifecycle of a registered node:
15+
// 1. Generate an admin key.
16+
// 2. Create a registered node with a block node service endpoint.
17+
// 3. Query the RegisteredNodeAddressBook to confirm it appears.
18+
// 4. Update the registered node with a new description and a second endpoint.
19+
// 5. Associate the registered node with an existing consensus node.
20+
// 6. Delete the registered node.
21+
//
22+
// Environment:
23+
//
24+
// HEDERA_NETWORK — e.g. "previewnet", "testnet", or "mainnet"
25+
// GENESIS_OPERATOR_ID — optional override (defaults to "0.0.2")
26+
// GENESIS_OPERATOR_KEY — optional override (defaults to the genesis Ed25519 key)
27+
// CONSENSUS_NODE_ID — numeric consensus node ID to associate (optional; step 5 is skipped if unset)
28+
// CONSENSUS_NODE_ADMIN_KEY — DER-encoded admin key for that consensus node (optional; required if CONSENSUS_NODE_ID is set)
29+
func main() {
30+
client := setupClient()
31+
32+
// Step 1 — generate the registered node admin key.
33+
adminKey, err := hiero.PrivateKeyGenerateEd25519()
34+
if err != nil {
35+
panic(fmt.Sprintf("%v : error generating admin key", err))
36+
}
37+
fmt.Printf("Generated registered-node admin key: %s\n", adminKey.PublicKey().String())
38+
39+
// Steps 2-4 — create a new registered node.
40+
registeredNodeId := createRegisteredNode(client, adminKey)
41+
fmt.Printf("Created registered node with id: %d\n", registeredNodeId)
42+
43+
// Wait for the mirror node to ingest the new entry.
44+
time.Sleep(time.Second * 5)
45+
46+
// Step 5 — query the address book and confirm the new node is present.
47+
verifyInAddressBook(client, registeredNodeId)
48+
49+
// Steps 6-7 — update the registered node with a new description and both endpoints.
50+
updateRegisteredNode(client, adminKey, registeredNodeId)
51+
52+
// Step 8 — associate with an existing consensus node (optional).
53+
associateWithConsensusNode(client, registeredNodeId)
54+
55+
// Step 9 — delete the registered node.
56+
deleteRegisteredNode(client, adminKey, registeredNodeId)
57+
58+
if err := client.Close(); err != nil {
59+
panic(fmt.Sprintf("%v : error closing client", err))
60+
}
61+
}
62+
63+
// setupClient builds a Hiero client from HEDERA_NETWORK and configures the
64+
// operator. The operator defaults to account 0.0.2 + genesis Ed25519 key (the
65+
// standard local hedera-services bootstrap, also what the HIP-1137 e2e tests
66+
// use); override via GENESIS_OPERATOR_ID / GENESIS_OPERATOR_KEY if your
67+
// network has a different system account.
68+
func setupClient() *hiero.Client {
69+
client, err := hiero.ClientForName(os.Getenv("HEDERA_NETWORK"))
70+
if err != nil {
71+
panic(fmt.Sprintf("%v : error creating client", err))
72+
}
73+
74+
operatorIDStr := os.Getenv("GENESIS_OPERATOR_ID")
75+
if operatorIDStr == "" {
76+
operatorIDStr = "0.0.2"
77+
}
78+
operatorAccountID, err := hiero.AccountIDFromString(operatorIDStr)
79+
if err != nil {
80+
panic(fmt.Sprintf("%v : error parsing operator ID", err))
81+
}
82+
83+
operatorKeyStr := os.Getenv("GENESIS_OPERATOR_KEY")
84+
if operatorKeyStr == "" {
85+
operatorKeyStr = "302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"
86+
}
87+
operatorKey, err := hiero.PrivateKeyFromString(operatorKeyStr)
88+
if err != nil {
89+
panic(fmt.Sprintf("%v : error parsing operator key", err))
90+
}
91+
92+
client.SetOperator(operatorAccountID, operatorKey)
93+
return client
94+
}
95+
96+
// createRegisteredNode submits a RegisteredNodeCreateTransaction with a single
97+
// block-node endpoint and returns the assigned registeredNodeId.
98+
func createRegisteredNode(client *hiero.Client, adminKey hiero.PrivateKey) uint64 {
99+
primaryEndpoint := &hiero.BlockNodeServiceEndpoint{}
100+
primaryEndpoint.
101+
SetIPAddress(net.IPv4(192, 168, 1, 1).To4()).
102+
SetPort(50211).
103+
SetRequiresTls(true).
104+
SetEndpointApis([]hiero.BlockNodeApi{
105+
hiero.BlockNodeApiSubscribeStream,
106+
hiero.BlockNodeApiStatus,
107+
})
108+
109+
createTx, err := hiero.NewRegisteredNodeCreateTransaction().
110+
SetAdminKey(adminKey).
111+
SetDescription("My Block Node").
112+
SetServiceEndpoints([]hiero.RegisteredServiceEndpoint{primaryEndpoint}).
113+
FreezeWith(client)
114+
if err != nil {
115+
panic(fmt.Sprintf("%v : error freezing create tx", err))
116+
}
117+
118+
createResp, err := createTx.Sign(adminKey).Execute(client)
119+
if err != nil {
120+
panic(fmt.Sprintf("%v : error executing create tx", err))
121+
}
122+
123+
createReceipt, err := createResp.SetValidateStatus(true).GetReceipt(client)
124+
if err != nil {
125+
panic(fmt.Sprintf("%v : error fetching create receipt", err))
126+
}
127+
128+
if createReceipt.RegisteredNodeId == nil || *createReceipt.RegisteredNodeId == 0 {
129+
panic("expected a non-zero registeredNodeId on the receipt")
130+
}
131+
return *createReceipt.RegisteredNodeId
132+
}
133+
134+
// verifyInAddressBook queries the mirror node and panics if the given
135+
// registered node id is missing.
136+
func verifyInAddressBook(client *hiero.Client, registeredNodeId uint64) {
137+
book, err := hiero.NewRegisteredNodeAddressBookQuery().
138+
SetRegisteredNodeId(registeredNodeId).
139+
Execute(client)
140+
if err != nil {
141+
panic(fmt.Sprintf("%v : error executing address book query", err))
142+
}
143+
144+
for _, n := range book.RegisteredNodes {
145+
if n.RegisteredNodeID == registeredNodeId {
146+
fmt.Printf("Address book returned registered node: id=%d description=%q\n",
147+
n.RegisteredNodeID, n.Description)
148+
return
149+
}
150+
}
151+
panic("registered node was not found in the address book")
152+
}
153+
154+
// updateRegisteredNode submits a RegisteredNodeUpdateTransaction with a new
155+
// description and adds a second (domain-name) endpoint alongside the original.
156+
func updateRegisteredNode(client *hiero.Client, adminKey hiero.PrivateKey, registeredNodeId uint64) {
157+
primaryEndpoint := &hiero.BlockNodeServiceEndpoint{}
158+
primaryEndpoint.
159+
SetIPAddress(net.IPv4(192, 168, 1, 1).To4()).
160+
SetPort(50211).
161+
SetRequiresTls(true).
162+
SetEndpointApis([]hiero.BlockNodeApi{
163+
hiero.BlockNodeApiSubscribeStream,
164+
hiero.BlockNodeApiStatus,
165+
})
166+
167+
secondaryEndpoint := &hiero.BlockNodeServiceEndpoint{}
168+
secondaryEndpoint.
169+
SetDomainName("block.example.com").
170+
SetPort(50212).
171+
SetRequiresTls(true).
172+
SetEndpointApis([]hiero.BlockNodeApi{hiero.BlockNodeApiStatus})
173+
174+
updateTx, err := hiero.NewRegisteredNodeUpdateTransaction().
175+
SetRegisteredNodeId(registeredNodeId).
176+
SetDescription("My Updated Block Node").
177+
SetServiceEndpoints([]hiero.RegisteredServiceEndpoint{primaryEndpoint, secondaryEndpoint}).
178+
FreezeWith(client)
179+
if err != nil {
180+
panic(fmt.Sprintf("%v : error freezing update tx", err))
181+
}
182+
183+
updateResp, err := updateTx.Sign(adminKey).Execute(client)
184+
if err != nil {
185+
panic(fmt.Sprintf("%v : error executing update tx", err))
186+
}
187+
188+
updateReceipt, err := updateResp.SetValidateStatus(true).GetReceipt(client)
189+
if err != nil {
190+
panic(fmt.Sprintf("%v : error fetching update receipt", err))
191+
}
192+
fmt.Printf("Update receipt status: %s\n", updateReceipt.Status)
193+
}
194+
195+
// associateWithConsensusNode runs step 8 of the lifecycle. Skipped when
196+
// CONSENSUS_NODE_ID is unset, since most operators don't hold a consensus
197+
// node's admin key.
198+
func associateWithConsensusNode(client *hiero.Client, registeredNodeId uint64) {
199+
consensusNodeIDStr := os.Getenv("CONSENSUS_NODE_ID")
200+
if consensusNodeIDStr == "" {
201+
fmt.Println("CONSENSUS_NODE_ID not set — skipping consensus-node association step")
202+
return
203+
}
204+
205+
var consensusNodeID uint64
206+
if _, err := fmt.Sscanf(consensusNodeIDStr, "%d", &consensusNodeID); err != nil {
207+
panic(fmt.Sprintf("%v : error parsing CONSENSUS_NODE_ID", err))
208+
}
209+
210+
consensusAdminKey, err := hiero.PrivateKeyFromString(os.Getenv("CONSENSUS_NODE_ADMIN_KEY"))
211+
if err != nil {
212+
panic(fmt.Sprintf("%v : error parsing CONSENSUS_NODE_ADMIN_KEY", err))
213+
}
214+
215+
nodeUpdateTx, err := hiero.NewNodeUpdateTransaction().
216+
SetNodeID(consensusNodeID).
217+
AddAssociatedRegisteredNode(registeredNodeId).
218+
FreezeWith(client)
219+
if err != nil {
220+
panic(fmt.Sprintf("%v : error freezing node update tx", err))
221+
}
222+
223+
nodeUpdateResp, err := nodeUpdateTx.Sign(consensusAdminKey).Execute(client)
224+
if err != nil {
225+
panic(fmt.Sprintf("%v : error executing node update tx", err))
226+
}
227+
228+
nodeUpdateReceipt, err := nodeUpdateResp.SetValidateStatus(true).GetReceipt(client)
229+
if err != nil {
230+
panic(fmt.Sprintf("%v : error fetching node update receipt", err))
231+
}
232+
fmt.Printf("Consensus node %d updated with associated registered node %d: %s\n",
233+
consensusNodeID, registeredNodeId, nodeUpdateReceipt.Status)
234+
}
235+
236+
// deleteRegisteredNode submits a RegisteredNodeDeleteTransaction and waits for
237+
// the receipt.
238+
func deleteRegisteredNode(client *hiero.Client, adminKey hiero.PrivateKey, registeredNodeId uint64) {
239+
deleteTx, err := hiero.NewRegisteredNodeDeleteTransaction().
240+
SetRegisteredNodeId(registeredNodeId).
241+
FreezeWith(client)
242+
if err != nil {
243+
panic(fmt.Sprintf("%v : error freezing delete tx", err))
244+
}
245+
246+
deleteResp, err := deleteTx.Sign(adminKey).Execute(client)
247+
if err != nil {
248+
panic(fmt.Sprintf("%v : error executing delete tx", err))
249+
}
250+
251+
deleteReceipt, err := deleteResp.SetValidateStatus(true).GetReceipt(client)
252+
if err != nil {
253+
panic(fmt.Sprintf("%v : error fetching delete receipt", err))
254+
}
255+
fmt.Printf("Delete receipt status: %s\n", deleteReceipt.Status)
256+
}

proto/services/transaction_receipt.pb.go

Lines changed: 47 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)