Skip to content

Commit 1dbeb43

Browse files
authored
feat: Add persistent shard and realm support to Client (#1395)
* experimental draft Signed-off-by: Ivan Ivanov <[email protected]> * chore: fix tests Signed-off-by: Ivan Ivanov <[email protected]> * introduce structs Signed-off-by: Ivan Ivanov <[email protected]> * chore: cleanup Signed-off-by: Ivan Ivanov <[email protected]> * chore: refactor Signed-off-by: Ivan Ivanov <[email protected]> * test: config Signed-off-by: Ivan Ivanov <[email protected]> * chore: refactor Signed-off-by: Ivan Ivanov <[email protected]> * test: unit Signed-off-by: Ivan Ivanov <[email protected]> * chore: resolve feedback Signed-off-by: Ivan Ivanov <[email protected]> * chore: resolve feedback Signed-off-by: Ivan Ivanov <[email protected]> --------- Signed-off-by: Ivan Ivanov <[email protected]>
1 parent 2abe795 commit 1dbeb43

File tree

9 files changed

+162
-36
lines changed

9 files changed

+162
-36
lines changed

examples/construct_client/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ func main() {
4848
}
4949

5050
// Set network for customClient which uses the above custom network
51-
customClient := hiero.ClientForNetwork(customNetwork)
51+
customClient, err := hiero.ClientForNetworkV2(customNetwork)
52+
if err != nil {
53+
panic(fmt.Sprintf("%v : error creating client for network", err))
54+
}
5255
// Setting NetworkName for the CustomClient, is only needed if you need to validate ID checksums
5356
customClient.SetLedgerID(*hiero.NewLedgerIDTestnet())
5457

sdk/client.go

Lines changed: 78 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ type Client struct {
4747
networkUpdateContext context.Context
4848
cancelNetworkUpdate context.CancelFunc
4949
logger Logger
50+
shard uint64
51+
realm uint64
5052
}
5153

5254
// TransactionSigner is a closure or function that defines how transactions will be signed
@@ -65,15 +67,15 @@ var previewnetMirror = []string{"previewnet.mirrornode.hedera.com:443"}
6567

6668
// ClientForMirrorNetwork constructs a client given a set of mirror network nodes.
6769
func ClientForMirrorNetwork(mirrorNetwork []string) (*Client, error) {
68-
return ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork, 0, 0)
70+
return ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork, 0, 0)
6971
}
7072

71-
// ClientForMirrorNetworkWithRealmAndShard constructs a client given a set of mirror network nodes and the realm/shard of the address book.
72-
func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint64, shard uint64) (*Client, error) {
73+
// constructs a client given a set of mirror network nodes and the shard/realm of the address book.
74+
func ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork []string, shard uint64, realm uint64) (*Client, error) {
7375
net := _NewNetwork()
74-
client := _NewClient(net, mirrorNetwork, nil, true)
76+
client := _NewClient(net, mirrorNetwork, nil, true, shard, realm)
7577
addressbook, err := NewAddressBookQuery().
76-
SetFileID(GetAddressBookFileIDFor(realm, shard)).
78+
SetFileID(GetAddressBookFileIDFor(shard, realm)).
7779
Execute(client)
7880
if err != nil {
7981
return nil, fmt.Errorf("failed to query address book: %v", err)
@@ -82,41 +84,83 @@ func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint6
8284
return client, nil
8385
}
8486

87+
// Deprecated: Use ClientForMirrorNetworkWithShardAndRealm instead.
88+
func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint64, shard uint64) (*Client, error) {
89+
return ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork, shard, realm)
90+
}
91+
8592
// ClientForNetwork constructs a client given a set of nodes.
93+
// Deprecated: Use ClientForNetworkV2 instead.
8694
func ClientForNetwork(network map[string]AccountID) *Client {
8795
net := _NewNetwork()
88-
client := _NewClient(net, []string{}, nil, true)
96+
client := _NewClient(net, []string{}, nil, true, 0, 0)
8997
_ = client.SetNetwork(network)
9098
return client
9199
}
92100

101+
// ClientForNetworkV2 constructs a client given a set of nodes.
102+
func ClientForNetworkV2(network map[string]AccountID) (*Client, error) {
103+
isValidNetwork := true
104+
var shard uint64
105+
var realm uint64
106+
107+
if len(network) == 0 {
108+
return nil, errors.New("network is empty")
109+
}
110+
111+
for _, accountID := range network {
112+
if shard == 0 {
113+
shard = accountID.Shard
114+
}
115+
if realm == 0 {
116+
realm = accountID.Realm
117+
}
118+
if shard != accountID.Shard || realm != accountID.Realm {
119+
isValidNetwork = false
120+
break
121+
}
122+
}
123+
124+
if !isValidNetwork {
125+
return nil, errors.New("network is not valid, all nodes must be in the same shard and realm")
126+
}
127+
128+
net := _NewNetwork()
129+
client := _NewClient(net, []string{}, nil, true, shard, realm)
130+
err := client.SetNetwork(network)
131+
if err != nil {
132+
return nil, err
133+
}
134+
return client, nil
135+
}
136+
93137
// ClientForMainnet returns a preconfigured client for use with the standard
94138
// Hiero mainnet.
95139
// Most users will want to set an _Operator account with .SetOperator so
96140
// transactions can be automatically given TransactionIDs and signed.
97141
func ClientForMainnet() *Client {
98-
return _NewClient(*_NetworkForMainnet(mainnetNodes._ToMap()), mainnetMirror, NewLedgerIDMainnet(), true)
142+
return _NewClient(*_NetworkForMainnet(mainnetNodes._ToMap()), mainnetMirror, NewLedgerIDMainnet(), true, 0, 0)
99143
}
100144

101145
// ClientForTestnet returns a preconfigured client for use with the standard
102146
// Hiero testnet.
103147
// Most users will want to set an _Operator account with .SetOperator so
104148
// transactions can be automatically given TransactionIDs and signed.
105149
func ClientForTestnet() *Client {
106-
return _NewClient(*_NetworkForTestnet(testnetNodes._ToMap()), testnetMirror, NewLedgerIDTestnet(), true)
150+
return _NewClient(*_NetworkForTestnet(testnetNodes._ToMap()), testnetMirror, NewLedgerIDTestnet(), true, 0, 0)
107151
}
108152

109153
// ClientForPreviewnet returns a preconfigured client for use with the standard
110154
// Hiero previewnet.
111155
// Most users will want to set an _Operator account with .SetOperator so
112156
// transactions can be automatically given TransactionIDs and signed.
113157
func ClientForPreviewnet() *Client {
114-
return _NewClient(*_NetworkForPreviewnet(previewnetNodes._ToMap()), previewnetMirror, NewLedgerIDPreviewnet(), true)
158+
return _NewClient(*_NetworkForPreviewnet(previewnetNodes._ToMap()), previewnetMirror, NewLedgerIDPreviewnet(), true, 0, 0)
115159
}
116160

117161
// newClient takes in a map of _Node addresses to their respective IDS (_Network)
118162
// and returns a Client instance which can be used to
119-
func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, shouldScheduleNetworkUpdate bool) *Client {
163+
func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, shouldScheduleNetworkUpdate bool, shard uint64, realm uint64) *Client {
120164
ctx, cancel := context.WithCancel(context.Background())
121165
logger := NewLogger("hiero-sdk-go", LogLevel(os.Getenv("HEDERA_SDK_GO_LOG_LEVEL")))
122166
var defaultLogger Logger = logger
@@ -134,6 +178,8 @@ func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, sh
134178
networkUpdateContext: ctx,
135179
cancelNetworkUpdate: cancel,
136180
logger: defaultLogger,
181+
shard: shard,
182+
realm: realm,
137183
}
138184

139185
client.SetMirrorNetwork(mirrorNetwork)
@@ -153,7 +199,7 @@ func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, sh
153199

154200
func (client *Client) _UpdateAddressBook() {
155201
addressbook, err := NewAddressBookQuery().
156-
SetFileID(FileIDForAddressBook()).
202+
SetFileID(GetAddressBookFileIDFor(client.shard, client.realm)).
157203
Execute(client)
158204
if err == nil && len(addressbook.NodeAddresses) > 0 {
159205
client.SetNetworkFromAddressBook(addressbook)
@@ -203,7 +249,10 @@ func ClientForName(name string) (*Client, error) {
203249
network := make(map[string]AccountID)
204250
network["127.0.0.1:50211"] = AccountID{Account: 3}
205251
mirror := []string{"127.0.0.1:5600"}
206-
client := ClientForNetwork(network)
252+
client, err := ClientForNetworkV2(network)
253+
if err != nil {
254+
return nil, err
255+
}
207256
client.SetMirrorNetwork(mirror)
208257
return client, nil
209258
default:
@@ -220,6 +269,8 @@ type _ConfigOperator struct {
220269
type _ClientConfig struct {
221270
Network interface{} `json:"network"`
222271
MirrorNetwork interface{} `json:"mirrorNetwork"`
272+
Shard uint64 `json:"shard"`
273+
Realm uint64 `json:"realm"`
223274
Operator *_ConfigOperator `json:"operator"`
224275
}
225276

@@ -293,20 +344,20 @@ func clientFromConfig(jsonBytes []byte, shouldScheduleNetworkUpdate bool) (*Clie
293344
return client, errors.New("mirrorNetwork is expected to be either string or an array of strings")
294345
}
295346
}
296-
client = _NewClient(network, arr, nil, shouldScheduleNetworkUpdate)
347+
client = _NewClient(network, arr, nil, shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm)
297348
case string:
298349
if len(mirror) > 0 {
299350
switch mirror {
300351
case string(NetworkNameMainnet):
301-
client = _NewClient(network, mainnetMirror, NewLedgerIDMainnet(), shouldScheduleNetworkUpdate)
352+
client = _NewClient(network, mainnetMirror, NewLedgerIDMainnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm)
302353
case string(NetworkNameTestnet):
303-
client = _NewClient(network, testnetMirror, NewLedgerIDTestnet(), shouldScheduleNetworkUpdate)
354+
client = _NewClient(network, testnetMirror, NewLedgerIDTestnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm)
304355
case string(NetworkNamePreviewnet):
305-
client = _NewClient(network, previewnetMirror, NewLedgerIDPreviewnet(), shouldScheduleNetworkUpdate)
356+
client = _NewClient(network, previewnetMirror, NewLedgerIDPreviewnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm)
306357
}
307358
}
308359
case nil:
309-
client = _NewClient(network, []string{}, nil, true)
360+
client = _NewClient(network, []string{}, nil, true, clientConfig.Shard, clientConfig.Realm)
310361
default:
311362
return client, errors.New("mirrorNetwork is expected to be a string, an array of strings or nil")
312363
}
@@ -509,6 +560,16 @@ func (client *Client) GetMirrorNetwork() []string {
509560
return client.mirrorNetwork._GetNetwork()
510561
}
511562

563+
// GetShard returns the shard for the Client.
564+
func (client *Client) GetShard() uint64 {
565+
return client.shard
566+
}
567+
568+
// GetRealm returns the realm for the Client.
569+
func (client *Client) GetRealm() uint64 {
570+
return client.realm
571+
}
572+
512573
// SetTransportSecurity sets if transport security should be used to connect to consensus nodes.
513574
// If transport security is enabled all connections to consensus nodes will use TLS, and
514575
// the server's certificate hash will be compared to the hash stored in the NodeAddressBook

sdk/client_e2e_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ func TestIntegrationClientCanExecuteSerializedTransactionFromAnotherClient(t *te
1717
t.Parallel()
1818
env := NewIntegrationTestEnv(t)
1919
defer CloseIntegrationTestEnv(env, nil)
20-
client2 := ClientForNetwork(env.Client.GetNetwork())
20+
client2, err := ClientForNetworkV2(env.Client.GetNetwork())
21+
require.NoError(t, err)
2122
client2.SetOperator(env.OperatorID, env.OperatorKey)
2223

2324
tx, err := NewTransferTransaction().AddHbarTransfer(env.OperatorID, HbarFromTinybar(-1)).
@@ -51,7 +52,8 @@ func TestIntegrationClientCanFailGracefullyWhenDoesNotHaveNodeOfAnotherClient(t
5152
address: {Account: 99},
5253
}
5354

54-
client2 := ClientForNetwork(network)
55+
client2, err := ClientForNetworkV2(network)
56+
require.NoError(t, err)
5557
client2.SetOperator(env.OperatorID, env.OperatorKey)
5658

5759
// Create a transaction with a node using original client
@@ -78,7 +80,7 @@ func DisabledTestIntegrationClientPingAllBadNetwork(t *testing.T) { // nolint
7880
netwrk := _NewNetwork()
7981
netwrk.SetNetwork(env.Client.GetNetwork())
8082

81-
tempClient := _NewClient(netwrk, env.Client.GetMirrorNetwork(), env.Client.GetLedgerID(), true)
83+
tempClient := _NewClient(netwrk, env.Client.GetMirrorNetwork(), env.Client.GetLedgerID(), true, 0, 0)
8284
tempClient.SetOperator(env.OperatorID, env.OperatorKey)
8385

8486
tempClient.SetMaxNodeAttempts(1)
@@ -133,7 +135,7 @@ func TestClientInitWithMirrorNetwork(t *testing.T) {
133135
assert.Equal(t, mirrorNetworkString, mirrorNetwork[0])
134136
assert.NotEmpty(t, client.GetNetwork())
135137

136-
client, err = ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 0, 0)
138+
client, err = ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 0, 0)
137139
require.NoError(t, err)
138140

139141
mirrorNetwork = client.GetMirrorNetwork()
@@ -142,20 +144,22 @@ func TestClientInitWithMirrorNetwork(t *testing.T) {
142144
assert.NotEmpty(t, client.GetNetwork())
143145
}
144146

145-
func TestClientForMirrorNetworkWithRealmAndShard(t *testing.T) {
147+
func TestClientIntegrationForMirrorNetworkWithShardAndRealm(t *testing.T) {
146148
t.Parallel()
147149

148150
mirrorNetworkString := "testnet.mirrornode.hedera.com:443"
149-
client, err := ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 0, 0)
151+
client, err := ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 0, 0)
150152
require.NoError(t, err)
151153
require.NotNil(t, client)
152154

153155
// TODO enable when we have non-zero realm and shard env
154-
// client, err = ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 5, 3)
156+
// client, err = ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 5, 3)
155157
// require.NoError(t, err)
156158
// require.NotNil(t, client)
159+
// require.Equal(t, uint64(5), client.GetShard())
160+
// require.Equal(t, uint64(3), client.GetRealm())
157161

158-
client, err = ClientForMirrorNetworkWithRealmAndShard([]string{}, 0, 0)
162+
client, err = ClientForMirrorNetworkWithShardAndRealm([]string{}, 0, 0)
159163
require.Nil(t, client)
160164
assert.Contains(t, err.Error(), "failed to query address book: no healthy nodes")
161165
}

sdk/client_unit_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ func TestUnitClientFromConfig(t *testing.T) {
2626
assert.NotNil(t, client)
2727
assert.True(t, len(client.network.network) > 0)
2828
assert.Nil(t, client.operator)
29+
assert.Equal(t, uint64(3), client.GetShard())
30+
assert.Equal(t, uint64(5), client.GetRealm())
2931
}
3032

3133
func TestUnitClientFromConfigWithOperator(t *testing.T) {
@@ -321,3 +323,50 @@ func TestUnitClientClientFromConfigWithoutScheduleNetworkUpdate(t *testing.T) {
321323
assert.True(t, len(client.network.network) > 0)
322324
assert.Equal(t, time.Duration(0), client.GetNetworkUpdatePeriod())
323325
}
326+
327+
func TestUnitClientPersistsShardAndRealm(t *testing.T) {
328+
t.Parallel()
329+
330+
network := _NewNetwork()
331+
client := _NewClient(network, []string{}, NewLedgerIDTestnet(), true, 1, 2)
332+
assert.Equal(t, uint64(1), client.GetShard())
333+
assert.Equal(t, uint64(2), client.GetRealm())
334+
}
335+
336+
func TestUnitClientForNetworkV2(t *testing.T) {
337+
t.Parallel()
338+
network := map[string]AccountID{
339+
"127.0.0.1:50211": {Account: 3, Shard: 1, Realm: 2},
340+
"127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2},
341+
"127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2},
342+
}
343+
client, err := ClientForNetworkV2(network)
344+
require.NoError(t, err)
345+
assert.Equal(t, uint64(1), client.GetShard())
346+
assert.Equal(t, uint64(2), client.GetRealm())
347+
348+
network = map[string]AccountID{
349+
"127.0.0.1:50211": {Account: 3, Shard: 2, Realm: 2},
350+
"127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2},
351+
"127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2},
352+
}
353+
354+
client, err = ClientForNetworkV2(network)
355+
require.Error(t, err)
356+
assert.Equal(t, err.Error(), "network is not valid, all nodes must be in the same shard and realm")
357+
358+
network = map[string]AccountID{
359+
"127.0.0.1:50211": {Account: 3, Shard: 1, Realm: 1},
360+
"127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2},
361+
"127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2},
362+
}
363+
364+
client, err = ClientForNetworkV2(network)
365+
require.Error(t, err)
366+
assert.Equal(t, err.Error(), "network is not valid, all nodes must be in the same shard and realm")
367+
368+
network = make(map[string]AccountID)
369+
client, err = ClientForNetworkV2(network)
370+
require.Error(t, err)
371+
assert.Equal(t, err.Error(), "network is empty")
372+
}

sdk/file_id.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func FileIDForExchangeRate() FileID {
3434
}
3535

3636
// GetAddressBookFileIDFor returns the public node address book FileID for the given realm and shard.
37-
func GetAddressBookFileIDFor(realm uint64, shard uint64) FileID {
37+
func GetAddressBookFileIDFor(shard uint64, realm uint64) FileID {
3838
return FileID{
3939
Shard: shard,
4040
Realm: realm,
@@ -43,7 +43,7 @@ func GetAddressBookFileIDFor(realm uint64, shard uint64) FileID {
4343
}
4444

4545
// GetFeeScheduleFileIDFor returns the fee schedule FileID for the given realm and shard.
46-
func GetFeeScheduleFileIDFor(realm uint64, shard uint64) FileID {
46+
func GetFeeScheduleFileIDFor(shard uint64, realm uint64) FileID {
4747
return FileID{
4848
Shard: shard,
4949
Realm: realm,
@@ -52,7 +52,7 @@ func GetFeeScheduleFileIDFor(realm uint64, shard uint64) FileID {
5252
}
5353

5454
// GetExchangeRatesFileIDFor returns the exchange rates FileID for the given realm and shard.
55-
func GetExchangeRatesFileIDFor(realm uint64, shard uint64) FileID {
55+
func GetExchangeRatesFileIDFor(shard uint64, realm uint64) FileID {
5656
return FileID{
5757
Shard: shard,
5858
Realm: realm,

sdk/file_id_unit_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestUnitGetAddressBookFileIDFor(t *testing.T) {
7171
assert.Equal(t, uint64(102), fileID.File)
7272
assert.Equal(t, FileIDForAddressBook(), fileID)
7373

74-
fileID = GetAddressBookFileIDFor(5, 3)
74+
fileID = GetAddressBookFileIDFor(3, 5)
7575
assert.Equal(t, uint64(3), fileID.Shard)
7676
assert.Equal(t, uint64(5), fileID.Realm)
7777
assert.Equal(t, uint64(102), fileID.File)
@@ -86,7 +86,7 @@ func TestUnitGetFeeScheduleFileIDFor(t *testing.T) {
8686
assert.Equal(t, uint64(111), fileID.File)
8787
assert.Equal(t, FileIDForFeeSchedule(), fileID)
8888

89-
fileID = GetFeeScheduleFileIDFor(5, 3)
89+
fileID = GetFeeScheduleFileIDFor(3, 5)
9090
assert.Equal(t, uint64(3), fileID.Shard)
9191
assert.Equal(t, uint64(5), fileID.Realm)
9292
assert.Equal(t, uint64(111), fileID.File)
@@ -101,7 +101,7 @@ func TestUnitGetExchangeRatesFileIDFor(t *testing.T) {
101101
assert.Equal(t, uint64(112), fileID.File)
102102
assert.Equal(t, FileIDForExchangeRate(), fileID)
103103

104-
fileID = GetExchangeRatesFileIDFor(5, 3)
104+
fileID = GetExchangeRatesFileIDFor(3, 5)
105105
assert.Equal(t, uint64(3), fileID.Shard)
106106
assert.Equal(t, uint64(5), fileID.Realm)
107107
assert.Equal(t, uint64(112), fileID.File)

0 commit comments

Comments
 (0)