com.hedera.hashgraph.sdk.NodeUpdateTransaction Maven / Gradle / Ivy
/*-
*
* Hedera Java SDK
*
* Copyright (C) 2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.hedera.hashgraph.sdk;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.StringValue;
import com.hedera.hashgraph.sdk.proto.AddressBookServiceGrpc;
import com.hedera.hashgraph.sdk.proto.NodeUpdateTransactionBody;
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.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;
import org.bouncycastle.util.Arrays;
/**
* A transaction to modify address book node attributes.
*
* - This transaction SHALL enable the node operator, as identified by the
* `admin_key`, to modify operational attributes of the node.
* - This transaction MUST be signed by the active `admin_key` for the node.
* - If this transaction sets a new value for the `admin_key`, then both the
* current `admin_key`, and the new `admin_key` MUST sign this transaction.
* - This transaction SHALL NOT change any field that is not set (is null) in
* this transaction body.
* - This SHALL create a pending update to the node, but the change SHALL NOT
* be immediately applied to the active configuration.
* - All pending node updates SHALL be applied to the active network
* configuration during the next `freeze` transaction with the field
* `freeze_type` set to `PREPARE_UPGRADE`.
*
* ### Record Stream Effects
* Upon completion the `node_id` for the updated entry SHALL be in the
* transaction receipt.
*/
public class NodeUpdateTransaction extends Transaction {
/**
* A consensus node identifier in the network state.
*
* The node identified MUST exist in the network address book.
* The node identified MUST NOT be deleted.
* This value is REQUIRED.
*/
private long nodeId = 0;
/**
* An account identifier.
*
* If set, this SHALL replace the node account identifier.
* If set, this transaction MUST be signed by the active `key` for _both_
* the current node account _and_ the identified new node account.
*/
@Nullable
private AccountId accountId = null;
/**
* A short description of the node.
*
* This value, if set, MUST NOT exceed 100 bytes when encoded as UTF-8.
* If set, this value SHALL replace the previous value.
*/
@Nullable
private String description = null;
/**
* A list of service endpoints for gossip.
*
* If set, this list MUST meet the following requirements.
*
* These endpoints SHALL represent the published endpoints to which other
* consensus nodes may _gossip_ transactions.
* These endpoints SHOULD NOT specify both address and DNS name.
* This list MUST NOT be empty.
* This list MUST NOT contain more than `10` entries.
* The first two entries in this list SHALL be the endpoints published to
* all consensus nodes.
* All other entries SHALL be reserved for future use.
*
* Each network may have additional requirements for these endpoints.
* A client MUST check network-specific documentation for those
* details.
*
Example
* Hedera Mainnet _requires_ that address be specified, and does not
* permit DNS name (FQDN) to be specified.
* Mainnet also requires that the first entry be an "internal" IP
* address and the second entry be an "external" IP address.
*
*
* Solo, however, _requires_ DNS name (FQDN) but also permits
* address.
*
*
* If set, the new list SHALL replace the existing list.
*/
private List gossipEndpoints = new ArrayList<>();
/**
* A list of service endpoints for gRPC calls.
*
* If set, this list MUST meet the following requirements.
*
* These endpoints SHALL represent the published endpoints to which clients
* may submit transactions.
* These endpoints SHOULD specify address and port.
* These endpoints MAY specify a DNS name.
* These endpoints SHOULD NOT specify both address and DNS name.
* This list MUST NOT be empty.
* This list MUST NOT contain more than `8` entries.
*
* Each network may have additional requirements for these endpoints.
* A client MUST check network-specific documentation for those
* details.
*
* If set, the new list SHALL replace the existing list.
*/
private List serviceEndpoints = new ArrayList<>();
/**
* A certificate used to sign gossip events.
*
* This value MUST be a certificate of a type permitted for gossip
* signatures.
* This value MUST be the DER encoding of the certificate presented.
*
* If set, the new value SHALL replace the existing bytes value.
*/
@Nullable
private byte[] gossipCaCertificate = null;
/**
* A hash of the node gRPC TLS certificate.
*
* This value MAY be used to verify the certificate presented by the node
* during TLS negotiation for gRPC.
* This value MUST be a SHA-384 hash.
* The TLS certificate to be hashed MUST first be in PEM format and MUST be
* encoded with UTF-8 NFKD encoding to a stream of bytes provided to
* the hash algorithm.
*
* If set, the new value SHALL replace the existing hash value.
*/
@Nullable
private byte[] grpcCertificateHash = null;
/**
* An administrative key controlled by the node operator.
*
* This field is OPTIONAL.
* If set, this key MUST sign this transaction.
* If set, this key MUST sign each subsequent transaction to
* update this node.
* If set, this field MUST contain a valid `Key` value.
* If set, this field MUST NOT be set to an empty `KeyList`.
*/
@Nullable
private Key adminKey = null;
/**
* Constructor.
*/
public NodeUpdateTransaction() {}
/**
* Constructor.
*
* @param txs Compound list of transaction id's list of (AccountId, Transaction) records
* @throws InvalidProtocolBufferException when there is an issue with the protobuf
*/
NodeUpdateTransaction(
LinkedHashMap> txs)
throws InvalidProtocolBufferException {
super(txs);
initFromTransactionBody();
}
/**
* Constructor.
*
* @param txBody protobuf TransactionBody
*/
NodeUpdateTransaction(com.hedera.hashgraph.sdk.proto.TransactionBody txBody) {
super(txBody);
initFromTransactionBody();
}
/**
* Extract the consensus node identifier in the network state.
* @return the consensus node identifier in the network state.
*/
public long getNodeId() {
return nodeId;
}
/**
* Assign the consensus node identifier in the network state.
* @param nodeId the consensus node identifier in the network state.
* @return {@code this}
*/
public NodeUpdateTransaction setNodeId(long nodeId) {
requireNotFrozen();
this.nodeId = nodeId;
return this;
}
/**
* Extract the Account ID of the Node.
* @return the Account ID of the Node.
*/
public AccountId getAccountId() {
return accountId;
}
/**
* Assign the Account ID of the Node.
* @param accountId the Account ID of the Node.
* @return {@code this}
*/
public NodeUpdateTransaction setAccountId(AccountId accountId) {
Objects.requireNonNull(accountId);
requireNotFrozen();
this.accountId = accountId;
return this;
}
/**
* Extract the description of the node.
* @return the node's description.
*/
@Nullable
public String getDescription() {
return description;
}
/**
* Sets the description of the node.
* @param description The String to be set as the description of the node.
* @return {@code this}
*/
public NodeUpdateTransaction setDescription(String description) {
requireNotFrozen();
Objects.requireNonNull(description);
this.description = description;
return this;
}
/**
* Remove the description contents.
* @return {@code this}
*/
public NodeUpdateTransaction clearDescription() {
requireNotFrozen();
description = "";
return this;
}
/**
* Extract the list of service endpoints for gossip.
* @return the list of service endpoints for gossip.
*/
public List getGossipEndpoints() {
return gossipEndpoints;
}
/**
* Assign the list of service endpoints for gossip.
* @param gossipEndpoints the list of service endpoints for gossip.
* @return {@code this}
*/
public NodeUpdateTransaction setGossipEndpoints(List gossipEndpoints) {
requireNotFrozen();
Objects.requireNonNull(gossipEndpoints);
this.gossipEndpoints = new ArrayList<>(gossipEndpoints);
return this;
}
/**
* Add an endpoint for gossip to the list of service endpoints for gossip.
* @param gossipEndpoint endpoints for gossip to add.
* @return {@code this}
*/
public NodeUpdateTransaction addGossipEndpoint(Endpoint gossipEndpoint) {
requireNotFrozen();
gossipEndpoints.add(gossipEndpoint);
return this;
}
/**
* Extract the list of service endpoints for gRPC calls.
* @return the list of service endpoints for gRPC calls.
*/
public List getServiceEndpoints() {
return serviceEndpoints;
}
/**
* Assign the list of service endpoints for gRPC calls.
* @param serviceEndpoints list of service endpoints for gRPC calls.
* @return {@code this}
*/
public NodeUpdateTransaction setServiceEndpoints(List serviceEndpoints) {
requireNotFrozen();
Objects.requireNonNull(serviceEndpoints);
this.serviceEndpoints = new ArrayList<>(serviceEndpoints);
return this;
}
/**
* Add an endpoint for gRPC calls to the list of service endpoints for gRPC calls.
* @param serviceEndpoint endpoints for gRPC calls to add.
* @return {@code this}
*/
public NodeUpdateTransaction addServiceEndpoint(Endpoint serviceEndpoint) {
requireNotFrozen();
serviceEndpoints.add(serviceEndpoint);
return this;
}
/**
* Extract the certificate used to sign gossip events.
* @return the DER encoding of the certificate presented.
*/
@Nullable
public byte[] getGossipCaCertificate() {
return gossipCaCertificate != null ? Arrays.copyOf(gossipCaCertificate, gossipCaCertificate.length) : null;
}
/**
* Sets the certificate used to sign gossip events.
*
* This value MUST be the DER encoding of the certificate presented.
* @param gossipCaCertificate the DER encoding of the certificate presented.
* @return {@code this}
*/
public NodeUpdateTransaction setGossipCaCertificate(byte[] gossipCaCertificate) {
Objects.requireNonNull(gossipCaCertificate);
requireNotFrozen();
this.gossipCaCertificate = Arrays.copyOf(gossipCaCertificate, gossipCaCertificate.length);
return this;
}
/**
* Extract the hash of the node gRPC TLS certificate.
* @return SHA-384 hash of the node gRPC TLS certificate.
*/
@Nullable
public byte[] getGrpcCertificateHash() {
return grpcCertificateHash != null ? Arrays.copyOf(grpcCertificateHash, grpcCertificateHash.length) : null;
}
/**
* Sets the hash of the node gRPC TLS certificate.
*
* This value MUST be a SHA-384 hash.
* @param grpcCertificateHash SHA-384 hash of the node gRPC TLS certificate.
* @return {@code this}
*/
public NodeUpdateTransaction setGrpcCertificateHash(byte[] grpcCertificateHash) {
Objects.requireNonNull(grpcCertificateHash);
requireNotFrozen();
this.grpcCertificateHash = Arrays.copyOf(grpcCertificateHash, grpcCertificateHash.length);
return this;
}
/**
* Get an administrative key controlled by the node operator.
* @return an administrative key controlled by the node operator.
*/
@Nullable
public Key getAdminKey() {
return adminKey;
}
/**
* Sets an administrative key controlled by the node operator.
* @param adminKey an administrative key to be set.
* @return {@code this}
*/
public NodeUpdateTransaction setAdminKey(Key adminKey) {
Objects.requireNonNull(adminKey);
requireNotFrozen();
this.adminKey = adminKey;
return this;
}
/**
* Build the transaction body.
*
* @return {@link com.hedera.hashgraph.sdk.proto.NodeUpdateTransactionBody}
*/
NodeUpdateTransactionBody.Builder build() {
var builder = NodeUpdateTransactionBody.newBuilder();
builder.setNodeId(nodeId);
if (accountId != null) {
builder.setAccountId(accountId.toProtobuf());
}
if (description != null) {
builder.setDescription(StringValue.of(description));
}
for (Endpoint gossipEndpoint : gossipEndpoints) {
builder.addGossipEndpoint(gossipEndpoint.toProtobuf());
}
for (Endpoint serviceEndpoint : serviceEndpoints) {
builder.addServiceEndpoint(serviceEndpoint.toProtobuf());
}
if (gossipCaCertificate != null) {
builder.setGossipCaCertificate(BytesValue.of(ByteString.copyFrom(gossipCaCertificate)));
}
if (grpcCertificateHash != null) {
builder.setGrpcCertificateHash(BytesValue.of(ByteString.copyFrom(grpcCertificateHash)));
}
if (adminKey != null) {
builder.setAdminKey(adminKey.toProtobufKey());
}
return builder;
}
/**
* Initialize from the transaction body.
*/
void initFromTransactionBody() {
var body = sourceTransactionBody.getNodeUpdate();
nodeId = body.getNodeId();
if (body.hasAccountId()) {
accountId = AccountId.fromProtobuf(body.getAccountId());
}
description = body.getDescription().getValue();
for (var gossipEndpoint : body.getGossipEndpointList()) {
gossipEndpoints.add(Endpoint.fromProtobuf(gossipEndpoint));
}
for (var serviceEndpoint : body.getServiceEndpointList()) {
serviceEndpoints.add(Endpoint.fromProtobuf(serviceEndpoint));
}
gossipCaCertificate = body.getGossipCaCertificate().getValue().toByteArray();
grpcCertificateHash = body.getGrpcCertificateHash().getValue().toByteArray();
if (body.hasAdminKey()) {
adminKey = Key.fromProtobufKey(body.getAdminKey());
}
}
@Override
void validateChecksums(Client client) throws BadEntityIdException {
if (accountId != null) {
accountId.validateChecksum(client);
}
}
@Override
MethodDescriptor getMethodDescriptor() {
return AddressBookServiceGrpc.getUpdateNodeMethod();
}
@Override
void onFreeze(TransactionBody.Builder bodyBuilder) {
bodyBuilder.setNodeUpdate(build());
}
@Override
void onScheduled(SchedulableTransactionBody.Builder scheduled) {
scheduled.setNodeUpdate(build());
}
}