diff --git a/examples/construct_client/main.go b/examples/construct_client/main.go index 3e3e907b..3c573f56 100644 --- a/examples/construct_client/main.go +++ b/examples/construct_client/main.go @@ -48,7 +48,10 @@ func main() { } // Set network for customClient which uses the above custom network - customClient := hiero.ClientForNetwork(customNetwork) + customClient, err := hiero.ClientForNetworkV2(customNetwork) + if err != nil { + panic(fmt.Sprintf("%v : error creating client for network", err)) + } // Setting NetworkName for the CustomClient, is only needed if you need to validate ID checksums customClient.SetLedgerID(*hiero.NewLedgerIDTestnet()) diff --git a/sdk/client.go b/sdk/client.go index 87503dee..d19affa0 100644 --- a/sdk/client.go +++ b/sdk/client.go @@ -47,6 +47,8 @@ type Client struct { networkUpdateContext context.Context cancelNetworkUpdate context.CancelFunc logger Logger + shard uint64 + realm uint64 } // 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"} // ClientForMirrorNetwork constructs a client given a set of mirror network nodes. func ClientForMirrorNetwork(mirrorNetwork []string) (*Client, error) { - return ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork, 0, 0) + return ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork, 0, 0) } -// ClientForMirrorNetworkWithRealmAndShard constructs a client given a set of mirror network nodes and the realm/shard of the address book. -func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint64, shard uint64) (*Client, error) { +// constructs a client given a set of mirror network nodes and the shard/realm of the address book. +func ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork []string, shard uint64, realm uint64) (*Client, error) { net := _NewNetwork() - client := _NewClient(net, mirrorNetwork, nil, true) + client := _NewClient(net, mirrorNetwork, nil, true, shard, realm) addressbook, err := NewAddressBookQuery(). - SetFileID(GetAddressBookFileIDFor(realm, shard)). + SetFileID(GetAddressBookFileIDFor(shard, realm)). Execute(client) if err != nil { return nil, fmt.Errorf("failed to query address book: %v", err) @@ -82,20 +84,62 @@ func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint6 return client, nil } +// Deprecated: Use ClientForMirrorNetworkWithShardAndRealm instead. +func ClientForMirrorNetworkWithRealmAndShard(mirrorNetwork []string, realm uint64, shard uint64) (*Client, error) { + return ClientForMirrorNetworkWithShardAndRealm(mirrorNetwork, shard, realm) +} + // ClientForNetwork constructs a client given a set of nodes. +// Deprecated: Use ClientForNetworkV2 instead. func ClientForNetwork(network map[string]AccountID) *Client { net := _NewNetwork() - client := _NewClient(net, []string{}, nil, true) + client := _NewClient(net, []string{}, nil, true, 0, 0) _ = client.SetNetwork(network) return client } +// ClientForNetworkV2 constructs a client given a set of nodes. +func ClientForNetworkV2(network map[string]AccountID) (*Client, error) { + isValidNetwork := true + var shard uint64 + var realm uint64 + + if len(network) == 0 { + return nil, errors.New("network is empty") + } + + for _, accountID := range network { + if shard == 0 { + shard = accountID.Shard + } + if realm == 0 { + realm = accountID.Realm + } + if shard != accountID.Shard || realm != accountID.Realm { + isValidNetwork = false + break + } + } + + if !isValidNetwork { + return nil, errors.New("network is not valid, all nodes must be in the same shard and realm") + } + + net := _NewNetwork() + client := _NewClient(net, []string{}, nil, true, shard, realm) + err := client.SetNetwork(network) + if err != nil { + return nil, err + } + return client, nil +} + // ClientForMainnet returns a preconfigured client for use with the standard // Hiero mainnet. // Most users will want to set an _Operator account with .SetOperator so // transactions can be automatically given TransactionIDs and signed. func ClientForMainnet() *Client { - return _NewClient(*_NetworkForMainnet(mainnetNodes._ToMap()), mainnetMirror, NewLedgerIDMainnet(), true) + return _NewClient(*_NetworkForMainnet(mainnetNodes._ToMap()), mainnetMirror, NewLedgerIDMainnet(), true, 0, 0) } // ClientForTestnet returns a preconfigured client for use with the standard @@ -103,7 +147,7 @@ func ClientForMainnet() *Client { // Most users will want to set an _Operator account with .SetOperator so // transactions can be automatically given TransactionIDs and signed. func ClientForTestnet() *Client { - return _NewClient(*_NetworkForTestnet(testnetNodes._ToMap()), testnetMirror, NewLedgerIDTestnet(), true) + return _NewClient(*_NetworkForTestnet(testnetNodes._ToMap()), testnetMirror, NewLedgerIDTestnet(), true, 0, 0) } // ClientForPreviewnet returns a preconfigured client for use with the standard @@ -111,12 +155,12 @@ func ClientForTestnet() *Client { // Most users will want to set an _Operator account with .SetOperator so // transactions can be automatically given TransactionIDs and signed. func ClientForPreviewnet() *Client { - return _NewClient(*_NetworkForPreviewnet(previewnetNodes._ToMap()), previewnetMirror, NewLedgerIDPreviewnet(), true) + return _NewClient(*_NetworkForPreviewnet(previewnetNodes._ToMap()), previewnetMirror, NewLedgerIDPreviewnet(), true, 0, 0) } // newClient takes in a map of _Node addresses to their respective IDS (_Network) // and returns a Client instance which can be used to -func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, shouldScheduleNetworkUpdate bool) *Client { +func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, shouldScheduleNetworkUpdate bool, shard uint64, realm uint64) *Client { ctx, cancel := context.WithCancel(context.Background()) logger := NewLogger("hiero-sdk-go", LogLevel(os.Getenv("HEDERA_SDK_GO_LOG_LEVEL"))) var defaultLogger Logger = logger @@ -134,6 +178,8 @@ func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, sh networkUpdateContext: ctx, cancelNetworkUpdate: cancel, logger: defaultLogger, + shard: shard, + realm: realm, } client.SetMirrorNetwork(mirrorNetwork) @@ -153,7 +199,7 @@ func _NewClient(network _Network, mirrorNetwork []string, ledgerId *LedgerID, sh func (client *Client) _UpdateAddressBook() { addressbook, err := NewAddressBookQuery(). - SetFileID(FileIDForAddressBook()). + SetFileID(GetAddressBookFileIDFor(client.shard, client.realm)). Execute(client) if err == nil && len(addressbook.NodeAddresses) > 0 { client.SetNetworkFromAddressBook(addressbook) @@ -203,7 +249,10 @@ func ClientForName(name string) (*Client, error) { network := make(map[string]AccountID) network["127.0.0.1:50213"] = AccountID{Account: 3} mirror := []string{"127.0.0.1:5600"} - client := ClientForNetwork(network) + client, err := ClientForNetworkV2(network) + if err != nil { + return nil, err + } client.SetMirrorNetwork(mirror) return client, nil default: @@ -220,6 +269,8 @@ type _ConfigOperator struct { type _ClientConfig struct { Network interface{} `json:"network"` MirrorNetwork interface{} `json:"mirrorNetwork"` + Shard uint64 `json:"shard"` + Realm uint64 `json:"realm"` Operator *_ConfigOperator `json:"operator"` } @@ -293,20 +344,20 @@ func clientFromConfig(jsonBytes []byte, shouldScheduleNetworkUpdate bool) (*Clie return client, errors.New("mirrorNetwork is expected to be either string or an array of strings") } } - client = _NewClient(network, arr, nil, shouldScheduleNetworkUpdate) + client = _NewClient(network, arr, nil, shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm) case string: if len(mirror) > 0 { switch mirror { case string(NetworkNameMainnet): - client = _NewClient(network, mainnetMirror, NewLedgerIDMainnet(), shouldScheduleNetworkUpdate) + client = _NewClient(network, mainnetMirror, NewLedgerIDMainnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm) case string(NetworkNameTestnet): - client = _NewClient(network, testnetMirror, NewLedgerIDTestnet(), shouldScheduleNetworkUpdate) + client = _NewClient(network, testnetMirror, NewLedgerIDTestnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm) case string(NetworkNamePreviewnet): - client = _NewClient(network, previewnetMirror, NewLedgerIDPreviewnet(), shouldScheduleNetworkUpdate) + client = _NewClient(network, previewnetMirror, NewLedgerIDPreviewnet(), shouldScheduleNetworkUpdate, clientConfig.Shard, clientConfig.Realm) } } case nil: - client = _NewClient(network, []string{}, nil, true) + client = _NewClient(network, []string{}, nil, true, clientConfig.Shard, clientConfig.Realm) default: return client, errors.New("mirrorNetwork is expected to be a string, an array of strings or nil") } @@ -509,6 +560,16 @@ func (client *Client) GetMirrorNetwork() []string { return client.mirrorNetwork._GetNetwork() } +// GetShard returns the shard for the Client. +func (client *Client) GetShard() uint64 { + return client.shard +} + +// GetRealm returns the realm for the Client. +func (client *Client) GetRealm() uint64 { + return client.realm +} + // SetTransportSecurity sets if transport security should be used to connect to consensus nodes. // If transport security is enabled all connections to consensus nodes will use TLS, and // the server's certificate hash will be compared to the hash stored in the NodeAddressBook diff --git a/sdk/client_e2e_test.go b/sdk/client_e2e_test.go index 19fe93bd..488e8629 100644 --- a/sdk/client_e2e_test.go +++ b/sdk/client_e2e_test.go @@ -17,7 +17,8 @@ func TestIntegrationClientCanExecuteSerializedTransactionFromAnotherClient(t *te t.Parallel() env := NewIntegrationTestEnv(t) defer CloseIntegrationTestEnv(env, nil) - client2 := ClientForNetwork(env.Client.GetNetwork()) + client2, err := ClientForNetworkV2(env.Client.GetNetwork()) + require.NoError(t, err) client2.SetOperator(env.OperatorID, env.OperatorKey) tx, err := NewTransferTransaction().AddHbarTransfer(env.OperatorID, HbarFromTinybar(-1)). @@ -51,7 +52,8 @@ func TestIntegrationClientCanFailGracefullyWhenDoesNotHaveNodeOfAnotherClient(t address: {Account: 99}, } - client2 := ClientForNetwork(network) + client2, err := ClientForNetworkV2(network) + require.NoError(t, err) client2.SetOperator(env.OperatorID, env.OperatorKey) // Create a transaction with a node using original client @@ -78,7 +80,7 @@ func DisabledTestIntegrationClientPingAllBadNetwork(t *testing.T) { // nolint netwrk := _NewNetwork() netwrk.SetNetwork(env.Client.GetNetwork()) - tempClient := _NewClient(netwrk, env.Client.GetMirrorNetwork(), env.Client.GetLedgerID(), true) + tempClient := _NewClient(netwrk, env.Client.GetMirrorNetwork(), env.Client.GetLedgerID(), true, 0, 0) tempClient.SetOperator(env.OperatorID, env.OperatorKey) tempClient.SetMaxNodeAttempts(1) @@ -133,7 +135,7 @@ func TestClientInitWithMirrorNetwork(t *testing.T) { assert.Equal(t, mirrorNetworkString, mirrorNetwork[0]) assert.NotEmpty(t, client.GetNetwork()) - client, err = ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 0, 0) + client, err = ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 0, 0) require.NoError(t, err) mirrorNetwork = client.GetMirrorNetwork() @@ -142,20 +144,22 @@ func TestClientInitWithMirrorNetwork(t *testing.T) { assert.NotEmpty(t, client.GetNetwork()) } -func TestClientForMirrorNetworkWithRealmAndShard(t *testing.T) { +func TestClientIntegrationForMirrorNetworkWithShardAndRealm(t *testing.T) { t.Parallel() mirrorNetworkString := "testnet.mirrornode.hedera.com:443" - client, err := ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 0, 0) + client, err := ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 0, 0) require.NoError(t, err) require.NotNil(t, client) // TODO enable when we have non-zero realm and shard env - // client, err = ClientForMirrorNetworkWithRealmAndShard([]string{mirrorNetworkString}, 5, 3) + // client, err = ClientForMirrorNetworkWithShardAndRealm([]string{mirrorNetworkString}, 5, 3) // require.NoError(t, err) // require.NotNil(t, client) + // require.Equal(t, uint64(5), client.GetShard()) + // require.Equal(t, uint64(3), client.GetRealm()) - client, err = ClientForMirrorNetworkWithRealmAndShard([]string{}, 0, 0) + client, err = ClientForMirrorNetworkWithShardAndRealm([]string{}, 0, 0) require.Nil(t, client) assert.Contains(t, err.Error(), "failed to query address book: no healthy nodes") } diff --git a/sdk/client_unit_test.go b/sdk/client_unit_test.go index 9d72a185..e6e7bc45 100644 --- a/sdk/client_unit_test.go +++ b/sdk/client_unit_test.go @@ -26,6 +26,8 @@ func TestUnitClientFromConfig(t *testing.T) { assert.NotNil(t, client) assert.True(t, len(client.network.network) > 0) assert.Nil(t, client.operator) + assert.Equal(t, uint64(3), client.GetShard()) + assert.Equal(t, uint64(5), client.GetRealm()) } func TestUnitClientFromConfigWithOperator(t *testing.T) { @@ -321,3 +323,50 @@ func TestUnitClientClientFromConfigWithoutScheduleNetworkUpdate(t *testing.T) { assert.True(t, len(client.network.network) > 0) assert.Equal(t, time.Duration(0), client.GetNetworkUpdatePeriod()) } + +func TestUnitClientPersistsShardAndRealm(t *testing.T) { + t.Parallel() + + network := _NewNetwork() + client := _NewClient(network, []string{}, NewLedgerIDTestnet(), true, 1, 2) + assert.Equal(t, uint64(1), client.GetShard()) + assert.Equal(t, uint64(2), client.GetRealm()) +} + +func TestUnitClientForNetworkV2(t *testing.T) { + t.Parallel() + network := map[string]AccountID{ + "127.0.0.1:50211": {Account: 3, Shard: 1, Realm: 2}, + "127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2}, + "127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2}, + } + client, err := ClientForNetworkV2(network) + require.NoError(t, err) + assert.Equal(t, uint64(1), client.GetShard()) + assert.Equal(t, uint64(2), client.GetRealm()) + + network = map[string]AccountID{ + "127.0.0.1:50211": {Account: 3, Shard: 2, Realm: 2}, + "127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2}, + "127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2}, + } + + client, err = ClientForNetworkV2(network) + require.Error(t, err) + assert.Equal(t, err.Error(), "network is not valid, all nodes must be in the same shard and realm") + + network = map[string]AccountID{ + "127.0.0.1:50211": {Account: 3, Shard: 1, Realm: 1}, + "127.0.0.1:50212": {Account: 4, Shard: 1, Realm: 2}, + "127.0.0.1:50213": {Account: 5, Shard: 1, Realm: 2}, + } + + client, err = ClientForNetworkV2(network) + require.Error(t, err) + assert.Equal(t, err.Error(), "network is not valid, all nodes must be in the same shard and realm") + + network = make(map[string]AccountID) + client, err = ClientForNetworkV2(network) + require.Error(t, err) + assert.Equal(t, err.Error(), "network is empty") +} diff --git a/sdk/file_id.go b/sdk/file_id.go index e4d7a65b..f41bbac6 100644 --- a/sdk/file_id.go +++ b/sdk/file_id.go @@ -34,7 +34,7 @@ func FileIDForExchangeRate() FileID { } // GetAddressBookFileIDFor returns the public node address book FileID for the given realm and shard. -func GetAddressBookFileIDFor(realm uint64, shard uint64) FileID { +func GetAddressBookFileIDFor(shard uint64, realm uint64) FileID { return FileID{ Shard: shard, Realm: realm, @@ -43,7 +43,7 @@ func GetAddressBookFileIDFor(realm uint64, shard uint64) FileID { } // GetFeeScheduleFileIDFor returns the fee schedule FileID for the given realm and shard. -func GetFeeScheduleFileIDFor(realm uint64, shard uint64) FileID { +func GetFeeScheduleFileIDFor(shard uint64, realm uint64) FileID { return FileID{ Shard: shard, Realm: realm, @@ -52,7 +52,7 @@ func GetFeeScheduleFileIDFor(realm uint64, shard uint64) FileID { } // GetExchangeRatesFileIDFor returns the exchange rates FileID for the given realm and shard. -func GetExchangeRatesFileIDFor(realm uint64, shard uint64) FileID { +func GetExchangeRatesFileIDFor(shard uint64, realm uint64) FileID { return FileID{ Shard: shard, Realm: realm, diff --git a/sdk/file_id_unit_test.go b/sdk/file_id_unit_test.go index 0bf20764..fbb35230 100644 --- a/sdk/file_id_unit_test.go +++ b/sdk/file_id_unit_test.go @@ -71,7 +71,7 @@ func TestUnitGetAddressBookFileIDFor(t *testing.T) { assert.Equal(t, uint64(102), fileID.File) assert.Equal(t, FileIDForAddressBook(), fileID) - fileID = GetAddressBookFileIDFor(5, 3) + fileID = GetAddressBookFileIDFor(3, 5) assert.Equal(t, uint64(3), fileID.Shard) assert.Equal(t, uint64(5), fileID.Realm) assert.Equal(t, uint64(102), fileID.File) @@ -86,7 +86,7 @@ func TestUnitGetFeeScheduleFileIDFor(t *testing.T) { assert.Equal(t, uint64(111), fileID.File) assert.Equal(t, FileIDForFeeSchedule(), fileID) - fileID = GetFeeScheduleFileIDFor(5, 3) + fileID = GetFeeScheduleFileIDFor(3, 5) assert.Equal(t, uint64(3), fileID.Shard) assert.Equal(t, uint64(5), fileID.Realm) assert.Equal(t, uint64(111), fileID.File) @@ -101,7 +101,7 @@ func TestUnitGetExchangeRatesFileIDFor(t *testing.T) { assert.Equal(t, uint64(112), fileID.File) assert.Equal(t, FileIDForExchangeRate(), fileID) - fileID = GetExchangeRatesFileIDFor(5, 3) + fileID = GetExchangeRatesFileIDFor(3, 5) assert.Equal(t, uint64(3), fileID.Shard) assert.Equal(t, uint64(5), fileID.Realm) assert.Equal(t, uint64(112), fileID.File) diff --git a/sdk/node_create_transaction_e2e_test.go b/sdk/node_create_transaction_e2e_test.go index c7cf6a2e..7bac8139 100644 --- a/sdk/node_create_transaction_e2e_test.go +++ b/sdk/node_create_transaction_e2e_test.go @@ -19,7 +19,8 @@ func TestIntegrationCanExecuteNodeCreateTransaction(t *testing.T) { // Set the network network := make(map[string]AccountID) network["localhost:50211"] = AccountID{Account: 3} - client := ClientForNetwork(network) + client, err := ClientForNetworkV2(network) + require.NoError(t, err) mirror := []string{"localhost:5600"} client.SetMirrorNetwork(mirror) diff --git a/sdk/one_signature_e2e_test.go b/sdk/one_signature_e2e_test.go index 85dd9267..3552fc63 100644 --- a/sdk/one_signature_e2e_test.go +++ b/sdk/one_signature_e2e_test.go @@ -28,7 +28,9 @@ func TestIntegrationOneSignature(t *testing.T) { env := NewIntegrationTestEnv(t) defer CloseIntegrationTestEnv(env, nil) - client := ClientForNetwork(env.Client.GetNetwork()).SetOperatorWith(env.OriginalOperatorID, env.OriginalOperatorKey, signingServiceTwo) + client, err := ClientForNetworkV2(env.Client.GetNetwork()) + require.NoError(t, err) + client.SetOperatorWith(env.OriginalOperatorID, env.OriginalOperatorKey, signingServiceTwo) response, err := NewTransferTransaction(). AddHbarTransfer(env.OriginalOperatorID, NewHbar(-1)). AddHbarTransfer(AccountID{Account: 3}, NewHbar(1)). diff --git a/sdk/utilities_for_test.go b/sdk/utilities_for_test.go index b7e747f6..10476efc 100644 --- a/sdk/utilities_for_test.go +++ b/sdk/utilities_for_test.go @@ -28,6 +28,8 @@ var testTransactionID TransactionID = TransactionID{ } const testClientJSON string = `{ + "shard": 3, + "realm": 5, "network": { "35.237.200.180:50211": "0.0.3", "35.186.191.247:50211": "0.0.4", @@ -62,7 +64,8 @@ func NewIntegrationTestEnv(t *testing.T) IntegrationTestEnv { network := make(map[string]AccountID) network["127.0.0.1:50213"] = AccountID{Account: 3} mirror := []string{"127.0.0.1:5600"} - env.Client = ClientForNetwork(network) + env.Client, err = ClientForNetworkV2(network) + require.NoError(t, err) env.Client.SetMirrorNetwork(mirror) } else if os.Getenv("HEDERA_NETWORK") == "testnet" { env.Client = ClientForTestnet() @@ -173,7 +176,10 @@ func _NewMockClient() (*Client, error) { var net = make(map[string]AccountID) net["nonexistent-testnet:56747"] = AccountID{Account: 3} - client := ClientForNetwork(net) + client, err := ClientForNetworkV2(net) + if err != nil { + return nil, err + } defaultNetwork := []string{"nonexistent-mirror-testnet:443"} client.SetMirrorNetwork(defaultNetwork) client.SetOperator(AccountID{Account: 2}, privateKey)