Skip to content

Commit

Permalink
test: add mocking test to confirm transaction IDs are being generated…
Browse files Browse the repository at this point in the history
… on demand

Signed-off-by: Daniel Akhterov <[email protected]>
  • Loading branch information
janaakhterov committed Jan 15, 2022
1 parent 127ea15 commit 1d369d9
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 10 deletions.
11 changes: 11 additions & 0 deletions sdk/src/main/java/com/hedera/hashgraph/sdk/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ public final class Client implements AutoCloseable, WithPing, WithPingAll {

private boolean autoValidateChecksums = false;

private boolean defaultRegenerateTransactionId = true;

Client(ExecutorService executor, Network network, MirrorNetwork mirrorNetwork) {
this.executor = executor;
this.network = network;
Expand Down Expand Up @@ -793,6 +795,15 @@ public synchronized Client setMaxQueryPayment(Hbar maxQueryPayment) {
return setDefaultMaxQueryPayment(maxQueryPayment);
}

public synchronized boolean getDefaultRegenerateTransactionId() {
return defaultRegenerateTransactionId;
}

public synchronized Client setDefaultRegenerateTransactionId(boolean regenerateTransactionId) {
this.defaultRegenerateTransactionId = regenerateTransactionId;
return this;
}

/**
* Maximum amount of time a request can run
*
Expand Down
30 changes: 22 additions & 8 deletions sdk/src/main/java/com/hedera/hashgraph/sdk/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public abstract class Transaction<T extends Transaction<T>>
private Hbar maxTransactionFee = null;
private String memo = "";
protected boolean transactionIdsLocked = false;
protected boolean regenerateTransactionId = false;
protected Boolean regenerateTransactionId = null;

Transaction() {
setTransactionValidDuration(DEFAULT_TRANSACTION_VALID_DURATION);
Expand Down Expand Up @@ -158,10 +158,10 @@ public static Transaction<?> fromBytes(byte[] bytes) throws InvalidProtocolBuffe
txBody = TransactionBody.parseFrom(transaction.getBodyBytes());

transaction.setSignedTransactionBytes(SignedTransaction.newBuilder()
.setBodyBytes(transaction.getBodyBytes())
.setSigMap(transaction.getSigMap())
.build()
.toByteString())
.setBodyBytes(transaction.getBodyBytes())
.setSigMap(transaction.getSigMap())
.build()
.toByteString())
.clearBodyBytes()
.clearSigMap();
} else {
Expand Down Expand Up @@ -641,6 +641,17 @@ public final T setTransactionId(TransactionId transactionId) {
return (T) this;
}

public final Boolean getRegenerateTransactionId() {
return regenerateTransactionId;
}

public final T setRegenerateTransactionId(boolean regenerateTransactionId) {
this.regenerateTransactionId = regenerateTransactionId;

// noinspection unchecked
return (T) this;
}

public final T sign(PrivateKey privateKey) {
return signWith(privateKey.getPublicKey(), privateKey::sign);
}
Expand Down Expand Up @@ -833,6 +844,9 @@ public T freezeWith(@Nullable Client client) {
generateTransactionIds(transactionIds.get(0), requiredChunks);
wipeTransactionLists(requiredChunks);

var clientDefaultRegenerateTransactionId = client != null ? client.getDefaultRegenerateTransactionId() : null;
regenerateTransactionId = regenerateTransactionId != null ? regenerateTransactionId : clientDefaultRegenerateTransactionId;

// noinspection unchecked
return (T) this;
}
Expand Down Expand Up @@ -913,8 +927,8 @@ void buildTransaction(int index) {
}

private static boolean publicKeyIsInSigPairList(ByteString publicKeyBytes, List<SignaturePair> sigPairList) {
for(var pair : sigPairList) {
if(pair.getPubKeyPrefix().equals(publicKeyBytes)) {
for (var pair : sigPairList) {
if (pair.getPubKeyPrefix().equals(publicKeyBytes)) {
return true;
}
}
Expand Down Expand Up @@ -1019,7 +1033,7 @@ CompletableFuture<Void> onExecuteAsync(Client client) {
ExecutionState shouldRetry(Status status, com.hedera.hashgraph.sdk.proto.TransactionResponse response) {
switch (status) {
case TRANSACTION_EXPIRED:
if (regenerateTransactionId && transactionIdsLocked) {
if ((regenerateTransactionId == null || regenerateTransactionId) && transactionIdsLocked) {
return ExecutionState.RequestError;
} else {
var firstTransactionId = Objects.requireNonNull(transactionIds.get(0));
Expand Down
12 changes: 10 additions & 2 deletions sdk/src/test/java/com/hedera/hashgraph/sdk/Mocker.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.hedera.hashgraph.sdk;

import com.hedera.hashgraph.sdk.proto.CryptoServiceGrpc;
import com.hedera.hashgraph.sdk.proto.*;
import io.grpc.MethodDescriptor;
import io.grpc.Server;
import io.grpc.ServerMethodDefinition;
Expand All @@ -21,7 +21,13 @@
public class Mocker implements AutoCloseable {
private static final PrivateKey PRIVATE_KEY = PrivateKey.fromString("302e020100300506032b657004220420d45e1557156908c967804615af59a000be88c7aa7058bfcbe0f46b16c28f887d");

private final List<ServiceDescriptor> services = List.of(CryptoServiceGrpc.getServiceDescriptor());
private final List<ServiceDescriptor> services = List.of(
CryptoServiceGrpc.getServiceDescriptor(),
FileServiceGrpc.getServiceDescriptor(),
SmartContractServiceGrpc.getServiceDescriptor(),
ConsensusServiceGrpc.getServiceDescriptor(),
TokenServiceGrpc.getServiceDescriptor()
);
private final List<List<Object>> responses;
private final List<Server> servers = new ArrayList<>();

Expand Down Expand Up @@ -59,6 +65,8 @@ public class Mocker implements AutoCloseable {
r = ((Function<Object, Object>) r).apply(request);
}

System.out.println(r);

if (r instanceof Throwable) {
responseObserver.onError((Throwable) r);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.hedera.hashgraph.sdk;

import com.hedera.hashgraph.sdk.proto.*;
import com.hedera.hashgraph.sdk.proto.Transaction;
import com.hedera.hashgraph.sdk.proto.TransactionResponse;
import io.grpc.Status;
import java8.util.function.Function;
import org.junit.jupiter.api.Test;

import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

public class RegenerateTransactionIdsTest {
@Test
void regeneratesTransactionIdsWhenTransactionExpiredIsReturned() throws PrecheckStatusException, TimeoutException, InterruptedException {
var transactionIds = new HashSet<TransactionId>();
AtomicInteger count = new AtomicInteger(0);

var responses = List.of(
TransactionResponse.newBuilder().setNodeTransactionPrecheckCode(ResponseCodeEnum.TRANSACTION_EXPIRED).build(),
TransactionResponse.newBuilder().setNodeTransactionPrecheckCode(ResponseCodeEnum.TRANSACTION_EXPIRED).build(),
TransactionResponse.newBuilder().setNodeTransactionPrecheckCode(ResponseCodeEnum.TRANSACTION_EXPIRED).build(),
TransactionResponse.newBuilder().setNodeTransactionPrecheckCode(ResponseCodeEnum.OK).build()
);

var call = (Function<Object, Object>) o -> {
try {
var transaction = (Transaction) o;
var signedTransaction = SignedTransaction.parseFrom(transaction.getSignedTransactionBytes());
var transactionBody = TransactionBody.parseFrom(signedTransaction.getBodyBytes());
var transactionId = TransactionId.fromProtobuf(transactionBody.getTransactionID());

if (transactionIds.contains(transactionId)) {
return Status.Code.ABORTED.toStatus().asRuntimeException();
}

transactionIds.add(transactionId);

return responses.get(count.getAndIncrement());
} catch (Throwable e) {
return new RuntimeException(e);
}
};

List<Object> responses1 = List.of(
call, call, call, call
);

try (var mocker = Mocker.withResponses(List.of(responses1))) {
new FileCreateTransaction().execute(mocker.client);
}
}
}

0 comments on commit 1d369d9

Please sign in to comment.