Skip to content

Client.setNetworkFromAddressBook can silently produce empty network, leading to confusing “transaction must have been frozen” error #3550

@rwalworth

Description

@rwalworth

Summary

When using Client.setNetworkFromAddressBook(...) with a custom network, the JavaScript Hiero SDK can end up with an empty network without surfacing any error. In this state:

  • transaction.freezeWith(client) appears to succeed (no error thrown),
  • but await transaction.getTransactionHash() later throws:

Error: transaction must have been frozen before calculating the hash will be stable, try calling 'freeze'

From the user’s perspective, they did call freezeWith(client). The real problem is that the client’s network is {} due to how setNetworkFromAddressBook filters nodes.


Details

In Network.js, setNetworkFromAddressBook currently looks roughly like:

setNetworkFromAddressBook(addressBook) {
    /** @type {Record<string, AccountId>} */
    const network = {};
    const port = this.isTransportSecurity() ? 50212 : 50211;

    for (const nodeAddress of addressBook.nodeAddresses) {
        for (const endpoint of nodeAddress.addresses) {
            // TODO: We hard code ports too much, should fix
            if (endpoint.port === port && nodeAddress.accountId != null) {
                network[endpoint.toString()] = nodeAddress.accountId;
            }
        }
    }

    this.setNetwork(network);
    return this;
}

If the address book entries use a different port (e.g. port: "1" due to misconfiguration), no nodes are added to the network object. The SDK then:

  • Silently sets the network to {}.
  • Does not warn or throw when the resulting network is empty.
  • Allows freezeWith(client) to be called; this “succeeds” but doesn’t actually mark the transaction as frozen, because no signed transactions are generated (no nodes to sign for).
  • Later, getTransactionHash() checks whether the transaction is frozen and throws the generic “transaction must have been frozen” error.

Example Scenario

const client = Client.forNetwork({}); // will be re-initialized from address book

const nodeAddressBook = await new AddressBookQuery()
    .setFileId(FileId.ADDRESS_BOOK)
    .execute(client);

client.setNetworkFromAddressBook(nodeAddressBook);

if (ledgerId) {
    client.setLedgerId(ledgerId);
}

Address book entries exist and look valid at a glance (node IDs, account IDs, etc.), but all endpoints are:

"addresses": [
  { "address": "1.0.0.0", "port": "1" }
]

Because these ports do not match 50211 or 50212, no nodes are added, so:

  • client.network() is {} (but this is not surfaced unless explicitly checked).
  • On the backend:
transaction.freezeWith(client);
await transaction.getTransactionHash(); // throws
  • Error observed:
Error: transaction must have been frozen before calculating the hash will be stable, try calling `freeze`

Even though freezeWith(client) was called, the transaction never became “frozen” due to the empty network.


Actual behavior

  • Client.setNetworkFromAddressBook(addressBook) silently sets an empty network if no endpoints match the expected port (50211/50212).
  • freezeWith(client) does not throw or log anything in this state.
  • getTransactionHash() throws transaction must have been frozen..., which misleads the user into thinking they misused freezeWith instead of pointing them toward the network/address book issue.

Expected behavior

At minimum:

  1. Validation when setting the network

    If setNetworkFromAddressBook results in an empty network (no usable nodes), the SDK should:

  • Throw a descriptive error, or
  • Log a clear warning and refuse to overwrite the network with {}.
  1. More actionable error messaging

    When the network is empty, subsequent operations (e.g. freezeWith, getTransactionHash) should surface that there are no nodes configured, e.g.:

    Client network is empty after initializing from address book; no usable nodes were found (check ports/IPs).

    This would have immediately pointed us toward the misconfigured address book (ports being "1" instead of 50211/50212) and avoided confusion around freezing vs. transaction hash generation.


Proposed improvements

  • In setNetworkFromAddressBook:

    After building network, if Object.keys(network).length === 0, throw an error such as:

    setNetworkFromAddressBook: address book contained no nodes matching the expected port (50211/50212); network would be empty.

  • Optionally include basic debug info (number of addresses scanned, distinct ports seen, etc.), or provide a helper that can show “what the SDK parsed” from the address book for easier troubleshooting.

  • (Optional / future) Revisit the hard-coded port behavior and the // TODO: We hard code ports too much, should fix comment, to support more flexible/custom node ports in the JS SDK.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions