All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.hashgraph.sdk.TransactionId Maven / Gradle / Ivy

The newest version!
/*-
 *
 * Hedera Java SDK
 *
 * Copyright (C) 2020 - 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 static java.util.concurrent.CompletableFuture.completedFuture;
import static java.util.concurrent.CompletableFuture.failedFuture;

import com.google.errorprone.annotations.Var;
import com.google.protobuf.InvalidProtocolBufferException;
import com.hedera.hashgraph.sdk.proto.TransactionID;
import java.time.Duration;
import java.time.Instant;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import javax.annotation.Nullable;

/**
 * The client-generated ID for a transaction.
 *
 * 

This is used for retrieving receipts and records for a transaction, for appending to a file * right after creating it, for instantiating a smart contract with bytecode in a file just created, * and internally by the network for detecting when duplicate transactions are submitted. */ public final class TransactionId implements Comparable { /** * The Account ID that paid for this transaction. */ @Nullable public final AccountId accountId; /** * The time from when this transaction is valid. * *

When a transaction is submitted there is additionally a validDuration (defaults to 120s) * and together they define a time window that a transaction may be processed in. */ @Nullable public final Instant validStart; private boolean scheduled = false; @Nullable private Integer nonce = null; private static final long NANOSECONDS_PER_MILLISECOND = 1_000_000L; private static final long TIMESTAMP_INCREMENT_NANOSECONDS = 1_000L; private static final long NANOSECONDS_TO_REMOVE = 10000000000L; private static final AtomicLong monotonicTime = new AtomicLong(); /** * No longer part of the public API. Use `Transaction.withValidStart()` instead. * * @param accountId the account id * @param validStart the valid start time */ public TransactionId(@Nullable AccountId accountId, @Nullable Instant validStart) { this.accountId = accountId; this.validStart = validStart; this.scheduled = false; } /** * Create a transaction id. * * @param accountId the account id * @param validStart the valid start time * @return the new transaction id */ public static TransactionId withValidStart(AccountId accountId, Instant validStart) { return new TransactionId(accountId, validStart); } /** * Generates a new transaction ID for the given account ID. * *

Note that transaction IDs are made of the valid start of the transaction and the account * that will be charged the transaction fees for the transaction. * * @param accountId the ID of the Hedera account that will be charge the transaction fees. * @return {@link com.hedera.hashgraph.sdk.TransactionId} */ public static TransactionId generate(AccountId accountId) { long currentTime; long lastTime; // Loop to ensure the generated timestamp is strictly increasing, // and it handles the case where the system clock appears to move backward // or if multiple threads attempt to generate a timestamp concurrently. do { // Get the current time in nanoseconds and remove a few seconds to allow for some time drift // between the client and the receiving node and prevented spurious INVALID_TRANSACTION_START. currentTime = System.currentTimeMillis() * NANOSECONDS_PER_MILLISECOND - NANOSECONDS_TO_REMOVE; // Get the last recorded timestamp. lastTime = monotonicTime.get(); // If the current time is less than or equal to the last recorded time, // adjust the timestamp to ensure it is strictly increasing. if (currentTime <= lastTime) { currentTime = lastTime + TIMESTAMP_INCREMENT_NANOSECONDS; } } while (!monotonicTime.compareAndSet(lastTime, currentTime)); return new TransactionId(accountId, Instant.ofEpochSecond(0, currentTime)); } /** * Create a transaction id from a protobuf. * * @param transactionID the protobuf * @return the new transaction id */ static TransactionId fromProtobuf(TransactionID transactionID) { var accountId = transactionID.hasAccountID() ? AccountId.fromProtobuf(transactionID.getAccountID()) : null; var validStart = transactionID.hasTransactionValidStart() ? InstantConverter.fromProtobuf(transactionID.getTransactionValidStart()) : null; return new TransactionId(accountId, validStart) .setScheduled(transactionID.getScheduled()) .setNonce((transactionID.getNonce() != 0) ? transactionID.getNonce() : null); } /** * Create a new transaction id from a string. * * @param s the string representing the transaction id * @return the new transaction id */ public static TransactionId fromString(String s) { @Var var parts = s.split("/", 2); var nonce = (parts.length == 2) ? Integer.parseInt(parts[1]) : null; parts = parts[0].split("\\?", 2); var scheduled = parts.length == 2 && parts[1].equals("scheduled"); parts = parts[0].split("@", 2); if (parts.length != 2) { throw new IllegalArgumentException("expecting {account}@{seconds}.{nanos}[?scheduled][/nonce]"); } @Nullable AccountId accountId = AccountId.fromString(parts[0]); var validStartParts = parts[1].split("\\.", 2); if (validStartParts.length != 2) { throw new IllegalArgumentException("expecting {account}@{seconds}.{nanos}"); } @Nullable Instant validStart = Instant.ofEpochSecond( Long.parseLong(validStartParts[0]), Long.parseLong(validStartParts[1])); return new TransactionId(accountId, validStart).setScheduled(scheduled).setNonce(nonce); } /** * Create a new transaction id from a byte array. * * @param bytes the byte array * @return the new transaction id * @throws InvalidProtocolBufferException when there is an issue with the protobuf */ public static TransactionId fromBytes(byte[] bytes) throws InvalidProtocolBufferException { return fromProtobuf(TransactionID.parseFrom(bytes).toBuilder().build()); } /** * Extract the scheduled status. * * @return the scheduled status */ public boolean getScheduled() { return scheduled; } /** * Assign the scheduled status. * * @param scheduled the scheduled status * @return {@code this} */ public TransactionId setScheduled(boolean scheduled) { this.scheduled = scheduled; return this; } /** * Extract the nonce. * * @return the nonce value */ @Nullable public Integer getNonce() { return nonce; } /** * Assign the nonce value. * * @param nonce the nonce value * @return {@code this} */ public TransactionId setNonce(@Nullable Integer nonce) { this.nonce = nonce; return this; } /** * Fetch the receipt of the transaction. * * @param client The client with which this will be executed. * @return the transaction receipt * @throws TimeoutException when the transaction times out * @throws PrecheckStatusException when the precheck fails * @throws ReceiptStatusException when there is an issue with the receipt */ public TransactionReceipt getReceipt(Client client) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { return getReceipt(client, client.getRequestTimeout()); } /** * Fetch the receipt of the transaction. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @return the transaction receipt * @throws TimeoutException when the transaction times out * @throws PrecheckStatusException when the precheck fails * @throws ReceiptStatusException when there is an issue with the receipt */ public TransactionReceipt getReceipt(Client client, Duration timeout) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { var receipt = new TransactionReceiptQuery() .setTransactionId(this) .execute(client, timeout); if (receipt.status != Status.SUCCESS) { throw new ReceiptStatusException(this, receipt); } return receipt; } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @return future result of the transaction receipt */ public CompletableFuture getReceiptAsync(Client client) { return getReceiptAsync(client, client.getRequestTimeout()); } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @return the transaction receipt */ public CompletableFuture getReceiptAsync(Client client, Duration timeout) { return new TransactionReceiptQuery() .setTransactionId(this) .executeAsync(client, timeout) .thenCompose(receipt -> { if (receipt.status != Status.SUCCESS) { return failedFuture(new ReceiptStatusException(this, receipt)); } return completedFuture(receipt); }); } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @param callback a BiConsumer which handles the result or error. */ public void getReceiptAsync(Client client, BiConsumer callback) { ConsumerHelper.biConsumer(getReceiptAsync(client), callback); } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @param callback a BiConsumer which handles the result or error. */ public void getReceiptAsync(Client client, Duration timeout, BiConsumer callback) { ConsumerHelper.biConsumer(getReceiptAsync(client, timeout), callback); } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void getReceiptAsync(Client client, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(getReceiptAsync(client), onSuccess, onFailure); } /** * Fetch the receipt of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void getReceiptAsync(Client client, Duration timeout, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(getReceiptAsync(client, timeout), onSuccess, onFailure); } /** * Fetch the record of the transaction. * * @param client The client with which this will be executed. * @return the transaction record * @throws TimeoutException when the transaction times out * @throws PrecheckStatusException when the precheck fails * @throws ReceiptStatusException when there is an issue with the receipt */ public TransactionRecord getRecord(Client client) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { return getRecord(client, client.getRequestTimeout()); } /** * Fetch the record of the transaction. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @return the transaction record * @throws TimeoutException when the transaction times out * @throws PrecheckStatusException when the precheck fails * @throws ReceiptStatusException when there is an issue with the receipt */ public TransactionRecord getRecord(Client client, Duration timeout) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { getReceipt(client, timeout); return new TransactionRecordQuery() .setTransactionId(this) .execute(client, timeout); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @return future result of the transaction record */ public CompletableFuture getRecordAsync(Client client) { return getRecordAsync(client, client.getRequestTimeout()); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @return future result of the transaction record */ public CompletableFuture getRecordAsync(Client client, Duration timeout) { // note: we get the receipt first to ensure consensus has been reached return getReceiptAsync(client, timeout).thenCompose(receipt -> new TransactionRecordQuery() .setTransactionId(this) .executeAsync(client, timeout)); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @param callback a BiConsumer which handles the result or error. */ public void getRecordAsync(Client client, BiConsumer callback) { ConsumerHelper.biConsumer(getRecordAsync(client), callback); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @param callback a BiConsumer which handles the result or error. */ public void getRecordAsync(Client client, Duration timeout, BiConsumer callback) { ConsumerHelper.biConsumer(getRecordAsync(client, timeout), callback); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void getRecordAsync(Client client, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(getRecordAsync(client), onSuccess, onFailure); } /** * Fetch the record of the transaction asynchronously. * * @param client The client with which this will be executed. * @param timeout The timeout after which the execution attempt will be cancelled. * @param onSuccess a Consumer which consumes the result on success. * @param onFailure a Consumer which consumes the error on failure. */ public void getRecordAsync(Client client, Duration timeout, Consumer onSuccess, Consumer onFailure) { ConsumerHelper.twoConsumers(getRecordAsync(client, timeout), onSuccess, onFailure); } /** * Extract the transaction id protobuf. * * @return the protobuf representation */ TransactionID toProtobuf() { var id = TransactionID.newBuilder() .setScheduled(scheduled) .setNonce((nonce != null) ? nonce : 0); if (accountId != null) { id.setAccountID(accountId.toProtobuf()); } if (validStart != null) { id.setTransactionValidStart(InstantConverter.toProtobuf(validStart)); } return id.build(); } private String toStringPostfix() { Objects.requireNonNull(validStart); return "@" + validStart.getEpochSecond() + "." + String.format("%09d", validStart.getNano()) + (scheduled ? "?scheduled" : "") + ((nonce != null) ? "/" + nonce : ""); } @Override public String toString() { if (accountId != null && validStart != null) { return "" + accountId + toStringPostfix(); } else { throw new IllegalStateException("`TransactionId.toString()` is non-exhaustive"); } } /** * Convert to a string representation with checksum. * * @param client the configured client * @return the string representation with checksum */ public String toStringWithChecksum(Client client) { if (accountId != null && validStart != null) { return "" + accountId.toStringWithChecksum(client) + toStringPostfix(); } else { throw new IllegalStateException("`TransactionId.toStringWithChecksum()` is non-exhaustive"); } } /** * Extract the byte array representation. * * @return the byte array representation */ public byte[] toBytes() { return toProtobuf().toByteArray(); } @Override public boolean equals( Object object) { if (!(object instanceof TransactionId)) { return false; } var id = (TransactionId) object; if (accountId != null && validStart != null && id.accountId != null && id.validStart != null) { return id.accountId.equals(accountId) && id.validStart.equals(validStart) && scheduled == id.scheduled; } else { return false; } } @Override public int hashCode() { return toString().hashCode(); } @Override public int compareTo(TransactionId o) { Objects.requireNonNull(o); if (scheduled != o.scheduled) { return scheduled ? 1 : -1; } var thisAccountIdIsNull = (accountId == null); var otherAccountIdIsNull = (o.accountId == null); if (thisAccountIdIsNull != otherAccountIdIsNull) { return thisAccountIdIsNull ? -1 : 1; } if (!thisAccountIdIsNull) { int accountIdComparison = accountId.compareTo(o.accountId); if (accountIdComparison != 0) { return accountIdComparison; } } var thisStartIsNull = (validStart == null); var otherStartIsNull = (o.validStart == null); if (thisStartIsNull != otherStartIsNull) { return thisAccountIdIsNull ? -1 : 1; } if (!thisStartIsNull) { return validStart.compareTo(o.validStart); } return 0; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy