Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions .github/workflows/zxc-build-library.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,23 @@ jobs:
uses: hiero-ledger/hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # v0.16.0
with:
installMirrorNode: true
hieroVersion: v0.65.0

mirrorNodeVersion: v0.142.0
hieroVersion: v0.68.4
dualMode: true

- name: Start CTest suite (Debug)
run: ${{ steps.cgroup.outputs.exec }} ctest -j 6 -C Debug --test-dir build/${{ matrix.preset }}-debug --output-on-failure
# - name: Start CTest suite (Debug)
# run: ${{ steps.cgroup.outputs.exec }} ctest -j 6 -C Debug --test-dir build/${{ matrix.preset }}-debug -E NodeUpdateTransactionIntegrationTests --output-on-failure

- name: Start CTest suite (Release)
# - name: Start CTest suite (Release)
# if: github.event.pull_request.merged == true
# run: ${{ steps.cgroup.outputs.exec }} ctest -j 6 -C Debug --test-dir build/${{ matrix.preset }}-release -E NodeUpdateTransactionIntegrationTests --output-on-failure

- name: Run Node Update Integration Tests (Debug)
run: ${{ steps.cgroup.outputs.exec }} ctest -C Debug --test-dir build/${{ matrix.preset }}-debug -R NodeUpdateTransactionIntegrationTests --output-on-failure

- name: Run Node Update Integration Tests (Release)
if: github.event.pull_request.merged == true
run: ${{ steps.cgroup.outputs.exec }} ctest -j 6 -C Debug --test-dir build/${{ matrix.preset }}-release --output-on-failure
run: ${{ steps.cgroup.outputs.exec }} ctest -C Debug --test-dir build/${{ matrix.preset }}-release -R NodeUpdateTransactionIntegrationTests --output-on-failure

- name: Compute Short SHA
id: sha
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,7 @@ src/proto/*.proto
/proto/event
/proto/sdk
/proto/platform
/proto/block

### Ignore Generated Package Files
package
Expand Down
7 changes: 4 additions & 3 deletions HieroApi.cmake
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
set(HAPI_VERSION_TAG "v0.62.0" CACHE STRING "Use the configured version tag for the Hiero API protobufs")
set(HAPI_COMMIT_HASH "e7db7cd74e1709ca719d1fcc9119aa062e82930f" CACHE STRING "Use the configured commit hash for the Hiero API protobufs (overrides version tag if provided)")
set(HAPI_VERSION_TAG "v0.68.1-rc.1" CACHE STRING "Use the configured version tag for the Hiero API protobufs")
set(HAPI_COMMIT_HASH "fadd38a6b2badec02bee35272f03fe8fafadea00" CACHE STRING "Use the configured commit hash for the Hiero API protobufs (overrides version tag if provided)")

if (HAPI_VERSION_TAG STREQUAL "")
set(HAPI_VERSION_TAG "v0.62.0")
set(HAPI_VERSION_TAG "v0.68.1-rc.1")
endif ()

# Use commit hash if provided, otherwise use version tag
Expand Down Expand Up @@ -61,6 +61,7 @@ file(INSTALL ${PROJECT_SOURCE_DIR}/proto/service-external-proto/sdk/ DESTINATION

file(INSTALL ${hproto_SOURCE_DIR}/hapi/hedera-protobuf-java-api/src/main/proto/platform/ DESTINATION ${PROTO_SRC}/platform)
file(INSTALL ${hproto_SOURCE_DIR}/hapi/hedera-protobuf-java-api/src/main/proto/services/ DESTINATION ${PROTO_SRC}/services)
file(INSTALL ${hproto_SOURCE_DIR}/hapi/hedera-protobuf-java-api/src/main/proto/block/ DESTINATION ${PROTO_SRC}/block)

add_subdirectory(proto)

Expand Down
4 changes: 4 additions & 0 deletions proto/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Begin Protobuf Definitions
set(PROTO_FILES
block/stream/chain_of_trust_proof.proto
transaction_list.proto

mirror/consensus_service.proto
Expand Down Expand Up @@ -56,6 +57,9 @@ set(PROTO_FILES
services/get_account_details.proto
services/get_by_key.proto
services/get_by_solidity_id.proto
services/hook_dispatch.proto
services/hook_types.proto
services/lambda_sstore.proto
services/network_get_execution_time.proto
services/network_get_version_info.proto
services/network_service.proto
Expand Down
8 changes: 8 additions & 0 deletions src/sdk/main/include/Client.h
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ class Client
*/
Client& setNetworkFromAddressBook(const NodeAddressBook& addressBook);

/**
* Update the address book from the network immediately.
* This method is typically called when a node returns INVALID_NODE_ACCOUNT.
* The update is done synchronously to ensure the network has the latest nodes
* before retrying with another node.
*/
void updateAddressBook();

/**
* Set the consensus network with which this Client should communicate.
*
Expand Down
13 changes: 12 additions & 1 deletion src/sdk/main/include/Executable.h
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@ class Executable
Executable(Executable&&) noexcept = default;
Executable& operator=(Executable&&) noexcept = default;

/**
* Get the maximum number of attempts that have been explicitly set for this Executable.
*
* @return The maximum number of attempts, or std::nullopt if not set.
*/
[[nodiscard]] inline std::optional<uint32_t> getMaxAttemptsSet() const { return mMaxAttempts; }

/**
* Enumeration describing the status of a submitted Executable.
*/
Expand All @@ -292,7 +299,11 @@ class Executable
/**
* The call was successful but an operation did not complete.
*/
RETRY
RETRY,
/**
* The node account is invalid; retry with another node and update address book.
*/
RETRY_WITH_ANOTHER_NODE
};

/**
Expand Down
7 changes: 6 additions & 1 deletion src/sdk/main/include/Status.h
Original file line number Diff line number Diff line change
Expand Up @@ -1855,7 +1855,12 @@ enum class Status
/**
* An NFT transfers list referenced a token type other than NON_FUNGIBLE_UNIQUE.
*/
NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE
NFT_TRANSFERS_ONLY_ALLOWED_FOR_NON_FUNGIBLE_UNIQUE,

/**
* The node account has a zero balance.
*/
NODE_ACCOUNT_HAS_ZERO_BALANCE,
};

/**
Expand Down
11 changes: 11 additions & 0 deletions src/sdk/main/include/impl/BaseNetwork.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,17 @@ class BaseNetwork
return mNetwork;
}

/**
* Set the internal network map. This should be used carefully when updating node keys.
*
* @param network The new network map.
*/
inline void setNetworkInternal(
const std::unordered_map<KeyType, std::unordered_set<std::shared_ptr<NodeType>>>& network)
{
mNetwork = network;
}

/**
* Get the list of NodeTypes on this BaseNetwork.
*
Expand Down
16 changes: 14 additions & 2 deletions src/sdk/main/include/impl/Network.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ class Network : public BaseNetwork<Network, AccountId, Node>
const NodeAddressBook& addressBook,
unsigned int port);

/**
* Update node account IDs from an address book without closing connections.
* This is used when nodes' account IDs change (e.g., during node update transactions).
* Nodes are matched by their IP address, and their AccountIds are updated in place.
*
* @param addressBook The address book containing the updated node account IDs.
* @param port The port to use when matching nodes.
* @return A reference to this Network object with updated node account IDs.
*/
Network& updateNodeAccountIds(const NodeAddressBook& addressBook, unsigned int port);

/**
* Derived from BaseNetwork. Set the ledger ID of this Network.
*
Expand Down Expand Up @@ -118,12 +129,13 @@ class Network : public BaseNetwork<Network, AccountId, Node>
[[nodiscard]] unsigned int getNumberOfNodesForRequest() const;

/**
* Get a list of node account IDs on which to execute. This will pick 1/3 of the available nodes sorted by health and
* Get a list of node account IDs on which to execute. This will return up to maxAttempts nodes sorted by health and
* expected delay from the network.
*
* @param maxAttempts The maximum number of attempts that will be made for this execution.
* @return A list of AccountIds that are running nodes on which should be executed.
*/
[[nodiscard]] std::vector<AccountId> getNodeAccountIdsForExecute();
[[nodiscard]] std::vector<AccountId> getNodeAccountIdsForExecute(unsigned int maxAttempts);

/**
* Get a map of this Network, mapping the Node addresses to their AccountIds.
Expand Down
13 changes: 13 additions & 0 deletions src/sdk/main/include/impl/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,19 @@ class Node : public BaseNode<Node, AccountId>
return mAccountId;
};

/**
* Update the AccountId of this Node.
* This is used when a node's account ID changes (e.g., during a node update transaction)
* without needing to close and recreate the connection.
*
* @param accountId The new AccountId for this Node.
*/
inline void setAccountId(const AccountId& accountId)
{
std::unique_lock lock(*getLock());
mAccountId = accountId;
};

/**
* Get the node certificate hash of this Node.
*
Expand Down
49 changes: 44 additions & 5 deletions src/sdk/main/src/Client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,30 @@ Client& Client::setNetworkFromAddressBook(const NodeAddressBook& addressBook)
return *this;
}

//-----
void Client::updateAddressBook()
{
try
{
mImpl->mLogger.trace("Updating address book after INVALID_NODE_ACCOUNT response");

// Get the address book - do NOT hold the mutex during query execution
// as execute() will call other Client methods that also need the mutex
const NodeAddressBook addressBook = AddressBookQuery().setFileId(FileId::ADDRESS_BOOK).execute(*this);

// Only acquire the mutex for the actual update
std::unique_lock lock(mImpl->mMutex);
setNetworkFromAddressBookInternal(addressBook);

mImpl->mLogger.trace("Address book successfully updated");
}
catch (const std::exception& exception)
{
mImpl->mLogger.warn(std::string("Failed to update address book: ") + exception.what());
throw;
}
}

//-----
Client& Client::setNetwork(const std::unordered_map<std::string, AccountId>& networkMap)
{
Expand Down Expand Up @@ -1073,11 +1097,26 @@ std::shared_ptr<internal::MirrorNetwork> Client::getClientMirrorNetwork() const
//-----
void Client::setNetworkFromAddressBookInternal(const NodeAddressBook& addressBook)
{
mImpl->mNetwork->setNetwork(internal::Network::getNetworkFromAddressBook(
addressBook,
mImpl->mNetwork->isTransportSecurity() == internal::TLSBehavior::REQUIRE
? internal::BaseNodeAddress::PORT_NODE_TLS
: internal::BaseNodeAddress::PORT_NODE_PLAIN));
// First try the port based on TLS setting
unsigned int preferredPort = mImpl->mNetwork->isTransportSecurity() == internal::TLSBehavior::REQUIRE
? internal::BaseNodeAddress::PORT_NODE_TLS
: internal::BaseNodeAddress::PORT_NODE_PLAIN;

// Try to update node account IDs without closing connections
mImpl->mNetwork->updateNodeAccountIds(addressBook, preferredPort);

// If that didn't find any matches, try the other port
// (This handles the case where the TLS setting doesn't match the actual ports)
auto networkMap = internal::Network::getNetworkFromAddressBook(addressBook, preferredPort);

if (networkMap.empty())
{
unsigned int alternatePort = (preferredPort == internal::BaseNodeAddress::PORT_NODE_TLS)
? internal::BaseNodeAddress::PORT_NODE_PLAIN
: internal::BaseNodeAddress::PORT_NODE_TLS;

mImpl->mNetwork->updateNodeAccountIds(addressBook, alternatePort);
}
}

//-----
Expand Down
Loading