From 2dc3cb05f797d4e7bda00e90872c85fa5808a727 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 11 Mar 2026 12:40:24 +0530 Subject: [PATCH 01/27] feat: added the protos for hip-1137 Signed-off-by: Manish Dait --- sdk/src/main/proto/basic_types.proto | 1 + sdk/src/main/proto/block_info.proto | 20 +++++++++++++++++++ sdk/src/main/proto/node_update.proto | 1 + sdk/src/main/proto/registered_node.proto | 17 ++++++++++++++++ .../main/proto/registered_node_create.proto | 17 ++++++++++++++++ .../main/proto/registered_node_update.proto | 20 +++++++++++++++++++ .../proto/registered_service_endpoint.proto | 2 +- 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/sdk/src/main/proto/basic_types.proto b/sdk/src/main/proto/basic_types.proto index c2b8b7be32..c43a9a1494 100644 --- a/sdk/src/main/proto/basic_types.proto +++ b/sdk/src/main/proto/basic_types.proto @@ -1840,6 +1840,7 @@ enum HederaFunctionality { AtomicBatch = 108; /** + * Update one or more storage slots in an lambda EVM hook. * (DEPRECATED) Remove once no production throttle assets reference it. */ LambdaSStore = 109; diff --git a/sdk/src/main/proto/block_info.proto b/sdk/src/main/proto/block_info.proto index 31b911c089..782965a10a 100644 --- a/sdk/src/main/proto/block_info.proto +++ b/sdk/src/main/proto/block_info.proto @@ -109,4 +109,24 @@ message BlockInfo { * at which an interval of time-dependent events were processed. */ proto.Timestamp last_interval_process_time = 8; + + /** + * The root hash of the previous wrapped record block. + */ + bytes previous_wrapped_record_block_root_hash = 10; + + /** + * The intermediate hashes, calculated for all historical wrapped + * record blocks, needed for subroot 2 in the block merkle + * tree structure. These hashes SHALL include the minimum required + * wrapped record block root hashes needed to construct subroot 2's + * final state at the end of the previous block. + */ + repeated bytes wrapped_intermediate_previous_block_root_hashes = 11; + + /** + * The number of leaves in the intermediate wrapped record block + * roots subtree. + */ + uint64 wrapped_intermediate_block_roots_leaf_count = 12; } diff --git a/sdk/src/main/proto/node_update.proto b/sdk/src/main/proto/node_update.proto index 738ce99428..fe77d59cc4 100644 --- a/sdk/src/main/proto/node_update.proto +++ b/sdk/src/main/proto/node_update.proto @@ -168,6 +168,7 @@ message NodeUpdateTransactionBody { */ proto.ServiceEndpoint grpc_proxy_endpoint = 10; + /** * A list of registered nodes operated by the same entity as this node.
* This value may contain a list of "registered nodes" (as described in diff --git a/sdk/src/main/proto/registered_node.proto b/sdk/src/main/proto/registered_node.proto index 7bdd96ba08..8182ecabb8 100644 --- a/sdk/src/main/proto/registered_node.proto +++ b/sdk/src/main/proto/registered_node.proto @@ -64,4 +64,21 @@ message RegisteredNode { * This list SHALL NOT contain more than `50` entries. */ repeated com.hedera.hapi.node.addressbook.RegisteredServiceEndpoint service_endpoint = 4; + + /** + * An account identifier.
+ * This account identifies the entity financially responsible for this + * registered node. + *

+ * This field is OPTIONAL.
+ * Individual node operators SHALL have full authority to set, change, or + * remove their node account ID.
+ * If set and the node account ID does not resolve to an existing and active + * account, then the transaction SHALL fail.
+ * Node operators SHOULD ensure that the node account is kept up to date + * and always refers to a valid and active `Account`.
+ * The owner of the node account MAY be different from the owner of the + * registered node. + */ + proto.AccountID node_account = 5; } diff --git a/sdk/src/main/proto/registered_node_create.proto b/sdk/src/main/proto/registered_node_create.proto index 37861550be..a4e87a12ee 100644 --- a/sdk/src/main/proto/registered_node_create.proto +++ b/sdk/src/main/proto/registered_node_create.proto @@ -58,4 +58,21 @@ message RegisteredNodeCreateTransactionBody { * This list MUST NOT contain more than `50` entries. */ repeated RegisteredServiceEndpoint service_endpoint = 3; + + /** + * An account identifier.
+ * This account identifies the entity financially responsible for this + * registered node. + *

+ * This field is OPTIONAL.
+ * Individual node operators SHALL have full authority to set, change, or + * remove their node account ID.
+ * If set and the node account ID does not resolve to an existing and active + * account, then the transaction SHALL fail.
+ * Node operators SHOULD ensure that the node account is kept up to date + * and always refers to a valid and active `Account`.
+ * The owner of the node account MAY be different from the owner of the + * registered node. + */ + proto.AccountID node_account = 4; } diff --git a/sdk/src/main/proto/registered_node_update.proto b/sdk/src/main/proto/registered_node_update.proto index b579b7cb64..f3f4cc6e7f 100644 --- a/sdk/src/main/proto/registered_node_update.proto +++ b/sdk/src/main/proto/registered_node_update.proto @@ -75,4 +75,24 @@ message RegisteredNodeUpdateTransactionBody { * If set, this list SHALL _replace_ the previous list. */ repeated RegisteredServiceEndpoint service_endpoint = 4; + + /** + * An account identifier.
+ * This account identifies the entity financially responsible for this + * registered node. + *

+ * This field is OPTIONAL.
+ * If not set, the field SHALL NOT be updated.
+ * Individual node operators SHALL have full authority to set, change, or + * remove their node account ID.
+ * If set and the node account ID does not resolve to an existing and active + * account, then the transaction SHALL fail.
+ * Node operators SHOULD ensure that the node account is kept up to date + * and always refers to a valid and active `Account`.
+ * The owner of the node account MAY be different from the owner of the + * registered node.
+ * If set to account ID `0.0.0`, the node account SHALL be removed from + * this registered node. + */ + proto.AccountID node_account = 5; } diff --git a/sdk/src/main/proto/registered_service_endpoint.proto b/sdk/src/main/proto/registered_service_endpoint.proto index 6bdbabf36c..2415b4c1ac 100644 --- a/sdk/src/main/proto/registered_service_endpoint.proto +++ b/sdk/src/main/proto/registered_service_endpoint.proto @@ -157,4 +157,4 @@ message RegisteredServiceEndpoint { */ message RpcRelayEndpoint { } -} \ No newline at end of file +} From 1b416c7cd3fe396c2ff4101ff6a618277b66b25b Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 12 Mar 2026 22:25:58 +0530 Subject: [PATCH 02/27] feat: added the new classed Signed-off-by: Manish Dait --- .../hedera/hashgraph/sdk/BlockNodeApi.java | 56 +++ .../sdk/BlockNodeServiceEndpoint.java | 87 +++++ .../sdk/MirrorNodeServiceEndpoint.java | 63 ++++ .../hedera/hashgraph/sdk/RegisteredNode.java | 143 ++++++++ .../sdk/RegisteredNodeAddressBook.java | 18 + .../sdk/RegisteredNodeCreateTransaction.java | 289 +++++++++++++++ .../sdk/RegisteredNodeDeleteTransaction.java | 143 ++++++++ .../sdk/RegisteredNodeUpdateTransaction.java | 333 ++++++++++++++++++ .../sdk/RegisteredServiceEndpoint.java | 112 ++++++ .../sdk/RegisteredServiceEndpointBase.java | 59 ++++ .../sdk/RpcRelayServiceEndpoint.java | 62 ++++ .../com/hedera/hashgraph/sdk/Transaction.java | 12 + .../hashgraph/sdk/TransactionReceipt.java | 18 + .../hashgraph/sdk/BlockNodeApiTest.java | 32 ++ .../sdk/BlockNodeServiceEndpointTest.java | 115 ++++++ .../sdk/BlockNodeServiceEndpointTest.snap | 18 + .../sdk/MirrorNodeServiceEndpointTest.java | 102 ++++++ .../sdk/MirrorNodeServiceEndpointTest.snap | 18 + .../RegisteredNodeCreateTransactionTest.java | 245 +++++++++++++ .../RegisteredNodeCreateTransactionTest.snap | 3 + .../RegisteredNodeDeleteTransactionTest.java | 154 ++++++++ .../RegisteredNodeDeleteTransactionTest.snap | 3 + .../hashgraph/sdk/RegisteredNodeTest.java | 65 ++++ .../hashgraph/sdk/RegisteredNodeTest.snap | 18 + .../RegisteredNodeUpdateTransactionTest.java | 277 +++++++++++++++ .../RegisteredNodeUpdateTransactionTest.snap | 3 + .../sdk/RpcRelayServiceEndpointTest.java | 102 ++++++ .../sdk/RpcRelayServiceEndpointTest.snap | 18 + .../hashgraph/sdk/TransactionReceiptTest.java | 1 + .../hashgraph/sdk/TransactionReceiptTest.snap | 4 +- .../hashgraph/sdk/TransactionRecordTest.snap | 6 +- ...dNodeCreateTransactionIntegrationTest.java | 42 +++ 32 files changed, 2616 insertions(+), 5 deletions(-) create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeApi.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransaction.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpointBase.java create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeApiTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap create mode 100644 sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeApi.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeApi.java new file mode 100644 index 0000000000..7e75ab514d --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeApi.java @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.BlockNodeEndpoint; + +/** + * An enumeration of well-known block node endpoint APIs. + */ +public enum BlockNodeApi { + /** + * Any other API type associated with a block node. + */ + OTHER(BlockNodeEndpoint.BlockNodeApi.OTHER), + + /** + * The Block Node Status API. + */ + STATUS(BlockNodeEndpoint.BlockNodeApi.STATUS), + + /** + * The Block Node Publish API. + */ + PUBLISH(BlockNodeEndpoint.BlockNodeApi.PUBLISH), + + /** + * The Block Node Subscribe Stream API. + */ + SUBSCRIBE_STREAM(BlockNodeEndpoint.BlockNodeApi.SUBSCRIBE_STREAM), + + /** + * The Block Node State Proof API. + */ + STATE_PROOF(BlockNodeEndpoint.BlockNodeApi.STATE_PROOF); + + final BlockNodeEndpoint.BlockNodeApi code; + + BlockNodeApi(BlockNodeEndpoint.BlockNodeApi code) { + this.code = code; + } + + static BlockNodeApi valueOf(BlockNodeEndpoint.BlockNodeApi code) { + return switch (code) { + case OTHER -> OTHER; + case STATUS -> STATUS; + case PUBLISH -> PUBLISH; + case SUBSCRIBE_STREAM -> SUBSCRIBE_STREAM; + case STATE_PROOF -> STATE_PROOF; + default -> throw new IllegalArgumentException("Unhandled BlockNodeApi code"); + }; + } + + @Override + public String toString() { + return code.name(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java new file mode 100644 index 0000000000..dc33752c85 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import java.util.Objects; + +/** + * Represent a Registered Block Node + */ +public class BlockNodeServiceEndpoint extends RegisteredServiceEndpointBase { + /** + * An indicator of what API this endpoint supports. + */ + private BlockNodeApi endpointApi = BlockNodeApi.OTHER; + + /** + * Constructor. + */ + public BlockNodeServiceEndpoint() {} + + public BlockNodeApi getEndpointApi() { + return endpointApi; + } + + public BlockNodeServiceEndpoint setEndpointApi(BlockNodeApi endpointApi) { + this.endpointApi = endpointApi; + return this; + } + + /** + * Create a BlockNodeServiceEndpoint object from protobuf + * + * @param serviceEndpoint the protobuf object + * @return the new instance of BlockNodeServiceEndpoint + */ + static BlockNodeServiceEndpoint fromProtobuf( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); + + var blockNodeEndpoint = new BlockNodeServiceEndpoint() + .setPort(serviceEndpoint.getPort()) + .setRequiresTls(serviceEndpoint.getRequiresTls()) + .setEndpointApi(BlockNodeApi.valueOf(serviceEndpoint.getBlockNode().getEndpointApi())); + + if (serviceEndpoint.hasIpAddress()) { + blockNodeEndpoint.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); + } + + if (serviceEndpoint.hasDomainName()) { + blockNodeEndpoint.setDomainName(serviceEndpoint.getDomainName()); + } + + return blockNodeEndpoint; + } + + @Override + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { + if (ipAddress == null && domainName == null) { + throw new IllegalArgumentException( + "RegisterServiceEndpoint must define either an ipAddress or domainName"); + } + + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setPort(port) + .setRequiresTls(requiresTls) + .setBlockNode(com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .setEndpointApi(endpointApi.code) + .build()); + + if (ipAddress != null) { + registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(this.ipAddress)); + } + + if (domainName != null) { + registeredServiceEndpoint.setDomainName(this.domainName); + } + + return registeredServiceEndpoint.build(); + } + + @Override + public String toString() { + return toStringHelper() + .add("endpointApi", endpointApi) + .toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java new file mode 100644 index 0000000000..8a0ccb0869 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import java.util.Objects; + +/** + * Represent a Registered Mirror Node + */ +public class MirrorNodeServiceEndpoint extends RegisteredServiceEndpointBase { + /** + * Constructor. + * + */ + public MirrorNodeServiceEndpoint() {} + + /** + * Create a MirrorNodeServiceEndpoint object from protobuf + * + * @param serviceEndpoint the protobuf object + * @return the new instance of MirrorNodeServiceEndpoint + */ + static MirrorNodeServiceEndpoint fromProtobuf( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); + + var mirrorNode = new MirrorNodeServiceEndpoint() + .setPort(serviceEndpoint.getPort()) + .setRequiresTls(serviceEndpoint.getRequiresTls()); + + if (serviceEndpoint.hasIpAddress()) { + mirrorNode.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); + } + + if (serviceEndpoint.hasDomainName()) { + mirrorNode.setDomainName(serviceEndpoint.getDomainName()); + } + + return mirrorNode; + } + + @Override + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setPort(port) + .setRequiresTls(requiresTls); + + if (ipAddress != null) { + registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(ipAddress)); + } + + if (domainName != null) { + registeredServiceEndpoint.setDomainName(domainName); + } + + return registeredServiceEndpoint.build(); + } + + @Override + public String toString() { + return toStringHelper().toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java new file mode 100644 index 0000000000..8f5971e441 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.common.base.MoreObjects; +import com.google.protobuf.InvalidProtocolBufferException; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nonnegative; +import javax.annotation.Nullable; + +/** + * Class representing single registered node in the network state. + * Each registered node in the network state SHALL represent a single + * non-consensus node that is registered on the network. + * Registered node identifiers SHALL only be unique within a single + * realm and shard combination. + */ +public class RegisteredNode { + /** + * A registered node identifier. + */ + @Nonnegative + public final long registeredNodeId; + + /** + * An administrative key controlled by the node operator. + */ + public final Key adminKey; + + /** + * A short description of the node. + */ + public final String description; + + /** + * A list of service endpoints for client calls. + */ + public final List serviceEndpoints; + + /** + * An account identifier. + * This account identifies the entity financially responsible for this + * registered node. + */ + @Nullable + public final AccountId nodeAccount; + + /** + * Constructor. + * + * @param registeredNodeId the registered node identifier. + * @param adminKey the admin key. + * @param description the description of the node. + * @param serviceEndpoint the list of service endpoints. + * @param nodeAccount the account identifier. + */ + RegisteredNode( + long registeredNodeId, + Key adminKey, + String description, + List serviceEndpoint, + @Nullable AccountId nodeAccount) { + this.registeredNodeId = registeredNodeId; + this.adminKey = adminKey; + this.description = description; + this.serviceEndpoints = Collections.unmodifiableList(serviceEndpoint); + this.nodeAccount = nodeAccount; + } + + /** + * Extract the registeredNode from the protobuf. + * + * @param registeredNode the protobuf + * @return {@code this} the contract object + */ + static RegisteredNode fromProtobuf(com.hedera.hashgraph.sdk.proto.RegisteredNode registeredNode) { + Objects.requireNonNull(registeredNode, "registeredNode cannot be null"); + var registerNodeId = registeredNode.getRegisteredNodeId(); + var adminKey = Key.fromProtobufKey(registeredNode.getAdminKey()); + var description = registeredNode.getDescription(); + + var serviceEndpoint = registeredNode.getServiceEndpointList().stream() + .map(s -> RegisteredServiceEndpoint.fromProtobuf(s)) + .toList(); + var nodeAccount = + registeredNode.hasNodeAccount() ? AccountId.fromProtobuf(registeredNode.getNodeAccount()) : null; + + return new RegisteredNode(registerNodeId, adminKey, description, serviceEndpoint, nodeAccount); + } + + /** + * Extract the registeredNode from a byte array. + * + * @param bytes the byte array + * @return {@code RegisteredNode} the extracted registeredNode + * @throws InvalidProtocolBufferException when there is an issue with the protobuf + */ + public static RegisteredNode fromBytes(byte[] bytes) throws InvalidProtocolBufferException { + return fromProtobuf(com.hedera.hashgraph.sdk.proto.RegisteredNode.parseFrom(bytes).toBuilder() + .build()); + } + + /** + * Build the protobuf. + * @return {@code this} the protobuf representation + */ + com.hedera.hashgraph.sdk.proto.RegisteredNode toProtobuf() { + var registeredNode = com.hedera.hashgraph.sdk.proto.RegisteredNode.newBuilder() + .setRegisteredNodeId(registeredNodeId) + .setAdminKey(adminKey.toProtobufKey()) + .setDescription(description); + + if (nodeAccount != null) { + registeredNode.setNodeAccount(nodeAccount.toProtobuf()); + } + + for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { + registeredNode.addServiceEndpoint(serviceEndpoint.toProtobuf()); + } + + return registeredNode.build(); + } + + /** + * Create a byte array representation. + * @return {@code byte[]} the byte array representation + */ + public byte[] toBytes() { + return toProtobuf().toByteArray(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("registeredNodeId", registeredNodeId) + .add("adminKey", adminKey) + .add("description", description) + .add("nodeAccount", nodeAccount) + .add("serviceEndpoints", serviceEndpoints) + .toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java new file mode 100644 index 0000000000..34ee5dd67d --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import java.util.List; + +public class RegisteredNodeAddressBook { + public final List registeredNodes; + + RegisteredNodeAddressBook(List registeredNodes) { + this.registeredNodes = registeredNodes; + } + + static RegisteredNodeAddressBook fromProtobuf(List registeredNodes) { + return new RegisteredNodeAddressBook(registeredNodes.stream() + .map(n -> RegisteredNode.fromProtobuf(n)) + .toList()); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java new file mode 100644 index 0000000000..826fba4418 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.hedera.hashgraph.sdk.proto.AddressBookServiceGrpc; +import com.hedera.hashgraph.sdk.proto.RegisteredNodeCreateTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionResponse; +import io.grpc.MethodDescriptor; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * A transaction to create a new registered node in the network + * address book. + *

+ * This transaction, once complete, SHALL add a new registered node to the + * network state. + * The new registered node SHALL be visible and discoverable upon + * completion of this transaction. + */ +public class RegisteredNodeCreateTransaction extends Transaction { + private Key adminKey; + private String description = ""; + private List serviceEndpoints = new ArrayList<>(); + + @Nullable + private AccountId nodeAccount; + + /** + * Constructor. + */ + public RegisteredNodeCreateTransaction() {} + + /** + * Constructor. + * + * @param txs Compound list of transaction id's list of (AccountId, Transaction) records + * @throws InvalidProtocolBufferException when there is an issue with the protobuf + */ + RegisteredNodeCreateTransaction( + LinkedHashMap> txs) + throws InvalidProtocolBufferException { + super(txs); + initFromTransactionBody(); + } + + /** + * Constructor. + * + * @param txBody protobuf TransactionBody + */ + RegisteredNodeCreateTransaction(com.hedera.hashgraph.sdk.proto.TransactionBody txBody) { + super(txBody); + initFromTransactionBody(); + } + + /** + * Get administrative key controlled by the node operator. + * @return {@code Key} the admin key + */ + public Key getAdminKey() { + return adminKey; + } + + /** + * Set administrative key controlled by the node operator. + *

+ * This key MUST sign this transaction.
+ * This key MUST sign each transaction to update this node.
+ * This field MUST contain a valid `Key` value.
+ * This field is REQUIRED. + * + * @param adminKey the admin key for the registered node. + * @return {@code this} + */ + public RegisteredNodeCreateTransaction setAdminKey(Key adminKey) { + this.requireNotFrozen(); + this.adminKey = adminKey; + return this; + } + + /** + * Get short description of the node. + * @return {@code String} the node's description + */ + public String getDescription() { + return description; + } + + /** + * A short description of the node. + *

+ * This value, if set, MUST NOT exceed 100 bytes when encoded as UTF-8.
+ * This field is OPTIONAL. + * + * @param description The string to be set as description for the node. + * @return {@code this} + */ + public RegisteredNodeCreateTransaction setDescription(@Nullable String description) { + this.requireNotFrozen(); + if (description == null) { + this.description = ""; + return this; + } + + if (description.getBytes(StandardCharsets.UTF_8).length > 100) { + throw new IllegalArgumentException("description must not exceed 100 bytes when UTF-8 encoded"); + } + this.description = description; + return this; + } + + /** + * Get list of service endpoints for client calls. + * @return {@code List} list of service endpoints + */ + public List getServiceEndpoints() { + return serviceEndpoints; + } + + /** + * A list of service endpoints for client calls. + *

+ * These endpoints SHALL represent the published endpoints to which + * clients may submit requests.
+ * Endpoints in this list MAY supply either IP address or FQDN, but MUST + * NOT supply both values for the same endpoint.
+ * Multiple endpoints in this list MAY resolve to the same interface.
+ * One Registered Node MAY expose endpoints for multiple service types.
+ * This list MUST NOT be empty.
+ * This list MUST NOT contain more than `50` entries. + * + * @param serviceEndpoints the list of service endpoints for the client calls. + * @return {@code this} + */ + public RegisteredNodeCreateTransaction setServiceEndpoints(List serviceEndpoints) { + this.requireNotFrozen(); + Objects.requireNonNull(serviceEndpoints, "serviceEndpoints cannot be null"); + + if (serviceEndpoints.isEmpty()) { + throw new IllegalArgumentException("serviceEndpoints list must not be empty."); + } + if (serviceEndpoints.size() > 50) { + throw new IllegalArgumentException("serviceEndpoints must not contain more than 50 entries"); + } + + for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { + RegisteredServiceEndpoint.validateNoIpAndDomain(serviceEndpoint); + } + + this.serviceEndpoints = serviceEndpoints; + return this; + } + + /** + * Add a service endpoint for the client calls. + * @param serviceEndpoint the service endpoint + * @return {@code this} + */ + public RegisteredNodeCreateTransaction addServiceEndpoint(RegisteredServiceEndpoint serviceEndpoint) { + requireNotFrozen(); + if (serviceEndpoints.size() >= 50) { + throw new IllegalArgumentException("serviceEndpoints must not contain more than 50 entries"); + } + + RegisteredServiceEndpoint.validateNoIpAndDomain(serviceEndpoint); + serviceEndpoints.add(serviceEndpoint); + return this; + } + + /** Get account identifier for the registered node. + * @return {@code AccountId} the account identifier if set or null + */ + public @Nullable AccountId getNodeAccount() { + return nodeAccount; + } + + /** + * Set account identifier.
+ * This account identifies the entity financially responsible for this + * registered node. + *

+ * This field is OPTIONAL.
+ * Individual node operators SHALL have full authority to set, change, or + * remove their node account ID.
+ * If set and the node account ID does not resolve to an existing and active + * account, then the transaction SHALL fail.
+ * Node operators SHOULD ensure that the node account is kept up to date + * and always refers to a valid and active `Account`.
+ * The owner of the node account MAY be different from the owner of the + * registered node. + * + * @param nodeAccount the account identifier. + * @return {@code this} + */ + public RegisteredNodeCreateTransaction setNodeAccount(@Nullable AccountId nodeAccount) { + this.requireNotFrozen(); + this.nodeAccount = nodeAccount; + return this; + } + + /** + * Build the transaction body. + * @return {@link com.hedera.hashgraph.sdk.proto.RegisteredNodeCreateTransactionBody} + */ + RegisteredNodeCreateTransactionBody.Builder build() { + var builder = RegisteredNodeCreateTransactionBody.newBuilder().setDescription(description); + + if (adminKey != null) { + builder.setAdminKey(adminKey.toProtobufKey()); + } + + if (nodeAccount != null) { + builder.setNodeAccount(nodeAccount.toProtobuf()); + } + + for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { + builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); + } + + return builder; + } + + /** + * Initialize from the transaction body. + */ + void initFromTransactionBody() { + var body = sourceTransactionBody.getRegisteredNodeCreate(); + + if (body.hasAdminKey()) { + adminKey = Key.fromProtobufKey(body.getAdminKey()); + } + + description = body.getDescription(); + + if (body.hasNodeAccount()) { + nodeAccount = AccountId.fromProtobuf(body.getNodeAccount()); + } + + serviceEndpoints.clear(); + for (com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint : body.getServiceEndpointList()) { + serviceEndpoints.add(RegisteredServiceEndpoint.fromProtobuf(serviceEndpoint)); + } + } + + @Override + void validateChecksums(Client client) throws BadEntityIdException { + if (nodeAccount != null) { + nodeAccount.validateChecksum(client); + } + } + + @Override + MethodDescriptor getMethodDescriptor() { + return AddressBookServiceGrpc.getCreateRegisteredNodeMethod(); + } + + @Override + void onFreeze(TransactionBody.Builder bodyBuilder) { + bodyBuilder.setRegisteredNodeCreate(build()); + } + + @Override + void onScheduled(SchedulableTransactionBody.Builder scheduled) { + scheduled.setRegisteredNodeCreate(build()); + } + + /** + * Freeze this transaction with the given client. + * + * @param client the client to freeze with + * @return this transaction + * @throws IllegalStateException if adminKey is not set + */ + @Override + public RegisteredNodeCreateTransaction freezeWith(@Nullable Client client) { + if (adminKey == null) { + throw new IllegalStateException( + "RegisteredNodeCreateTransaction: 'adminKey' must be explicitly set before calling freeze()."); + } + return super.freezeWith(client); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransaction.java new file mode 100644 index 0000000000..1a0a03d562 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransaction.java @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.hedera.hashgraph.sdk.proto.AddressBookServiceGrpc; +import com.hedera.hashgraph.sdk.proto.RegisteredNodeDeleteTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionResponse; +import io.grpc.MethodDescriptor; +import java.util.LinkedHashMap; +import javax.annotation.Nullable; + +/** + * A transaction to delete a registered node from the network + * address book. + *

+ * This transaction, once complete, SHALL remove the identified registered + * node from the network state. + * This transaction MUST be signed by the existing entry `admin_key` or + * authorized by the Hiero network governance structure. + */ +public class RegisteredNodeDeleteTransaction extends Transaction { + private Long registeredNodeId; + + /** + * Constructor. + */ + public RegisteredNodeDeleteTransaction() {} + + /** + * Constructor. + * + * @param txs Compound list of transaction id's list of (AccountId, Transaction) records + * @throws InvalidProtocolBufferException when there is an issue with the protobuf + */ + RegisteredNodeDeleteTransaction( + LinkedHashMap> txs) + throws InvalidProtocolBufferException { + super(txs); + initFromTransactionBody(); + } + + /** + * Constructor. + * + * @param txBody protobuf TransactionBody + */ + RegisteredNodeDeleteTransaction(TransactionBody txBody) { + super(txBody); + initFromTransactionBody(); + } + + /** + * Get registered node identifier in the network state. + * @return the registered node id + * @throws IllegalStateException when register node id is not being set + */ + public long getRegisteredNodeId() { + if (registeredNodeId == null) { + throw new IllegalStateException("RegisteredNodeDeleteTransaction: 'registeredNodeId' has not been set"); + } + + return registeredNodeId; + } + + /** + * SET registered node identifier in the network state. + *

+ * The node identified MUST exist in the registered address book.
+ * The node identified MUST NOT be deleted.
+ * This value is REQUIRED. + * + * @param registeredNodeId the registered node identifier. + * @return {@code this} + * @throws IllegalArgumentException if registeredNodeId is negative + */ + public RegisteredNodeDeleteTransaction setRegisteredNodeId(long registeredNodeId) { + this.requireNotFrozen(); + if (registeredNodeId < 0) { + throw new IllegalArgumentException( + "RegisteredNodeDeleteTransaction: 'registeredNodeId' must be non-negative"); + } + this.registeredNodeId = registeredNodeId; + return this; + } + + /** + * Build the transaction body. + * @return {@link com.hedera.hashgraph.sdk.proto.RegisteredNodeDeleteTransactionBody} + */ + RegisteredNodeDeleteTransactionBody.Builder build() { + var builder = RegisteredNodeDeleteTransactionBody.newBuilder(); + if (registeredNodeId != null) { + builder.setRegisteredNodeId(registeredNodeId); + } + return builder; + } + + /** + * Initialize from the transaction body. + */ + void initFromTransactionBody() { + var body = sourceTransactionBody.getRegisteredNodeDelete(); + registeredNodeId = body.getRegisteredNodeId(); + } + + @Override + void validateChecksums(Client client) throws BadEntityIdException { + // no-op + } + + @Override + void onFreeze(TransactionBody.Builder bodyBuilder) { + bodyBuilder.setRegisteredNodeDelete(build()); + } + + @Override + void onScheduled(SchedulableTransactionBody.Builder scheduled) { + scheduled.setRegisteredNodeDelete(build()); + } + + @Override + MethodDescriptor getMethodDescriptor() { + return AddressBookServiceGrpc.getDeleteRegisteredNodeMethod(); + } + + /** + * Freeze this transaction with the given client. + * + * @param client the client to freeze with + * @return this transaction + * @throws IllegalStateException if registeredNodeId is not set + */ + @Override + public RegisteredNodeDeleteTransaction freezeWith(@Nullable Client client) { + if (registeredNodeId == null) { + throw new IllegalStateException( + "RegisteredNodeDeleteTransaction: 'registeredNodeId' must be explicitly set before calling freeze()."); + } + return super.freezeWith(client); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java new file mode 100644 index 0000000000..664b9dace4 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.StringValue; +import com.hedera.hashgraph.sdk.proto.AddressBookServiceGrpc; +import com.hedera.hashgraph.sdk.proto.RegisteredNodeUpdateTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionResponse; +import io.grpc.MethodDescriptor; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * A transaction to update an existing registered node in the network + * address book. + *

+ * This transaction, once complete, SHALL modify the identified registered + * node state as requested. + */ +public class RegisteredNodeUpdateTransaction extends Transaction { + private Long registeredNodeId; + + @Nullable + private Key adminKey; + + @Nullable + private String description; + + private List serviceEndpoints = new ArrayList<>(); + + @Nullable + private AccountId nodeAccount; + + /** + * Constructor. + */ + public RegisteredNodeUpdateTransaction() {} + + /** + * Constructor. + * + * @param txs Compound list of transaction id's list of (AccountId, Transaction) records + * @throws InvalidProtocolBufferException when there is an issue with the protobuf + */ + RegisteredNodeUpdateTransaction( + LinkedHashMap> txs) + throws InvalidProtocolBufferException { + super(txs); + initFromTransactionBody(); + } + + /** + * Constructor. + * + * @param txBody protobuf TransactionBody + */ + RegisteredNodeUpdateTransaction(com.hedera.hashgraph.sdk.proto.TransactionBody txBody) { + super(txBody); + initFromTransactionBody(); + } + + /** + * Get registered node identifier in the network state. + * @return the registered node id + * @throws IllegalStateException when register node id is not being set + */ + public long getRegisteredNodeId() { + if (registeredNodeId == null) { + throw new IllegalStateException("RegisteredNodeUpdateTransaction: 'registeredNodeId' has not been set"); + } + + return registeredNodeId; + } + + /** + * Set registered node identifier in the network state. + *

+ * The node identified MUST exist in the registered address book.
+ * The node identified MUST NOT be deleted.
+ * This value is REQUIRED. + * + * @param registeredNodeId the registered node identifier. + * @return {@code this} + * @throws IllegalArgumentException if registeredNodeId is negative + */ + public RegisteredNodeUpdateTransaction setRegisteredNodeId(long registeredNodeId) { + this.requireNotFrozen(); + if (registeredNodeId < 0) { + throw new IllegalArgumentException( + "RegisteredNodeDeleteTransaction: 'registeredNodeId' must be non-negative"); + } + this.registeredNodeId = registeredNodeId; + return this; + } + + /** + * Get administrative key controlled by the node operator. + * @return {@code Key} the admin key + */ + public @Nullable Key getAdminKey() { + return adminKey; + } + + /** + * Set administrative key controlled by the node operator. + *

+ * This key MUST sign this transaction.
+ * This key MUST sign each transaction to update this node.
+ * This field MUST contain a valid `Key` value.
+ * This field is REQUIRED. + * + * @param adminKey the admin key for the registered node. + * @return {@code this} + */ + public RegisteredNodeUpdateTransaction setAdminKey(@Nullable Key adminKey) { + this.requireNotFrozen(); + this.adminKey = adminKey; + return this; + } + + /** + * Get short description of the node. + * @return {@code String} the node's description + */ + public @Nullable String getDescription() { + return description; + } + + /** + * A short description of the node. + *

+ * This value, if set, MUST NOT exceed 100 bytes when encoded as UTF-8.
+ * This field is OPTIONAL. + * + * @param description The string to be set as description for the node. + * @return {@code this} + */ + public RegisteredNodeUpdateTransaction setDescription(@Nullable String description) { + this.requireNotFrozen(); + if (description != null && description.getBytes(StandardCharsets.UTF_8).length > 100) { + throw new IllegalArgumentException("description must not exceed 100 bytes when UTF-8 encoded"); + } + this.description = description; + return this; + } + + /** + * Get list of service endpoints for client calls. + * @return {@code List} list of service endpoints + */ + public List getServiceEndpoints() { + return serviceEndpoints; + } + + /** + * A list of service endpoints for client calls. + *

+ * These endpoints SHALL represent the published endpoints to which + * clients may submit requests.
+ * Endpoints in this list MAY supply either IP address or FQDN, but MUST + * NOT supply both values for the same endpoint.
+ * Multiple endpoints in this list MAY resolve to the same interface.
+ * One Registered Node MAY expose endpoints for multiple service types.
+ * This list MUST NOT be empty.
+ * This list MUST NOT contain more than `50` entries. + * + * @param serviceEndpoints the list of service endpoints for the client calls. + * @return {@code this} + */ + public RegisteredNodeUpdateTransaction setServiceEndpoints(List serviceEndpoints) { + this.requireNotFrozen(); + Objects.requireNonNull(serviceEndpoints, "serviceEndpoints cannot be null"); + + if (serviceEndpoints.isEmpty()) { + throw new IllegalArgumentException("ServiceEndpoints list must not be empty."); + } + if (serviceEndpoints.size() > 50) { + throw new IllegalArgumentException("ServiceEndpoints list must not contain more than 50 entries."); + } + + for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { + RegisteredServiceEndpoint.validateNoIpAndDomain(serviceEndpoint); + } + + this.serviceEndpoints = serviceEndpoints; + return this; + } + + /** + * Add a service endpoint for the client calls. + * @param serviceEndpoint the service endpoint + * @return {@code this} + */ + public RegisteredNodeUpdateTransaction addServiceEndpoint(RegisteredServiceEndpoint serviceEndpoint) { + requireNotFrozen(); + if (serviceEndpoints.size() >= 50) { + throw new IllegalArgumentException("serviceEndpoints must not contain more than 50 entries"); + } + + RegisteredServiceEndpoint.validateNoIpAndDomain(serviceEndpoint); + serviceEndpoints.add(serviceEndpoint); + return this; + } + + /** Get account identifier for the registered node. + * @return {@code AccountId} the account identifier if set or null + */ + public @Nullable AccountId getNodeAccount() { + return nodeAccount; + } + + /** + * Set account identifier.
+ * This account identifies the entity financially responsible for this + * registered node. + *

+ * This field is OPTIONAL.
+ * Individual node operators SHALL have full authority to set, change, or + * remove their node account ID.
+ * If set and the node account ID does not resolve to an existing and active + * account, then the transaction SHALL fail.
+ * Node operators SHOULD ensure that the node account is kept up to date + * and always refers to a valid and active `Account`.
+ * The owner of the node account MAY be different from the owner of the + * registered node. + * + * @param nodeAccount the account identifier. + * @return {@code this} + */ + public RegisteredNodeUpdateTransaction setNodeAccount(AccountId nodeAccount) { + this.requireNotFrozen(); + this.nodeAccount = nodeAccount; + return this; + } + + /** + * Build the transaction body. + * @return {@link com.hedera.hashgraph.sdk.proto.RegisteredNodeUpdateTransactionBody} + */ + RegisteredNodeUpdateTransactionBody.Builder build() { + var builder = RegisteredNodeUpdateTransactionBody.newBuilder(); + + if (registeredNodeId != null) { + builder.setRegisteredNodeId(registeredNodeId); + } + + if (adminKey != null) { + builder.setAdminKey(adminKey.toProtobufKey()); + } + + if (description != null) { + builder.setDescription(StringValue.of(description)); + } + + if (nodeAccount != null) { + builder.setNodeAccount(nodeAccount.toProtobuf()); + } + + for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { + builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); + } + + return builder; + } + + /** + * Initialize from the transaction body. + */ + void initFromTransactionBody() { + var body = sourceTransactionBody.getRegisteredNodeUpdate(); + registeredNodeId = body.getRegisteredNodeId(); + + if (body.hasAdminKey()) { + adminKey = Key.fromProtobufKey(body.getAdminKey()); + } + + if (body.hasDescription()) { + description = body.getDescription().getValue(); + } + if (body.hasNodeAccount()) { + nodeAccount = AccountId.fromProtobuf(body.getNodeAccount()); + } + + serviceEndpoints.clear(); + for (com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint : body.getServiceEndpointList()) { + serviceEndpoints.add(RegisteredServiceEndpoint.fromProtobuf(serviceEndpoint)); + } + } + + @Override + void onFreeze(TransactionBody.Builder bodyBuilder) { + bodyBuilder.setRegisteredNodeUpdate(build()); + } + + @Override + void onScheduled(SchedulableTransactionBody.Builder scheduled) { + scheduled.setRegisteredNodeUpdate(build()); + } + + @Override + void validateChecksums(Client client) throws BadEntityIdException { + if (nodeAccount != null) { + nodeAccount.validateChecksum(client); + } + } + + @Override + MethodDescriptor getMethodDescriptor() { + return AddressBookServiceGrpc.getUpdateRegisteredNodeMethod(); + } + + /** + * Freeze this transaction with the given client. + * + * @param client the client to freeze with + * @return this transaction + * @throws IllegalStateException if registeredNodeId is not set + */ + @Override + public RegisteredNodeUpdateTransaction freezeWith(@Nullable Client client) { + if (registeredNodeId == null) { + throw new IllegalStateException( + "RegisteredNodeUpdateTransaction: 'registeredNodeId' must be explicitly set before calling freeze()."); + } + return super.freezeWith(client); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java new file mode 100644 index 0000000000..2b98a07821 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.common.base.MoreObjects; + +import java.util.Arrays; +import java.util.Objects; +import javax.annotation.Nullable; + +/** + * Abstract class representing the service endpoint published by a registered node. + */ +public abstract class RegisteredServiceEndpoint { + @Nullable + protected byte[] ipAddress; + + @Nullable + protected String domainName; + + protected int port; + protected boolean requiresTls; + + RegisteredServiceEndpoint() {} + + /** + * Get the IP address of the endpoint. + * + * @return the IP address, or null if using a domain name + */ + @Nullable + public byte[] getIpAddress() { + return ipAddress != null ? ipAddress.clone() : null; + } + + /** + * Get the domain name of the endpoint. + * + * @return the domain name, or null if using an IP address + */ + @Nullable + public String getDomainName() { + return domainName; + } + + /** + * Get the port used by this endpoint. + * + * @return the port number + */ + public int getPort() { + return port; + } + + /** + * Check whether TLS is required for this endpoint. + * + * @return true if TLS is required + */ + public boolean isRequiresTls() { + return requiresTls; + } + + /** + * Validate that the endpoint does not contain both an IP address and a domain name. + * + * @param serviceEndpoint the endpoint to validate + * @throws IllegalArgumentException if both ipAddressV4 and domainName are present + */ + public static void validateNoIpAndDomain(RegisteredServiceEndpoint serviceEndpoint) { + if (serviceEndpoint == null) { + return; + } + if (serviceEndpoint.getIpAddress() != null) { + var dn = serviceEndpoint.getDomainName(); + if (dn != null && !dn.isEmpty()) { + throw new IllegalArgumentException("Service endpoint must not contain both ipAddressV4 and domainName"); + } + } + } + + static RegisteredServiceEndpoint fromProtobuf( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint cannot be null"); + + return switch (serviceEndpoint.getEndpointTypeCase()) { + case BLOCK_NODE -> BlockNodeServiceEndpoint.fromProtobuf(serviceEndpoint); + case MIRROR_NODE -> MirrorNodeServiceEndpoint.fromProtobuf(serviceEndpoint); + case RPC_RELAY -> RpcRelayServiceEndpoint.fromProtobuf(serviceEndpoint); + default -> throw new IllegalArgumentException("Unable to decode registered service endpoint"); + }; + } + + /** + * Build the protobuf. + * + * @return the protobuf representation + */ + abstract com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf(); + + /** + * Serializes the class to ToStringHelper + * + * @return the {@link com.google.common.base.MoreObjects.ToStringHelper} + */ + protected MoreObjects.ToStringHelper toStringHelper() { + return MoreObjects.toStringHelper(this) + .add("ipAddress", ipAddress) + .add("domainName", domainName) + .add("port", port) + .add("requiresTls", requiresTls); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpointBase.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpointBase.java new file mode 100644 index 0000000000..1a1eadb21f --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpointBase.java @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import javax.annotation.Nullable; + +abstract class RegisteredServiceEndpointBase> + extends RegisteredServiceEndpoint { + /** + * Set the IP address of the endpoint. + * + * @param ipAddress the IPv4 or IPv6 address + * @return this endpoint + */ + public T setIpAddress(@Nullable byte[] ipAddress) { + this.ipAddress = ipAddress; + // noinspection unchecked + return (T) this; + } + + /** + * Set the domain name of the endpoint. + * + * @param domainName the fully qualified domain name + * @return this endpoint + */ + public T setDomainName(@Nullable String domainName) { + this.domainName = domainName; + // noinspection unchecked + return (T) this; + } + + /** + * Set the port used by this endpoint. + * + * @param port the port number + * @return this endpoint + * @throws IllegalArgumentException if the port is outside the valid range + */ + public T setPort(int port) { + if (port < 0 || port > 65535) { + throw new IllegalArgumentException("Port must be in range [0, 65535]"); + } + this.port = port; + // noinspection unchecked + return (T) this; + } + + /** + * Set whether TLS is required for this endpoint. + * + * @param requiresTls true if TLS is required + * @return this endpoint + */ + public T setRequiresTls(boolean requiresTls) { + this.requiresTls = requiresTls; + // noinspection unchecked + return (T) this; + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java new file mode 100644 index 0000000000..ec329ed122 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import java.util.Objects; + +/** + * Represent a Registered Rpc Relay + */ +public class RpcRelayServiceEndpoint extends RegisteredServiceEndpointBase { + /** + * Constructor. + * + */ + public RpcRelayServiceEndpoint() {} + + /** + * Create a RpcRelayServiceEndpoint object from protobuf + * + * @param serviceEndpoint the protobuf object + * @return the new instance of RpcRelayServiceEndpoint + */ + static RpcRelayServiceEndpoint fromProtobuf( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); + var rpcRelay = new RpcRelayServiceEndpoint() + .setPort(serviceEndpoint.getPort()) + .setRequiresTls(serviceEndpoint.getRequiresTls()); + + if (serviceEndpoint.hasIpAddress()) { + rpcRelay.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); + } + + if (serviceEndpoint.hasDomainName()) { + rpcRelay.setDomainName(serviceEndpoint.getDomainName()); + } + + return rpcRelay; + } + + @Override + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setPort(port) + .setRequiresTls(requiresTls); + + if (ipAddress != null) { + registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(ipAddress)); + } + + if (domainName != null) { + registeredServiceEndpoint.setDomainName(domainName); + } + + return registeredServiceEndpoint.build(); + } + + @Override + public String toString() { + return toStringHelper().toString(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java index e09d638435..bcdca0098d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java @@ -439,6 +439,9 @@ private static Transaction createTransactionFromDataCase( case CRYPTODELETEALLOWANCE -> new AccountAllowanceDeleteTransaction(txs); case ATOMIC_BATCH -> new BatchTransaction(txs); case HOOK_STORE -> new HookStoreTransaction(txs); + case REGISTEREDNODECREATE -> new RegisteredNodeCreateTransaction(txs); + case REGISTEREDNODEUPDATE -> new RegisteredNodeUpdateTransaction(txs); + case REGISTEREDNODEDELETE -> new RegisteredNodeDeleteTransaction(txs); default -> throw new IllegalArgumentException("parsed transaction body has no data"); }; } @@ -594,6 +597,15 @@ public static Transaction fromScheduledTransaction( case SCHEDULEDELETE -> new ScheduleDeleteTransaction( body.setScheduleDelete(scheduled.getScheduleDelete()).build()); + case REGISTEREDNODECREATE -> + new RegisteredNodeCreateTransaction(body.setRegisteredNodeCreate(scheduled.getRegisteredNodeCreate()) + .build()); + case REGISTEREDNODEUPDATE -> + new RegisteredNodeUpdateTransaction(body.setRegisteredNodeUpdate(scheduled.getRegisteredNodeUpdate()) + .build()); + case REGISTEREDNODEDELETE -> + new RegisteredNodeDeleteTransaction(body.setRegisteredNodeDelete(scheduled.getRegisteredNodeDelete()) + .build()); default -> throw new IllegalStateException("schedulable transaction did not have a transaction set"); }; } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionReceipt.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionReceipt.java index 016ed95ab1..6a7ee0d28a 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionReceipt.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/TransactionReceipt.java @@ -118,6 +118,16 @@ public final class TransactionReceipt { */ public final long nodeId; + /** + * The identifier of a newly created RegisteredNode. + *

+ * This value SHALL be set following a `createRegisteredNode` + * transaction.
+ * This value SHALL NOT be set following any other transaction.
+ * This value SHALL be unique within a given network. + */ + public final long registeredNodeId; + /** * The receipts of processing all transactions with the given id, in consensus time order. */ @@ -146,6 +156,7 @@ public final class TransactionReceipt { @Nullable TransactionId scheduledTransactionId, List serials, long nodeId, + long registeredNodeId, List duplicates, List children) { this.transactionId = transactionId; @@ -164,6 +175,7 @@ public final class TransactionReceipt { this.scheduledTransactionId = scheduledTransactionId; this.serials = serials; this.nodeId = nodeId; + this.registeredNodeId = registeredNodeId; this.duplicates = duplicates; this.children = children; } @@ -218,6 +230,8 @@ static TransactionReceipt fromProtobuf( var nodeId = transactionReceipt.getNodeId(); + var registeredNodeId = transactionReceipt.getRegisteredNodeId(); + return new TransactionReceipt( transactionId, status, @@ -235,6 +249,7 @@ static TransactionReceipt fromProtobuf( scheduledTransactionId, serials, nodeId, + registeredNodeId, duplicates, children); } @@ -345,6 +360,8 @@ com.hedera.hashgraph.sdk.proto.TransactionReceipt toProtobuf() { transactionReceiptBuilder.setNodeId(nodeId); + transactionReceiptBuilder.setRegisteredNodeId(registeredNodeId); + return transactionReceiptBuilder.build(); } @@ -367,6 +384,7 @@ public String toString() { .add("scheduledTransactionId", scheduledTransactionId) .add("serials", serials) .add("nodeId", nodeId) + .add("registeredNodeId", registeredNodeId) .add("duplicates", duplicates) .add("children", children) .toString(); diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeApiTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeApiTest.java new file mode 100644 index 0000000000..cecff9f9b6 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeApiTest.java @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.BlockNodeEndpoint; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BlockNodeApiTest { + @Test + @DisplayName("BlockNodeApi can be constructed for BlockNodeEndpoint API") + void blockNodeApiCodeToBlockNodeApi() { + for (BlockNodeEndpoint.BlockNodeApi code : BlockNodeEndpoint.BlockNodeApi.values()) { + if (code == BlockNodeEndpoint.BlockNodeApi.UNRECOGNIZED) { + continue; + } + + BlockNodeApi blockNodeApi = BlockNodeApi.valueOf(code); + assertThat(code.getNumber()).isEqualTo(blockNodeApi.code.getNumber()); + } + } + + @Test + @DisplayName("BlockNodeApi throws on Unrecognized") + void blockNodeApiUnrecognized() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> BlockNodeApi.valueOf(BlockNodeEndpoint.BlockNodeApi.UNRECOGNIZED)) + .withMessage("Unhandled BlockNodeApi code"); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java new file mode 100644 index 0000000000..830a7d6b60 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java @@ -0,0 +1,115 @@ +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class BlockNodeServiceEndpointTest { + private final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; + private final String TEST_DOMAIN_NAME = "test.block.com"; + private final int TEST_PORT = 443; + private final boolean TEST_REQUIRES_TLS = true; + private final BlockNodeApi TEST_BLOCK_API = BlockNodeApi.STATUS; + + private final RegisteredServiceEndpoint blockNodeEndpointWithDomain = RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setBlockNode( + RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .setEndpointApi(TEST_BLOCK_API.code) + ) + .build(); + + private final RegisteredServiceEndpoint blockNodeEndpointWithIp = RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setBlockNode( + RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .setEndpointApi(RegisteredServiceEndpoint.BlockNodeEndpoint.BlockNodeApi.STATUS) + ) + .build(); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void fromProtobufWithDomain() { + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithDomain() { + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void fromProtobufWithIp() { + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithIp() { + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void setIpAddress() { + var endpoint = new BlockNodeServiceEndpoint().setIpAddress(TEST_IP_ADDRESS); + assertThat(endpoint.getIpAddress()).isEqualTo(TEST_IP_ADDRESS); + } + + @Test + void setDomainName() { + var endpoint = new BlockNodeServiceEndpoint().setDomainName(TEST_DOMAIN_NAME); + assertThat(endpoint.getDomainName()).isEqualTo(TEST_DOMAIN_NAME); + } + + @Test + void setPort() { + var endpoint = new BlockNodeServiceEndpoint().setPort(TEST_PORT); + assertThat(endpoint.getPort()).isEqualTo(TEST_PORT); + } + + @Test + void setPortThrowsOnNegative() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setPortThrowsOnGreaterThan65535() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(65536)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setRequiresTls() { + var endpoint = new BlockNodeServiceEndpoint().setRequiresTls(TEST_REQUIRES_TLS); + assertThat(endpoint.isRequiresTls()).isEqualTo(TEST_REQUIRES_TLS); + } + + @Test + void setEndpointApi() { + var endpoint = new BlockNodeServiceEndpoint().setEndpointApi(TEST_BLOCK_API); + assertThat(endpoint.getEndpointApi()).isEqualTo(TEST_BLOCK_API); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap new file mode 100644 index 0000000000..9f9127bf90 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap @@ -0,0 +1,18 @@ +com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.fromProtobufWithDomain=[ + "BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}" +] + + +com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.fromProtobufWithIp=[ + "BlockNodeServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=443, requiresTls=true, endpointApi=STATUS}" +] + + +com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.toProtobufWithDomain=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@855c97bd\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\ndomain_name: \"test.block.com\"\nport: 443\nrequires_tls: true" +] + + +com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.toProtobufWithIp=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@86e941f\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java new file mode 100644 index 0000000000..0d48552d80 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java @@ -0,0 +1,102 @@ +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class MirrorNodeServiceEndpointTest { + private final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; + private final String TEST_DOMAIN_NAME = "test.mirror.com"; + private final int TEST_PORT = 443; + private final boolean TEST_REQUIRES_TLS = true; + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithDomain = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) + .build(); + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithIp = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) + .build(); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void fromProtobufWithDomain() { + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithDomain() { + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void fromProtobufWithIp() { + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithIp() { + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void setIpAddress() { + var endpoint = new BlockNodeServiceEndpoint().setIpAddress(TEST_IP_ADDRESS); + assertThat(endpoint.getIpAddress()).isEqualTo(TEST_IP_ADDRESS); + } + + @Test + void setDomainName() { + var endpoint = new BlockNodeServiceEndpoint().setDomainName(TEST_DOMAIN_NAME); + assertThat(endpoint.getDomainName()).isEqualTo(TEST_DOMAIN_NAME); + } + + @Test + void setPort() { + var endpoint = new BlockNodeServiceEndpoint().setPort(TEST_PORT); + assertThat(endpoint.getPort()).isEqualTo(TEST_PORT); + } + + @Test + void setPortThrowsOnNegative() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setPortThrowsOnGreaterThan65535() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(65536)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setRequiresTls() { + var endpoint = new BlockNodeServiceEndpoint().setRequiresTls(TEST_REQUIRES_TLS); + assertThat(endpoint.isRequiresTls()).isEqualTo(TEST_REQUIRES_TLS); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap new file mode 100644 index 0000000000..e4018ffc07 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap @@ -0,0 +1,18 @@ +com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.fromProtobufWithDomain=[ + "MirrorNodeServiceEndpoint{ipAddress=null, domainName=test.mirror.com, port=443, requiresTls=true}" +] + + +com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.fromProtobufWithIp=[ + "MirrorNodeServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=443, requiresTls=true}" +] + + +com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.toProtobufWithDomain=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@14147303\ndomain_name: \"test.mirror.com\"\nport: 443\nrequires_tls: true" +] + + +com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.toProtobufWithIp=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@26cccebb\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java new file mode 100644 index 0000000000..388eac13db --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.hedera.hashgraph.sdk.proto.RegisteredNodeCreateTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import io.github.jsonSnapshot.SnapshotMatcher; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeCreateTransactionTest { + private static final PrivateKey TEST_PRIVATE_KEY = PrivateKey.fromString( + "302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"); + + private static final AccountId TEST_ACCOUNT_ID = AccountId.fromString("0.6.9"); + + private static final String TEST_DESCRIPTION = "Test description"; + + private static final List TEST_SERVICE_ENDPOINT = + List.of(spawnTestEndpoint((byte) 0), spawnTestEndpoint((byte) 1), spawnTestEndpoint((byte) 2)); + + private static final PublicKey TEST_ADMIN_KEY = PrivateKey.fromString( + "302e020100300506032b65700422042062c4b69e9f45a554e5424fb5a6fe5e6ac1f19ead31dc7718c2d980fd1f998d4b") + .getPublicKey(); + + final Instant TEST_VALID_START = Instant.ofEpochSecond(1554158542); + + static RegisteredServiceEndpoint spawnTestEndpoint(byte offset) { + return new BlockNodeServiceEndpoint() + .setDomainName("example.block.com") + .setPort(443 + offset) + .setRequiresTls(true) + .setEndpointApi(BlockNodeApi.STATUS); + } + + RegisteredNodeCreateTransaction spawnTestTransaction() { + return new RegisteredNodeCreateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006"))) + .setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), TEST_VALID_START)) + .setAdminKey(TEST_ADMIN_KEY) + .setDescription(TEST_DESCRIPTION) + .setServiceEndpoints(TEST_SERVICE_ENDPOINT) + .setNodeAccount(TEST_ACCOUNT_ID) + .setMaxTransactionFee(new Hbar(1)) + .freeze() + .sign(TEST_PRIVATE_KEY); + } + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void shouldSerialize() { + SnapshotMatcher.expect(spawnTestTransaction().toString()).toMatchSnapshot(); + } + + @Test + void shouldBytes() throws Exception { + var tx1 = spawnTestTransaction(); + var tx2 = RegisteredNodeCreateTransaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void shouldBytesNoSetters() throws Exception { + var tx1 = new RegisteredNodeCreateTransaction(); + var tx2 = Transaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void fromScheduledTransaction() { + var transactionBody = SchedulableTransactionBody.newBuilder() + .setRegisteredNodeCreate( + RegisteredNodeCreateTransactionBody.newBuilder().build()) + .build(); + + var tx = Transaction.fromScheduledTransaction(transactionBody); + + assertThat(tx).isInstanceOf(RegisteredNodeCreateTransaction.class); + } + + @Test + void setAdminKey() { + var tx = new RegisteredNodeCreateTransaction().setAdminKey(TEST_ADMIN_KEY); + assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); + } + + @Test + void setAdminKeyFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setAdminKey(TEST_ADMIN_KEY)); + } + + @Test + void setDescription() { + var tx = new RegisteredNodeCreateTransaction().setDescription(TEST_DESCRIPTION); + assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); + } + + @Test + void setDescriptionFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setDescription(TEST_DESCRIPTION)); + } + + @Test + void setDescriptionRejectsOver100Utf8Bytes() { + var tx = new RegisteredNodeCreateTransaction(); + String tooLong = "a".repeat(101); + assertThrows(IllegalArgumentException.class, () -> tx.setDescription(tooLong)); + } + + @Test + void setDescriptionAcceptsExactly100Utf8Bytes() { + var tx = new RegisteredNodeCreateTransaction(); + String exact = "a".repeat(100); + tx.setDescription(exact); + assertThat(tx.getDescription()).isEqualTo(exact); + } + + @Test + void setServiceEndpoint() { + var tx = new RegisteredNodeCreateTransaction().setServiceEndpoints(TEST_SERVICE_ENDPOINT); + assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); + assertThat(tx.getServiceEndpoints()).isEqualTo(TEST_SERVICE_ENDPOINT); + } + + @Test + void setServiceEndpointFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setServiceEndpoints(TEST_SERVICE_ENDPOINT)); + } + + @Test + void setServiceEndpointRejectsMoreThan50() { + var tx = new RegisteredNodeCreateTransaction(); + var serviceEndpoints = new ArrayList(); + for (int i = 0; i < 51; i++) { + serviceEndpoints.add(spawnTestEndpoint((byte) i)); + } + + assertThrows(IllegalArgumentException.class, () -> tx.setServiceEndpoints(serviceEndpoints)); + } + + @Test + void addServiceEndpoint() { + var tx = new RegisteredNodeCreateTransaction(); + var serviceEndpoint = spawnTestEndpoint((byte) 1); + + tx.addServiceEndpoint(serviceEndpoint); + assertThat(tx.getServiceEndpoints()).hasSize(1); + assertThat(tx.getServiceEndpoints().get(0)).isEqualTo(serviceEndpoint); + } + + @Test + void addServiceEndpointRejectsMoreThan50() { + var tx = new RegisteredNodeCreateTransaction(); + for (int i = 0; i < 50; i++) { + tx.addServiceEndpoint(spawnTestEndpoint((byte) i)); + } + + assertThrows(IllegalArgumentException.class, () -> tx.addServiceEndpoint(spawnTestEndpoint((byte) 50))); + } + + @Test + void setNodeAccount() { + var tx = new RegisteredNodeCreateTransaction().setNodeAccount(TEST_ACCOUNT_ID); + assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); + } + + @Test + void setNodeAccountFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setNodeAccount(TEST_ACCOUNT_ID)); + } + + @Test + void constructRegisteredNodeCreateTransactionFromTransactionBodyProtobuf() { + var transactionBodyBuilder = RegisteredNodeCreateTransactionBody.newBuilder(); + + transactionBodyBuilder.setAdminKey(TEST_ADMIN_KEY.toProtobufKey()); + transactionBodyBuilder.setDescription(TEST_DESCRIPTION); + transactionBodyBuilder.setNodeAccount(TEST_ACCOUNT_ID.toProtobuf()); + + for (RegisteredServiceEndpoint serviceEndpoint : TEST_SERVICE_ENDPOINT) { + transactionBodyBuilder.addServiceEndpoint(serviceEndpoint.toProtobuf()); + } + + var transactionBody = TransactionBody.newBuilder() + .setRegisteredNodeCreate(transactionBodyBuilder.build()) + .build(); + var tx = new RegisteredNodeCreateTransaction(transactionBody); + + assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); + assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); + assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); + assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); + } + + @Test + void shouldFreezeSuccessfullyWhenAdminKeySet() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeCreateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)) + .setAdminKey(TEST_ADMIN_KEY); + + assertThatCode(() -> tx.freezeWith(null)).doesNotThrowAnyException(); + assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); + } + + @Test + void shouldThrowErrorWhenFreezingWithoutAdminKey() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeCreateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)); + + var exception = assertThrows(IllegalStateException.class, () -> tx.freezeWith(null)); + assertThat(exception.getMessage()) + .isEqualTo( + "RegisteredNodeCreateTransaction: 'adminKey' must be explicitly set before calling freeze()."); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap new file mode 100644 index 0000000000..c2bc2ea1bc --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap @@ -0,0 +1,3 @@ +com.hedera.hashgraph.sdk.RegisteredNodeCreateTransactionTest.shouldSerialize=[ + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_create {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description: \"Test description\"\n node_account {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.java new file mode 100644 index 0000000000..679d46db58 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.java @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.hedera.hashgraph.sdk.proto.RegisteredNodeDeleteTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import io.github.jsonSnapshot.SnapshotMatcher; +import java.time.Instant; +import java.util.Arrays; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeDeleteTransactionTest { + private static final PrivateKey TEST_PRIVATE_KEY = PrivateKey.fromString( + "302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"); + + private static final long TEST_REGISTERED_NODE_ID = 420; + + final Instant TEST_VALID_START = Instant.ofEpochSecond(1554158542); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + RegisteredNodeDeleteTransaction spawnTestTransaction() { + return new RegisteredNodeDeleteTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006"))) + .setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), TEST_VALID_START)) + .setRegisteredNodeId(TEST_REGISTERED_NODE_ID) + .setMaxTransactionFee(new Hbar(1)) + .freeze() + .sign(TEST_PRIVATE_KEY); + } + + @Test + void shouldSerialize() { + SnapshotMatcher.expect(spawnTestTransaction().toString()).toMatchSnapshot(); + } + + @Test + void shouldBytes() throws Exception { + var tx1 = spawnTestTransaction(); + var tx2 = RegisteredNodeDeleteTransaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void shouldBytesNoSetters() throws Exception { + var tx1 = new RegisteredNodeDeleteTransaction(); + var tx2 = RegisteredNodeDeleteTransaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void fromScheduledTransaction() { + var transactionBody = SchedulableTransactionBody.newBuilder() + .setRegisteredNodeDelete( + RegisteredNodeDeleteTransactionBody.newBuilder().build()) + .build(); + + var tx = Transaction.fromScheduledTransaction(transactionBody); + assertThat(tx).isInstanceOf(RegisteredNodeDeleteTransaction.class); + } + + @Test + void constructNodeDeleteTransactionFromTransactionBodyProtobuf() { + var transactionBodyBuilder = RegisteredNodeDeleteTransactionBody.newBuilder(); + + transactionBodyBuilder.setRegisteredNodeId(TEST_REGISTERED_NODE_ID); + + var tx = TransactionBody.newBuilder() + .setRegisteredNodeDelete(transactionBodyBuilder.build()) + .build(); + var registeredNodeDeleteTransaction = new RegisteredNodeDeleteTransaction(tx); + + assertThat(registeredNodeDeleteTransaction.getRegisteredNodeId()).isEqualTo(TEST_REGISTERED_NODE_ID); + } + + @Test + void getSetRegisteredNodeId() { + var tx = new RegisteredNodeDeleteTransaction().setRegisteredNodeId(TEST_REGISTERED_NODE_ID); + assertThat(tx.getRegisteredNodeId()).isEqualTo(TEST_REGISTERED_NODE_ID); + } + + @Test + void getSetRegisteredNodeIdFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setRegisteredNodeId(TEST_REGISTERED_NODE_ID)); + } + + @Test + void shouldThrowErrorWhenGettingRegisteredNodeIdWithoutSettingIt() { + var tx = new RegisteredNodeDeleteTransaction(); + + var exception = assertThrows(IllegalStateException.class, () -> tx.getRegisteredNodeId()); + assertThat(exception.getMessage()) + .isEqualTo("RegisteredNodeDeleteTransaction: 'registeredNodeId' has not been set"); + } + + @Test + void shouldThrowErrorWhenSettingNegativeRegisteredNodeId() { + var tx = new RegisteredNodeDeleteTransaction(); + + var exception = assertThrows(IllegalArgumentException.class, () -> tx.setRegisteredNodeId(-1)); + assertThat(exception.getMessage()) + .isEqualTo("RegisteredNodeDeleteTransaction: 'registeredNodeId' must be non-negative"); + } + + @Test + void shouldAllowSettingRegisteredNodeIdToZero() { + var tx = new RegisteredNodeDeleteTransaction().setRegisteredNodeId(0); + assertThat(tx.getRegisteredNodeId()).isEqualTo(0); + } + + @Test + void shouldFreezeSuccessfullyWhenRegisteredNodeIdIsSet() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeDeleteTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)) + .setRegisteredNodeId(420); + + assertThatCode(() -> tx.freezeWith(null)).doesNotThrowAnyException(); + assertThat(tx.getRegisteredNodeId()).isEqualTo(420); + } + + @Test + void shouldThrowErrorWhenFreezingWithZeroRegisteredNodeId() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeDeleteTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)); + + var exception = assertThrows(IllegalStateException.class, () -> tx.freezeWith(null)); + assertThat(exception.getMessage()) + .isEqualTo( + "RegisteredNodeDeleteTransaction: 'registeredNodeId' must be explicitly set before calling freeze()."); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.snap new file mode 100644 index 0000000000..fe0c1e545e --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeDeleteTransactionTest.snap @@ -0,0 +1,3 @@ +com.hedera.hashgraph.sdk.RegisteredNodeDeleteTransactionTest.shouldSerialize=[ + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_delete {\n registered_node_id: 420\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java new file mode 100644 index 0000000000..7fe8ce892b --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.InvalidProtocolBufferException; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeTest { + private static final PrivateKey privateKey = PrivateKey.fromString( + "302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"); + + private final BlockNodeServiceEndpoint serviceEndpoint = new BlockNodeServiceEndpoint() + .setEndpointApi(BlockNodeApi.STATUS) + .setPort(443) + .setDomainName("test.block.com") + .setRequiresTls(true); + + private final com.hedera.hashgraph.sdk.proto.RegisteredNode registeredNode = + com.hedera.hashgraph.sdk.proto.RegisteredNode.newBuilder() + .setRegisteredNodeId(1) + .setDescription("Unit test registered node") + .setAdminKey(privateKey.getPublicKey().toProtobufKey()) + .addServiceEndpoint(serviceEndpoint.toProtobuf()) + .setNodeAccount(new AccountId(0, 0, 4).toProtobuf()) + .build(); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void fromProtobuf() { + SnapshotMatcher.expect(RegisteredNode.fromProtobuf(registeredNode).toString()) + .toMatchSnapshot(); + } + + @Test + void fromBytes() throws InvalidProtocolBufferException { + SnapshotMatcher.expect( + RegisteredNode.fromBytes(registeredNode.toByteArray()).toString()) + .toMatchSnapshot(); + } + + @Test + void toBytes() throws InvalidProtocolBufferException { + SnapshotMatcher.expect( + RegisteredNode.fromBytes(registeredNode.toByteArray()).toBytes()) + .toMatchSnapshot(); + } + + @Test + void toProtobuf() throws InvalidProtocolBufferException { + SnapshotMatcher.expect( + RegisteredNode.fromProtobuf(registeredNode).toProtobuf().toString()) + .toMatchSnapshot(); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap new file mode 100644 index 0000000000..ccab1b60d8 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap @@ -0,0 +1,18 @@ +com.hedera.hashgraph.sdk.RegisteredNodeTest.fromBytes=[ + "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, nodeAccount=0.0.4, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}]}" +] + + +com.hedera.hashgraph.sdk.RegisteredNodeTest.fromProtobuf=[ + "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, nodeAccount=0.0.4, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}]}" +] + + +com.hedera.hashgraph.sdk.RegisteredNodeTest.toBytes=[ + "CAESIhIg4MjsJ1ilh5/6wiahPAxRa3mecuNRQaDdgo+U03mIpLcaGVVuaXQgdGVzdCByZWdpc3RlcmVkIG5vZGUiGRIOdGVzdC5ibG9jay5jb20YuwMgASoCCAEqAhgE" +] + + +com.hedera.hashgraph.sdk.RegisteredNodeTest.toProtobuf=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredNode@ab45a662\nadmin_key {\n ed25519: \"\\340\\310\\354\\'X\\245\\207\\237\\372\\302&\\241<\\fQky\\236r\\343QA\\240\\335\\202\\217\\224\\323y\\210\\244\\267\"\n}\ndescription: \"Unit test registered node\"\nnode_account {\n account_num: 4\n realm_num: 0\n shard_num: 0\n}\nregistered_node_id: 1\nservice_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"test.block.com\"\n port: 443\n requires_tls: true\n}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java new file mode 100644 index 0000000000..62b6a0daac --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.protobuf.StringValue; +import com.hedera.hashgraph.sdk.proto.RegisteredNodeUpdateTransactionBody; +import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; +import com.hedera.hashgraph.sdk.proto.TransactionBody; +import io.github.jsonSnapshot.SnapshotMatcher; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeUpdateTransactionTest { + private static final PrivateKey TEST_PRIVATE_KEY = PrivateKey.fromString( + "302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"); + + private static final AccountId TEST_ACCOUNT_ID = AccountId.fromString("0.6.9"); + + private static final String TEST_DESCRIPTION = "Test description"; + + private static final long TEST_REGISTERED_NODE_ID = 43; + + private static final List TEST_SERVICE_ENDPOINT = + List.of(spawnTestEndpoint((byte) 0), spawnTestEndpoint((byte) 1), spawnTestEndpoint((byte) 2)); + + private static final PublicKey TEST_ADMIN_KEY = PrivateKey.fromString( + "302e020100300506032b65700422042062c4b69e9f45a554e5424fb5a6fe5e6ac1f19ead31dc7718c2d980fd1f998d4b") + .getPublicKey(); + + final Instant TEST_VALID_START = Instant.ofEpochSecond(1554158542); + + static RegisteredServiceEndpoint spawnTestEndpoint(byte offset) { + return new BlockNodeServiceEndpoint() + .setDomainName("example.block.com") + .setPort(443 + offset) + .setRequiresTls(true) + .setEndpointApi(BlockNodeApi.STATUS); + } + + RegisteredNodeUpdateTransaction spawnTestTransaction() { + return new RegisteredNodeUpdateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.5005"), AccountId.fromString("0.0.5006"))) + .setTransactionId(TransactionId.withValidStart(AccountId.fromString("0.0.5006"), TEST_VALID_START)) + .setRegisteredNodeId(TEST_REGISTERED_NODE_ID) + .setAdminKey(TEST_ADMIN_KEY) + .setDescription(TEST_DESCRIPTION) + .setServiceEndpoints(TEST_SERVICE_ENDPOINT) + .setNodeAccount(TEST_ACCOUNT_ID) + .setMaxTransactionFee(new Hbar(1)) + .freeze() + .sign(TEST_PRIVATE_KEY); + } + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void shouldSerialize() { + SnapshotMatcher.expect(spawnTestTransaction().toString()).toMatchSnapshot(); + } + + @Test + void shouldBytes() throws Exception { + var tx1 = spawnTestTransaction(); + var tx2 = RegisteredNodeUpdateTransaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void shouldBytesNoSetters() throws Exception { + var tx1 = new RegisteredNodeUpdateTransaction(); + var tx2 = Transaction.fromBytes(tx1.toBytes()); + assertThat(tx2.toString()).isEqualTo(tx1.toString()); + } + + @Test + void fromScheduledTransaction() { + var transactionBody = SchedulableTransactionBody.newBuilder() + .setRegisteredNodeUpdate( + RegisteredNodeUpdateTransactionBody.newBuilder().build()) + .build(); + + var tx = Transaction.fromScheduledTransaction(transactionBody); + + assertThat(tx).isInstanceOf(RegisteredNodeUpdateTransaction.class); + } + + @Test + void getRegisterNodeIdThrowErrorWhenNotSet() { + var tx = new RegisteredNodeUpdateTransaction(); + var exception = assertThrows(IllegalStateException.class, () -> tx.getRegisteredNodeId()); + assertThat(exception.getMessage()) + .isEqualTo("RegisteredNodeUpdateTransaction: 'registeredNodeId' has not been set"); + } + + @Test + void setRegisteredNodeId() { + var tx = new RegisteredNodeUpdateTransaction().setRegisteredNodeId(TEST_REGISTERED_NODE_ID); + assertThat(tx.getRegisteredNodeId()).isEqualTo(TEST_REGISTERED_NODE_ID); + } + + @Test + void setRegisteredNodeIdFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setRegisteredNodeId(TEST_REGISTERED_NODE_ID)); + } + + @Test + void setRegisteredNodeIdValueLessThanZero() { + var tx = new RegisteredNodeUpdateTransaction(); + assertThrows(IllegalArgumentException.class, () -> tx.setRegisteredNodeId(-1)); + } + + @Test + void setAdminKey() { + var tx = new RegisteredNodeUpdateTransaction().setAdminKey(TEST_ADMIN_KEY); + assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); + } + + @Test + void setAdminKeyFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setAdminKey(TEST_ADMIN_KEY)); + } + + @Test + void setDescription() { + var tx = new RegisteredNodeUpdateTransaction().setDescription(TEST_DESCRIPTION); + assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); + } + + @Test + void setDescriptionFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setDescription(TEST_DESCRIPTION)); + } + + @Test + void setDescriptionRejectsOver100Utf8Bytes() { + var tx = new RegisteredNodeUpdateTransaction(); + String tooLong = "a".repeat(101); + assertThrows(IllegalArgumentException.class, () -> tx.setDescription(tooLong)); + } + + @Test + void setDescriptionAcceptsExactly100Utf8Bytes() { + var tx = new RegisteredNodeUpdateTransaction(); + String exact = "a".repeat(100); + tx.setDescription(exact); + assertThat(tx.getDescription()).isEqualTo(exact); + } + + @Test + void setServiceEndpoint() { + var tx = new RegisteredNodeUpdateTransaction().setServiceEndpoints(TEST_SERVICE_ENDPOINT); + assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); + assertThat(tx.getServiceEndpoints()).isEqualTo(TEST_SERVICE_ENDPOINT); + } + + @Test + void setServiceEndpointFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setServiceEndpoints(TEST_SERVICE_ENDPOINT)); + } + + @Test + void setServiceEndpointRejectsMoreThan50() { + var tx = new RegisteredNodeUpdateTransaction(); + var serviceEndpoints = new ArrayList(); + for (int i = 0; i < 51; i++) { + serviceEndpoints.add(spawnTestEndpoint((byte) i)); + } + + assertThrows(IllegalArgumentException.class, () -> tx.setServiceEndpoints(serviceEndpoints)); + } + + @Test + void addServiceEndpoint() { + var tx = new RegisteredNodeUpdateTransaction(); + var serviceEndpoint = spawnTestEndpoint((byte) 1); + + tx.addServiceEndpoint(serviceEndpoint); + assertThat(tx.getServiceEndpoints()).hasSize(1); + assertThat(tx.getServiceEndpoints().get(0)).isEqualTo(serviceEndpoint); + } + + @Test + void addServiceEndpointRejectsMoreThan50() { + var tx = new RegisteredNodeUpdateTransaction(); + for (int i = 0; i < 50; i++) { + tx.addServiceEndpoint(spawnTestEndpoint((byte) i)); + } + + assertThrows(IllegalArgumentException.class, () -> tx.addServiceEndpoint(spawnTestEndpoint((byte) 50))); + } + + @Test + void setNodeAccount() { + var tx = new RegisteredNodeUpdateTransaction().setNodeAccount(TEST_ACCOUNT_ID); + assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); + } + + @Test + void setNodeAccountFrozen() { + var tx = spawnTestTransaction(); + assertThrows(IllegalStateException.class, () -> tx.setNodeAccount(TEST_ACCOUNT_ID)); + } + + @Test + void constructRegisteredNodeUpdateTransactionFromTransactionBodyProtobuf() { + var transactionBodyBuilder = RegisteredNodeUpdateTransactionBody.newBuilder(); + + transactionBodyBuilder.setRegisteredNodeId(TEST_REGISTERED_NODE_ID); + transactionBodyBuilder.setAdminKey(TEST_ADMIN_KEY.toProtobufKey()); + transactionBodyBuilder.setDescription(StringValue.of(TEST_DESCRIPTION)); + transactionBodyBuilder.setNodeAccount(TEST_ACCOUNT_ID.toProtobuf()); + + for (RegisteredServiceEndpoint serviceEndpoint : TEST_SERVICE_ENDPOINT) { + transactionBodyBuilder.addServiceEndpoint(serviceEndpoint.toProtobuf()); + } + + var transactionBody = TransactionBody.newBuilder() + .setRegisteredNodeUpdate(transactionBodyBuilder.build()) + .build(); + var tx = new RegisteredNodeUpdateTransaction(transactionBody); + + assertThat(tx.getRegisteredNodeId()).isEqualTo(TEST_REGISTERED_NODE_ID); + assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); + assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); + assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); + assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); + } + + @Test + void shouldFreezeSuccessfullyWhenRegisteredNodeIdSet() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeUpdateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)) + .setRegisteredNodeId(TEST_REGISTERED_NODE_ID); + + assertThatCode(() -> tx.freezeWith(null)).doesNotThrowAnyException(); + assertThat(tx.getRegisteredNodeId()).isEqualTo(TEST_REGISTERED_NODE_ID); + } + + @Test + void shouldThrowErrorWhenFreezingWithoutRegisteredNodeId() { + final Instant VALID_START = Instant.ofEpochSecond(1596210382); + final AccountId ACCOUNT_ID = AccountId.fromString("0.6.9"); + + var tx = new RegisteredNodeUpdateTransaction() + .setNodeAccountIds(Arrays.asList(AccountId.fromString("0.0.3"))) + .setTransactionId(TransactionId.withValidStart(ACCOUNT_ID, VALID_START)); + + var exception = assertThrows(IllegalStateException.class, () -> tx.freezeWith(null)); + assertThat(exception.getMessage()) + .isEqualTo( + "RegisteredNodeUpdateTransaction: 'registeredNodeId' must be explicitly set before calling freeze()."); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap new file mode 100644 index 0000000000..7e27605c0e --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap @@ -0,0 +1,3 @@ +com.hedera.hashgraph.sdk.RegisteredNodeUpdateTransactionTest.shouldSerialize=[ + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_update {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description {\n value: \"Test description\"\n }\n node_account {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n registered_node_id: 43\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java new file mode 100644 index 0000000000..1df1771c2c --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java @@ -0,0 +1,102 @@ +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class RpcRelayServiceEndpointTest { + private final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; + private final String TEST_DOMAIN_NAME = "test.mirror.com"; + private final int TEST_PORT = 443; + private final boolean TEST_REQUIRES_TLS = true; + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithDomain = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) + .build(); + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithIp = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) + .build(); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + @Test + void fromProtobufWithDomain() { + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithDomain() { + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void fromProtobufWithIp() { + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp).toString()).toMatchSnapshot(); + } + + @Test + void toProtobufWithIp() { + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp).toProtobuf().toString()) + .toMatchSnapshot(); + } + + @Test + void setIpAddress() { + var endpoint = new BlockNodeServiceEndpoint().setIpAddress(TEST_IP_ADDRESS); + assertThat(endpoint.getIpAddress()).isEqualTo(TEST_IP_ADDRESS); + } + + @Test + void setDomainName() { + var endpoint = new BlockNodeServiceEndpoint().setDomainName(TEST_DOMAIN_NAME); + assertThat(endpoint.getDomainName()).isEqualTo(TEST_DOMAIN_NAME); + } + + @Test + void setPort() { + var endpoint = new BlockNodeServiceEndpoint().setPort(TEST_PORT); + assertThat(endpoint.getPort()).isEqualTo(TEST_PORT); + } + + @Test + void setPortThrowsOnNegative() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(-1)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setPortThrowsOnGreaterThan65535() { + var endpoint = new BlockNodeServiceEndpoint(); + assertThatThrownBy(() -> endpoint.setPort(65536)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void setRequiresTls() { + var endpoint = new BlockNodeServiceEndpoint().setRequiresTls(TEST_REQUIRES_TLS); + assertThat(endpoint.isRequiresTls()).isEqualTo(TEST_REQUIRES_TLS); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap new file mode 100644 index 0000000000..208d1c81fb --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap @@ -0,0 +1,18 @@ +com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.fromProtobufWithDomain=[ + "RpcRelayServiceEndpoint{ipAddress=null, domainName=test.mirror.com, port=443, requiresTls=true}" +] + + +com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.fromProtobufWithIp=[ + "RpcRelayServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=443, requiresTls=true}" +] + + +com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.toProtobufWithDomain=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@14147303\ndomain_name: \"test.mirror.com\"\nport: 443\nrequires_tls: true" +] + + +com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.toProtobufWithIp=[ + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@26cccebb\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.java index a1744695af..7d00243a1b 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.java @@ -44,6 +44,7 @@ static TransactionReceipt spawnReceiptExample() { TransactionId.withValidStart(AccountId.fromString("3.3.3"), time), List.of(1L, 2L, 3L), 1, + 1, new ArrayList<>(), new ArrayList<>()); } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.snap index ffd1cfbf22..2feaddc354 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionReceiptTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.TransactionReceiptTest.shouldSerialize=[ - "TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, duplicates=[], children=[]}" -] + "TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap index c3ccf38f4a..b82db333b4 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap @@ -1,8 +1,8 @@ com.hedera.hashgraph.sdk.TransactionRecordTest.shouldSerialize2=[ - "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=null, prngNumber=4, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}], highVolumePricingMultiplier=1000}" + "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=null, prngNumber=4, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}]}" ] com.hedera.hashgraph.sdk.TransactionRecordTest.shouldSerialize=[ - "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=766572792072616e646f6d206279746573, prngNumber=null, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}], highVolumePricingMultiplier=1000}" -] \ No newline at end of file + "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=766572792072616e646f6d206279746573, prngNumber=null, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}]}" +] diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java new file mode 100644 index 0000000000..6fbfaef146 --- /dev/null +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -0,0 +1,42 @@ +package com.hedera.hashgraph.sdk.test.integration; + +import com.hedera.hashgraph.sdk.BlockNodeApi; +import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; +import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; +import com.hedera.hashgraph.sdk.Status; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + + +public class RegisteredNodeCreateTransactionIntegrationTest { + @Test + @DisplayName("Can create a registered node") + void canCreateRegisteredNode() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + List serviceEndpoints = List.of( + new BlockNodeServiceEndpoint() + .setDomainName("test.block.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.STATUS) + ); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + assertThat(receipt.registeredNodeId).isGreaterThan(0); + } + } +} From e92b6f116da6cf4e942bd303cb2fdf6ecb829bfa Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Fri, 13 Mar 2026 17:20:07 +0530 Subject: [PATCH 03/27] feat: added new fields to existing class Signed-off-by: Manish Dait --- .../sdk/BlockNodeServiceEndpoint.java | 7 +- .../hashgraph/sdk/NodeCreateTransaction.java | 57 +++++++++++++++++ .../hashgraph/sdk/NodeUpdateTransaction.java | 64 +++++++++++++++++++ .../hedera/hashgraph/sdk/RegisteredNode.java | 2 +- .../sdk/RegisteredNodeAddressBook.java | 19 +++++- .../sdk/RegisteredNodeCreateTransaction.java | 2 +- .../sdk/RegisteredServiceEndpoint.java | 10 ++- .../sdk/BlockNodeServiceEndpointTest.java | 55 ++++++++-------- .../sdk/MirrorNodeServiceEndpointTest.java | 61 ++++++++++-------- .../sdk/NodeCreateTransactionTest.java | 44 +++++++++++++ .../sdk/NodeCreateTransactionTest.snap | 2 +- .../sdk/NodeUpdateTransactionTest.java | 44 +++++++++++++ .../sdk/NodeUpdateTransactionTest.snap | 4 +- .../sdk/RpcRelayServiceEndpointTest.java | 61 ++++++++++-------- ...dNodeCreateTransactionIntegrationTest.java | 24 ++++--- 15 files changed, 347 insertions(+), 109 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java index dc33752c85..2e88c60bd5 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -40,7 +40,8 @@ static BlockNodeServiceEndpoint fromProtobuf( var blockNodeEndpoint = new BlockNodeServiceEndpoint() .setPort(serviceEndpoint.getPort()) .setRequiresTls(serviceEndpoint.getRequiresTls()) - .setEndpointApi(BlockNodeApi.valueOf(serviceEndpoint.getBlockNode().getEndpointApi())); + .setEndpointApi( + BlockNodeApi.valueOf(serviceEndpoint.getBlockNode().getEndpointApi())); if (serviceEndpoint.hasIpAddress()) { blockNodeEndpoint.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); @@ -80,8 +81,6 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { @Override public String toString() { - return toStringHelper() - .add("endpointApi", endpointApi) - .toString(); + return toStringHelper().add("endpointApi", endpointApi).toString(); } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeCreateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeCreateTransaction.java index 6257d4c093..8887535105 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeCreateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeCreateTransaction.java @@ -66,6 +66,8 @@ public class NodeCreateTransaction extends Transaction { @Nullable private Endpoint grpcWebProxyEndpoint = null; + private List associatedRegisteredNodes = new ArrayList<>(); + /** * Constructor. */ @@ -396,6 +398,53 @@ public NodeCreateTransaction setGrpcWebProxyEndpoint(@Nullable Endpoint grpcWebP return this; } + /** + * Get a list of registered nodes operated by the same entity as this node. + * @return {@code List} the list of associated registered node. + */ + public List getAssociatedRegisteredNodes() { + return associatedRegisteredNodes; + } + + /** + * Set a list of registered nodes operated by the same entity as this node.
+ * This value may contain a list of "registered nodes" (as described in + * HIP-1137) that are operated by the same entity that operates this + * consensus node. + *

+ * This field is OPTIONAL and MAY be empty.
+ * This field MUST NOT contain more than twenty(20) entries.
+ * Every entry in this list MUST be a valid `registered_node_id` for a + * current registered node. + * + * @param associatedRegisteredNodes list of associated registered node. + * @return {@code this} + */ + public NodeCreateTransaction setAssociatedRegisteredNodes(List associatedRegisteredNodes) { + requireNotFrozen(); + Objects.requireNonNull(associatedRegisteredNodes); + if (associatedRegisteredNodes.size() > 20) { + throw new IllegalArgumentException("associatedRegisteredNodes must not contain more than 20 entries"); + } + + this.associatedRegisteredNodes = new ArrayList<>(associatedRegisteredNodes); + return this; + } + + /** + * Add a registered nodes operated by the same entity as this node. + * @param associatedRegisteredNode the associated registered node. + * @return {@code this} + */ + public NodeCreateTransaction addAssociatedRegisteredNode(long associatedRegisteredNode) { + requireNotFrozen(); + if (associatedRegisteredNodes.size() >= 20) { + throw new IllegalArgumentException("associatedRegisteredNodes must not contain more than 20 entries"); + } + associatedRegisteredNodes.add(associatedRegisteredNode); + return this; + } + /** * Build the transaction body. * @@ -438,6 +487,10 @@ NodeCreateTransactionBody.Builder build() { builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); } + for (Long associatedRegisteredNode : associatedRegisteredNodes) { + builder.addAssociatedRegisteredNode(associatedRegisteredNode); + } + if (gossipCaCertificate != null) { builder.setGossipCaCertificate(ByteString.copyFrom(gossipCaCertificate)); } @@ -481,6 +534,10 @@ void initFromTransactionBody() { serviceEndpoints.add(Endpoint.fromProtobuf(serviceEndpoint)); } + for (var associatedRegisteredNode : body.getAssociatedRegisteredNodeList()) { + associatedRegisteredNodes.add(associatedRegisteredNode); + } + var protobufGossipCert = body.getGossipCaCertificate(); gossipCaCertificate = protobufGossipCert.equals(ByteString.empty()) ? null : protobufGossipCert.toByteArray(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java index eadf30aa3b..c630332c14 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java @@ -7,6 +7,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.StringValue; import com.hedera.hashgraph.sdk.proto.AddressBookServiceGrpc; +import com.hedera.hashgraph.sdk.proto.AssociatedRegisteredNodeList; import com.hedera.hashgraph.sdk.proto.NodeUpdateTransactionBody; import com.hedera.hashgraph.sdk.proto.SchedulableTransactionBody; import com.hedera.hashgraph.sdk.proto.TransactionBody; @@ -67,6 +68,8 @@ public class NodeUpdateTransaction extends Transaction { @Nullable private Endpoint grpcWebProxyEndpoint = null; + private List associatedRegisteredNodes = new ArrayList<>(); + /** * Constructor. */ @@ -455,6 +458,54 @@ public NodeUpdateTransaction setGrpcWebProxyEndpoint(@Nullable Endpoint grpcWebP return this; } + /** + * Get a list of registered nodes operated by the same entity as this node. + * @return {@code List} the list of associated registered node. + */ + public List getAssociatedRegisteredNodes() { + return associatedRegisteredNodes; + } + + /** + * Set a list of registered nodes operated by the same entity as this node.
+ * This value may contain a list of "registered nodes" (as described in + * HIP-1137) that are operated by the same entity that operates this + * consensus node. + *

+ * This field is OPTIONAL and MAY be empty.
+ * This field MUST NOT contain more than twenty(20) entries.
+ * Every entry in this list MUST be a valid `registered_node_id` for a + * current registered node. + * + * @param associatedRegisteredNodes list of associated registered node. + * @return {@code this} + * @throws IllegalArgumentException if the list is empty or contains more than 8 endpoints + */ + public NodeUpdateTransaction setAssociatedRegisteredNodes(List associatedRegisteredNodes) { + requireNotFrozen(); + Objects.requireNonNull(associatedRegisteredNodes); + if (associatedRegisteredNodes.size() > 20) { + throw new IllegalArgumentException("associatedRegisteredNodes must not contain more than 20 entries"); + } + + this.associatedRegisteredNodes = new ArrayList<>(associatedRegisteredNodes); + return this; + } + + /** + * Add a registered nodes operated by the same entity as this node. + * @param associatedRegisteredNode the associated registered node. + * @return {@code this} + */ + public NodeUpdateTransaction addAssociatedRegisteredNode(long associatedRegisteredNode) { + requireNotFrozen(); + if (associatedRegisteredNodes.size() >= 20) { + throw new IllegalArgumentException("associatedRegisteredNodes must not contain more than 20 entries"); + } + associatedRegisteredNodes.add(associatedRegisteredNode); + return this; + } + /** * Build the transaction body. * @@ -483,6 +534,12 @@ NodeUpdateTransactionBody.Builder build() { builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); } + if (!associatedRegisteredNodes.isEmpty()) { + builder.setAssociatedRegisteredNodeList(AssociatedRegisteredNodeList.newBuilder() + .addAllAssociatedRegisteredNode(associatedRegisteredNodes) + .build()); + } + if (gossipCaCertificate != null) { builder.setGossipCaCertificate(BytesValue.of(ByteString.copyFrom(gossipCaCertificate))); } @@ -549,6 +606,13 @@ void initFromTransactionBody() { if (body.hasGrpcProxyEndpoint()) { grpcWebProxyEndpoint = Endpoint.fromProtobuf(body.getGrpcProxyEndpoint()); } + + if (body.hasAssociatedRegisteredNodeList()) { + associatedRegisteredNodes = new ArrayList<>(); + for (long id : body.getAssociatedRegisteredNodeList().getAssociatedRegisteredNodeList()) { + associatedRegisteredNodes.add(id); + } + } } @Override diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java index 8f5971e441..8ca1281326 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java @@ -137,7 +137,7 @@ public String toString() { .add("adminKey", adminKey) .add("description", description) .add("nodeAccount", nodeAccount) - .add("serviceEndpoints", serviceEndpoints) + .add("serviceEndpoints", serviceEndpoints) .toString(); } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java index 34ee5dd67d..cc53f09bff 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBook.java @@ -3,16 +3,29 @@ import java.util.List; +/** + * Collection of RegisteredNode objects. + */ public class RegisteredNodeAddressBook { public final List registeredNodes; + /** + * Constructor. + * + * @param registeredNodes list of RegisterNode + */ RegisteredNodeAddressBook(List registeredNodes) { this.registeredNodes = registeredNodes; } + /** + * Build RegisterNodeAddressBook from protobuf. + * + * @param registeredNodes the list of RegisterNode protobuf representation. + * @return {@code RegisterNodeAddressBook} new object of RegisterNodeAddressBook. + */ static RegisteredNodeAddressBook fromProtobuf(List registeredNodes) { - return new RegisteredNodeAddressBook(registeredNodes.stream() - .map(n -> RegisteredNode.fromProtobuf(n)) - .toList()); + return new RegisteredNodeAddressBook( + registeredNodes.stream().map(RegisteredNode::fromProtobuf).toList()); } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java index 826fba4418..c0f5d42bc6 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java @@ -154,7 +154,7 @@ public RegisteredNodeCreateTransaction setServiceEndpoints(List(serviceEndpoints); return this; } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java index 2b98a07821..e6ed32cc80 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java @@ -2,8 +2,6 @@ package com.hedera.hashgraph.sdk; import com.google.common.base.MoreObjects; - -import java.util.Arrays; import java.util.Objects; import javax.annotation.Nullable; @@ -104,9 +102,9 @@ static RegisteredServiceEndpoint fromProtobuf( */ protected MoreObjects.ToStringHelper toStringHelper() { return MoreObjects.toStringHelper(this) - .add("ipAddress", ipAddress) - .add("domainName", domainName) - .add("port", port) - .add("requiresTls", requiresTls); + .add("ipAddress", ipAddress) + .add("domainName", domainName) + .add("port", port) + .add("requiresTls", requiresTls); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java index 830a7d6b60..7b90f54a8c 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; import static org.assertj.core.api.Assertions.assertThat; @@ -18,24 +19,20 @@ public class BlockNodeServiceEndpointTest { private final BlockNodeApi TEST_BLOCK_API = BlockNodeApi.STATUS; private final RegisteredServiceEndpoint blockNodeEndpointWithDomain = RegisteredServiceEndpoint.newBuilder() - .setDomainName(TEST_DOMAIN_NAME) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setBlockNode( - RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() - .setEndpointApi(TEST_BLOCK_API.code) - ) - .build(); + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setBlockNode( + RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder().setEndpointApi(TEST_BLOCK_API.code)) + .build(); private final RegisteredServiceEndpoint blockNodeEndpointWithIp = RegisteredServiceEndpoint.newBuilder() - .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setBlockNode( - RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() - .setEndpointApi(RegisteredServiceEndpoint.BlockNodeEndpoint.BlockNodeApi.STATUS) - ) - .build(); + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setBlockNode(RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .setEndpointApi(RegisteredServiceEndpoint.BlockNodeEndpoint.BlockNodeApi.STATUS)) + .build(); @BeforeAll public static void beforeAll() { @@ -49,24 +46,32 @@ public static void afterAll() { @Test void fromProtobufWithDomain() { - SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain).toString()).toMatchSnapshot(); + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain) + .toString()) + .toMatchSnapshot(); } @Test void toProtobufWithDomain() { - SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithDomain) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test void fromProtobufWithIp() { - SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp).toString()).toMatchSnapshot(); + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp) + .toString()) + .toMatchSnapshot(); } @Test void toProtobufWithIp() { - SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(BlockNodeServiceEndpoint.fromProtobuf(blockNodeEndpointWithIp) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test @@ -90,15 +95,13 @@ void setPort() { @Test void setPortThrowsOnNegative() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(-1)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(-1)).isInstanceOf(IllegalArgumentException.class); } @Test void setPortThrowsOnGreaterThan65535() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(65536)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(65536)).isInstanceOf(IllegalArgumentException.class); } @Test diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java index 0d48552d80..d4c0996f7a 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.java @@ -1,5 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.google.protobuf.ByteString; import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import io.github.jsonSnapshot.SnapshotMatcher; @@ -7,28 +11,27 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class MirrorNodeServiceEndpointTest { private final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; private final String TEST_DOMAIN_NAME = "test.mirror.com"; private final int TEST_PORT = 443; private final boolean TEST_REQUIRES_TLS = true; - private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithDomain = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() - .setDomainName(TEST_DOMAIN_NAME) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) - .build(); - - private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithIp = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() - .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) - .build(); + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithDomain = + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) + .build(); + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint mirrorNodeEndpointWithIp = + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()) + .build(); @BeforeAll public static void beforeAll() { @@ -42,24 +45,32 @@ public static void afterAll() { @Test void fromProtobufWithDomain() { - SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain).toString()).toMatchSnapshot(); + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain) + .toString()) + .toMatchSnapshot(); } @Test void toProtobufWithDomain() { - SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithDomain) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test void fromProtobufWithIp() { - SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp).toString()).toMatchSnapshot(); + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp) + .toString()) + .toMatchSnapshot(); } @Test void toProtobufWithIp() { - SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(MirrorNodeServiceEndpoint.fromProtobuf(mirrorNodeEndpointWithIp) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test @@ -83,15 +94,13 @@ void setPort() { @Test void setPortThrowsOnNegative() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(-1)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(-1)).isInstanceOf(IllegalArgumentException.class); } @Test void setPortThrowsOnGreaterThan65535() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(65536)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(65536)).isInstanceOf(IllegalArgumentException.class); } @Test diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.java index 73d41dc920..8d16dc1129 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.java @@ -40,6 +40,8 @@ public class NodeCreateTransactionTest { private static final byte[] TEST_GRPC_CERTIFICATE_HASH = new byte[] {5, 6, 7, 8, 9}; + private static final List TEST_ASSOCIATED_REGISTERED_NODES = List.of(1L, 2L); + private static final PublicKey TEST_ADMIN_KEY = PrivateKey.fromString( "302e020100300506032b65700422042062c4b69e9f45a554e5424fb5a6fe5e6ac1f19ead31dc7718c2d980fd1f998d4b") .getPublicKey(); @@ -80,6 +82,7 @@ private NodeCreateTransaction spawnTestTransaction() { .setMaxTransactionFee(new Hbar(1)) .setDeclineReward(false) // accepts the reward .setGrpcWebProxyEndpoint(TEST_GRPC_WEB_PROXY_ENDPOINT) + .setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES) .freeze() .sign(TEST_PRIVATE_KEY); } @@ -415,4 +418,45 @@ void setGrpcWebProxyEndpointRequiresFrozen() { var tx = spawnTestTransaction(); assertThrows(IllegalStateException.class, () -> tx.setGrpcWebProxyEndpoint(TEST_GRPC_WEB_PROXY_ENDPOINT)); } + + @Test + void setAssociatedRegisteredNodes() { + var tx = new NodeCreateTransaction().setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES); + assertThat(tx.getAssociatedRegisteredNodes()).isEqualTo(TEST_ASSOCIATED_REGISTERED_NODES); + } + + @Test + void setAssociatedRegisteredNodesFrozen() { + var tx = spawnTestTransaction(); + assertThrows( + IllegalStateException.class, () -> tx.setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES)); + } + + @Test + void setAssociatedRegisteredNodesMoreThan20() { + var tx = new NodeCreateTransaction(); + var nodes = new java.util.ArrayList(); + for (int i = 0; i < 21; i++) { + nodes.add((long) i); + } + + assertThrows(IllegalArgumentException.class, () -> tx.setAssociatedRegisteredNodes(nodes)); + } + + @Test + void addAssociatedRegisteredNode() { + var tx = new NodeCreateTransaction(); + tx.addAssociatedRegisteredNode(1L); + assertThat(tx.getAssociatedRegisteredNodes()).hasSize(1); + assertThat(tx.getAssociatedRegisteredNodes().get(0)).isEqualTo(1); + } + + @Test + void addAssociatedRegisteredNodeMoreThan20() { + var tx = new NodeCreateTransaction(); + for (int i = 0; i < 20; i++) { + tx.addAssociatedRegisteredNode((long) i); + } + assertThrows(IllegalArgumentException.class, () -> tx.addAssociatedRegisteredNode(21L)); + } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.snap index ab38ebfcfe..3cc56dcbd0 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeCreateTransactionTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.NodeCreateTransactionTest.shouldSerialize=[ - "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nnode_create {\n account_id {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description: \"Test description\"\n gossip_ca_certificate: \"\\000\\001\\002\\003\\004\"\n gossip_endpoint {\n domain_name: \"0unit.test.com\"\n port: 42\n }\n gossip_endpoint {\n domain_name: \"1unit.test.com\"\n port: 43\n }\n gossip_endpoint {\n domain_name: \"2unit.test.com\"\n port: 44\n }\n grpc_certificate_hash: \"\\005\\006\\a\\b\\t\"\n grpc_proxy_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n service_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n service_endpoint {\n domain_name: \"4unit.test.com\"\n port: 46\n }\n service_endpoint {\n domain_name: \"5unit.test.com\"\n port: 47\n }\n service_endpoint {\n domain_name: \"6unit.test.com\"\n port: 48\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nnode_create {\n account_id {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n associated_registered_node: 1\n associated_registered_node: 2\n description: \"Test description\"\n gossip_ca_certificate: \"\\000\\001\\002\\003\\004\"\n gossip_endpoint {\n domain_name: \"0unit.test.com\"\n port: 42\n }\n gossip_endpoint {\n domain_name: \"1unit.test.com\"\n port: 43\n }\n gossip_endpoint {\n domain_name: \"2unit.test.com\"\n port: 44\n }\n grpc_certificate_hash: \"\\005\\006\\a\\b\\t\"\n grpc_proxy_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n service_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n service_endpoint {\n domain_name: \"4unit.test.com\"\n port: 46\n }\n service_endpoint {\n domain_name: \"5unit.test.com\"\n port: 47\n }\n service_endpoint {\n domain_name: \"6unit.test.com\"\n port: 48\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" ] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.java index 1499c50729..d5546d248b 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.java @@ -47,6 +47,8 @@ public class NodeUpdateTransactionTest { private static final byte[] TEST_GRPC_CERTIFICATE_HASH = new byte[48]; // SHA-384 hash (48 bytes) + private static final List TEST_ASSOCIATED_REGISTERED_NODES = List.of(1L, 2L); + private static final PublicKey TEST_ADMIN_KEY = PrivateKey.fromString( "302e020100300506032b65700422042062c4b69e9f45a554e5424fb5a6fe5e6ac1f19ead31dc7718c2d980fd1f998d4b") .getPublicKey(); @@ -91,6 +93,7 @@ private NodeUpdateTransaction spawnTestTransaction() { .setMaxTransactionFee(new Hbar(1)) .setDeclineReward(true) .setGrpcWebProxyEndpoint(TEST_GRPC_WEB_PROXY_ENDPOINT) + .setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES) .freeze() .sign(TEST_PRIVATE_KEY); } @@ -618,4 +621,45 @@ void shouldAllowEmptyGrpcCertificateHash() { // Empty is allowed because network will validate it assertThatCode(() -> transaction.setGrpcCertificateHash(new byte[] {})).doesNotThrowAnyException(); } + + @Test + void setAssociatedRegisteredNodes() { + var tx = new NodeCreateTransaction().setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES); + assertThat(tx.getAssociatedRegisteredNodes()).isEqualTo(TEST_ASSOCIATED_REGISTERED_NODES); + } + + @Test + void setAssociatedRegisteredNodesFrozen() { + var tx = spawnTestTransaction(); + assertThrows( + IllegalStateException.class, () -> tx.setAssociatedRegisteredNodes(TEST_ASSOCIATED_REGISTERED_NODES)); + } + + @Test + void setAssociatedRegisteredNodesMoreThan20() { + var tx = new NodeCreateTransaction(); + var nodes = new java.util.ArrayList(); + for (int i = 0; i < 21; i++) { + nodes.add((long) i); + } + + assertThrows(IllegalArgumentException.class, () -> tx.setAssociatedRegisteredNodes(nodes)); + } + + @Test + void addAssociatedRegisteredNode() { + var tx = new NodeCreateTransaction(); + tx.addAssociatedRegisteredNode(1L); + assertThat(tx.getAssociatedRegisteredNodes()).hasSize(1); + assertThat(tx.getAssociatedRegisteredNodes().get(0)).isEqualTo(1); + } + + @Test + void addAssociatedRegisteredNodeMoreThan20() { + var tx = new NodeCreateTransaction(); + for (int i = 0; i < 20; i++) { + tx.addAssociatedRegisteredNode((long) i); + } + assertThrows(IllegalArgumentException.class, () -> tx.addAssociatedRegisteredNode(21L)); + } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.snap index df0510558f..6cd7b6e4d3 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/NodeUpdateTransactionTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.NodeUpdateTransactionTest.shouldSerialize=[ - "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nnode_update {\n account_id {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n decline_reward {\n value: true\n }\n description {\n value: \"Test description\"\n }\n gossip_ca_certificate {\n value: \"\\000\\001\\002\\003\\004\"\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 42\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 43\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 44\n }\n grpc_certificate_hash {\n value: \"\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\"\n }\n grpc_proxy_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n node_id: 420\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 45\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 46\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 47\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 48\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" -] + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nnode_update {\n account_id {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n associated_registered_node_list {\n associated_registered_node: 1\n associated_registered_node: 2\n }\n decline_reward {\n value: true\n }\n description {\n value: \"Test description\"\n }\n gossip_ca_certificate {\n value: \"\\000\\001\\002\\003\\004\"\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 42\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 43\n }\n gossip_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 44\n }\n grpc_certificate_hash {\n value: \"\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\"\n }\n grpc_proxy_endpoint {\n domain_name: \"3unit.test.com\"\n port: 45\n }\n node_id: 420\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 45\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 46\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 47\n }\n service_endpoint {\n ip_address_v4: \"\\000\\001\\002\\003\"\n port: 48\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" +] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java index 1df1771c2c..f78cb6ed39 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.java @@ -1,5 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.google.protobuf.ByteString; import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import io.github.jsonSnapshot.SnapshotMatcher; @@ -7,28 +11,27 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - public class RpcRelayServiceEndpointTest { private final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; private final String TEST_DOMAIN_NAME = "test.mirror.com"; private final int TEST_PORT = 443; private final boolean TEST_REQUIRES_TLS = true; - private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithDomain = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() - .setDomainName(TEST_DOMAIN_NAME) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) - .build(); - - private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithIp = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() - .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) - .build(); + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithDomain = + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) + .build(); + + private final com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint rpcEndpointWithIp = + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()) + .build(); @BeforeAll public static void beforeAll() { @@ -42,24 +45,32 @@ public static void afterAll() { @Test void fromProtobufWithDomain() { - SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain).toString()).toMatchSnapshot(); + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain) + .toString()) + .toMatchSnapshot(); } @Test void toProtobufWithDomain() { - SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithDomain) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test void fromProtobufWithIp() { - SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp).toString()).toMatchSnapshot(); + SnapshotMatcher.expect( + RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp).toString()) + .toMatchSnapshot(); } @Test void toProtobufWithIp() { - SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp).toProtobuf().toString()) - .toMatchSnapshot(); + SnapshotMatcher.expect(RpcRelayServiceEndpoint.fromProtobuf(rpcEndpointWithIp) + .toProtobuf() + .toString()) + .toMatchSnapshot(); } @Test @@ -83,15 +94,13 @@ void setPort() { @Test void setPortThrowsOnNegative() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(-1)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(-1)).isInstanceOf(IllegalArgumentException.class); } @Test void setPortThrowsOnGreaterThan65535() { var endpoint = new BlockNodeServiceEndpoint(); - assertThatThrownBy(() -> endpoint.setPort(65536)) - .isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> endpoint.setPort(65536)).isInstanceOf(IllegalArgumentException.class); } @Test diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index 6fbfaef146..317ffa2a17 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -1,17 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk.test.integration; +import static org.assertj.core.api.Assertions.assertThat; + import com.hedera.hashgraph.sdk.BlockNodeApi; import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; import com.hedera.hashgraph.sdk.Status; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; - public class RegisteredNodeCreateTransactionIntegrationTest { @Test @@ -19,19 +19,17 @@ public class RegisteredNodeCreateTransactionIntegrationTest { void canCreateRegisteredNode() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); - List serviceEndpoints = List.of( - new BlockNodeServiceEndpoint() + List serviceEndpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("test.block.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS) - ); + .setEndpointApi(BlockNodeApi.STATUS)); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); From 81571518090e362a46db6b07084781c2e09f6d4d Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Fri, 13 Mar 2026 18:19:59 +0530 Subject: [PATCH 04/27] feat: added class structure for registeredNodeAdressBook query Signed-off-by: Manish Dait --- .../sdk/RegisteredNodeAddressBookQuery.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java new file mode 100644 index 0000000000..1a6c5e62e9 --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -0,0 +1,29 @@ +package com.hedera.hashgraph.sdk; + +import java.util.concurrent.CompletableFuture; + +public class RegisteredNodeAddressBookQuery { + // TODO: Implementation should be deferred until the mirror node API is available + private String nodeType; + + public RegisteredNodeAddressBookQuery setNodeType(String nodeType) { + this.nodeType = nodeType; + return this; + } + + public String getNodeType() { + return this.nodeType; + } + + public RegisteredNodeAddressBook execute(Client client) { + throw new RuntimeException("Method not implemented"); + } + + private CompletableFuture executeMirrorNodeRequest(Client client) { + throw new RuntimeException("Method not implemented"); + } + + private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String responseBody) { + throw new RuntimeException("Method not implemented"); + } +} From ba6699ea8ccce816fa2f781d9b246524bd617f55 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Fri, 13 Mar 2026 18:44:04 +0530 Subject: [PATCH 05/27] feat: added e2e test for the registeredNodeTx Signed-off-by: Manish Dait --- .../sdk/RegisteredNodeAddressBookQuery.java | 1 + ...dNodeCreateTransactionIntegrationTest.java | 29 ++++++ ...dNodeDeleteTransactionIntegrationTest.java | 49 ++++++++++ ...dNodeUpdateTransactionIntegrationTest.java | 95 +++++++++++++++++++ 4 files changed, 174 insertions(+) create mode 100644 sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java create mode 100644 sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 1a6c5e62e9..8e66d9c13d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; import java.util.concurrent.CompletableFuture; diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index 317ffa2a17..7c6c5dd133 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -5,6 +5,7 @@ import com.hedera.hashgraph.sdk.BlockNodeApi; import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.MirrorNodeServiceEndpoint; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; @@ -37,4 +38,32 @@ void canCreateRegisteredNode() throws Exception { assertThat(receipt.registeredNodeId).isGreaterThan(0); } } + + @Test + @DisplayName("Can create a registered node with multiple service endpoints") + void canCreateRegisteredNodeWithMultipleServiceEndpoints() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + List serviceEndpoints = List.of( + new BlockNodeServiceEndpoint() + .setDomainName("test.block.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.STATUS), + new MirrorNodeServiceEndpoint() + .setDomainName("test.mirror.com") + .setPort(443)); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + assertThat(receipt.registeredNodeId).isGreaterThan(0); + } + } } diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java new file mode 100644 index 0000000000..7a7e424255 --- /dev/null +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk.test.integration; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.hedera.hashgraph.sdk.BlockNodeApi; +import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; +import com.hedera.hashgraph.sdk.RegisteredNodeDeleteTransaction; +import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; +import com.hedera.hashgraph.sdk.Status; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeDeleteTransactionIntegrationTest { + @Test + @DisplayName("Can delete a registered node") + void canDeleteRegisteredNode() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + + List endpoints = List.of(new BlockNodeServiceEndpoint() + .setDomainName("blocks.example.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.STATUS)); + + var createReceipt = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test node delete") + .setServiceEndpoints(endpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + var nodeId = createReceipt.registeredNodeId; + + var deleteReceipt = new RegisteredNodeDeleteTransaction() + .setRegisteredNodeId(nodeId) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + assertThat(deleteReceipt.status).isEqualTo(Status.SUCCESS); + } + } +} diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java new file mode 100644 index 0000000000..504f9a7f22 --- /dev/null +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk.test.integration; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import com.hedera.hashgraph.sdk.BlockNodeApi; +import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; +import com.hedera.hashgraph.sdk.RegisteredNodeUpdateTransaction; +import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; +import com.hedera.hashgraph.sdk.Status; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class RegisteredNodeUpdateTransactionIntegrationTest { + @Test + @DisplayName("Can update registered node endpoints and description") + void canUpdateRegisteredNode() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + + List endpoints = List.of(new BlockNodeServiceEndpoint() + .setDomainName("initial.blocks.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM)); + + var createResponse = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test initial node") + .setServiceEndpoints(endpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client); + + var createReceipt = createResponse.getReceipt(testEnv.client); + + var nodeId = createReceipt.registeredNodeId; + + List updatedEndpoints = List.of(new BlockNodeServiceEndpoint() + .setDomainName("updated.blocks.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.STATUS)); + + var updateResponse = new RegisteredNodeUpdateTransaction() + .setRegisteredNodeId(nodeId) + .setDescription("test updated node") + .setServiceEndpoints(updatedEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); + + var updateReceipt = updateResponse.getReceipt(testEnv.client); + + assertThat(updateReceipt.status).isEqualTo(Status.SUCCESS); + } + } + + @Test + @DisplayName("Can rotate registered node admin key") + void canRotateAdminKey() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var oldKey = PrivateKey.generateED25519(); + + List endpoints = List.of(new BlockNodeServiceEndpoint() + .setDomainName("blocks.example.com") + .setPort(443) + .setEndpointApi(BlockNodeApi.STATUS)); + + var createReceipt = new RegisteredNodeCreateTransaction() + .setAdminKey(oldKey) + .setDescription("test node") + .setServiceEndpoints(endpoints) + .freezeWith(testEnv.client) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + var nodeId = createReceipt.registeredNodeId; + + var newKey = PrivateKey.generateED25519(); + + var tx = new RegisteredNodeUpdateTransaction() + .setRegisteredNodeId(nodeId) + .setAdminKey(newKey) + .freezeWith(testEnv.client); + + tx.sign(oldKey); + tx.sign(newKey); + + var receipt = tx.execute(testEnv.client).getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + } + } +} From 8f03dc2dae9163eb0490c110f4e45a5e25990750 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 23 Mar 2026 17:02:19 +0530 Subject: [PATCH 06/27] chore: change acess modifier Signed-off-by: Manish Dait --- .../com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java index e6ed32cc80..84abbfa0bc 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java @@ -100,7 +100,7 @@ static RegisteredServiceEndpoint fromProtobuf( * * @return the {@link com.google.common.base.MoreObjects.ToStringHelper} */ - protected MoreObjects.ToStringHelper toStringHelper() { + MoreObjects.ToStringHelper toStringHelper() { return MoreObjects.toStringHelper(this) .add("ipAddress", ipAddress) .add("domainName", domainName) From 39a86c360f3fbc97f7ea18a1a31a38ba091c8b79 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 30 Mar 2026 13:57:40 +0530 Subject: [PATCH 07/27] chore: fix tx signing Signed-off-by: Manish Dait --- .../RegisteredNodeCreateTransactionIntegrationTest.java | 2 ++ .../RegisteredNodeDeleteTransactionIntegrationTest.java | 1 + .../RegisteredNodeUpdateTransactionIntegrationTest.java | 2 ++ 3 files changed, 5 insertions(+) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index 7c6c5dd133..bc03579313 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -30,6 +30,7 @@ void canCreateRegisteredNode() throws Exception { .setDescription("test description") .setServiceEndpoints(serviceEndpoints) .freezeWith(testEnv.client) + .sign(key) .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); @@ -58,6 +59,7 @@ void canCreateRegisteredNodeWithMultipleServiceEndpoints() throws Exception { .setDescription("test description") .setServiceEndpoints(serviceEndpoints) .freezeWith(testEnv.client) + .sign(key) .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java index 7a7e424255..3d95f4475d 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java @@ -31,6 +31,7 @@ void canDeleteRegisteredNode() throws Exception { .setDescription("test node delete") .setServiceEndpoints(endpoints) .freezeWith(testEnv.client) + .sign(key) .execute(testEnv.client) .getReceipt(testEnv.client); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java index 504f9a7f22..7c0387e4ad 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java @@ -31,6 +31,7 @@ void canUpdateRegisteredNode() throws Exception { .setDescription("test initial node") .setServiceEndpoints(endpoints) .freezeWith(testEnv.client) + .sign(key) .execute(testEnv.client); var createReceipt = createResponse.getReceipt(testEnv.client); @@ -72,6 +73,7 @@ void canRotateAdminKey() throws Exception { .setDescription("test node") .setServiceEndpoints(endpoints) .freezeWith(testEnv.client) + .sign(oldKey) .execute(testEnv.client) .getReceipt(testEnv.client); From 7c2eaa016b79f1e5369077fb3bc32d82fefec523 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 30 Mar 2026 14:50:18 +0530 Subject: [PATCH 08/27] chore: add initial example Signed-off-by: Manish Dait --- .../RegisteredNodeLifeCycleExample.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java new file mode 100644 index 0000000000..9ffc730ef4 --- /dev/null +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -0,0 +1,145 @@ +package com.hedera.hashgraph.sdk.examples; + +import com.hedera.hashgraph.sdk.AccountId; +import com.hedera.hashgraph.sdk.BlockNodeApi; +import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.Client; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; +import com.hedera.hashgraph.sdk.RegisteredNodeDeleteTransaction; +import com.hedera.hashgraph.sdk.RegisteredNodeUpdateTransaction; +import com.hedera.hashgraph.sdk.TransactionReceipt; +import com.hedera.hashgraph.sdk.TransactionResponse; +import com.hedera.hashgraph.sdk.logger.LogLevel; +import com.hedera.hashgraph.sdk.logger.Logger; +import io.github.cdimascio.dotenv.Dotenv; + +import java.util.List; +import java.util.Objects; + +public class RegisteredNodeLifeCycleExample { + /* + * See .env.sample in the examples folder root for how to specify values below + * or set environment variables with the same names. + */ + + /** + * Operator's account ID. + * Used to sign and pay for operations on Hedera. + */ + private static final AccountId OPERATOR_ID = + AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + + /** + * Operator's private key. + */ + private static final PrivateKey OPERATOR_KEY = + PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + + /** + * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. + * Network can be: localhost, testnet, previewnet or mainnet. + */ + private static final String HEDERA_NETWORK = Dotenv.load().get("HEDERA_NETWORK", "testnet"); + + /** + * SDK_LOG_LEVEL defaults to SILENT if not specified in dotenv file. + * Log levels can be: TRACE, DEBUG, INFO, WARN, ERROR, SILENT. + *

+ * Important pre-requisite: set simple logger log level to same level as the SDK_LOG_LEVEL, + * for example via VM options: -Dorg.slf4j.simpleLogger.log.org.hiero=trace + */ + private static final String SDK_LOG_LEVEL = Dotenv.load().get("SDK_LOG_LEVEL", "SILENT"); + + public static void main(String[] args) throws Exception { + System.out.println("Registered Node Lifecycle Example Start!"); + + /* + * Step 0: + * Create and configure the SDK Client. + */ + Client client = ClientHelper.forName(HEDERA_NETWORK); + // All generated transactions will be paid by this account and signed by this key. + client.setOperator(OPERATOR_ID, OPERATOR_KEY); + // Attach logger to the SDK Client. + client.setLogger(new Logger(LogLevel.valueOf(SDK_LOG_LEVEL))); + + /* + * Step 1: + * Generate an admin key pair and configure a BlockNodeServiceEndpoint + * for use in the RegisterNodeTransaction. + */ + PrivateKey adminKey = PrivateKey.generateED25519(); + BlockNodeServiceEndpoint initialEndpoint = new BlockNodeServiceEndpoint() + .setIpAddress(new byte[]{127, 0, 0, 1}) + .setPort(443) + .setRequiresTls(true) + .setEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); + + /* + * Step 2: + * Create Registered Node. + */ + RegisteredNodeCreateTransaction registeredNodeCreateTx = new RegisteredNodeCreateTransaction() + .setDescription("My Block Node") + .setAdminKey(adminKey) + .addServiceEndpoint(initialEndpoint) + .freezeWith(client) + .sign(adminKey); + + System.out.println("Creating Registered Node..."); + TransactionResponse registeredNodeCreateTxResponse = registeredNodeCreateTx.execute(client); + TransactionReceipt registeredNodeCreateTxReceipt = registeredNodeCreateTxResponse.getReceipt(client); + + if (registeredNodeCreateTxReceipt.registeredNodeId <= 0) { + throw new Exception("RegisteredNodeCreate transaction receipt was missing registeredNodeId. (Fail)"); + } + + /* + * Step 3: + * Execute a RegisteredNodeAddressBookQuery to verify the newly created + * registered node appears in the RegisteredNodeAddressBook. + */ + System.out.println("Skipping registered node address book query because mirror node API is not available..."); + + /* + * Step 4: + * Update the RegisteredNode with new Block Node endpoint. + */ + BlockNodeServiceEndpoint updateEndpoint = new BlockNodeServiceEndpoint() + .setDomainName("block-node.example.com") + .setPort(443) + .setRequiresTls(true) + .setEndpointApi(BlockNodeApi.STATUS); + + RegisteredNodeUpdateTransaction registeredNodeUpdateTx = new RegisteredNodeUpdateTransaction() + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .setDescription("My Updated Block Node") + .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) + .freezeWith(client) + .sign(adminKey); + + System.out.println("Updating Registered Node..."); + TransactionResponse registeredNodeUpdateTxResponse = registeredNodeUpdateTx.execute(client); + TransactionReceipt registeredNodeUpdateTxReceipt = registeredNodeUpdateTxResponse.getReceipt(client); + + /* + * Step 5: + * Add the registeredNodeId as associatedRegisteredNodes to a Node. + */ + // TODO + + /* + * Step 6: + * Delete the Registered Node. + */ + RegisteredNodeDeleteTransaction registeredNodeDeleteTx = new RegisteredNodeDeleteTransaction() + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .freezeWith(client) + .sign(adminKey); + + System.out.println("Deleting Registered Node..."); + TransactionResponse registeredNodeDeleteTxResponse = registeredNodeDeleteTx.execute(client); + registeredNodeDeleteTxResponse.getReceipt(client); + } +} From cb551a62a96bc898d63968bd9f8bd6d4670e88ad Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Tue, 31 Mar 2026 00:32:41 +0530 Subject: [PATCH 09/27] chore: added clearServiceEndpoint method Signed-off-by: Manish Dait --- .../hedera/hashgraph/sdk/NodeUpdateTransaction.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java index c630332c14..df5ddc80f6 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java @@ -319,6 +319,16 @@ public NodeUpdateTransaction addServiceEndpoint(Endpoint serviceEndpoint) { return this; } + /** + * Clear serviceEndpoint lists. + * @return {@code this} + */ + public NodeUpdateTransaction clearServiceEndpoint() { + requireNotFrozen(); + serviceEndpoints.clear(); + return this; + } + /** * Extract the certificate used to sign gossip events. * @return the DER encoding of the certificate presented. From 52f47a9c64c402825b3fd87a9de617c3e984d5cc Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Tue, 31 Mar 2026 13:33:40 +0530 Subject: [PATCH 10/27] chore: completed example Signed-off-by: Manish Dait --- .../RegisteredNodeLifeCycleExample.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java index 9ffc730ef4..d2be153c14 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -4,6 +4,7 @@ import com.hedera.hashgraph.sdk.BlockNodeApi; import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; import com.hedera.hashgraph.sdk.Client; +import com.hedera.hashgraph.sdk.NodeUpdateTransaction; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredNodeDeleteTransaction; @@ -127,19 +128,31 @@ public static void main(String[] args) throws Exception { * Step 5: * Add the registeredNodeId as associatedRegisteredNodes to a Node. */ - // TODO + long registeredNodeId = registeredNodeUpdateTxReceipt.registeredNodeId; + NodeUpdateTransaction associateTx = new NodeUpdateTransaction() + .setNodeId(0) + .addAssociatedRegisteredNode(registeredNodeId) + .freezeWith(client); + + System.out.println("Associating registered node " + registeredNodeId + " with consensus node..."); + TransactionResponse associateTxResponse = associateTx.execute(client); + associateTxResponse.getReceipt(client); + /* * Step 6: * Delete the Registered Node. */ - RegisteredNodeDeleteTransaction registeredNodeDeleteTx = new RegisteredNodeDeleteTransaction() + System.out.println("Deleting Registered Node..."); + new RegisteredNodeDeleteTransaction() .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) .freezeWith(client) - .sign(adminKey); + .sign(adminKey) + .execute(client) + .getReceipt(client); - System.out.println("Deleting Registered Node..."); - TransactionResponse registeredNodeDeleteTxResponse = registeredNodeDeleteTx.execute(client); - registeredNodeDeleteTxResponse.getReceipt(client); + client.close(); + + System.out.println("Registered Node Lifecycle Example Complete!"); } } From e0e2809b0fabb2e692fb4bbc0a7ba3a51e542ddf Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 15 Apr 2026 21:47:21 +0530 Subject: [PATCH 11/27] chore: updated the proto to use the fix protobuf Signed-off-by: Manish Dait --- sdk/src/main/proto/registered_node.proto | 17 -- .../main/proto/registered_node_create.proto | 17 -- .../main/proto/registered_node_update.proto | 20 -- .../proto/registered_service_endpoint.proto | 261 ++++++++++-------- 4 files changed, 141 insertions(+), 174 deletions(-) diff --git a/sdk/src/main/proto/registered_node.proto b/sdk/src/main/proto/registered_node.proto index 8182ecabb8..7bdd96ba08 100644 --- a/sdk/src/main/proto/registered_node.proto +++ b/sdk/src/main/proto/registered_node.proto @@ -64,21 +64,4 @@ message RegisteredNode { * This list SHALL NOT contain more than `50` entries. */ repeated com.hedera.hapi.node.addressbook.RegisteredServiceEndpoint service_endpoint = 4; - - /** - * An account identifier.
- * This account identifies the entity financially responsible for this - * registered node. - *

- * This field is OPTIONAL.
- * Individual node operators SHALL have full authority to set, change, or - * remove their node account ID.
- * If set and the node account ID does not resolve to an existing and active - * account, then the transaction SHALL fail.
- * Node operators SHOULD ensure that the node account is kept up to date - * and always refers to a valid and active `Account`.
- * The owner of the node account MAY be different from the owner of the - * registered node. - */ - proto.AccountID node_account = 5; } diff --git a/sdk/src/main/proto/registered_node_create.proto b/sdk/src/main/proto/registered_node_create.proto index a4e87a12ee..37861550be 100644 --- a/sdk/src/main/proto/registered_node_create.proto +++ b/sdk/src/main/proto/registered_node_create.proto @@ -58,21 +58,4 @@ message RegisteredNodeCreateTransactionBody { * This list MUST NOT contain more than `50` entries. */ repeated RegisteredServiceEndpoint service_endpoint = 3; - - /** - * An account identifier.
- * This account identifies the entity financially responsible for this - * registered node. - *

- * This field is OPTIONAL.
- * Individual node operators SHALL have full authority to set, change, or - * remove their node account ID.
- * If set and the node account ID does not resolve to an existing and active - * account, then the transaction SHALL fail.
- * Node operators SHOULD ensure that the node account is kept up to date - * and always refers to a valid and active `Account`.
- * The owner of the node account MAY be different from the owner of the - * registered node. - */ - proto.AccountID node_account = 4; } diff --git a/sdk/src/main/proto/registered_node_update.proto b/sdk/src/main/proto/registered_node_update.proto index f3f4cc6e7f..b579b7cb64 100644 --- a/sdk/src/main/proto/registered_node_update.proto +++ b/sdk/src/main/proto/registered_node_update.proto @@ -75,24 +75,4 @@ message RegisteredNodeUpdateTransactionBody { * If set, this list SHALL _replace_ the previous list. */ repeated RegisteredServiceEndpoint service_endpoint = 4; - - /** - * An account identifier.
- * This account identifies the entity financially responsible for this - * registered node. - *

- * This field is OPTIONAL.
- * If not set, the field SHALL NOT be updated.
- * Individual node operators SHALL have full authority to set, change, or - * remove their node account ID.
- * If set and the node account ID does not resolve to an existing and active - * account, then the transaction SHALL fail.
- * Node operators SHOULD ensure that the node account is kept up to date - * and always refers to a valid and active `Account`.
- * The owner of the node account MAY be different from the owner of the - * registered node.
- * If set to account ID `0.0.0`, the node account SHALL be removed from - * this registered node. - */ - proto.AccountID node_account = 5; } diff --git a/sdk/src/main/proto/registered_service_endpoint.proto b/sdk/src/main/proto/registered_service_endpoint.proto index 2415b4c1ac..4ff6558625 100644 --- a/sdk/src/main/proto/registered_service_endpoint.proto +++ b/sdk/src/main/proto/registered_service_endpoint.proto @@ -17,144 +17,165 @@ option java_multiple_files = true; * or MAY include a FQDN _instead of_ an IP address.
*/ message RegisteredServiceEndpoint { - /** - * An IP address or fully qualified domain name. - *

- * This oneof is REQUIRED. - */ - oneof address { /** - * A 32-bit IPv4 address or 128-bit IPv6 address.
- * This is the address of the endpoint, encoded in pure "big-endian" - * (i.e. left to right) order (e.g. IPv4 address `127.0.0.1` has - * hex bytes in the order `7F`, `00`, `00`, `01`.
- * IPv6 address `::1` has hex bytes `00`, `00`, `00`, `00`, `00`, `00`, - * `00`, `00`, `00`, `00`, `00`, `00`, `00`, `00`, `00`, `01`). + * An IP address or fully qualified domain name. + *

+ * This oneof is REQUIRED. */ - bytes ip_address = 1; + oneof address { + /** + * A 32-bit IPv4 address or 128-bit IPv6 address.
+ * This is the address of the endpoint, encoded in pure "big-endian" + * (i.e. left to right) order (e.g. IPv4 address `127.0.0.1` has + * hex bytes in the order `7F`, `00`, `00`, `01`.
+ * IPv6 address `::1` has hex bytes `00`, `00`, `00`, `00`, `00`, `00`, + * `00`, `00`, `00`, `00`, `00`, `00`, `00`, `00`, `00`, `01`). + */ + bytes ip_address = 1; + + /** + * A node domain name. + *

+ * This MUST be the fully qualified domain name of the node.
+ * This value MUST NOT exceed 250 ASCII characters.
+ * This value MUST meet all other DNS name requirements. + */ + string domain_name = 2; + } /** - * A node domain name. + * A network port to use. *

- * This MUST be the fully qualified domain name of the node.
- * This value MUST NOT exceed 250 ASCII characters.
- * This value MUST meet all other DNS name requirements. + * This value MUST be between 0 and 65535, inclusive.
+ * This value is REQUIRED. */ - string domain_name = 2; - } - - /** - * A network port to use. - *

- * This value MUST be between 0 and 65535, inclusive.
- * This value is REQUIRED. - */ - uint32 port = 3; - - /** - * A flag indicating if this endpoint requires TLS. - *

- * If this value is set true, then connections to this endpoint MUST - * enable TLS. - *

- * TLS endpoints MAY use self-signed certificates for this purpose, - * but use of self-signed certificates SHOULD be limited to testing and - * development environments to ensure production environments meet all - * expected characteristics for transport layer security. - */ - bool requires_tls = 4; - - /** - * An endpoint type.
- * This declares the type of registered node endpoint and includes any - * type-specific fields. - *

- * This oneof is REQUIRED. - */ - oneof endpoint_type { + uint32 port = 3; + /** - * A Block Node.
- * A Block Node stores the block chain, provides content proof services, - * and delivers the block stream to other clients. + * A flag indicating if this endpoint requires TLS. + *

+ * If this value is set true, then connections to this endpoint MUST + * enable TLS. + *

+ * TLS endpoints MAY use self-signed certificates for this purpose, + * but use of self-signed certificates SHOULD be limited to testing and + * development environments to ensure production environments meet all + * expected characteristics for transport layer security. */ - BlockNodeEndpoint block_node = 5; + bool requires_tls = 4; /** - * A Mirror Node.
- * A Mirror Node is an advanced indexing and query service that provides - * fast and flexible access to query the block chain and transaction - * history. A Mirror Node typically stores all recent blockchain data, - * and some store the entire history of the network. + * An endpoint type.
+ * This declares the type of registered node endpoint and includes any + * type-specific fields. + *

+ * This oneof is REQUIRED. */ - MirrorNodeEndpoint mirror_node = 6; + oneof endpoint_type { + /** + * A Block Node.
+ * A Block Node stores the block chain, provides content proof services, + * and delivers the block stream to other clients. + */ + BlockNodeEndpoint block_node = 5; + + /** + * A Mirror Node.
+ * A Mirror Node is an advanced indexing and query service that provides + * fast and flexible access to query the block chain and transaction + * history. A Mirror Node typically stores all recent blockchain data, + * and some store the entire history of the network. + */ + MirrorNodeEndpoint mirror_node = 6; + + /** + * A RPC Relay.
+ * A RPC Relay is a proxy and translator between EVM tooling and a + * Hiero consensus network. + */ + RpcRelayEndpoint rpc_relay = 7; + + /** + * A general service.
+ * A general service endpoint represents any network accessible service + * that is provided by a registered node but that is not a service + * currently defined as part of the Hiero Ledger system. + */ + GeneralServiceEndpoint general_service = 8; + } /** - * A RPC Relay.
- * A RPC Relay is a proxy and translator between EVM tooling and a - * Hiero consensus network. + * A message indicating this endpoint is a Block Node endpoint.
+ * Block Node endpoints offer one of several block node APIs, so this + * endpoint entry also declares which well-known Block Node API is + * supported by this endpoint, or "OTHER" for less common APIs. */ - RpcRelayEndpoint rpc_relay = 7; - } - - /** - * A message indicating this endpoint is a Block Node endpoint.
- * Block Node endpoints offer one of several block node APIs, so this - * endpoint entry also declares which well-known Block Node API is - * supported by this endpoint, or "OTHER" for less common APIs. - */ - message BlockNodeEndpoint { + message BlockNodeEndpoint { + /** + * An indicator of what API this endpoint supports. + *

+ * This field is REQUIRED. + */ + repeated BlockNodeApi endpoint_api = 1; + + /** + * An enumeration of well-known block node endpoint APIs, and a + * catch-all option for otherwise unknown endpoint APIs. + */ + enum BlockNodeApi { + /** + * Any other API type associated with a block node.
+ * The caller must consult local documentation to determine the + * correct call semantics.
+ * It is RECOMMENDED to call the detail status endpoint for further + * information before using this endpoint. + */ + OTHER = 0; + + /** + * The Block Node Status API. + */ + STATUS = 1; + + /** + * The Block Node Publish API. + */ + PUBLISH = 2; + + /** + * The Block Node Subscribe Stream API. + */ + SUBSCRIBE_STREAM = 3; + + /** + * The Block Node State Proof API. + */ + STATE_PROOF = 4; + } + } + /** - * An indicator of what API this endpoint supports. - *

- * This field is REQUIRED. + * A message indicating this endpoint is a Mirror Node endpoint. */ - BlockNodeApi endpoint_api = 1; + message MirrorNodeEndpoint { + } + + /** + * A message indicating this endpoint is a RPC Relay endpoint. + */ + message RpcRelayEndpoint { + } /** - * An enumeration of well-known block node endpoint APIs, and a - * catch-all option for otherwise unknown endpoint APIs. + * A message indicating this endpoint is a General Service endpoint. */ - enum BlockNodeApi { - /** - * Any other API type associated with a block node.
- * The caller must consult local documentation to determine the - * correct call semantics.
- * It is RECOMMENDED to call the detail status endpoint for further - * information before using this endpoint. - */ - OTHER = 0; - - /** - * The Block Node Status API. - */ - STATUS = 1; - - /** - * The Block Node Publish API. - */ - PUBLISH = 2; - - /** - * The Block Node Subscribe Stream API. - */ - SUBSCRIBE_STREAM = 3; - - /** - * The Block Node State Proof API. - */ - STATE_PROOF = 4; + message GeneralServiceEndpoint { + /** + * A short description of the service provided. + *

+ * This value, if set, MUST NOT exceed 100 bytes when encoded as UTF-8.
+ * This field is OPTIONAL. + */ + string description = 1; } - } - - /** - * A message indicating this endpoint is a Mirror Node endpoint. - */ - message MirrorNodeEndpoint { - } - - /** - * A message indicating this endpoint is a RPC Relay endpoint. - */ - message RpcRelayEndpoint { - } } From 3a30c6d22c97dcf6701674d1b7c7281ec3792a74 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 15 Apr 2026 23:51:00 +0530 Subject: [PATCH 12/27] feat: updated the hip impl for protobuf changes Signed-off-by: Manish Dait --- .../sdk/BlockNodeServiceEndpoint.java | 44 +++++++++---- .../hashgraph/sdk/GeneralServiceEndpoint.java | 64 +++++++++++++++++++ .../hedera/hashgraph/sdk/RegisteredNode.java | 26 +------- .../sdk/RegisteredNodeCreateTransaction.java | 48 +------------- .../sdk/RegisteredNodeUpdateTransaction.java | 47 +------------- .../sdk/BlockNodeServiceEndpointTest.java | 32 ++++++++-- .../sdk/BlockNodeServiceEndpointTest.snap | 8 +-- .../RegisteredNodeCreateTransactionTest.java | 17 +---- .../RegisteredNodeCreateTransactionTest.snap | 2 +- .../hashgraph/sdk/RegisteredNodeTest.java | 4 +- .../hashgraph/sdk/RegisteredNodeTest.snap | 8 +-- .../RegisteredNodeUpdateTransactionTest.java | 17 +---- .../RegisteredNodeUpdateTransactionTest.snap | 2 +- ...dNodeCreateTransactionIntegrationTest.java | 4 +- ...dNodeDeleteTransactionIntegrationTest.java | 2 +- ...dNodeUpdateTransactionIntegrationTest.java | 7 +- 16 files changed, 145 insertions(+), 187 deletions(-) create mode 100644 sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java index 2e88c60bd5..c123401e36 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -2,6 +2,9 @@ package com.hedera.hashgraph.sdk; import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; /** @@ -11,19 +14,31 @@ public class BlockNodeServiceEndpoint extends RegisteredServiceEndpointBase endpointApis = new ArrayList<>(); /** * Constructor. */ public BlockNodeServiceEndpoint() {} - public BlockNodeApi getEndpointApi() { - return endpointApi; + public List getEndpointApis() { + return endpointApis; } - public BlockNodeServiceEndpoint setEndpointApi(BlockNodeApi endpointApi) { - this.endpointApi = endpointApi; + public BlockNodeServiceEndpoint setEndpointApis(List endpointApis) { + Objects.requireNonNull(endpointApis, "endpointApis must not be null"); + this.endpointApis = new ArrayList<>(endpointApis); + return this; + } + + public BlockNodeServiceEndpoint addEndpointApi(BlockNodeApi endpointApi) { + Objects.requireNonNull(endpointApi, "endpointApi must not be null"); + this.endpointApis.add(endpointApi); + return this; + } + + public BlockNodeServiceEndpoint clearEndpointApis() { + endpointApis.clear(); return this; } @@ -39,9 +54,13 @@ static BlockNodeServiceEndpoint fromProtobuf( var blockNodeEndpoint = new BlockNodeServiceEndpoint() .setPort(serviceEndpoint.getPort()) - .setRequiresTls(serviceEndpoint.getRequiresTls()) - .setEndpointApi( - BlockNodeApi.valueOf(serviceEndpoint.getBlockNode().getEndpointApi())); + .setRequiresTls(serviceEndpoint.getRequiresTls()); + + if (serviceEndpoint.hasBlockNode()) { + for (var apiProto : serviceEndpoint.getBlockNode().getEndpointApiList()) { + blockNodeEndpoint.addEndpointApi(BlockNodeApi.valueOf(apiProto)); + } + } if (serviceEndpoint.hasIpAddress()) { blockNodeEndpoint.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); @@ -61,12 +80,13 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { "RegisterServiceEndpoint must define either an ipAddress or domainName"); } + var blockNodeBuilder = RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .addAllEndpointApi(endpointApis.stream().map(api -> api.code).toList()); + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() .setPort(port) .setRequiresTls(requiresTls) - .setBlockNode(com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() - .setEndpointApi(endpointApi.code) - .build()); + .setBlockNode(blockNodeBuilder); if (ipAddress != null) { registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(this.ipAddress)); @@ -81,6 +101,6 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { @Override public String toString() { - return toStringHelper().add("endpointApi", endpointApi).toString(); + return toStringHelper().add("endpointApis", endpointApis).toString(); } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java new file mode 100644 index 0000000000..bed207d7cf --- /dev/null +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import com.google.protobuf.ByteString; +import java.util.Objects; +import javax.annotation.Nullable; + +public class GeneralServiceEndpoint extends RegisteredServiceEndpointBase { + @Nullable + private String description; + + public GeneralServiceEndpoint() {} + + public GeneralServiceEndpoint setDescription(String description) { + this.description = description; + return this; + } + + static GeneralServiceEndpoint fromProtobuf( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); + + var generalEndpoint = new GeneralServiceEndpoint() + .setPort(serviceEndpoint.getPort()) + .setRequiresTls(serviceEndpoint.getRequiresTls()) + .setDescription(serviceEndpoint.getGeneralService().getDescription()); + + if (serviceEndpoint.hasIpAddress()) { + generalEndpoint.setIpAddress(serviceEndpoint.getIpAddress().toByteArray()); + } + + if (serviceEndpoint.hasDomainName()) { + generalEndpoint.setDomainName(serviceEndpoint.getDomainName()); + } + + return generalEndpoint; + } + + @Override + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { + if (ipAddress == null && domainName == null) { + throw new IllegalArgumentException( + "RegisterServiceEndpoint must define either an ipAddress or domainName"); + } + + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() + .setPort(port) + .setRequiresTls(requiresTls) + .setGeneralService( + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder() + .setDescription(this.description) + .build()); + + if (ipAddress != null) { + registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(this.ipAddress)); + } + + if (domainName != null) { + registeredServiceEndpoint.setDomainName(this.domainName); + } + + return registeredServiceEndpoint.build(); + } +} diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java index 8ca1281326..534b6b0f24 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Objects; import javax.annotation.Nonnegative; -import javax.annotation.Nullable; /** * Class representing single registered node in the network state. @@ -38,14 +37,6 @@ public class RegisteredNode { */ public final List serviceEndpoints; - /** - * An account identifier. - * This account identifies the entity financially responsible for this - * registered node. - */ - @Nullable - public final AccountId nodeAccount; - /** * Constructor. * @@ -53,19 +44,13 @@ public class RegisteredNode { * @param adminKey the admin key. * @param description the description of the node. * @param serviceEndpoint the list of service endpoints. - * @param nodeAccount the account identifier. */ RegisteredNode( - long registeredNodeId, - Key adminKey, - String description, - List serviceEndpoint, - @Nullable AccountId nodeAccount) { + long registeredNodeId, Key adminKey, String description, List serviceEndpoint) { this.registeredNodeId = registeredNodeId; this.adminKey = adminKey; this.description = description; this.serviceEndpoints = Collections.unmodifiableList(serviceEndpoint); - this.nodeAccount = nodeAccount; } /** @@ -83,10 +68,8 @@ static RegisteredNode fromProtobuf(com.hedera.hashgraph.sdk.proto.RegisteredNode var serviceEndpoint = registeredNode.getServiceEndpointList().stream() .map(s -> RegisteredServiceEndpoint.fromProtobuf(s)) .toList(); - var nodeAccount = - registeredNode.hasNodeAccount() ? AccountId.fromProtobuf(registeredNode.getNodeAccount()) : null; - return new RegisteredNode(registerNodeId, adminKey, description, serviceEndpoint, nodeAccount); + return new RegisteredNode(registerNodeId, adminKey, description, serviceEndpoint); } /** @@ -111,10 +94,6 @@ com.hedera.hashgraph.sdk.proto.RegisteredNode toProtobuf() { .setAdminKey(adminKey.toProtobufKey()) .setDescription(description); - if (nodeAccount != null) { - registeredNode.setNodeAccount(nodeAccount.toProtobuf()); - } - for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { registeredNode.addServiceEndpoint(serviceEndpoint.toProtobuf()); } @@ -136,7 +115,6 @@ public String toString() { .add("registeredNodeId", registeredNodeId) .add("adminKey", adminKey) .add("description", description) - .add("nodeAccount", nodeAccount) .add("serviceEndpoints", serviceEndpoints) .toString(); } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java index c0f5d42bc6..cb153fee39 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransaction.java @@ -29,9 +29,6 @@ public class RegisteredNodeCreateTransaction extends Transaction serviceEndpoints = new ArrayList<>(); - @Nullable - private AccountId nodeAccount; - /** * Constructor. */ @@ -174,37 +171,6 @@ public RegisteredNodeCreateTransaction addServiceEndpoint(RegisteredServiceEndpo return this; } - /** Get account identifier for the registered node. - * @return {@code AccountId} the account identifier if set or null - */ - public @Nullable AccountId getNodeAccount() { - return nodeAccount; - } - - /** - * Set account identifier.
- * This account identifies the entity financially responsible for this - * registered node. - *

- * This field is OPTIONAL.
- * Individual node operators SHALL have full authority to set, change, or - * remove their node account ID.
- * If set and the node account ID does not resolve to an existing and active - * account, then the transaction SHALL fail.
- * Node operators SHOULD ensure that the node account is kept up to date - * and always refers to a valid and active `Account`.
- * The owner of the node account MAY be different from the owner of the - * registered node. - * - * @param nodeAccount the account identifier. - * @return {@code this} - */ - public RegisteredNodeCreateTransaction setNodeAccount(@Nullable AccountId nodeAccount) { - this.requireNotFrozen(); - this.nodeAccount = nodeAccount; - return this; - } - /** * Build the transaction body. * @return {@link com.hedera.hashgraph.sdk.proto.RegisteredNodeCreateTransactionBody} @@ -216,10 +182,6 @@ RegisteredNodeCreateTransactionBody.Builder build() { builder.setAdminKey(adminKey.toProtobufKey()); } - if (nodeAccount != null) { - builder.setNodeAccount(nodeAccount.toProtobuf()); - } - for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); } @@ -239,10 +201,6 @@ void initFromTransactionBody() { description = body.getDescription(); - if (body.hasNodeAccount()) { - nodeAccount = AccountId.fromProtobuf(body.getNodeAccount()); - } - serviceEndpoints.clear(); for (com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint : body.getServiceEndpointList()) { serviceEndpoints.add(RegisteredServiceEndpoint.fromProtobuf(serviceEndpoint)); @@ -250,11 +208,7 @@ void initFromTransactionBody() { } @Override - void validateChecksums(Client client) throws BadEntityIdException { - if (nodeAccount != null) { - nodeAccount.validateChecksum(client); - } - } + void validateChecksums(Client client) throws BadEntityIdException {} @Override MethodDescriptor getMethodDescriptor() { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java index 664b9dace4..c800436d2b 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransaction.java @@ -34,9 +34,6 @@ public class RegisteredNodeUpdateTransaction extends Transaction serviceEndpoints = new ArrayList<>(); - @Nullable - private AccountId nodeAccount; - /** * Constructor. */ @@ -208,37 +205,6 @@ public RegisteredNodeUpdateTransaction addServiceEndpoint(RegisteredServiceEndpo return this; } - /** Get account identifier for the registered node. - * @return {@code AccountId} the account identifier if set or null - */ - public @Nullable AccountId getNodeAccount() { - return nodeAccount; - } - - /** - * Set account identifier.
- * This account identifies the entity financially responsible for this - * registered node. - *

- * This field is OPTIONAL.
- * Individual node operators SHALL have full authority to set, change, or - * remove their node account ID.
- * If set and the node account ID does not resolve to an existing and active - * account, then the transaction SHALL fail.
- * Node operators SHOULD ensure that the node account is kept up to date - * and always refers to a valid and active `Account`.
- * The owner of the node account MAY be different from the owner of the - * registered node. - * - * @param nodeAccount the account identifier. - * @return {@code this} - */ - public RegisteredNodeUpdateTransaction setNodeAccount(AccountId nodeAccount) { - this.requireNotFrozen(); - this.nodeAccount = nodeAccount; - return this; - } - /** * Build the transaction body. * @return {@link com.hedera.hashgraph.sdk.proto.RegisteredNodeUpdateTransactionBody} @@ -258,10 +224,6 @@ RegisteredNodeUpdateTransactionBody.Builder build() { builder.setDescription(StringValue.of(description)); } - if (nodeAccount != null) { - builder.setNodeAccount(nodeAccount.toProtobuf()); - } - for (RegisteredServiceEndpoint serviceEndpoint : serviceEndpoints) { builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); } @@ -283,9 +245,6 @@ void initFromTransactionBody() { if (body.hasDescription()) { description = body.getDescription().getValue(); } - if (body.hasNodeAccount()) { - nodeAccount = AccountId.fromProtobuf(body.getNodeAccount()); - } serviceEndpoints.clear(); for (com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint serviceEndpoint : body.getServiceEndpointList()) { @@ -304,11 +263,7 @@ void onScheduled(SchedulableTransactionBody.Builder scheduled) { } @Override - void validateChecksums(Client client) throws BadEntityIdException { - if (nodeAccount != null) { - nodeAccount.validateChecksum(client); - } - } + void validateChecksums(Client client) throws BadEntityIdException {} @Override MethodDescriptor getMethodDescriptor() { diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java index 7b90f54a8c..8ca291809e 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java @@ -7,6 +7,7 @@ import com.google.protobuf.ByteString; import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import io.github.jsonSnapshot.SnapshotMatcher; +import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -16,14 +17,14 @@ public class BlockNodeServiceEndpointTest { private final String TEST_DOMAIN_NAME = "test.block.com"; private final int TEST_PORT = 443; private final boolean TEST_REQUIRES_TLS = true; - private final BlockNodeApi TEST_BLOCK_API = BlockNodeApi.STATUS; + private final List TEST_BLOCK_APIS = List.of(BlockNodeApi.STATUS); private final RegisteredServiceEndpoint blockNodeEndpointWithDomain = RegisteredServiceEndpoint.newBuilder() .setDomainName(TEST_DOMAIN_NAME) .setPort(TEST_PORT) .setRequiresTls(TEST_REQUIRES_TLS) - .setBlockNode( - RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder().setEndpointApi(TEST_BLOCK_API.code)) + .setBlockNode(RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() + .addAllEndpointApi(TEST_BLOCK_APIS.stream().map(e -> e.code).toList())) .build(); private final RegisteredServiceEndpoint blockNodeEndpointWithIp = RegisteredServiceEndpoint.newBuilder() @@ -31,7 +32,7 @@ public class BlockNodeServiceEndpointTest { .setPort(TEST_PORT) .setRequiresTls(TEST_REQUIRES_TLS) .setBlockNode(RegisteredServiceEndpoint.BlockNodeEndpoint.newBuilder() - .setEndpointApi(RegisteredServiceEndpoint.BlockNodeEndpoint.BlockNodeApi.STATUS)) + .addAllEndpointApi(TEST_BLOCK_APIS.stream().map(e -> e.code).toList())) .build(); @BeforeAll @@ -111,8 +112,25 @@ void setRequiresTls() { } @Test - void setEndpointApi() { - var endpoint = new BlockNodeServiceEndpoint().setEndpointApi(TEST_BLOCK_API); - assertThat(endpoint.getEndpointApi()).isEqualTo(TEST_BLOCK_API); + void setEndpointApis() { + var endpoint = new BlockNodeServiceEndpoint().setEndpointApis(TEST_BLOCK_APIS); + assertThat(endpoint.getEndpointApis()).isEqualTo(TEST_BLOCK_APIS); + } + + @Test + void addEndpointApi() { + var endpoint = new BlockNodeServiceEndpoint() + .addEndpointApi(BlockNodeApi.STATUS) + .addEndpointApi(BlockNodeApi.OTHER); + + assertThat(endpoint.getEndpointApis()).containsExactly(BlockNodeApi.STATUS, BlockNodeApi.OTHER); + } + + @Test + void clearEndpointApis() { + var endpoint = + new BlockNodeServiceEndpoint().setEndpointApis(TEST_BLOCK_APIS).clearEndpointApis(); + + assertThat(endpoint.getEndpointApis()).isEmpty(); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap index 9f9127bf90..b91a3674c3 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.snap @@ -1,18 +1,18 @@ com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.fromProtobufWithDomain=[ - "BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}" + "BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApis=[STATUS]}" ] com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.fromProtobufWithIp=[ - "BlockNodeServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=443, requiresTls=true, endpointApi=STATUS}" + "BlockNodeServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=443, requiresTls=true, endpointApis=[STATUS]}" ] com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.toProtobufWithDomain=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@855c97bd\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\ndomain_name: \"test.block.com\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@855debe4\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\ndomain_name: \"test.block.com\"\nport: 443\nrequires_tls: true" ] com.hedera.hashgraph.sdk.BlockNodeServiceEndpointTest.toProtobufWithIp=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@86e941f\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@86fe846\nblock_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n}\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" ] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java index 388eac13db..b0193e7517 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.java @@ -39,7 +39,7 @@ static RegisteredServiceEndpoint spawnTestEndpoint(byte offset) { .setDomainName("example.block.com") .setPort(443 + offset) .setRequiresTls(true) - .setEndpointApi(BlockNodeApi.STATUS); + .addEndpointApi(BlockNodeApi.STATUS); } RegisteredNodeCreateTransaction spawnTestTransaction() { @@ -49,7 +49,6 @@ RegisteredNodeCreateTransaction spawnTestTransaction() { .setAdminKey(TEST_ADMIN_KEY) .setDescription(TEST_DESCRIPTION) .setServiceEndpoints(TEST_SERVICE_ENDPOINT) - .setNodeAccount(TEST_ACCOUNT_ID) .setMaxTransactionFee(new Hbar(1)) .freeze() .sign(TEST_PRIVATE_KEY); @@ -179,25 +178,12 @@ void addServiceEndpointRejectsMoreThan50() { assertThrows(IllegalArgumentException.class, () -> tx.addServiceEndpoint(spawnTestEndpoint((byte) 50))); } - @Test - void setNodeAccount() { - var tx = new RegisteredNodeCreateTransaction().setNodeAccount(TEST_ACCOUNT_ID); - assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); - } - - @Test - void setNodeAccountFrozen() { - var tx = spawnTestTransaction(); - assertThrows(IllegalStateException.class, () -> tx.setNodeAccount(TEST_ACCOUNT_ID)); - } - @Test void constructRegisteredNodeCreateTransactionFromTransactionBodyProtobuf() { var transactionBodyBuilder = RegisteredNodeCreateTransactionBody.newBuilder(); transactionBodyBuilder.setAdminKey(TEST_ADMIN_KEY.toProtobufKey()); transactionBodyBuilder.setDescription(TEST_DESCRIPTION); - transactionBodyBuilder.setNodeAccount(TEST_ACCOUNT_ID.toProtobuf()); for (RegisteredServiceEndpoint serviceEndpoint : TEST_SERVICE_ENDPOINT) { transactionBodyBuilder.addServiceEndpoint(serviceEndpoint.toProtobuf()); @@ -211,7 +197,6 @@ void constructRegisteredNodeCreateTransactionFromTransactionBodyProtobuf() { assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); - assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); } @Test diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap index c2bc2ea1bc..da4a6c84e6 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeCreateTransactionTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.RegisteredNodeCreateTransactionTest.shouldSerialize=[ - "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_create {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description: \"Test description\"\n node_account {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_create {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description: \"Test description\"\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" ] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java index 7fe8ce892b..b53e1124cd 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java @@ -12,7 +12,7 @@ public class RegisteredNodeTest { "302e020100300506032b657004220420db484b828e64b2d8f12ce3c0a0e93a0b8cce7af1bb8f39c97732394482538e10"); private final BlockNodeServiceEndpoint serviceEndpoint = new BlockNodeServiceEndpoint() - .setEndpointApi(BlockNodeApi.STATUS) + .addEndpointApi(BlockNodeApi.STATUS) .setPort(443) .setDomainName("test.block.com") .setRequiresTls(true); @@ -23,7 +23,6 @@ public class RegisteredNodeTest { .setDescription("Unit test registered node") .setAdminKey(privateKey.getPublicKey().toProtobufKey()) .addServiceEndpoint(serviceEndpoint.toProtobuf()) - .setNodeAccount(new AccountId(0, 0, 4).toProtobuf()) .build(); @BeforeAll @@ -56,6 +55,7 @@ void toBytes() throws InvalidProtocolBufferException { .toMatchSnapshot(); } + @Test void toProtobuf() throws InvalidProtocolBufferException { SnapshotMatcher.expect( diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap index ccab1b60d8..b1bcf59d52 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.snap @@ -1,18 +1,18 @@ com.hedera.hashgraph.sdk.RegisteredNodeTest.fromBytes=[ - "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, nodeAccount=0.0.4, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}]}" + "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApis=[STATUS]}]}" ] com.hedera.hashgraph.sdk.RegisteredNodeTest.fromProtobuf=[ - "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, nodeAccount=0.0.4, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApi=STATUS}]}" + "RegisteredNode{registeredNodeId=1, adminKey=302a300506032b6570032100e0c8ec2758a5879ffac226a13c0c516b799e72e35141a0dd828f94d37988a4b7, description=Unit test registered node, serviceEndpoints=[BlockNodeServiceEndpoint{ipAddress=null, domainName=test.block.com, port=443, requiresTls=true, endpointApis=[STATUS]}]}" ] com.hedera.hashgraph.sdk.RegisteredNodeTest.toBytes=[ - "CAESIhIg4MjsJ1ilh5/6wiahPAxRa3mecuNRQaDdgo+U03mIpLcaGVVuaXQgdGVzdCByZWdpc3RlcmVkIG5vZGUiGRIOdGVzdC5ibG9jay5jb20YuwMgASoCCAEqAhgE" + "CAESIhIg4MjsJ1ilh5/6wiahPAxRa3mecuNRQaDdgo+U03mIpLcaGVVuaXQgdGVzdCByZWdpc3RlcmVkIG5vZGUiGhIOdGVzdC5ibG9jay5jb20YuwMgASoDCgEB" ] com.hedera.hashgraph.sdk.RegisteredNodeTest.toProtobuf=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredNode@ab45a662\nadmin_key {\n ed25519: \"\\340\\310\\354\\'X\\245\\207\\237\\372\\302&\\241<\\fQky\\236r\\343QA\\240\\335\\202\\217\\224\\323y\\210\\244\\267\"\n}\ndescription: \"Unit test registered node\"\nnode_account {\n account_num: 4\n realm_num: 0\n shard_num: 0\n}\nregistered_node_id: 1\nservice_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"test.block.com\"\n port: 443\n requires_tls: true\n}" + "# com.hedera.hashgraph.sdk.proto.RegisteredNode@7c42c1c6\nadmin_key {\n ed25519: \"\\340\\310\\354\\'X\\245\\207\\237\\372\\302&\\241<\\fQky\\236r\\343QA\\240\\335\\202\\217\\224\\323y\\210\\244\\267\"\n}\ndescription: \"Unit test registered node\"\nregistered_node_id: 1\nservice_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"test.block.com\"\n port: 443\n requires_tls: true\n}" ] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java index 62b6a0daac..05230b8bf1 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.java @@ -42,7 +42,7 @@ static RegisteredServiceEndpoint spawnTestEndpoint(byte offset) { .setDomainName("example.block.com") .setPort(443 + offset) .setRequiresTls(true) - .setEndpointApi(BlockNodeApi.STATUS); + .addEndpointApi(BlockNodeApi.STATUS); } RegisteredNodeUpdateTransaction spawnTestTransaction() { @@ -53,7 +53,6 @@ RegisteredNodeUpdateTransaction spawnTestTransaction() { .setAdminKey(TEST_ADMIN_KEY) .setDescription(TEST_DESCRIPTION) .setServiceEndpoints(TEST_SERVICE_ENDPOINT) - .setNodeAccount(TEST_ACCOUNT_ID) .setMaxTransactionFee(new Hbar(1)) .freeze() .sign(TEST_PRIVATE_KEY); @@ -209,18 +208,6 @@ void addServiceEndpointRejectsMoreThan50() { assertThrows(IllegalArgumentException.class, () -> tx.addServiceEndpoint(spawnTestEndpoint((byte) 50))); } - @Test - void setNodeAccount() { - var tx = new RegisteredNodeUpdateTransaction().setNodeAccount(TEST_ACCOUNT_ID); - assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); - } - - @Test - void setNodeAccountFrozen() { - var tx = spawnTestTransaction(); - assertThrows(IllegalStateException.class, () -> tx.setNodeAccount(TEST_ACCOUNT_ID)); - } - @Test void constructRegisteredNodeUpdateTransactionFromTransactionBodyProtobuf() { var transactionBodyBuilder = RegisteredNodeUpdateTransactionBody.newBuilder(); @@ -228,7 +215,6 @@ void constructRegisteredNodeUpdateTransactionFromTransactionBodyProtobuf() { transactionBodyBuilder.setRegisteredNodeId(TEST_REGISTERED_NODE_ID); transactionBodyBuilder.setAdminKey(TEST_ADMIN_KEY.toProtobufKey()); transactionBodyBuilder.setDescription(StringValue.of(TEST_DESCRIPTION)); - transactionBodyBuilder.setNodeAccount(TEST_ACCOUNT_ID.toProtobuf()); for (RegisteredServiceEndpoint serviceEndpoint : TEST_SERVICE_ENDPOINT) { transactionBodyBuilder.addServiceEndpoint(serviceEndpoint.toProtobuf()); @@ -243,7 +229,6 @@ void constructRegisteredNodeUpdateTransactionFromTransactionBodyProtobuf() { assertThat(tx.getAdminKey()).isEqualTo(TEST_ADMIN_KEY); assertThat(tx.getDescription()).isEqualTo(TEST_DESCRIPTION); assertThat(tx.getServiceEndpoints()).hasSize(TEST_SERVICE_ENDPOINT.size()); - assertThat(tx.getNodeAccount()).isEqualTo(TEST_ACCOUNT_ID); } @Test diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap index 7e27605c0e..4166040302 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeUpdateTransactionTest.snap @@ -1,3 +1,3 @@ com.hedera.hashgraph.sdk.RegisteredNodeUpdateTransactionTest.shouldSerialize=[ - "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_update {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description {\n value: \"Test description\"\n }\n node_account {\n account_num: 9\n realm_num: 6\n shard_num: 0\n }\n registered_node_id: 43\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" + "# com.hedera.hashgraph.sdk.proto.TransactionBody\nnode_account_i_d {\n account_num: 5005\n realm_num: 0\n shard_num: 0\n}\nregistered_node_update {\n admin_key {\n ed25519: \"\\030\\214\\252`\\231\\024#O\\b\\370\\342\\232\\321g\\274\\273\\346\\221}\\211m\\244R\\306\\017\\230\\017j,\\246\\206\\001\"\n }\n description {\n value: \"Test description\"\n }\n registered_node_id: 43\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 443\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 444\n requires_tls: true\n }\n service_endpoint {\n block_node {\n endpoint_api: STATUS\n endpoint_api_value: 1\n }\n domain_name: \"example.block.com\"\n port: 445\n requires_tls: true\n }\n}\ntransaction_fee: 100000000\ntransaction_i_d {\n account_i_d {\n account_num: 5006\n realm_num: 0\n shard_num: 0\n }\n transaction_valid_start {\n seconds: 1554158542\n }\n}\ntransaction_valid_duration {\n seconds: 120\n}" ] \ No newline at end of file diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index bc03579313..37c3187e90 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -23,7 +23,7 @@ void canCreateRegisteredNode() throws Exception { List serviceEndpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("test.block.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS)); + .addEndpointApi(BlockNodeApi.STATUS)); var response = new RegisteredNodeCreateTransaction() .setAdminKey(key) @@ -49,7 +49,7 @@ void canCreateRegisteredNodeWithMultipleServiceEndpoints() throws Exception { new BlockNodeServiceEndpoint() .setDomainName("test.block.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS), + .addEndpointApi(BlockNodeApi.STATUS), new MirrorNodeServiceEndpoint() .setDomainName("test.mirror.com") .setPort(443)); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java index 3d95f4475d..5009ebd243 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java @@ -24,7 +24,7 @@ void canDeleteRegisteredNode() throws Exception { List endpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("blocks.example.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS)); + .addEndpointApi(BlockNodeApi.STATUS)); var createReceipt = new RegisteredNodeCreateTransaction() .setAdminKey(key) diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java index 7c0387e4ad..69c5291e01 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeUpdateTransactionIntegrationTest.java @@ -24,7 +24,7 @@ void canUpdateRegisteredNode() throws Exception { List endpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("initial.blocks.com") .setPort(443) - .setEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM)); + .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM)); var createResponse = new RegisteredNodeCreateTransaction() .setAdminKey(key) @@ -41,7 +41,7 @@ void canUpdateRegisteredNode() throws Exception { List updatedEndpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("updated.blocks.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS)); + .addEndpointApi(BlockNodeApi.STATUS)); var updateResponse = new RegisteredNodeUpdateTransaction() .setRegisteredNodeId(nodeId) @@ -66,7 +66,7 @@ void canRotateAdminKey() throws Exception { List endpoints = List.of(new BlockNodeServiceEndpoint() .setDomainName("blocks.example.com") .setPort(443) - .setEndpointApi(BlockNodeApi.STATUS)); + .addEndpointApi(BlockNodeApi.STATUS)); var createReceipt = new RegisteredNodeCreateTransaction() .setAdminKey(oldKey) @@ -90,7 +90,6 @@ void canRotateAdminKey() throws Exception { tx.sign(newKey); var receipt = tx.execute(testEnv.client).getReceipt(testEnv.client); - assertThat(receipt.status).isEqualTo(Status.SUCCESS); } } From 4f780ce19e2904b33084cf62655a11649b98acca Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 16 Apr 2026 00:24:32 +0530 Subject: [PATCH 13/27] chore: neat pick Signed-off-by: Manish Dait --- .../RegisteredNodeLifeCycleExample.java | 4 +- .../sdk/BlockNodeServiceEndpoint.java | 22 +++++++ .../hashgraph/sdk/GeneralServiceEndpoint.java | 30 +++++++++ .../sdk/RegisteredServiceEndpoint.java | 1 + .../sdk/GeneralServiceEndpointTest.java | 63 +++++++++++++++++++ .../sdk/GeneralServiceEndpointTest.snap | 8 +++ 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.snap diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java index d2be153c14..914044df73 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -75,7 +75,7 @@ public static void main(String[] args) throws Exception { .setIpAddress(new byte[]{127, 0, 0, 1}) .setPort(443) .setRequiresTls(true) - .setEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); + .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); /* * Step 2: @@ -111,7 +111,7 @@ public static void main(String[] args) throws Exception { .setDomainName("block-node.example.com") .setPort(443) .setRequiresTls(true) - .setEndpointApi(BlockNodeApi.STATUS); + .addEndpointApi(BlockNodeApi.STATUS); RegisteredNodeUpdateTransaction registeredNodeUpdateTx = new RegisteredNodeUpdateTransaction() .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java index c123401e36..cd84e0da23 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -21,22 +21,44 @@ public class BlockNodeServiceEndpoint extends RegisteredServiceEndpointBase getEndpointApis() { return endpointApis; } + /** + * Sets the list of APIs supported by this endpoint. + * + * @param endpointApis the list of APIs to support; must not be null + * @return {@code this} + */ public BlockNodeServiceEndpoint setEndpointApis(List endpointApis) { Objects.requireNonNull(endpointApis, "endpointApis must not be null"); this.endpointApis = new ArrayList<>(endpointApis); return this; } + /** + * Adds a single API to the list of supported APIs for this endpoint. + * + * @param endpointApi the API to add; must not be null + * @return {@code this} + */ public BlockNodeServiceEndpoint addEndpointApi(BlockNodeApi endpointApi) { Objects.requireNonNull(endpointApi, "endpointApi must not be null"); this.endpointApis.add(endpointApi); return this; } + /** + * Removes all supported APIs from this endpoint. + * + * @return {@code this} + */ public BlockNodeServiceEndpoint clearEndpointApis() { endpointApis.clear(); return this; diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java index bed207d7cf..2837fb42d7 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java @@ -5,12 +5,37 @@ import java.util.Objects; import javax.annotation.Nullable; +/** + * Represents a general-purpose service endpoint. + */ public class GeneralServiceEndpoint extends RegisteredServiceEndpointBase { + /** + * A short description of the service provided. + */ @Nullable private String description; + /** + * Constructor. + */ public GeneralServiceEndpoint() {} + /** + * Returns the description of the service provided by this endpoint. + * + * @return the service description, or null if not set + */ + public @Nullable String getDescription() { + return this.description; + } + + /** + * Sets a short description of the service provided. + * This value MUST NOT exceed 100 bytes when encoded as UTF-8. + * + * @param description a short description of the service + * @return {@code this} + */ public GeneralServiceEndpoint setDescription(String description) { this.description = description; return this; @@ -61,4 +86,9 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { return registeredServiceEndpoint.build(); } + + @Override + public String toString() { + return toStringHelper().add("description", description).toString(); + } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java index 84abbfa0bc..0f43d3f6ed 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java @@ -84,6 +84,7 @@ static RegisteredServiceEndpoint fromProtobuf( case BLOCK_NODE -> BlockNodeServiceEndpoint.fromProtobuf(serviceEndpoint); case MIRROR_NODE -> MirrorNodeServiceEndpoint.fromProtobuf(serviceEndpoint); case RPC_RELAY -> RpcRelayServiceEndpoint.fromProtobuf(serviceEndpoint); + case GENERAL_SERVICE -> GeneralServiceEndpoint.fromProtobuf(serviceEndpoint); default -> throw new IllegalArgumentException("Unable to decode registered service endpoint"); }; } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java new file mode 100644 index 0000000000..28e89f27ec --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java @@ -0,0 +1,63 @@ +package com.hedera.hashgraph.sdk; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; +import io.github.jsonSnapshot.SnapshotMatcher; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class GeneralServiceEndpointTest { + private static final byte[] TEST_IP_ADDRESS = new byte[] {1, 2, 3, 4}; + private static final String TEST_DOMAIN_NAME = "general.service.com"; + private static final String TEST_DESCRIPTION = "A general purpose endpoint."; + private static final int TEST_PORT = 8080; + private static final boolean TEST_REQUIRES_TLS = false; + + private final RegisteredServiceEndpoint generalEndpointWithDomain = RegisteredServiceEndpoint.newBuilder() + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder().setDescription(TEST_DESCRIPTION)) + .build(); + + private final RegisteredServiceEndpoint generalEndpointWithIp = RegisteredServiceEndpoint.newBuilder() + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder().setDescription(TEST_DESCRIPTION)) + .build(); + + @BeforeAll + public static void beforeAll() { + SnapshotMatcher.start(Snapshot::asJsonString); + } + + @AfterAll + public static void afterAll() { + SnapshotMatcher.validateSnapshots(); + } + + + @Test + void fromProtobufWithIp() { + SnapshotMatcher.expect(GeneralServiceEndpoint.fromProtobuf(generalEndpointWithIp) + .toString()) + .toMatchSnapshot(); + } + + @Test + void fromProtobufWithDomain() { + SnapshotMatcher.expect(GeneralServiceEndpoint.fromProtobuf(generalEndpointWithDomain) + .toString()) + .toMatchSnapshot(); + } + + @Test + void setDescription() { + var endpoint = new GeneralServiceEndpoint().setDescription(TEST_DESCRIPTION); + assertThat(endpoint.getDescription()).isEqualTo(TEST_DESCRIPTION); + } +} diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.snap new file mode 100644 index 0000000000..70c25ee9ca --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.snap @@ -0,0 +1,8 @@ +com.hedera.hashgraph.sdk.GeneralServiceEndpointTest.fromProtobufWithDomain=[ + "GeneralServiceEndpoint{ipAddress=null, domainName=general.service.com, port=8080, requiresTls=false, description=A general purpose endpoint.}" +] + + +com.hedera.hashgraph.sdk.GeneralServiceEndpointTest.fromProtobufWithIp=[ + "GeneralServiceEndpoint{ipAddress=[1, 2, 3, 4], domainName=null, port=8080, requiresTls=false, description=A general purpose endpoint.}" +] \ No newline at end of file From 0bba294335ffa6a489dfd81cca34bfbdf5b1b16f Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 16 Apr 2026 00:24:58 +0530 Subject: [PATCH 14/27] chore: spotless Signed-off-by: Manish Dait --- .../sdk/GeneralServiceEndpointTest.java | 32 ++++++++++--------- .../hashgraph/sdk/RegisteredNodeTest.java | 1 - 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java index 28e89f27ec..76f18aa02a 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/GeneralServiceEndpointTest.java @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; import static org.assertj.core.api.Assertions.assertThat; @@ -17,18 +18,20 @@ public class GeneralServiceEndpointTest { private static final boolean TEST_REQUIRES_TLS = false; private final RegisteredServiceEndpoint generalEndpointWithDomain = RegisteredServiceEndpoint.newBuilder() - .setDomainName(TEST_DOMAIN_NAME) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder().setDescription(TEST_DESCRIPTION)) - .build(); + .setDomainName(TEST_DOMAIN_NAME) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder() + .setDescription(TEST_DESCRIPTION)) + .build(); private final RegisteredServiceEndpoint generalEndpointWithIp = RegisteredServiceEndpoint.newBuilder() - .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) - .setPort(TEST_PORT) - .setRequiresTls(TEST_REQUIRES_TLS) - .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder().setDescription(TEST_DESCRIPTION)) - .build(); + .setIpAddress(ByteString.copyFrom(TEST_IP_ADDRESS)) + .setPort(TEST_PORT) + .setRequiresTls(TEST_REQUIRES_TLS) + .setGeneralService(RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder() + .setDescription(TEST_DESCRIPTION)) + .build(); @BeforeAll public static void beforeAll() { @@ -40,19 +43,18 @@ public static void afterAll() { SnapshotMatcher.validateSnapshots(); } - @Test void fromProtobufWithIp() { SnapshotMatcher.expect(GeneralServiceEndpoint.fromProtobuf(generalEndpointWithIp) - .toString()) - .toMatchSnapshot(); + .toString()) + .toMatchSnapshot(); } @Test void fromProtobufWithDomain() { SnapshotMatcher.expect(GeneralServiceEndpoint.fromProtobuf(generalEndpointWithDomain) - .toString()) - .toMatchSnapshot(); + .toString()) + .toMatchSnapshot(); } @Test diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java index b53e1124cd..599934e720 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeTest.java @@ -55,7 +55,6 @@ void toBytes() throws InvalidProtocolBufferException { .toMatchSnapshot(); } - @Test void toProtobuf() throws InvalidProtocolBufferException { SnapshotMatcher.expect( From bbbd65d6c35aaf1702c951d230d84f28f3a70a89 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 22 Apr 2026 22:20:50 +0530 Subject: [PATCH 15/27] feat: added initial registeredNode query Signed-off-by: Manish Dait --- .../sdk/BlockNodeServiceEndpoint.java | 10 -- .../sdk/RegisteredNodeAddressBookQuery.java | 145 ++++++++++++++++-- .../sdk/BlockNodeServiceEndpointTest.java | 8 - 3 files changed, 134 insertions(+), 29 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java index cd84e0da23..414c1cfd4a 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -54,16 +54,6 @@ public BlockNodeServiceEndpoint addEndpointApi(BlockNodeApi endpointApi) { return this; } - /** - * Removes all supported APIs from this endpoint. - * - * @return {@code this} - */ - public BlockNodeServiceEndpoint clearEndpointApis() { - endpointApis.clear(); - return this; - } - /** * Create a BlockNodeServiceEndpoint object from protobuf * diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 8e66d9c13d..2154da97c4 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -1,30 +1,153 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import static com.hedera.hashgraph.sdk.EntityIdHelper.performQueryToMirrorNodeAsync; public class RegisteredNodeAddressBookQuery { - // TODO: Implementation should be deferred until the mirror node API is available - private String nodeType; + private long registeredNodeId; - public RegisteredNodeAddressBookQuery setNodeType(String nodeType) { - this.nodeType = nodeType; + public RegisteredNodeAddressBookQuery setRegisteredNodeId(long registeredNodeId) { + this.registeredNodeId = registeredNodeId; return this; } - public String getNodeType() { - return this.nodeType; + public long getRegisteredNodeId() { + return registeredNodeId; } - public RegisteredNodeAddressBook execute(Client client) { - throw new RuntimeException("Method not implemented"); + + public RegisteredNodeAddressBook execute(Client client) throws ExecutionException, InterruptedException { + String json = executeMirrorNodeRequest(client).get(); + return parseRegisterNodeAddressBook(json); + } + + private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEndpoint) { + Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); + String type = serviceEndpoint.get("type").getAsString().toUpperCase(); + + int port = serviceEndpoint.get("port").getAsInt(); + boolean requiresTls = serviceEndpoint.get("requires_tls").getAsBoolean(); + + String rawIpAddress = serviceEndpoint.has("ip_address") && !serviceEndpoint.get("ip_address").isJsonNull() + ? serviceEndpoint.get("ip_address").getAsString() : null; + + byte[] ipAddressBytes = null; + + if (rawIpAddress != null && !rawIpAddress.isEmpty()) { + try { + ipAddressBytes = InetAddress.getByName(rawIpAddress).getAddress(); + } catch (UnknownHostException ignore) {} + } + + String domainName = serviceEndpoint.has("domain_name") && !serviceEndpoint.get("domain_name").isJsonNull() + ? serviceEndpoint.get("domain_name").getAsString() : null; + + switch (type) { + case "BLOCK_NODE": + List apis = new ArrayList<>(); + JsonObject blockNode = serviceEndpoint.getAsJsonObject("block_node"); + if (blockNode.has("endpoint_apis")) { + for (JsonElement api : blockNode.getAsJsonArray("endpoint_apis")) { + apis.add(api.getAsString()); + } + } + + return new BlockNodeServiceEndpoint() + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls) + .setEndpointApis(apis.stream().map(a -> BlockNodeApi.valueOf(a)).collect(Collectors.toUnmodifiableList())); + + case "MIRROR_NODE": + return new MirrorNodeServiceEndpoint() + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); + + case "RPC_RELAY": + return new RpcRelayServiceEndpoint() + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); + + case "GENERAL_SERVICE": + JsonObject generalService = serviceEndpoint.get("general_service").getAsJsonObject(); + String description = generalService.has("description") && !generalService.get("description").isJsonNull()? generalService.get("description").getAsString() : null; + return new GeneralServiceEndpoint() + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls) + .setDescription(description); + + default: + throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); + } + } + + private PublicKey parseJsonKey(JsonObject adminKey) { + Objects.requireNonNull(adminKey, "adminKey must not be null"); + String type = adminKey.get("_type").getAsString() != null ? adminKey.get("_type").getAsString() : ""; + + String key = adminKey.get("key").getAsString(); + switch (type) { + case "ED25519": + return PublicKey.fromStringED25519(key); + case "ECDSA_SECP256K1": + return PublicKey.fromStringECDSA(key); + default: + return PublicKey.fromString(key); + } } private CompletableFuture executeMirrorNodeRequest(Client client) { - throw new RuntimeException("Method not implemented"); + Objects.requireNonNull(client, "client must not be null"); + String apiEndpoint = "/api/v1/network/registered-nodes?registerednode.id=" + registeredNodeId; + String baseUrl = client.getMirrorRestBaseUrl(); + + return performQueryToMirrorNodeAsync(baseUrl, apiEndpoint, null).exceptionally(ex -> { + client.getLogger().error("Error while performing post request to Mirror Node: " + ex.getMessage()); + throw new CompletionException(ex); + }); } - private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String responseBody) { - throw new RuntimeException("Method not implemented"); + private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { + List registeredNodes = new ArrayList<>(); + + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + + JsonArray registeredNodesJSON = jsonObject.getAsJsonArray("registered_nodes"); + for (JsonElement node : registeredNodesJSON) { + long id = node.getAsJsonObject().get("registered_node_id").getAsLong(); + String description = node.getAsJsonObject().get("description").getAsString(); + PublicKey adminKey = parseJsonKey(node.getAsJsonObject().get("admin_key").getAsJsonObject()); + List serviceEndpoints = new ArrayList<>(); + + for (JsonElement endpoints : node.getAsJsonObject().getAsJsonArray("service_endpoints")) { + serviceEndpoints.add(parseJSONServiceEndpoint(endpoints.getAsJsonObject())); + } + + registeredNodes.add(new RegisteredNode(registeredNodeId, adminKey, description, serviceEndpoints)); + } + + return new RegisteredNodeAddressBook(registeredNodes); } } diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java index 8ca291809e..33318a9602 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpointTest.java @@ -125,12 +125,4 @@ void addEndpointApi() { assertThat(endpoint.getEndpointApis()).containsExactly(BlockNodeApi.STATUS, BlockNodeApi.OTHER); } - - @Test - void clearEndpointApis() { - var endpoint = - new BlockNodeServiceEndpoint().setEndpointApis(TEST_BLOCK_APIS).clearEndpointApis(); - - assertThat(endpoint.getEndpointApis()).isEmpty(); - } } From d3a7b76df4c9461fea49524f3d2e297ebb08a0cf Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 22 Apr 2026 23:19:48 +0530 Subject: [PATCH 16/27] chore: added unit test for initial query Signed-off-by: Manish Dait --- .../sdk/RegisteredNodeAddressBookQuery.java | 100 ++++++++++-------- .../RegisteredNodeAddressBookQueryTest.java | 66 ++++++++++++ 2 files changed, 123 insertions(+), 43 deletions(-) create mode 100644 sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQueryTest.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 2154da97c4..a80f17625b 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import static com.hedera.hashgraph.sdk.EntityIdHelper.performQueryToMirrorNodeAsync; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; - import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; @@ -16,8 +17,6 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static com.hedera.hashgraph.sdk.EntityIdHelper.performQueryToMirrorNodeAsync; - public class RegisteredNodeAddressBookQuery { private long registeredNodeId; @@ -30,12 +29,22 @@ public long getRegisteredNodeId() { return registeredNodeId; } - public RegisteredNodeAddressBook execute(Client client) throws ExecutionException, InterruptedException { String json = executeMirrorNodeRequest(client).get(); return parseRegisterNodeAddressBook(json); } + CompletableFuture executeMirrorNodeRequest(Client client) { + Objects.requireNonNull(client, "client must not be null"); + String apiEndpoint = "/api/v1/network/registered-nodes?registerednode.id=" + registeredNodeId; + String baseUrl = client.getMirrorRestBaseUrl(); + + return performQueryToMirrorNodeAsync(baseUrl, apiEndpoint, null).exceptionally(ex -> { + client.getLogger().error("Error while performing post request to Mirror Node: " + ex.getMessage()); + throw new CompletionException(ex); + }); + } + private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEndpoint) { Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); String type = serviceEndpoint.get("type").getAsString().toUpperCase(); @@ -43,19 +52,24 @@ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEnd int port = serviceEndpoint.get("port").getAsInt(); boolean requiresTls = serviceEndpoint.get("requires_tls").getAsBoolean(); - String rawIpAddress = serviceEndpoint.has("ip_address") && !serviceEndpoint.get("ip_address").isJsonNull() - ? serviceEndpoint.get("ip_address").getAsString() : null; + String rawIpAddress = serviceEndpoint.has("ip_address") + && !serviceEndpoint.get("ip_address").isJsonNull() + ? serviceEndpoint.get("ip_address").getAsString() + : null; byte[] ipAddressBytes = null; if (rawIpAddress != null && !rawIpAddress.isEmpty()) { try { ipAddressBytes = InetAddress.getByName(rawIpAddress).getAddress(); - } catch (UnknownHostException ignore) {} + } catch (UnknownHostException ignore) { + } } - String domainName = serviceEndpoint.has("domain_name") && !serviceEndpoint.get("domain_name").isJsonNull() - ? serviceEndpoint.get("domain_name").getAsString() : null; + String domainName = serviceEndpoint.has("domain_name") + && !serviceEndpoint.get("domain_name").isJsonNull() + ? serviceEndpoint.get("domain_name").getAsString() + : null; switch (type) { case "BLOCK_NODE": @@ -68,35 +82,43 @@ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEnd } return new BlockNodeServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls) - .setEndpointApis(apis.stream().map(a -> BlockNodeApi.valueOf(a)).collect(Collectors.toUnmodifiableList())); + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls) + .setEndpointApis(apis.stream() + .map(a -> BlockNodeApi.valueOf(a)) + .collect(Collectors.toUnmodifiableList())); case "MIRROR_NODE": + // JsonObject mirrorNode = serviceEndpoint.getAsJsonObject("mirror_node"); return new MirrorNodeServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls); + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); case "RPC_RELAY": + // JsonObject rpcRelay = serviceEndpoint.getAsJsonObject("rpc_relay"); return new RpcRelayServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls); + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); case "GENERAL_SERVICE": - JsonObject generalService = serviceEndpoint.get("general_service").getAsJsonObject(); - String description = generalService.has("description") && !generalService.get("description").isJsonNull()? generalService.get("description").getAsString() : null; + JsonObject generalService = + serviceEndpoint.get("general_service").getAsJsonObject(); + String description = generalService.has("description") + && !generalService.get("description").isJsonNull() + ? generalService.get("description").getAsString() + : null; return new GeneralServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls) - .setDescription(description); + .setIpAddress(ipAddressBytes) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls) + .setDescription(description); default: throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); @@ -105,7 +127,9 @@ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEnd private PublicKey parseJsonKey(JsonObject adminKey) { Objects.requireNonNull(adminKey, "adminKey must not be null"); - String type = adminKey.get("_type").getAsString() != null ? adminKey.get("_type").getAsString() : ""; + String type = adminKey.get("_type").getAsString() != null + ? adminKey.get("_type").getAsString() + : ""; String key = adminKey.get("key").getAsString(); switch (type) { @@ -118,17 +142,6 @@ private PublicKey parseJsonKey(JsonObject adminKey) { } } - private CompletableFuture executeMirrorNodeRequest(Client client) { - Objects.requireNonNull(client, "client must not be null"); - String apiEndpoint = "/api/v1/network/registered-nodes?registerednode.id=" + registeredNodeId; - String baseUrl = client.getMirrorRestBaseUrl(); - - return performQueryToMirrorNodeAsync(baseUrl, apiEndpoint, null).exceptionally(ex -> { - client.getLogger().error("Error while performing post request to Mirror Node: " + ex.getMessage()); - throw new CompletionException(ex); - }); - } - private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { List registeredNodes = new ArrayList<>(); @@ -138,14 +151,15 @@ private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { for (JsonElement node : registeredNodesJSON) { long id = node.getAsJsonObject().get("registered_node_id").getAsLong(); String description = node.getAsJsonObject().get("description").getAsString(); - PublicKey adminKey = parseJsonKey(node.getAsJsonObject().get("admin_key").getAsJsonObject()); + PublicKey adminKey = + parseJsonKey(node.getAsJsonObject().get("admin_key").getAsJsonObject()); List serviceEndpoints = new ArrayList<>(); for (JsonElement endpoints : node.getAsJsonObject().getAsJsonArray("service_endpoints")) { serviceEndpoints.add(parseJSONServiceEndpoint(endpoints.getAsJsonObject())); } - registeredNodes.add(new RegisteredNode(registeredNodeId, adminKey, description, serviceEndpoints)); + registeredNodes.add(new RegisteredNode(id, adminKey, description, serviceEndpoints)); } return new RegisteredNodeAddressBook(registeredNodes); diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQueryTest.java b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQueryTest.java new file mode 100644 index 0000000000..7d32c10fe3 --- /dev/null +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQueryTest.java @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +package com.hedera.hashgraph.sdk; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class RegisteredNodeAddressBookQueryTest { + private RegisteredNodeAddressBookQuery query; + private Client mockClient; + + @BeforeEach + void setUp() { + query = Mockito.spy(new RegisteredNodeAddressBookQuery()); + mockClient = Mockito.mock(Client.class); + Mockito.when(mockClient.getMirrorRestBaseUrl()).thenReturn("https://testnet.mirrornode.hedera.com"); + } + + @Test + void testExecuteSuccess() throws ExecutionException, InterruptedException { + var mockResponse = "{" + " \"registered_nodes\": [" + + " {" + + " \"registered_node_id\": 123," + + " \"description\": \"Test Node\"," + + " \"admin_key\": {" + + " \"_type\": \"ED25519\"," + + " \"key\": \"302a300506032b6570032100e0c8ec27b039a7d094a6132049386d9a0d8e8751508241473919e1c455f75605\"" + + " }," + + " \"service_endpoints\": [" + + " {" + + " \"type\": \"BLOCK_NODE\"," + + " \"port\": 50211," + + " \"requires_tls\": true," + + " \"ip_address\": \"127.0.0.1\"," + + " \"block_node\": { \"endpoint_apis\": [\"OTHER\"] }" + + " }" + + " ]" + + " }" + + " ]" + + "}"; + + Mockito.doReturn(CompletableFuture.completedFuture(mockResponse)) + .when(query) + .executeMirrorNodeRequest(Mockito.any(Client.class)); + + query.setRegisteredNodeId(123L); + var result = query.execute(mockClient); + + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.registeredNodes).hasSize(1); + + var node = result.registeredNodes.get(0); + Assertions.assertThat(node.registeredNodeId).isEqualTo(123L); + Assertions.assertThat(node.description).isEqualTo("Test Node"); + Assertions.assertThat(node.adminKey).isNotNull(); + Assertions.assertThat(node.adminKey instanceof PublicKey).isTrue(); + + var endpoint = node.serviceEndpoints.get(0); + Assertions.assertThat(endpoint instanceof BlockNodeServiceEndpoint).isTrue(); + Assertions.assertThat(endpoint.getPort()).isEqualTo(50211); + Assertions.assertThat(endpoint.isRequiresTls()).isTrue(); + } +} From 52bda95a1da0d046e54a9b5f71a427e7fbbccefa Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Wed, 22 Apr 2026 23:30:47 +0530 Subject: [PATCH 17/27] chore: added docs Signed-off-by: Manish Dait --- .../sdk/RegisteredNodeAddressBookQuery.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index a80f17625b..4a2b27838a 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -17,18 +17,40 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +/** + * Query the mirror node for the RegisteredAddressBook. + */ public class RegisteredNodeAddressBookQuery { private long registeredNodeId; + /** + * Sets the ID of the registered node to retrieve. + * + * @param registeredNodeId The unique identifier of the node. + * @return {@code this} + */ public RegisteredNodeAddressBookQuery setRegisteredNodeId(long registeredNodeId) { this.registeredNodeId = registeredNodeId; return this; } + /** + * Returns the set registered node ID. + * + * @return The registered node ID. + */ public long getRegisteredNodeId() { return registeredNodeId; } + /** + * Executes the query with the user supplied client + * + * @param client The Client instance to perform the operation with + * @return The registeredAddressBook + * @throws ExecutionException + * @throws InterruptedException + */ public RegisteredNodeAddressBook execute(Client client) throws ExecutionException, InterruptedException { String json = executeMirrorNodeRequest(client).get(); return parseRegisterNodeAddressBook(json); @@ -45,6 +67,9 @@ CompletableFuture executeMirrorNodeRequest(Client client) { }); } + /** + * Parses a single service endpoint from a JSON object. + */ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEndpoint) { Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); String type = serviceEndpoint.get("type").getAsString().toUpperCase(); @@ -125,6 +150,9 @@ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEnd } } + /** + * Parses the admin key from the JSON representation. + */ private PublicKey parseJsonKey(JsonObject adminKey) { Objects.requireNonNull(adminKey, "adminKey must not be null"); String type = adminKey.get("_type").getAsString() != null @@ -142,6 +170,9 @@ private PublicKey parseJsonKey(JsonObject adminKey) { } } + /** + * Converts the Mirror Node JSON response to {@link RegisteredNodeAddressBook}. + */ private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { List registeredNodes = new ArrayList<>(); From 16d735e08413506c6b635423a8da5f5ff63cc2f1 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 23 Apr 2026 14:57:40 +0530 Subject: [PATCH 18/27] feat: added missing status enum Signed-off-by: Manish Dait --- .github/workflows/build.yml | 2 +- .../java/com/hedera/hashgraph/sdk/Status.java | 49 ++++++++++++++++++- sdk/src/main/proto/response_code.proto | 40 +++++++++++++++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9060e29fda..30029ae80b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -176,7 +176,7 @@ jobs: - name: Prepare Hiero Solo id: solo - uses: hiero-ledger/hiero-solo-action@328bc84c3b00a990a151418144fd682a4eb76ea6 # v0.17 + uses: hiero-ledger/hiero-solo-action@4d42a74e8e644a2753f3bb7a2afa429305375b14 # v0.16 with: installMirrorNode: true soloVersion: v0.65.0 diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/Status.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/Status.java index a589279228..f1c5951c24 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/Status.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/Status.java @@ -2087,7 +2087,47 @@ public enum Status { /** * The number of hook invocations exceeds the maximum allowed per transaction. */ - TOO_MANY_HOOK_INVOCATIONS(ResponseCodeEnum.TOO_MANY_HOOK_INVOCATIONS); + TOO_MANY_HOOK_INVOCATIONS(ResponseCodeEnum.TOO_MANY_HOOK_INVOCATIONS), + + /** + * A registered node ID is invalid or does not exist. + */ + INVALID_REGISTERED_NODE_ID(ResponseCodeEnum.INVALID_REGISTERED_NODE_ID), + + /** + * A registered service endpoint is invalid.
+ * The port is out of range, or the address field is not set. + */ + INVALID_REGISTERED_ENDPOINT(ResponseCodeEnum.INVALID_REGISTERED_ENDPOINT), + + /** + * The number of registered service endpoints exceeds the configured limit. + */ + REGISTERED_ENDPOINTS_EXCEEDED_LIMIT(ResponseCodeEnum.REGISTERED_ENDPOINTS_EXCEEDED_LIMIT), + + /** + * A registered service endpoint has an invalid address.
+ * The IP address length is not 4 (IPv4) or 16 (IPv6), or the + * domain name is not a valid ASCII FQDN. + */ + INVALID_REGISTERED_ENDPOINT_ADDRESS(ResponseCodeEnum.INVALID_REGISTERED_ENDPOINT_ADDRESS), + + /** + * A registered service endpoint does not specify an endpoint type.
+ * Exactly one of block_node, mirror_node, or rpc_relay MUST be set. + */ + INVALID_REGISTERED_ENDPOINT_TYPE(ResponseCodeEnum.INVALID_REGISTERED_ENDPOINT_TYPE), + + /** + * A registered node cannot be deleted because it is still associated + * with a consensus node via their associated registered node list. + */ + REGISTERED_NODE_STILL_ASSOCIATED(ResponseCodeEnum.REGISTERED_NODE_STILL_ASSOCIATED), + + /** + * The number of associated registered nodes exceeds the maximum allowed limit. + */ + MAX_REGISTERED_NODES_EXCEEDED(ResponseCodeEnum.MAX_REGISTERED_NODES_EXCEEDED); final ResponseCodeEnum code; @@ -2498,6 +2538,13 @@ static Status valueOf(ResponseCodeEnum code) { case NODE_ACCOUNT_HAS_ZERO_BALANCE -> NODE_ACCOUNT_HAS_ZERO_BALANCE; case TRANSFER_TO_FEE_COLLECTION_ACCOUNT_NOT_ALLOWED -> TRANSFER_TO_FEE_COLLECTION_ACCOUNT_NOT_ALLOWED; case TOO_MANY_HOOK_INVOCATIONS -> TOO_MANY_HOOK_INVOCATIONS; + case INVALID_REGISTERED_NODE_ID -> INVALID_REGISTERED_NODE_ID; + case INVALID_REGISTERED_ENDPOINT -> INVALID_REGISTERED_ENDPOINT; + case REGISTERED_ENDPOINTS_EXCEEDED_LIMIT -> REGISTERED_ENDPOINTS_EXCEEDED_LIMIT; + case INVALID_REGISTERED_ENDPOINT_ADDRESS -> INVALID_REGISTERED_ENDPOINT_ADDRESS; + case INVALID_REGISTERED_ENDPOINT_TYPE -> INVALID_REGISTERED_ENDPOINT_TYPE; + case REGISTERED_NODE_STILL_ASSOCIATED -> REGISTERED_NODE_STILL_ASSOCIATED; + case MAX_REGISTERED_NODES_EXCEEDED -> MAX_REGISTERED_NODES_EXCEEDED; case UNRECOGNIZED -> // NOTE: Protobuf deserialization will not give us the code on the wire throw new IllegalArgumentException( diff --git a/sdk/src/main/proto/response_code.proto b/sdk/src/main/proto/response_code.proto index a48d14dd5d..72a89742b0 100644 --- a/sdk/src/main/proto/response_code.proto +++ b/sdk/src/main/proto/response_code.proto @@ -1935,4 +1935,44 @@ enum ResponseCodeEnum { * The number of hook invocations exceeds the maximum allowed per transaction. */ TOO_MANY_HOOK_INVOCATIONS = 528; + + /** + * A registered node ID is invalid or does not exist. + */ + INVALID_REGISTERED_NODE_ID = 529; + + /** + * A registered service endpoint is invalid.
+ * The port is out of range, or the address field is not set. + */ + INVALID_REGISTERED_ENDPOINT = 530; + + /** + * The number of registered service endpoints exceeds the configured limit. + */ + REGISTERED_ENDPOINTS_EXCEEDED_LIMIT = 531; + + /** + * A registered service endpoint has an invalid address.
+ * The IP address length is not 4 (IPv4) or 16 (IPv6), or the + * domain name is not a valid ASCII FQDN. + */ + INVALID_REGISTERED_ENDPOINT_ADDRESS = 532; + + /** + * A registered service endpoint does not specify an endpoint type.
+ * Exactly one of block_node, mirror_node, or rpc_relay MUST be set. + */ + INVALID_REGISTERED_ENDPOINT_TYPE = 533; + + /** + * A registered node cannot be deleted because it is still associated + * with a consensus node via their associated registered node list. + */ + REGISTERED_NODE_STILL_ASSOCIATED = 534; + + /** + * The number of associated registered nodes exceeds the maximum allowed limit. + */ + MAX_REGISTERED_NODES_EXCEEDED = 535; } From 0ea81f3d47564a601fa64a57b6f2aae30423eb4e Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 23 Apr 2026 16:26:50 +0530 Subject: [PATCH 19/27] chore: improve the structure Signed-off-by: Manish Dait --- .github/workflows/build.yml | 4 +- .../RegisteredNodeLifeCycleExample.java | 59 +++--- .../sdk/RegisteredNodeAddressBookQuery.java | 193 +++++++++--------- 3 files changed, 130 insertions(+), 126 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 30029ae80b..49b7136ca6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -255,8 +255,8 @@ jobs: installMirrorNode: true soloVersion: v0.65.0 dualMode: true - hieroVersion: v0.68.0 - mirrorNodeVersion: v0.142.0 + hieroVersion: v0.73.0 + mirrorNodeVersion: v0.152.0 - name: Build SDK run: ./gradlew assemble diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java index 914044df73..dc42fcee1b 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk.examples; import com.hedera.hashgraph.sdk.AccountId; @@ -14,7 +15,6 @@ import com.hedera.hashgraph.sdk.logger.LogLevel; import com.hedera.hashgraph.sdk.logger.Logger; import io.github.cdimascio.dotenv.Dotenv; - import java.util.List; import java.util.Objects; @@ -29,13 +29,13 @@ public class RegisteredNodeLifeCycleExample { * Used to sign and pay for operations on Hedera. */ private static final AccountId OPERATOR_ID = - AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); /** * Operator's private key. */ private static final PrivateKey OPERATOR_KEY = - PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); /** * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. @@ -72,21 +72,21 @@ public static void main(String[] args) throws Exception { */ PrivateKey adminKey = PrivateKey.generateED25519(); BlockNodeServiceEndpoint initialEndpoint = new BlockNodeServiceEndpoint() - .setIpAddress(new byte[]{127, 0, 0, 1}) - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); + .setIpAddress(new byte[] {127, 0, 0, 1}) + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); /* * Step 2: * Create Registered Node. */ RegisteredNodeCreateTransaction registeredNodeCreateTx = new RegisteredNodeCreateTransaction() - .setDescription("My Block Node") - .setAdminKey(adminKey) - .addServiceEndpoint(initialEndpoint) - .freezeWith(client) - .sign(adminKey); + .setDescription("My Block Node") + .setAdminKey(adminKey) + .addServiceEndpoint(initialEndpoint) + .freezeWith(client) + .sign(adminKey); System.out.println("Creating Registered Node..."); TransactionResponse registeredNodeCreateTxResponse = registeredNodeCreateTx.execute(client); @@ -108,17 +108,17 @@ public static void main(String[] args) throws Exception { * Update the RegisteredNode with new Block Node endpoint. */ BlockNodeServiceEndpoint updateEndpoint = new BlockNodeServiceEndpoint() - .setDomainName("block-node.example.com") - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.STATUS); + .setDomainName("block-node.example.com") + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.STATUS); RegisteredNodeUpdateTransaction registeredNodeUpdateTx = new RegisteredNodeUpdateTransaction() - .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) - .setDescription("My Updated Block Node") - .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) - .freezeWith(client) - .sign(adminKey); + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .setDescription("My Updated Block Node") + .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) + .freezeWith(client) + .sign(adminKey); System.out.println("Updating Registered Node..."); TransactionResponse registeredNodeUpdateTxResponse = registeredNodeUpdateTx.execute(client); @@ -130,26 +130,25 @@ public static void main(String[] args) throws Exception { */ long registeredNodeId = registeredNodeUpdateTxReceipt.registeredNodeId; NodeUpdateTransaction associateTx = new NodeUpdateTransaction() - .setNodeId(0) - .addAssociatedRegisteredNode(registeredNodeId) - .freezeWith(client); + .setNodeId(0) + .addAssociatedRegisteredNode(registeredNodeId) + .freezeWith(client); System.out.println("Associating registered node " + registeredNodeId + " with consensus node..."); TransactionResponse associateTxResponse = associateTx.execute(client); associateTxResponse.getReceipt(client); - /* * Step 6: * Delete the Registered Node. */ System.out.println("Deleting Registered Node..."); new RegisteredNodeDeleteTransaction() - .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) - .freezeWith(client) - .sign(adminKey) - .execute(client) - .getReceipt(client); + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .freezeWith(client) + .sign(adminKey) + .execute(client) + .getReceipt(client); client.close(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 4a2b27838a..431f4be5a5 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -16,6 +16,7 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import javax.annotation.Nullable; /** * Query the mirror node for the RegisteredAddressBook. @@ -67,87 +68,69 @@ CompletableFuture executeMirrorNodeRequest(Client client) { }); } + /** + * Converts the Mirror Node JSON response to {@link RegisteredNodeAddressBook}. + */ + private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { + List registeredNodes = new ArrayList<>(); + + JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + JsonArray registeredNodesJSON = jsonObject.getAsJsonArray("registered_nodes"); + + for (JsonElement node : registeredNodesJSON) { + registeredNodes.add(parseRegisteredNode(node.getAsJsonObject())); + } + + return new RegisteredNodeAddressBook(registeredNodes); + } + + /** + * Parses a single node entry from the Mirror Node 'registered_nodes' array. + */ + private RegisteredNode parseRegisteredNode(JsonObject nodeJson) { + long id = nodeJson.get("registered_node_id").getAsLong(); + String description = nodeJson.get("description").getAsString(); + PublicKey adminKey = parseJsonKey(nodeJson.get("admin_key").getAsJsonObject()); + + List endpoints = new ArrayList<>(); + for (JsonElement endpoint : nodeJson.getAsJsonArray("service_endpoints")) { + endpoints.add(parseJSONServiceEndpoint(endpoint.getAsJsonObject())); + } + + return new RegisteredNode(id, adminKey, description, endpoints); + } + /** * Parses a single service endpoint from a JSON object. */ private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEndpoint) { Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); - String type = serviceEndpoint.get("type").getAsString().toUpperCase(); + String type = serviceEndpoint.get("type").getAsString().toUpperCase(); int port = serviceEndpoint.get("port").getAsInt(); boolean requiresTls = serviceEndpoint.get("requires_tls").getAsBoolean(); - String rawIpAddress = serviceEndpoint.has("ip_address") - && !serviceEndpoint.get("ip_address").isJsonNull() - ? serviceEndpoint.get("ip_address").getAsString() - : null; - - byte[] ipAddressBytes = null; - - if (rawIpAddress != null && !rawIpAddress.isEmpty()) { - try { - ipAddressBytes = InetAddress.getByName(rawIpAddress).getAddress(); - } catch (UnknownHostException ignore) { - } - } - String domainName = serviceEndpoint.has("domain_name") && !serviceEndpoint.get("domain_name").isJsonNull() ? serviceEndpoint.get("domain_name").getAsString() : null; - switch (type) { - case "BLOCK_NODE": - List apis = new ArrayList<>(); - JsonObject blockNode = serviceEndpoint.getAsJsonObject("block_node"); - if (blockNode.has("endpoint_apis")) { - for (JsonElement api : blockNode.getAsJsonArray("endpoint_apis")) { - apis.add(api.getAsString()); - } - } - - return new BlockNodeServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls) - .setEndpointApis(apis.stream() - .map(a -> BlockNodeApi.valueOf(a)) - .collect(Collectors.toUnmodifiableList())); - - case "MIRROR_NODE": - // JsonObject mirrorNode = serviceEndpoint.getAsJsonObject("mirror_node"); - return new MirrorNodeServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls); - - case "RPC_RELAY": - // JsonObject rpcRelay = serviceEndpoint.getAsJsonObject("rpc_relay"); - return new RpcRelayServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls); - - case "GENERAL_SERVICE": - JsonObject generalService = - serviceEndpoint.get("general_service").getAsJsonObject(); - String description = generalService.has("description") - && !generalService.get("description").isJsonNull() - ? generalService.get("description").getAsString() - : null; - return new GeneralServiceEndpoint() - .setIpAddress(ipAddressBytes) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls) - .setDescription(description); - - default: - throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); - } + byte[] ipAddress = parseIpAddress(serviceEndpoint); + + RegisteredServiceEndpointBase registeredServiceEndpoint = + switch (type) { + case "BLOCK_NODE" -> buildBlockNodeEndpoint(serviceEndpoint.getAsJsonObject("block_node")); + case "MIRROR_NODE" -> buildMirrorNodeEndpoint(serviceEndpoint.getAsJsonObject("mirror_node")); + case "RPC_RELAY" -> buildRpcRelayEndpoint(serviceEndpoint.getAsJsonObject("rpc_relay")); + case "GENERAL_SERVICE" -> buildGeneralEndpoint(serviceEndpoint.getAsJsonObject("general_service")); + default -> throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); + }; + + return registeredServiceEndpoint + .setIpAddress(ipAddress) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); } /** @@ -160,39 +143,61 @@ private PublicKey parseJsonKey(JsonObject adminKey) { : ""; String key = adminKey.get("key").getAsString(); - switch (type) { - case "ED25519": - return PublicKey.fromStringED25519(key); - case "ECDSA_SECP256K1": - return PublicKey.fromStringECDSA(key); - default: - return PublicKey.fromString(key); - } + return switch (type) { + case "ED25519" -> PublicKey.fromStringED25519(key); + case "ECDSA_SECP256K1" -> PublicKey.fromStringECDSA(key); + default -> PublicKey.fromString(key); + }; } - /** - * Converts the Mirror Node JSON response to {@link RegisteredNodeAddressBook}. - */ - private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { - List registeredNodes = new ArrayList<>(); + @Nullable + private byte[] parseIpAddress(JsonObject json) { + if (json.has("ip_address") && !json.get("ip_address").isJsonNull()) { + String rawIp = json.get("ip_address").getAsString(); + if (!rawIp.isEmpty()) { + try { + return InetAddress.getByName(rawIp).getAddress(); + } catch (UnknownHostException ignored) { + } + } + } - JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject(); + return null; + } - JsonArray registeredNodesJSON = jsonObject.getAsJsonArray("registered_nodes"); - for (JsonElement node : registeredNodesJSON) { - long id = node.getAsJsonObject().get("registered_node_id").getAsLong(); - String description = node.getAsJsonObject().get("description").getAsString(); - PublicKey adminKey = - parseJsonKey(node.getAsJsonObject().get("admin_key").getAsJsonObject()); - List serviceEndpoints = new ArrayList<>(); - - for (JsonElement endpoints : node.getAsJsonObject().getAsJsonArray("service_endpoints")) { - serviceEndpoints.add(parseJSONServiceEndpoint(endpoints.getAsJsonObject())); - } + private BlockNodeServiceEndpoint buildBlockNodeEndpoint(JsonObject blockNode) { + Objects.requireNonNull(blockNode, "blockNode must not be null"); - registeredNodes.add(new RegisteredNode(id, adminKey, description, serviceEndpoints)); + List apis = new ArrayList<>(); + if (blockNode.has("endpoint_apis")) { + for (JsonElement api : blockNode.getAsJsonArray("endpoint_apis")) { + apis.add(api.getAsString()); + } } - return new RegisteredNodeAddressBook(registeredNodes); + return new BlockNodeServiceEndpoint() + .setEndpointApis( + apis.stream().map(a -> BlockNodeApi.valueOf(a)).collect(Collectors.toUnmodifiableList())); + } + + private MirrorNodeServiceEndpoint buildMirrorNodeEndpoint(JsonObject mirrorNode) { + Objects.requireNonNull(mirrorNode, "mirrorNode must not be null"); + return new MirrorNodeServiceEndpoint(); + } + + private RpcRelayServiceEndpoint buildRpcRelayEndpoint(JsonObject rpcRelay) { + Objects.requireNonNull(rpcRelay, "rpcRelay must not be null"); + return new RpcRelayServiceEndpoint(); + } + + private GeneralServiceEndpoint buildGeneralEndpoint(JsonObject generalService) { + Objects.requireNonNull(generalService, "generalService must not be null"); + + String description = generalService.has("description") + && !generalService.get("description").isJsonNull() + ? generalService.get("description").getAsString() + : null; + + return new GeneralServiceEndpoint().setDescription(description); } } From cbd005936628c103d847554b76758e89d5ba01f8 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Fri, 24 Apr 2026 01:41:57 +0530 Subject: [PATCH 20/27] chore: refactor code for parsing json Signed-off-by: Manish Dait --- .../sdk/BlockNodeServiceEndpoint.java | 24 ++++ .../hashgraph/sdk/GeneralServiceEndpoint.java | 17 +++ .../sdk/MirrorNodeServiceEndpoint.java | 12 ++ .../hedera/hashgraph/sdk/RegisteredNode.java | 39 ++++++ .../sdk/RegisteredNodeAddressBookQuery.java | 123 +----------------- .../sdk/RegisteredServiceEndpoint.java | 55 ++++++++ .../sdk/RpcRelayServiceEndpoint.java | 12 ++ 7 files changed, 160 insertions(+), 122 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java index 414c1cfd4a..07fbaf1f8d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/BlockNodeServiceEndpoint.java @@ -1,11 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.stream.Collectors; /** * Represent a Registered Block Node @@ -111,6 +114,27 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { return registeredServiceEndpoint.build(); } + /** + * Parses BlockNodeServiceEndpoint from the type-specific JSON object the MirrorNode. + * + * @param json the json containing block node specific data + * @return {@code this} + */ + static BlockNodeServiceEndpoint fromJson(JsonObject json) { + Objects.requireNonNull(json, "json must not be null"); + + List apis = new ArrayList<>(); + if (json.has("endpoint_apis")) { + for (JsonElement api : json.getAsJsonArray("endpoint_apis")) { + apis.add(api.getAsString()); + } + } + + return new BlockNodeServiceEndpoint() + .setEndpointApis( + apis.stream().map(a -> BlockNodeApi.valueOf(a)).collect(Collectors.toUnmodifiableList())); + } + @Override public String toString() { return toStringHelper().add("endpointApis", endpointApis).toString(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java index 2837fb42d7..8a5a8b62c5 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import java.util.Objects; import javax.annotation.Nullable; @@ -61,6 +62,22 @@ static GeneralServiceEndpoint fromProtobuf( return generalEndpoint; } + /** + * Parses GeneralServiceEndpoint from the type-specific JSON object the MirrorNode. + * + * @param json the json containing general service specific data + * @return {@code this} + */ + static GeneralServiceEndpoint fromJson(JsonObject json) { + Objects.requireNonNull(json, "json must not be null"); + + String description = json.has("description") && !json.get("description").isJsonNull() + ? json.get("description").getAsString() + : null; + + return new GeneralServiceEndpoint().setDescription(description); + } + @Override com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { if (ipAddress == null && domainName == null) { diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java index 8a0ccb0869..e59cc920f9 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import java.util.Objects; @@ -56,6 +57,17 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { return registeredServiceEndpoint.build(); } + /** + * Parses MirrorNodeServiceEndpoint from the type-specific JSON object the MirrorNode. + * + * @param json the json containing mirror node specific data + * @return {@code this} + */ + static MirrorNodeServiceEndpoint fromJson(JsonObject json) { + Objects.requireNonNull(json, "json must not be null"); + return new MirrorNodeServiceEndpoint(); + } + @Override public String toString() { return toStringHelper().toString(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java index 534b6b0f24..19207e6dd8 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNode.java @@ -2,7 +2,10 @@ package com.hedera.hashgraph.sdk; import com.google.common.base.MoreObjects; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.protobuf.InvalidProtocolBufferException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -72,6 +75,42 @@ static RegisteredNode fromProtobuf(com.hedera.hashgraph.sdk.proto.RegisteredNode return new RegisteredNode(registerNodeId, adminKey, description, serviceEndpoint); } + /** + * Parses a single node entry from the Mirror Node 'registered_nodes' array. + * + * @param json the json containing specific data for registered node + * @return {@code this} + */ + static RegisteredNode fromJson(JsonObject json) { + long id = json.get("registered_node_id").getAsLong(); + String description = json.get("description").getAsString(); + PublicKey adminKey = parseJsonKey(json.get("admin_key").getAsJsonObject()); + + List endpoints = new ArrayList<>(); + for (JsonElement endpoint : json.getAsJsonArray("service_endpoints")) { + endpoints.add(RegisteredServiceEndpoint.fromJson(endpoint.getAsJsonObject())); + } + + return new RegisteredNode(id, adminKey, description, endpoints); + } + + /** + * Parses the admin key from the JSON representation. + */ + private static PublicKey parseJsonKey(JsonObject adminKey) { + Objects.requireNonNull(adminKey, "adminKey must not be null"); + String type = adminKey.get("_type").getAsString() != null + ? adminKey.get("_type").getAsString() + : ""; + + String key = adminKey.get("key").getAsString(); + return switch (type) { + case "ED25519" -> PublicKey.fromStringED25519(key); + case "ECDSA_SECP256K1" -> PublicKey.fromStringECDSA(key); + default -> PublicKey.fromString(key); + }; + } + /** * Extract the registeredNode from a byte array. * diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 431f4be5a5..3fff01e22a 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -7,16 +7,12 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import javax.annotation.Nullable; /** * Query the mirror node for the RegisteredAddressBook. @@ -78,126 +74,9 @@ private RegisteredNodeAddressBook parseRegisterNodeAddressBook(String json) { JsonArray registeredNodesJSON = jsonObject.getAsJsonArray("registered_nodes"); for (JsonElement node : registeredNodesJSON) { - registeredNodes.add(parseRegisteredNode(node.getAsJsonObject())); + registeredNodes.add(RegisteredNode.fromJson(node.getAsJsonObject())); } return new RegisteredNodeAddressBook(registeredNodes); } - - /** - * Parses a single node entry from the Mirror Node 'registered_nodes' array. - */ - private RegisteredNode parseRegisteredNode(JsonObject nodeJson) { - long id = nodeJson.get("registered_node_id").getAsLong(); - String description = nodeJson.get("description").getAsString(); - PublicKey adminKey = parseJsonKey(nodeJson.get("admin_key").getAsJsonObject()); - - List endpoints = new ArrayList<>(); - for (JsonElement endpoint : nodeJson.getAsJsonArray("service_endpoints")) { - endpoints.add(parseJSONServiceEndpoint(endpoint.getAsJsonObject())); - } - - return new RegisteredNode(id, adminKey, description, endpoints); - } - - /** - * Parses a single service endpoint from a JSON object. - */ - private RegisteredServiceEndpoint parseJSONServiceEndpoint(JsonObject serviceEndpoint) { - Objects.requireNonNull(serviceEndpoint, "serviceEndpoint must not be null"); - - String type = serviceEndpoint.get("type").getAsString().toUpperCase(); - int port = serviceEndpoint.get("port").getAsInt(); - boolean requiresTls = serviceEndpoint.get("requires_tls").getAsBoolean(); - - String domainName = serviceEndpoint.has("domain_name") - && !serviceEndpoint.get("domain_name").isJsonNull() - ? serviceEndpoint.get("domain_name").getAsString() - : null; - - byte[] ipAddress = parseIpAddress(serviceEndpoint); - - RegisteredServiceEndpointBase registeredServiceEndpoint = - switch (type) { - case "BLOCK_NODE" -> buildBlockNodeEndpoint(serviceEndpoint.getAsJsonObject("block_node")); - case "MIRROR_NODE" -> buildMirrorNodeEndpoint(serviceEndpoint.getAsJsonObject("mirror_node")); - case "RPC_RELAY" -> buildRpcRelayEndpoint(serviceEndpoint.getAsJsonObject("rpc_relay")); - case "GENERAL_SERVICE" -> buildGeneralEndpoint(serviceEndpoint.getAsJsonObject("general_service")); - default -> throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); - }; - - return registeredServiceEndpoint - .setIpAddress(ipAddress) - .setDomainName(domainName) - .setPort(port) - .setRequiresTls(requiresTls); - } - - /** - * Parses the admin key from the JSON representation. - */ - private PublicKey parseJsonKey(JsonObject adminKey) { - Objects.requireNonNull(adminKey, "adminKey must not be null"); - String type = adminKey.get("_type").getAsString() != null - ? adminKey.get("_type").getAsString() - : ""; - - String key = adminKey.get("key").getAsString(); - return switch (type) { - case "ED25519" -> PublicKey.fromStringED25519(key); - case "ECDSA_SECP256K1" -> PublicKey.fromStringECDSA(key); - default -> PublicKey.fromString(key); - }; - } - - @Nullable - private byte[] parseIpAddress(JsonObject json) { - if (json.has("ip_address") && !json.get("ip_address").isJsonNull()) { - String rawIp = json.get("ip_address").getAsString(); - if (!rawIp.isEmpty()) { - try { - return InetAddress.getByName(rawIp).getAddress(); - } catch (UnknownHostException ignored) { - } - } - } - - return null; - } - - private BlockNodeServiceEndpoint buildBlockNodeEndpoint(JsonObject blockNode) { - Objects.requireNonNull(blockNode, "blockNode must not be null"); - - List apis = new ArrayList<>(); - if (blockNode.has("endpoint_apis")) { - for (JsonElement api : blockNode.getAsJsonArray("endpoint_apis")) { - apis.add(api.getAsString()); - } - } - - return new BlockNodeServiceEndpoint() - .setEndpointApis( - apis.stream().map(a -> BlockNodeApi.valueOf(a)).collect(Collectors.toUnmodifiableList())); - } - - private MirrorNodeServiceEndpoint buildMirrorNodeEndpoint(JsonObject mirrorNode) { - Objects.requireNonNull(mirrorNode, "mirrorNode must not be null"); - return new MirrorNodeServiceEndpoint(); - } - - private RpcRelayServiceEndpoint buildRpcRelayEndpoint(JsonObject rpcRelay) { - Objects.requireNonNull(rpcRelay, "rpcRelay must not be null"); - return new RpcRelayServiceEndpoint(); - } - - private GeneralServiceEndpoint buildGeneralEndpoint(JsonObject generalService) { - Objects.requireNonNull(generalService, "generalService must not be null"); - - String description = generalService.has("description") - && !generalService.get("description").isJsonNull() - ? generalService.get("description").getAsString() - : null; - - return new GeneralServiceEndpoint().setDescription(description); - } } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java index 0f43d3f6ed..2716e60b93 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredServiceEndpoint.java @@ -2,6 +2,9 @@ package com.hedera.hashgraph.sdk; import com.google.common.base.MoreObjects; +import com.google.gson.JsonObject; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Objects; import javax.annotation.Nullable; @@ -89,6 +92,58 @@ static RegisteredServiceEndpoint fromProtobuf( }; } + /** + * Parse RegisteredServiceEndpoint from MirrorNode json response `service_endpoint` + * + * @param json representing a single service endpoint entry from the Mirror Node REST API. + * @return {@code this} + */ + static RegisteredServiceEndpoint fromJson(JsonObject json) { + Objects.requireNonNull(json, "serviceEndpoint must not be null"); + + String type = json.get("type").getAsString().toUpperCase(); + + int port = json.get("port").getAsInt(); + boolean requiresTls = json.get("requires_tls").getAsBoolean(); + String domainName = json.has("domain_name") && !json.get("domain_name").isJsonNull() + ? json.get("domain_name").getAsString() + : null; + byte[] ipAddress = parseIpAddress(json); + + RegisteredServiceEndpointBase registeredServiceEndpoint = + switch (type) { + case "BLOCK_NODE" -> BlockNodeServiceEndpoint.fromJson(json.getAsJsonObject("block_node")); + case "MIRROR_NODE" -> MirrorNodeServiceEndpoint.fromJson(json.getAsJsonObject("mirror_node")); + case "RPC_RELAY" -> RpcRelayServiceEndpoint.fromJson(json.getAsJsonObject("rpc_relay")); + case "GENERAL_SERVICE" -> GeneralServiceEndpoint.fromJson(json.getAsJsonObject("general_service")); + default -> throw new IllegalArgumentException("Unknown type for serviceEndpoint " + type); + }; + + return registeredServiceEndpoint + .setIpAddress(ipAddress) + .setDomainName(domainName) + .setPort(port) + .setRequiresTls(requiresTls); + } + + /** + * Parse IpAddress from json response. + */ + @Nullable + private static byte[] parseIpAddress(JsonObject json) { + if (json.has("ip_address") && !json.get("ip_address").isJsonNull()) { + String rawIp = json.get("ip_address").getAsString(); + if (!rawIp.isEmpty()) { + try { + return InetAddress.getByName(rawIp).getAddress(); + } catch (UnknownHostException ignored) { + } + } + } + + return null; + } + /** * Build the protobuf. * diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java index ec329ed122..71dbb3f08d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk; +import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import java.util.Objects; @@ -55,6 +56,17 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { return registeredServiceEndpoint.build(); } + /** + * Parses RpcRelayEndpoint from the type-specific JSON object the MirrorNode. + * + * @param json the json containing rpc relay specific data + * @return {@code this} + */ + static RpcRelayServiceEndpoint fromJson(JsonObject json) { + Objects.requireNonNull(json, "json must not be null"); + return new RpcRelayServiceEndpoint(); + } + @Override public String toString() { return toStringHelper().toString(); From 88d67a51da6089d4e14a4969c45b108a050adac7 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 27 Apr 2026 19:08:40 +0530 Subject: [PATCH 21/27] chore: added more test for the create tx Signed-off-by: Manish Dait --- .../RegisteredNodeLifeCycleExample.java | 76 +++++++++------ .../hashgraph/sdk/NodeUpdateTransaction.java | 4 +- ...dNodeCreateTransactionIntegrationTest.java | 96 ++++++++++++++++--- 3 files changed, 133 insertions(+), 43 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java index dc42fcee1b..1d067fc54a 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -29,13 +29,13 @@ public class RegisteredNodeLifeCycleExample { * Used to sign and pay for operations on Hedera. */ private static final AccountId OPERATOR_ID = - AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); /** * Operator's private key. */ private static final PrivateKey OPERATOR_KEY = - PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); /** * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. @@ -72,21 +72,21 @@ public static void main(String[] args) throws Exception { */ PrivateKey adminKey = PrivateKey.generateED25519(); BlockNodeServiceEndpoint initialEndpoint = new BlockNodeServiceEndpoint() - .setIpAddress(new byte[] {127, 0, 0, 1}) - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); + .setIpAddress(new byte[] {127, 0, 0, 1}) + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); /* * Step 2: * Create Registered Node. */ RegisteredNodeCreateTransaction registeredNodeCreateTx = new RegisteredNodeCreateTransaction() - .setDescription("My Block Node") - .setAdminKey(adminKey) - .addServiceEndpoint(initialEndpoint) - .freezeWith(client) - .sign(adminKey); + .setDescription("My Block Node") + .setAdminKey(adminKey) + .addServiceEndpoint(initialEndpoint) + .freezeWith(client) + .sign(adminKey); System.out.println("Creating Registered Node..."); TransactionResponse registeredNodeCreateTxResponse = registeredNodeCreateTx.execute(client); @@ -96,6 +96,8 @@ public static void main(String[] args) throws Exception { throw new Exception("RegisteredNodeCreate transaction receipt was missing registeredNodeId. (Fail)"); } + long registeredNodeId = registeredNodeCreateTxReceipt.registeredNodeId; + /* * Step 3: * Execute a RegisteredNodeAddressBookQuery to verify the newly created @@ -108,31 +110,31 @@ public static void main(String[] args) throws Exception { * Update the RegisteredNode with new Block Node endpoint. */ BlockNodeServiceEndpoint updateEndpoint = new BlockNodeServiceEndpoint() - .setDomainName("block-node.example.com") - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.STATUS); + .setDomainName("block-node.example.com") + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.STATUS); RegisteredNodeUpdateTransaction registeredNodeUpdateTx = new RegisteredNodeUpdateTransaction() - .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) - .setDescription("My Updated Block Node") - .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) - .freezeWith(client) - .sign(adminKey); + .setRegisteredNodeId(registeredNodeId) + .setDescription("My Updated Block Node") + .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) + .freezeWith(client) + .sign(adminKey); System.out.println("Updating Registered Node..."); TransactionResponse registeredNodeUpdateTxResponse = registeredNodeUpdateTx.execute(client); - TransactionReceipt registeredNodeUpdateTxReceipt = registeredNodeUpdateTxResponse.getReceipt(client); + registeredNodeUpdateTxResponse.getReceipt(client); /* * Step 5: * Add the registeredNodeId as associatedRegisteredNodes to a Node. */ - long registeredNodeId = registeredNodeUpdateTxReceipt.registeredNodeId; + NodeUpdateTransaction associateTx = new NodeUpdateTransaction() - .setNodeId(0) - .addAssociatedRegisteredNode(registeredNodeId) - .freezeWith(client); + .setNodeId(0) + .addAssociatedRegisteredNode(registeredNodeId) + .freezeWith(client); System.out.println("Associating registered node " + registeredNodeId + " with consensus node..."); TransactionResponse associateTxResponse = associateTx.execute(client); @@ -140,15 +142,29 @@ public static void main(String[] args) throws Exception { /* * Step 6: + * Remove the registeredNodeId as associatedRegisteredNodes from a Node. + */ + + NodeUpdateTransaction disassociateTx = new NodeUpdateTransaction() + .setNodeId(0) + .setAssociatedRegisteredNodes(List.of()) // Empty list clear associated registeredNode + .freezeWith(client); + + System.out.println("Disassociating registered node " + registeredNodeId + " with consensus node..."); + disassociateTx.execute(client); + associateTxResponse.getReceipt(client); + + /* + * Step 7: * Delete the Registered Node. */ System.out.println("Deleting Registered Node..."); new RegisteredNodeDeleteTransaction() - .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) - .freezeWith(client) - .sign(adminKey) - .execute(client) - .getReceipt(client); + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .freezeWith(client) + .sign(adminKey) + .execute(client) + .getReceipt(client); client.close(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java index df5ddc80f6..7ac01f3b8d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java @@ -68,7 +68,7 @@ public class NodeUpdateTransaction extends Transaction { @Nullable private Endpoint grpcWebProxyEndpoint = null; - private List associatedRegisteredNodes = new ArrayList<>(); + private List associatedRegisteredNodes = null; /** * Constructor. @@ -544,7 +544,7 @@ NodeUpdateTransactionBody.Builder build() { builder.addServiceEndpoint(serviceEndpoint.toProtobuf()); } - if (!associatedRegisteredNodes.isEmpty()) { + if (associatedRegisteredNodes != null) { builder.setAssociatedRegisteredNodeList(AssociatedRegisteredNodeList.newBuilder() .addAllAssociatedRegisteredNode(associatedRegisteredNodes) .build()); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index 37c3187e90..131ec05bd6 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -5,10 +5,12 @@ import com.hedera.hashgraph.sdk.BlockNodeApi; import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.GeneralServiceEndpoint; import com.hedera.hashgraph.sdk.MirrorNodeServiceEndpoint; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; +import com.hedera.hashgraph.sdk.RpcRelayServiceEndpoint; import com.hedera.hashgraph.sdk.Status; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -16,22 +18,94 @@ public class RegisteredNodeCreateTransactionIntegrationTest { @Test - @DisplayName("Can create a registered node") - void canCreateRegisteredNode() throws Exception { + @DisplayName("Can create a registered node with blockNodeServiceEndpoint") + void canCreateRegisteredNodeWithBlockNode() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); List serviceEndpoints = List.of(new BlockNodeServiceEndpoint() - .setDomainName("test.block.com") - .setPort(443) - .addEndpointApi(BlockNodeApi.STATUS)); + .setDomainName("test.block.com") + .setPort(443) + .addEndpointApi(BlockNodeApi.STATUS)); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(key) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + assertThat(receipt.registeredNodeId).isGreaterThan(0); + } + } + + @Test + @DisplayName("Can create a registered node with mirrorNodeServiceEndpoint") + void canCreateRegisteredNodeWitMirrorNode() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + List serviceEndpoints = List.of(new MirrorNodeServiceEndpoint() + .setDomainName("test.mirror.com") + .setPort(443)); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + assertThat(receipt.registeredNodeId).isGreaterThan(0); + } + } + + @Test + @DisplayName("Can create a registered node with rpcRelayServiceEndpoint") + void canCreateRegisteredNodeWithRpcRelay() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + List serviceEndpoints = List.of( + new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443)); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + + assertThat(receipt.status).isEqualTo(Status.SUCCESS); + assertThat(receipt.registeredNodeId).isGreaterThan(0); + } + } + + @Test + @DisplayName("Can create a registered node with generalServiceEndpoint") + void canCreateRegisteredNodeWithGeneralService() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + List serviceEndpoints = List.of(new GeneralServiceEndpoint() + .setDomainName("test.general.com") + .setDescription("GeneralEndpoint") + .setPort(443)); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); From f7faf94b571c09f616906a1b0b10b865c3380fd8 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 27 Apr 2026 19:45:42 +0530 Subject: [PATCH 22/27] chore: fix to proto to include mirror and rpc ep Signed-off-by: Manish Dait --- .../sdk/MirrorNodeServiceEndpoint.java | 5 +- .../sdk/RpcRelayServiceEndpoint.java | 4 +- .../sdk/MirrorNodeServiceEndpointTest.snap | 4 +- .../sdk/RpcRelayServiceEndpointTest.snap | 4 +- ...dNodeCreateTransactionIntegrationTest.java | 72 ++++++++++--------- 5 files changed, 49 insertions(+), 40 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java index e59cc920f9..645c2be28f 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import java.util.Objects; /** @@ -44,7 +45,8 @@ static MirrorNodeServiceEndpoint fromProtobuf( com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() .setPort(port) - .setRequiresTls(requiresTls); + .setRequiresTls(requiresTls) + .setMirrorNode(RegisteredServiceEndpoint.MirrorNodeEndpoint.newBuilder()); if (ipAddress != null) { registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(ipAddress)); @@ -54,6 +56,7 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { registeredServiceEndpoint.setDomainName(domainName); } + System.out.println(registeredServiceEndpoint.build()); return registeredServiceEndpoint.build(); } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java index 71dbb3f08d..21f3a8b305 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpoint.java @@ -3,6 +3,7 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; +import com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint; import java.util.Objects; /** @@ -43,7 +44,8 @@ static RpcRelayServiceEndpoint fromProtobuf( com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() .setPort(port) - .setRequiresTls(requiresTls); + .setRequiresTls(requiresTls) + .setRpcRelay(RegisteredServiceEndpoint.RpcRelayEndpoint.newBuilder()); if (ipAddress != null) { registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(ipAddress)); diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap index e4018ffc07..5d2379d626 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpointTest.snap @@ -9,10 +9,10 @@ com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.fromProtobufWithIp=[ com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.toProtobufWithDomain=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@14147303\ndomain_name: \"test.mirror.com\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@28438c0e\ndomain_name: \"test.mirror.com\"\nmirror_node {\n}\nport: 443\nrequires_tls: true" ] com.hedera.hashgraph.sdk.MirrorNodeServiceEndpointTest.toProtobufWithIp=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@26cccebb\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@86e8926\nip_address: \"\\001\\002\\003\\004\"\nmirror_node {\n}\nport: 443\nrequires_tls: true" ] \ No newline at end of file diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap index 208d1c81fb..49c91ffe50 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/RpcRelayServiceEndpointTest.snap @@ -9,10 +9,10 @@ com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.fromProtobufWithIp=[ com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.toProtobufWithDomain=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@14147303\ndomain_name: \"test.mirror.com\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@28438c0e\ndomain_name: \"test.mirror.com\"\nport: 443\nrequires_tls: true\nrpc_relay {\n}" ] com.hedera.hashgraph.sdk.RpcRelayServiceEndpointTest.toProtobufWithIp=[ - "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@26cccebb\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true" + "# com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint@86e8926\nip_address: \"\\001\\002\\003\\004\"\nport: 443\nrequires_tls: true\nrpc_relay {\n}" ] \ No newline at end of file diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index 131ec05bd6..b84c7444a3 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -23,17 +23,17 @@ void canCreateRegisteredNodeWithBlockNode() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); List serviceEndpoints = List.of(new BlockNodeServiceEndpoint() - .setDomainName("test.block.com") - .setPort(443) - .addEndpointApi(BlockNodeApi.STATUS)); + .setDomainName("test.block.com") + .setPort(443) + .addEndpointApi(BlockNodeApi.STATUS)); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(key) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); @@ -48,16 +48,18 @@ void canCreateRegisteredNodeWitMirrorNode() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); List serviceEndpoints = List.of(new MirrorNodeServiceEndpoint() - .setDomainName("test.mirror.com") - .setPort(443)); + .setDomainName("test.mirror.com") + .setPort(443)); + + System.out.println(serviceEndpoints); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(key) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); @@ -72,15 +74,15 @@ void canCreateRegisteredNodeWithRpcRelay() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); List serviceEndpoints = List.of( - new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443)); + new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443)); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(key) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); @@ -95,17 +97,17 @@ void canCreateRegisteredNodeWithGeneralService() throws Exception { try (var testEnv = new IntegrationTestEnv(1)) { var key = PrivateKey.generateED25519(); List serviceEndpoints = List.of(new GeneralServiceEndpoint() - .setDomainName("test.general.com") - .setDescription("GeneralEndpoint") - .setPort(443)); + .setDomainName("test.general.com") + .setDescription("GeneralEndpoint") + .setPort(443)); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(key) - .setDescription("test description") - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(key) - .execute(testEnv.client); + .setAdminKey(key) + .setDescription("test description") + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); @@ -125,9 +127,11 @@ void canCreateRegisteredNodeWithMultipleServiceEndpoints() throws Exception { .setPort(443) .addEndpointApi(BlockNodeApi.STATUS), new MirrorNodeServiceEndpoint() - .setDomainName("test.mirror.com") + .setIpAddress(new byte[] {127, 0, 0, 1}) .setPort(443)); + System.out.println(serviceEndpoints); + var response = new RegisteredNodeCreateTransaction() .setAdminKey(key) .setDescription("test description") From 04df3f838a3aa78f8d387d0b9bc999507ccdea52 Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Mon, 27 Apr 2026 20:29:00 +0530 Subject: [PATCH 23/27] chore: expand test for delete node Signed-off-by: Manish Dait --- .../RegisteredNodeLifeCycleExample.java | 62 +++++++++---------- .../sdk/MirrorNodeServiceEndpoint.java | 1 - .../hashgraph/sdk/NodeUpdateTransaction.java | 6 ++ ...dNodeCreateTransactionIntegrationTest.java | 9 +-- ...dNodeDeleteTransactionIntegrationTest.java | 47 ++++++++++++++ 5 files changed, 89 insertions(+), 36 deletions(-) diff --git a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java index 1d067fc54a..9cd49b841c 100644 --- a/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java +++ b/examples/src/main/java/com/hedera/hashgraph/sdk/examples/RegisteredNodeLifeCycleExample.java @@ -29,13 +29,13 @@ public class RegisteredNodeLifeCycleExample { * Used to sign and pay for operations on Hedera. */ private static final AccountId OPERATOR_ID = - AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); + AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID"))); /** * Operator's private key. */ private static final PrivateKey OPERATOR_KEY = - PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); + PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY"))); /** * HEDERA_NETWORK defaults to testnet if not specified in dotenv file. @@ -72,21 +72,21 @@ public static void main(String[] args) throws Exception { */ PrivateKey adminKey = PrivateKey.generateED25519(); BlockNodeServiceEndpoint initialEndpoint = new BlockNodeServiceEndpoint() - .setIpAddress(new byte[] {127, 0, 0, 1}) - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); + .setIpAddress(new byte[] {127, 0, 0, 1}) + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.SUBSCRIBE_STREAM); /* * Step 2: * Create Registered Node. */ RegisteredNodeCreateTransaction registeredNodeCreateTx = new RegisteredNodeCreateTransaction() - .setDescription("My Block Node") - .setAdminKey(adminKey) - .addServiceEndpoint(initialEndpoint) - .freezeWith(client) - .sign(adminKey); + .setDescription("My Block Node") + .setAdminKey(adminKey) + .addServiceEndpoint(initialEndpoint) + .freezeWith(client) + .sign(adminKey); System.out.println("Creating Registered Node..."); TransactionResponse registeredNodeCreateTxResponse = registeredNodeCreateTx.execute(client); @@ -110,17 +110,17 @@ public static void main(String[] args) throws Exception { * Update the RegisteredNode with new Block Node endpoint. */ BlockNodeServiceEndpoint updateEndpoint = new BlockNodeServiceEndpoint() - .setDomainName("block-node.example.com") - .setPort(443) - .setRequiresTls(true) - .addEndpointApi(BlockNodeApi.STATUS); + .setDomainName("block-node.example.com") + .setPort(443) + .setRequiresTls(true) + .addEndpointApi(BlockNodeApi.STATUS); RegisteredNodeUpdateTransaction registeredNodeUpdateTx = new RegisteredNodeUpdateTransaction() - .setRegisteredNodeId(registeredNodeId) - .setDescription("My Updated Block Node") - .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) - .freezeWith(client) - .sign(adminKey); + .setRegisteredNodeId(registeredNodeId) + .setDescription("My Updated Block Node") + .setServiceEndpoints(List.of(initialEndpoint, updateEndpoint)) + .freezeWith(client) + .sign(adminKey); System.out.println("Updating Registered Node..."); TransactionResponse registeredNodeUpdateTxResponse = registeredNodeUpdateTx.execute(client); @@ -132,9 +132,9 @@ public static void main(String[] args) throws Exception { */ NodeUpdateTransaction associateTx = new NodeUpdateTransaction() - .setNodeId(0) - .addAssociatedRegisteredNode(registeredNodeId) - .freezeWith(client); + .setNodeId(0) + .addAssociatedRegisteredNode(registeredNodeId) + .freezeWith(client); System.out.println("Associating registered node " + registeredNodeId + " with consensus node..."); TransactionResponse associateTxResponse = associateTx.execute(client); @@ -146,9 +146,9 @@ public static void main(String[] args) throws Exception { */ NodeUpdateTransaction disassociateTx = new NodeUpdateTransaction() - .setNodeId(0) - .setAssociatedRegisteredNodes(List.of()) // Empty list clear associated registeredNode - .freezeWith(client); + .setNodeId(0) + .setAssociatedRegisteredNodes(List.of()) // Empty list clear associated registeredNode + .freezeWith(client); System.out.println("Disassociating registered node " + registeredNodeId + " with consensus node..."); disassociateTx.execute(client); @@ -160,11 +160,11 @@ public static void main(String[] args) throws Exception { */ System.out.println("Deleting Registered Node..."); new RegisteredNodeDeleteTransaction() - .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) - .freezeWith(client) - .sign(adminKey) - .execute(client) - .getReceipt(client); + .setRegisteredNodeId(registeredNodeCreateTxReceipt.registeredNodeId) + .freezeWith(client) + .sign(adminKey) + .execute(client) + .getReceipt(client); client.close(); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java index 645c2be28f..906c999b5a 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/MirrorNodeServiceEndpoint.java @@ -56,7 +56,6 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { registeredServiceEndpoint.setDomainName(domainName); } - System.out.println(registeredServiceEndpoint.build()); return registeredServiceEndpoint.build(); } diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java index 7ac01f3b8d..a1aba466eb 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/NodeUpdateTransaction.java @@ -68,6 +68,7 @@ public class NodeUpdateTransaction extends Transaction { @Nullable private Endpoint grpcWebProxyEndpoint = null; + @Nullable private List associatedRegisteredNodes = null; /** @@ -509,9 +510,14 @@ public NodeUpdateTransaction setAssociatedRegisteredNodes(List associatedR */ public NodeUpdateTransaction addAssociatedRegisteredNode(long associatedRegisteredNode) { requireNotFrozen(); + if (associatedRegisteredNodes == null) { + associatedRegisteredNodes = new ArrayList<>(); + } + if (associatedRegisteredNodes.size() >= 20) { throw new IllegalArgumentException("associatedRegisteredNodes must not contain more than 20 entries"); } + associatedRegisteredNodes.add(associatedRegisteredNode); return this; } diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java index b84c7444a3..70d78cb5a1 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeCreateTransactionIntegrationTest.java @@ -51,8 +51,6 @@ void canCreateRegisteredNodeWitMirrorNode() throws Exception { .setDomainName("test.mirror.com") .setPort(443)); - System.out.println(serviceEndpoints); - var response = new RegisteredNodeCreateTransaction() .setAdminKey(key) .setDescription("test description") @@ -128,10 +126,13 @@ void canCreateRegisteredNodeWithMultipleServiceEndpoints() throws Exception { .addEndpointApi(BlockNodeApi.STATUS), new MirrorNodeServiceEndpoint() .setIpAddress(new byte[] {127, 0, 0, 1}) + .setPort(443), + new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443), + new GeneralServiceEndpoint() + .setDomainName("test.general.com") + .setDescription("GeneralEndpoint") .setPort(443)); - System.out.println(serviceEndpoints); - var response = new RegisteredNodeCreateTransaction() .setAdminKey(key) .setDescription("test description") diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java index 5009ebd243..fbce069775 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeDeleteTransactionIntegrationTest.java @@ -2,10 +2,13 @@ package com.hedera.hashgraph.sdk.test.integration; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import com.hedera.hashgraph.sdk.BlockNodeApi; import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.NodeUpdateTransaction; import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.ReceiptStatusException; import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredNodeDeleteTransaction; import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; @@ -47,4 +50,48 @@ void canDeleteRegisteredNode() throws Exception { assertThat(deleteReceipt.status).isEqualTo(Status.SUCCESS); } } + + @Test + @DisplayName("Should return REGISTERED_NODE_STILL_ASSOCIATED when deleting an associated node") + void shouldCauseReceiptStatusWhenNodeStillAssociated() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var key = PrivateKey.generateED25519(); + + List endpoints = List.of(new BlockNodeServiceEndpoint() + .setDomainName("blocks.example.com") + .setPort(443) + .addEndpointApi(BlockNodeApi.STATUS)); + + var createReceipt = new RegisteredNodeCreateTransaction() + .setAdminKey(key) + .setDescription("test node delete") + .setServiceEndpoints(endpoints) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + var registeredNodeId = createReceipt.registeredNodeId; + + new NodeUpdateTransaction() + .setNodeId(0) + .addAssociatedRegisteredNode(registeredNodeId) + .freezeWith(testEnv.client) + .execute(testEnv.client) + .getReceipt(testEnv.client); + + var deleteTx = new RegisteredNodeDeleteTransaction() + .setRegisteredNodeId(registeredNodeId) + .freezeWith(testEnv.client) + .sign(key) + .execute(testEnv.client); + + assertThatThrownBy(() -> deleteTx.getReceipt(testEnv.client)) + .isInstanceOf(ReceiptStatusException.class) + .satisfies(e -> { + assertThat(((ReceiptStatusException) e).receipt.status) + .isEqualTo(Status.REGISTERED_NODE_STILL_ASSOCIATED); + }); + } + } } From bf1dc22f78fbaf0c08e32a6e78727467162f062d Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 30 Apr 2026 02:44:38 +0530 Subject: [PATCH 24/27] chore: added test for the registered address book query Signed-off-by: Manish Dait --- .../hashgraph/sdk/GeneralServiceEndpoint.java | 11 +-- .../sdk/RegisteredNodeAddressBookQuery.java | 13 +++- ...edNodeAddressBookQueryIntegrationTest.java | 67 +++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java index 8a5a8b62c5..6f79af3d37 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java @@ -85,13 +85,16 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { "RegisterServiceEndpoint must define either an ipAddress or domainName"); } + var generalService = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder(); + if (description != null) { + generalService.setDescription(description); + } + var registeredServiceEndpoint = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.newBuilder() .setPort(port) .setRequiresTls(requiresTls) - .setGeneralService( - com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder() - .setDescription(this.description) - .build()); + .setGeneralService(generalService); + if (ipAddress != null) { registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(this.ipAddress)); diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java index 3fff01e22a..b0dbc89b8d 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/RegisteredNodeAddressBookQuery.java @@ -50,14 +50,25 @@ public long getRegisteredNodeId() { */ public RegisteredNodeAddressBook execute(Client client) throws ExecutionException, InterruptedException { String json = executeMirrorNodeRequest(client).get(); + System.out.println(json); return parseRegisterNodeAddressBook(json); } CompletableFuture executeMirrorNodeRequest(Client client) { Objects.requireNonNull(client, "client must not be null"); - String apiEndpoint = "/api/v1/network/registered-nodes?registerednode.id=" + registeredNodeId; + String apiEndpoint = "/network/registered-nodes?registerednode.id=" + registeredNodeId; String baseUrl = client.getMirrorRestBaseUrl(); + // For localhost registered node calls, override to use port 8084 unless system property overrides + if (baseUrl.contains("localhost:5551") || baseUrl.contains("127.0.0.1:5551")) { + String registeredNodePort = System.getProperty("hedera.mirror.registerednode.port"); + if (registeredNodePort != null && !registeredNodePort.isEmpty()) { + baseUrl = baseUrl.replace(":5551", ":" + registeredNodePort); + } else { + baseUrl = baseUrl.replace(":5551", ":8084"); + } + } + return performQueryToMirrorNodeAsync(baseUrl, apiEndpoint, null).exceptionally(ex -> { client.getLogger().error("Error while performing post request to Mirror Node: " + ex.getMessage()); throw new CompletionException(ex); diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java new file mode 100644 index 0000000000..d4d9395c6e --- /dev/null +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java @@ -0,0 +1,67 @@ +package com.hedera.hashgraph.sdk.test.integration; + +import com.hedera.hashgraph.sdk.BlockNodeApi; +import com.hedera.hashgraph.sdk.BlockNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.GeneralServiceEndpoint; +import com.hedera.hashgraph.sdk.MirrorNodeServiceEndpoint; +import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.RegisteredNodeAddressBookQuery; +import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; +import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; +import com.hedera.hashgraph.sdk.RpcRelayServiceEndpoint; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class RegisteredNodeAddressBookQueryIntegrationTest { + @Test + @DisplayName("Should query registered node using node id") + void canCreateAndVerifyRegisteredNodeWithPolling() throws Exception { + try (var testEnv = new IntegrationTestEnv(1)) { + var adminKey = PrivateKey.generateED25519(); + var description = "Test Registered Node"; + + List serviceEndpoints = List.of( + new BlockNodeServiceEndpoint().setDomainName("test.block.com").setPort(443).addEndpointApi(BlockNodeApi.STATUS), + new MirrorNodeServiceEndpoint().setIpAddress(new byte[]{127, 0, 0, 1}).setPort(443), + new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443), + new GeneralServiceEndpoint().setDomainName("test.general.com").setPort(8080) + ); + + System.out.println(testEnv.client); + + var response = new RegisteredNodeCreateTransaction() + .setAdminKey(adminKey) + .setDescription(description) + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(adminKey) + .execute(testEnv.client); + + var receipt = response.getReceipt(testEnv.client); + var nodeId = receipt.registeredNodeId; + + // Wait for mirror node to update + Thread.sleep(5000); + + var registeredNodeBook = new RegisteredNodeAddressBookQuery() + .setRegisteredNodeId(nodeId) + .execute(testEnv.client); + + Assertions.assertThat(registeredNodeBook.registeredNodes).hasSize(1); + + var node = registeredNodeBook.registeredNodes.get(0); + + Assertions.assertThat(node.description).isEqualTo(description); + Assertions.assertThat(node.serviceEndpoints).hasSize(4); + + Assertions.assertThat(node.serviceEndpoints) + .filteredOn(e -> e instanceof BlockNodeServiceEndpoint) + .first() + .extracting("domainName") + .isEqualTo("test.block.com"); + } + } +} From 48215d0afa181a09a042fc6e6c88074ab9e2781f Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 30 Apr 2026 02:45:39 +0530 Subject: [PATCH 25/27] chore: updated mirrornode version Signed-off-by: Manish Dait --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49b7136ca6..245258b6fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -256,7 +256,7 @@ jobs: soloVersion: v0.65.0 dualMode: true hieroVersion: v0.73.0 - mirrorNodeVersion: v0.152.0 + mirrorNodeVersion: v0.153.0 - name: Build SDK run: ./gradlew assemble From a3f1785899dcc0fb0fa49185c265feabd253eb6a Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 30 Apr 2026 02:46:17 +0530 Subject: [PATCH 26/27] chore: apply spotless Signed-off-by: Manish Dait --- .../hashgraph/sdk/GeneralServiceEndpoint.java | 4 +- ...edNodeAddressBookQueryIntegrationTest.java | 44 +++++++++++-------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java index 6f79af3d37..c19578bbdd 100644 --- a/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java +++ b/sdk/src/main/java/com/hedera/hashgraph/sdk/GeneralServiceEndpoint.java @@ -85,7 +85,8 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { "RegisterServiceEndpoint must define either an ipAddress or domainName"); } - var generalService = com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder(); + var generalService = + com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint.GeneralServiceEndpoint.newBuilder(); if (description != null) { generalService.setDescription(description); } @@ -95,7 +96,6 @@ com.hedera.hashgraph.sdk.proto.RegisteredServiceEndpoint toProtobuf() { .setRequiresTls(requiresTls) .setGeneralService(generalService); - if (ipAddress != null) { registeredServiceEndpoint.setIpAddress(ByteString.copyFrom(this.ipAddress)); } diff --git a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java index d4d9395c6e..bc45005da6 100644 --- a/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java +++ b/sdk/src/testIntegration/java/com/hedera/hashgraph/sdk/test/integration/RegisteredNodeAddressBookQueryIntegrationTest.java @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 package com.hedera.hashgraph.sdk.test.integration; import com.hedera.hashgraph.sdk.BlockNodeApi; @@ -9,12 +10,11 @@ import com.hedera.hashgraph.sdk.RegisteredNodeCreateTransaction; import com.hedera.hashgraph.sdk.RegisteredServiceEndpoint; import com.hedera.hashgraph.sdk.RpcRelayServiceEndpoint; +import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.List; - public class RegisteredNodeAddressBookQueryIntegrationTest { @Test @DisplayName("Should query registered node using node id") @@ -24,21 +24,27 @@ void canCreateAndVerifyRegisteredNodeWithPolling() throws Exception { var description = "Test Registered Node"; List serviceEndpoints = List.of( - new BlockNodeServiceEndpoint().setDomainName("test.block.com").setPort(443).addEndpointApi(BlockNodeApi.STATUS), - new MirrorNodeServiceEndpoint().setIpAddress(new byte[]{127, 0, 0, 1}).setPort(443), - new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443), - new GeneralServiceEndpoint().setDomainName("test.general.com").setPort(8080) - ); + new BlockNodeServiceEndpoint() + .setDomainName("test.block.com") + .setPort(443) + .addEndpointApi(BlockNodeApi.STATUS), + new MirrorNodeServiceEndpoint() + .setIpAddress(new byte[] {127, 0, 0, 1}) + .setPort(443), + new RpcRelayServiceEndpoint().setDomainName("test.rpc.com").setPort(443), + new GeneralServiceEndpoint() + .setDomainName("test.general.com") + .setPort(8080)); System.out.println(testEnv.client); var response = new RegisteredNodeCreateTransaction() - .setAdminKey(adminKey) - .setDescription(description) - .setServiceEndpoints(serviceEndpoints) - .freezeWith(testEnv.client) - .sign(adminKey) - .execute(testEnv.client); + .setAdminKey(adminKey) + .setDescription(description) + .setServiceEndpoints(serviceEndpoints) + .freezeWith(testEnv.client) + .sign(adminKey) + .execute(testEnv.client); var receipt = response.getReceipt(testEnv.client); var nodeId = receipt.registeredNodeId; @@ -47,8 +53,8 @@ void canCreateAndVerifyRegisteredNodeWithPolling() throws Exception { Thread.sleep(5000); var registeredNodeBook = new RegisteredNodeAddressBookQuery() - .setRegisteredNodeId(nodeId) - .execute(testEnv.client); + .setRegisteredNodeId(nodeId) + .execute(testEnv.client); Assertions.assertThat(registeredNodeBook.registeredNodes).hasSize(1); @@ -58,10 +64,10 @@ void canCreateAndVerifyRegisteredNodeWithPolling() throws Exception { Assertions.assertThat(node.serviceEndpoints).hasSize(4); Assertions.assertThat(node.serviceEndpoints) - .filteredOn(e -> e instanceof BlockNodeServiceEndpoint) - .first() - .extracting("domainName") - .isEqualTo("test.block.com"); + .filteredOn(e -> e instanceof BlockNodeServiceEndpoint) + .first() + .extracting("domainName") + .isEqualTo("test.block.com"); } } } From aa110b11556d397ebcee8257a65d504090ed814a Mon Sep 17 00:00:00 2001 From: Manish Dait Date: Thu, 30 Apr 2026 18:13:42 +0530 Subject: [PATCH 27/27] chore fix rebase Signed-off-by: Manish Dait --- .../com/hedera/hashgraph/sdk/TransactionRecordTest.snap | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap index b82db333b4..fb89c30f96 100644 --- a/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap +++ b/sdk/src/test/java/com/hedera/hashgraph/sdk/TransactionRecordTest.snap @@ -1,8 +1,8 @@ com.hedera.hashgraph.sdk.TransactionRecordTest.shouldSerialize2=[ - "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=null, prngNumber=4, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}]}" + "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=null, prngNumber=4, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}], highVolumePricingMultiplier=1000}" ] com.hedera.hashgraph.sdk.TransactionRecordTest.shouldSerialize=[ - "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=766572792072616e646f6d206279746573, prngNumber=null, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}]}" -] + "TransactionRecord{receipt=TransactionReceipt{transactionId=null, status=SCHEDULE_ALREADY_DELETED, exchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, nextExchangeRate=ExchangeRate{hbars=3, cents=4, expirationTime=2019-04-01T22:42:22Z, exchangeRateInCents=1.3333333333333333}, accountId=1.2.3, fileId=4.5.6, contractId=3.2.1, topicId=9.8.7, tokenId=6.5.4, topicSequenceNumber=3, topicRunningHash=[54, 56, 54, 102, 55, 55, 50, 48, 54, 101, 54, 102, 55, 55, 50, 48, 54, 50, 55, 50, 54, 102, 55, 55, 54, 101, 50, 48, 54, 51, 54, 102, 55, 55], totalSupply=30, scheduleId=1.1.1, scheduledTransactionId=3.3.3@1554158542.000000000, serials=[1, 2, 3], nodeId=1, registeredNodeId=1, duplicates=[], children=[]}, transactionHash=68656c6c6f, consensusTimestamp=2019-04-01T22:42:22Z, transactionId=3.3.3@1554158542.000000000, transactionMemo=memo, transactionFee=3000 tℏ, contractFunctionResult=ContractFunctionResult{contractId=1.2.3, evmAddress=1.2.98329e006610472e6b372c080833f6d79ed833cf, errorMessage=null, bloom=, gasUsed=0, logs=[], createdContractIds=[], stateChanges=[], gas=0, hbarAmount=0 tℏ, contractFunctionparametersBytes=, rawResult=00000000000000000000000000000000000000000000000000000000ffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000011223344556677889900aabbccddeeff00112233ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000d48656c6c6f2c20776f726c642100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001448656c6c6f2c20776f726c642c20616761696e21000000000000000000000000, senderAccountId=1.2.3, contractNonces=[], signerNonce=0}, transfers=[Transfer{accountId=4.4.4, amount=5 ℏ}], tokenTransfers={6.6.6={1.1.1=4}}, tokenNftTransfers={4.4.4=[TokenNftTransfer{tokenId=4.4.4, sender=1.2.3, receiver=3.2.1, serial=4, isApproved=true}]}, scheduleRef=3.3.3, assessedCustomFees=[AssessedCustomFee{amount=4, tokenId=4.5.6, feeCollectorAccountId=8.6.5, payerAccountIdList=[3.3.3]}], automaticTokenAssociations=[TokenAssociation{tokenId=5.4.3, accountId=8.7.6}], aliasKey=3036301006072a8648ce3d020106052b8104000a03220002703a9370b0443be6ae7c507b0aec81a55e94e4a863b9655360bd65358caa6588, children=[], duplicates=[], parentConsensusTimestamp=2019-04-01T22:42:22Z, ethereumHash=536f6d652068617368, paidStakingRewards=[Transfer{accountId=1.2.3, amount=8 ℏ}], prngBytes=766572792072616e646f6d206279746573, prngNumber=null, evmAddress=30783030, pendingAirdropRecords=[PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=null, nftId=0.0.5005/1234}, pendingAirdropAmount=123}, PendingAirdropRecord{pendingAirdropId=PendingAirdropId{sender=0.0.123, receiver=0.0.124, tokenId=0.0.12345, nftId=null}, pendingAirdropAmount=123}], highVolumePricingMultiplier=1000}" +] \ No newline at end of file