Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add in simulate function for ATC and EmptyTransactionSigner #728

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- run: mvn test
integration-test:
machine:
image: "ubuntu-2204:2022.04.2"
image: default
resource_class: medium
steps:
- checkout
Expand Down
10 changes: 9 additions & 1 deletion examples/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<dependency>
<groupId>com.algorand</groupId>
<artifactId>algosdk</artifactId>
<version>2.0.0</version>
<version>2.5.0</version>
</dependency>
</dependencies>

Expand All @@ -114,6 +114,14 @@
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,18 @@ public class ExampleUtils {
private static int indexer_port = 8980;
private static String indexer_token = "a".repeat(64);

private static String testnet_algod_host = "https://testnet-api.algonode.cloud";
private static int testnet_algod_port = 443;
private static String testnet_algod_token = "";

public static AlgodClient getAlgodClient() {
return new AlgodClient(algod_host, algod_port, algod_token);
}

public static AlgodClient getAlgodTestnetClient() {
return new AlgodClient(testnet_algod_host, testnet_algod_port, testnet_algod_token);
}

public static IndexerClient getIndexerClient() {
return new IndexerClient(indexer_host, indexer_port, indexer_token);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@
import com.algorand.algosdk.crypto.Digest;
import com.algorand.algosdk.util.Encoder;
import com.algorand.algosdk.v2.client.Utils;
import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation;
import com.algorand.algosdk.v2.client.algod.SimulateTransaction;
import com.algorand.algosdk.v2.client.common.AlgodClient;
import com.algorand.algosdk.v2.client.common.Client;
import com.algorand.algosdk.v2.client.common.Response;
import com.algorand.algosdk.v2.client.model.PendingTransactionResponse;
import com.algorand.algosdk.v2.client.model.PostTransactionsResponse;
import com.algorand.algosdk.v2.client.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.ArrayUtils;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.*;

public class AtomicTransactionComposer {
public enum Status {
Expand Down Expand Up @@ -126,7 +124,7 @@ public List<TransactionWithSigner> buildGroup() throws IOException {
if (this.status.compareTo(Status.BUILT) >= 0)
return this.transactionList;

if (this.transactionList.size() == 0)
if (this.transactionList.isEmpty())
throw new IllegalArgumentException("should not build transaction group with 0 transaction in composer");
else if (this.transactionList.size() > 1) {
List<Transaction> groupTxns = new ArrayList<>();
Expand Down Expand Up @@ -228,6 +226,57 @@ public List<String> submit(AlgodClient client) throws Exception {
return this.getTxIDs();
}

/**
* Simulate simulates the transaction group against the network.
* <p>
* The composer's status must be SUBMITTED or lower before calling this method. Simulation will not
* advance the status of the composer beyond SIGNED.
* <p>
* The `request` argument can be used to customize the characteristics of the simulation.
* <p>
* Returns a models.SimulateResponse and an ABIResult for each method call in this group.
*/
public SimulateResult simulate(AlgodClient client, SimulateRequest request) throws Exception {
if (this.status.ordinal() > Status.SUBMITTED.ordinal()) {
throw new Exception("Status must be SUBMITTED or lower in order to call Simulate()");
}

List<SignedTransaction> stxs = gatherSignatures();
if (stxs == null) {
throw new Exception("Error gathering signatures");
}

SimulateRequestTransactionGroup txnGroups = new SimulateRequestTransactionGroup();
txnGroups.txns = stxs;
request.txnGroups = new ArrayList<>();
request.txnGroups.add(txnGroups);

SimulateTransaction st = new SimulateTransaction(client);
SimulateResponse simulateResponse = st.request(request).execute().body();

if (simulateResponse == null) {
throw new Exception("Error in simulation response");
}

List<ReturnValue> methodResults = new ArrayList<>();
for (int i = 0; i < stxs.size(); i++) {
SignedTransaction stx = stxs.get(i);
PendingTransactionResponse pendingTransactionResponse = simulateResponse.txnGroups.get(0).txnResults.get(i).txnResult;

if (!this.methodMap.containsKey(i))
continue;

ReturnValue returnValue = parseMethodResponse(this.methodMap.get(i), stx, pendingTransactionResponse);
methodResults.add(returnValue);
}

SimulateResult result = new SimulateResult();
result.setMethodResults(methodResults);
result.setSimulateResponse(simulateResponse);

return result;
}

/**
* Send the transaction group to the network and wait until it's committed to a block. An error
* will be thrown if submission or execution fails.
Expand Down Expand Up @@ -326,6 +375,78 @@ public ExecuteResult execute(AlgodClient client, int waitRounds) throws Exceptio
return new ExecuteResult(txInfo.confirmedRound, this.getTxIDs(), retList);
}

public static class SimulateResult {
private SimulateResponse simulateResponse;
michaeltchuang marked this conversation as resolved.
Show resolved Hide resolved
private List<ReturnValue> methodResults;

public SimulateResponse getSimulateResponse() {
return simulateResponse;
}

public void setSimulateResponse(SimulateResponse simulateResponse) {
this.simulateResponse = simulateResponse;
}

public List<ReturnValue> getMethodResults() {
return methodResults;
}

public void setMethodResults(List<ReturnValue> methodResults) {
this.methodResults = methodResults;
}
}

/**
* Parses a single ABI Method transaction log into a ABI result object.
*
* @param method
* @param stx
* @param pendingTransactionResponse
* @return An ReturnValue object
*/
public ReturnValue parseMethodResponse(Method method, SignedTransaction stx, PendingTransactionResponse pendingTransactionResponse) {
ReturnValue returnValue = new ReturnValue(
stx.transactionID,
new byte[0],
null,
method,
null,
pendingTransactionResponse
);
try {
if (!method.returns.type.equals(Method.Returns.VoidRetType)) {
List<byte[]> logs = pendingTransactionResponse.logs;
if (logs == null || logs.isEmpty()) {
throw new Exception("App call transaction did not log a return value");
}

byte[] lastLog = logs.get(logs.size() - 1);
if (lastLog.length < 4 || !hasPrefix(lastLog, ABI_RET_HASH)) {
throw new Exception("App call transaction did not log a return value");
}

returnValue.rawValue = Arrays.copyOfRange(lastLog, ABI_RET_HASH.length, lastLog.length);
returnValue.value = method.returns.parsedType.decode(returnValue.rawValue);
}
} catch (Exception e) {
returnValue.parseError = e;
}

return returnValue;
}

private static boolean hasPrefix(byte[] array, byte[] prefix) {
if (array.length < prefix.length) {
return false;
}
for (int i = 0; i < prefix.length; i++) {
if (array[i] != prefix[i]) {
return false;
}
}
return true;
}

private static boolean checkLogRet(byte[] logLine) {
if (logLine.length < ABI_RET_HASH.length)
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.algorand.algosdk.transaction;

import com.algorand.algosdk.crypto.Address;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

public class EmptyTransactionSigner implements TxnSigner {

private String authAddr;
/**
* EmptyTransactionSigner is a TransactionSigner that produces signed transaction objects without
* signatures. This is useful for simulating transactions, but it won't work for actual submission.
*/
public EmptyTransactionSigner(String authAddr) {
super();
this.authAddr = authAddr;
}

/**
* SignTransactions returns SignedTxn bytes but does not sign them.
*
* @param txnGroup The group of transactions to be signed.
* @param indicesToSign The indexes of the transactions to sign.
* @return A list of signed transaction bytes.
*/
@Override
public SignedTransaction[] signTxnGroup(Transaction[] txnGroup, int[] indicesToSign) throws NoSuchAlgorithmException {
SignedTransaction[] stxs = new SignedTransaction[indicesToSign.length];

for (int pos : indicesToSign) {
SignedTransaction stx = new SignedTransaction(txnGroup[pos]);

try {
if (authAddr != null) {
Address address = new Address(authAddr);
stx.authAddr(address.getBytes());
}
} catch (IllegalArgumentException ignored) { }

stxs[pos] = stx;
}
return stxs;
}

/**
* Equals returns true if the other TransactionSigner equals this one.
*
* @param other The other TransactionSigner to compare.
* @return true if equal, false otherwise.
*/
@Override
public boolean equals(Object other) {
return other instanceof EmptyTransactionSigner;
}

@Override
public int hashCode() {
return Objects.hash(EmptyTransactionSigner.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public SignedTransaction(Transaction tx, LogicsigSignature lSig, String txId) {
private SignedTransaction() {
}

public SignedTransaction(Transaction transaction) {
this.tx = transaction;
}

public SignedTransaction authAddr(Address authAddr) {
this.authAddr = authAddr;
return this;
Expand Down
1 change: 1 addition & 0 deletions src/test/integration.tags
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
@rekey_v1
@send
@send.keyregtxn
@simulate
Loading