From 9c862c55dbddf689c62bcc7bb0155062b5459eb7 Mon Sep 17 00:00:00 2001 From: michaeldiamant Date: Fri, 1 Jul 2022 09:26:21 -0400 Subject: [PATCH 01/20] Run non-Cucumber unit tests during make test --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index c9fce9856..e0b988d61 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ unit: + mvn test mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" integration: From d5f71c03ad4e85312a25d448c66cabd240dba08b Mon Sep 17 00:00:00 2001 From: michaeldiamant Date: Fri, 1 Jul 2022 10:41:12 -0400 Subject: [PATCH 02/20] Revert "Run non-Cucumber unit tests during make test" This reverts commit 9c862c55dbddf689c62bcc7bb0155062b5459eb7. --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index e0b988d61..c9fce9856 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ unit: - mvn test mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" integration: From 240df9ebcc6de82e20a5eeab9ec25b3904c031c9 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 6 Jul 2022 16:26:28 -0400 Subject: [PATCH 03/20] Boxes: Support for Box array transaction field (#340) --- .gitignore | 1 + run_integration_tests.sh | 4 +- .../ApplicationBaseTransactionBuilder.java | 21 + .../ApplicationCallReferencesSetter.java | 9 +- .../MethodCallTransactionBuilder.java | 19 +- .../algosdk/transaction/AppBoxReference.java | 49 ++ .../algosdk/transaction/MethodCallParams.java | 54 +- .../algosdk/transaction/Transaction.java | 769 ++++++++++-------- .../algosdk/v2/client/common/Client.java | 2 +- .../algorand/algosdk/v2/client/model/Box.java | 50 ++ .../cucumber/shared/TransactionSteps.java | 7 +- .../algosdk/integration/Applications.java | 7 +- .../transaction/TestAppBoxReference.java | 23 + .../algosdk/transaction/TestBoxReference.java | 77 ++ .../algosdk/transaction/TestTransaction.java | 35 +- .../com/algorand/algosdk/unit/AlgodPaths.java | 12 +- .../algosdk/util/ConversionUtils.java | 32 +- 17 files changed, 789 insertions(+), 382 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/Box.java create mode 100644 src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java create mode 100644 src/test/java/com/algorand/algosdk/transaction/TestBoxReference.java diff --git a/.gitignore b/.gitignore index d0c6e05ae..3938a2dbd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/test/resources/**/*.json src/test/resources/**/*.base64 src/test/resources/**/*.teal src/test/resources/**/*.tok +src/test/resources/**/*.txt # OS X .DS_Store diff --git a/run_integration_tests.sh b/run_integration_tests.sh index 0fcdccce7..fbba8cf10 100755 --- a/run_integration_tests.sh +++ b/run_integration_tests.sh @@ -6,7 +6,7 @@ pushd $rootdir SKIP_TEST_CONTAINER=0 UPDATE_FEATURE_FILES_ONLY=0 -TEST_BRANCH=master +TEST_BRANCH=feature/box-storage function help { echo "Options:" @@ -77,6 +77,6 @@ docker build -t java-sdk-testing -f Dockerfile "$(pwd)" # Launch SDK testing docker run -it \ --network host \ - java-sdk-testing:latest + java-sdk-testing:latest my_exit 0 diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java index e231ac4ec..ed03eb75d 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java @@ -1,6 +1,7 @@ package com.algorand.algosdk.builder.transaction; import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.transaction.AppBoxReference; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.util.Encoder; @@ -15,6 +16,7 @@ public abstract class ApplicationBaseTransactionBuilder accounts; private List foreignApps; private List foreignAssets; + private List appBoxReferences; private Long applicationId; /** @@ -36,6 +38,7 @@ protected void applyTo(Transaction txn) { if (accounts != null) txn.accounts = accounts; if (foreignApps != null) txn.foreignApps = foreignApps; if (foreignAssets != null) txn.foreignAssets = foreignAssets; + if (appBoxReferences != null) txn.boxReferences = convertBoxes(appBoxReferences, foreignApps, applicationId); } @Override @@ -63,6 +66,7 @@ public T args(List args) { /** * ApplicationArgs lists some transaction-specific arguments accessible from application logic. + * * @param args List of Base64 encoded strings. */ public T argsBase64Encoded(List args) { @@ -90,4 +94,21 @@ public T foreignAssets(List foreignAssets) { this.foreignAssets = foreignAssets; return (T) this; } + + private List convertBoxes(List abrs, List foreignApps, Long curApp) { + ArrayList xs = new ArrayList<>(); + for (AppBoxReference abr : abrs) { + xs.add(Transaction.BoxReference.fromAppBoxReference(abr, foreignApps, curApp)); + } + return xs; + } + + /** + * BoxReferences lists the boxes whose state may be accessed during the execution + * of this application call. The access is read-only. + */ + public T boxReferences(List boxReferences) { + this.appBoxReferences = boxReferences; + return (T) this; + } } diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java index ddf5b84a2..8e5490386 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java @@ -3,9 +3,10 @@ import java.util.List; import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.transaction.AppBoxReference; public interface ApplicationCallReferencesSetter> { - + /** * ApplicationID is the application being interacted with, or 0 if creating a new application. */ @@ -27,4 +28,10 @@ public interface ApplicationCallReferencesSetter foreignAssets); + + /** + * BoxReferences lists the boxes whose state may be accessed during evaluation of this application call. The apps + * the boxes belong to must be present in ForeignApps. The access is read-only. + */ + public T boxReferences(List boxReferences); } diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java index 012a6837a..1c818e3c3 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java @@ -4,6 +4,7 @@ import com.algorand.algosdk.crypto.Address; import com.algorand.algosdk.crypto.TEALProgram; import com.algorand.algosdk.logic.StateSchema; +import com.algorand.algosdk.transaction.AppBoxReference; import com.algorand.algosdk.transaction.MethodCallParams; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.transaction.TxnSigner; @@ -22,6 +23,7 @@ public class MethodCallTransactionBuilder foreignAccounts = new ArrayList<>(); protected List foreignAssets = new ArrayList<>(); protected List foreignApps = new ArrayList<>(); + protected List boxReferences = new ArrayList<>(); protected TEALProgram approvalProgram, clearStateProgram; protected StateSchema localStateSchema; @@ -62,7 +64,7 @@ public T method(Method method) { /** * Specify arguments for the ABI method invocation. - * + *

* This will reset the arguments list to what is passed in by the caller. */ public T methodArguments(List arguments) { @@ -72,7 +74,7 @@ public T methodArguments(List arguments) { /** * Specify arguments for the ABI method invocation. - * + *

* This will add the arguments passed in by the caller to the existing list of arguments. */ public T addMethodArguments(List arguments) { @@ -82,7 +84,7 @@ public T addMethodArguments(List arguments) { /** * Specify arguments for the ABI method invocation. - * + *

* This will add the argument passed in by the caller to the existing list of arguments. */ public T addMethodArgument(Object argument) { @@ -125,6 +127,15 @@ public T foreignAssets(List foreignAssets) { return (T) this; } + @Override + public T boxReferences(List boxReferences) { + if (boxReferences != null) + this.boxReferences = new ArrayList<>(new HashSet<>(boxReferences)); + else + this.boxReferences.clear(); + return (T) this; + } + @Override public T approvalProgram(TEALProgram approvalProgram) { this.approvalProgram = approvalProgram; @@ -165,7 +176,7 @@ public MethodCallParams build() { return new MethodCallParams( appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash, firstValid, lastValid, fee, flatFee, rekeyTo, signer, foreignAccounts, foreignAssets, foreignApps, - approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages + boxReferences, approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages ); } } diff --git a/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java new file mode 100644 index 000000000..ea93c3186 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java @@ -0,0 +1,49 @@ +package com.algorand.algosdk.transaction; + +import java.util.Arrays; +import java.util.Objects; + +public class AppBoxReference { + // the app ID of the app this box belongs to. Instead of serializing this value, + // it's used to calculate the appIdx for AppBoxReference. + private final long appId; + + // the name of the box unique to the app it belongs to + private final byte[] name; + + public AppBoxReference(long appId, byte[] name) { + this.appId = appId; + this.name = Arrays.copyOf(name, name.length); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AppBoxReference that = (AppBoxReference) o; + return appId == that.appId && Arrays.equals(name, that.name); + } + + @Override + public int hashCode() { + int result = Objects.hash(appId); + result = 31 * result + Arrays.hashCode(name); + return result; + } + + public long getAppId() { + return appId; + } + + public byte[] getName() { + return Arrays.copyOf(name, name.length); + } + + @Override + public String toString() { + return "AppBoxReference{" + + "appID=" + appId + + ", name=" + Arrays.toString(name) + + '}'; + } +} diff --git a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java index fda05a1e9..edc4b0a8c 100644 --- a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java +++ b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java @@ -35,7 +35,8 @@ public class MethodCallParams { public final List

foreignAccounts; public final List foreignAssets; public final List foreignApps; - + public final List boxReferences; + public final TEALProgram approvalProgram, clearProgram; public final StateSchema globalStateSchema, localStateSchema; public final Long extraPages; @@ -62,7 +63,7 @@ public MethodCallParams(Long appID, Method method, List methodArgs, Addr Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, Address rekeyTo, TxnSigner signer, - List
fAccounts, List fAssets, List fApps, + List
fAccounts, List fAssets, List fApps, List boxes, TEALProgram approvalProgram, TEALProgram clearProgram, StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { if (appID == null || method == null || sender == null || onCompletion == null || signer == null || genesisID == null || genesisHash == null || firstValid == null || lastValid == null || (fee == null && flatFee == null)) @@ -113,6 +114,7 @@ public MethodCallParams(Long appID, Method method, List methodArgs, Addr this.foreignAccounts = new ArrayList<>(fAccounts); this.foreignAssets = new ArrayList<>(fAssets); this.foreignApps = new ArrayList<>(fApps); + this.boxReferences = new ArrayList<>(boxes); this.approvalProgram = approvalProgram; this.clearProgram = clearProgram; this.globalStateSchema = globalStateSchema; @@ -122,7 +124,7 @@ public MethodCallParams(Long appID, Method method, List methodArgs, Addr /** * Create the transactions which will carry out the specified method call. - * + *

* The list of transactions returned by this function will have the same length as method.getTxnCallCount(). */ public List createTransactions() { @@ -136,6 +138,7 @@ public List createTransactions() { List

foreignAccounts = new ArrayList<>(this.foreignAccounts); List foreignAssets = new ArrayList<>(this.foreignAssets); List foreignApps = new ArrayList<>(this.foreignApps); + List boxReferences = new ArrayList<>(this.boxReferences); for (int i = 0; i < this.method.args.size(); i++) { Method.Arg argT = this.method.args.get(i); @@ -199,21 +202,22 @@ public List createTransactions() { ApplicationCallTransactionBuilder txBuilder = ApplicationCallTransactionBuilder.Builder(); txBuilder - .firstValid(this.firstValid) - .lastValid(this.lastValid) - .genesisHash(this.genesisHash) - .genesisID(this.genesisID) - .fee(this.fee) - .flatFee(this.flatFee) - .note(this.note) - .lease(this.lease) - .rekey(this.rekeyTo) - .sender(this.sender) - .applicationId(this.appID) - .args(encodedABIArgs) - .accounts(foreignAccounts) - .foreignApps(foreignApps) - .foreignAssets(foreignAssets); + .firstValid(this.firstValid) + .lastValid(this.lastValid) + .genesisHash(this.genesisHash) + .genesisID(this.genesisID) + .fee(this.fee) + .flatFee(this.flatFee) + .note(this.note) + .lease(this.lease) + .rekey(this.rekeyTo) + .sender(this.sender) + .applicationId(this.appID) + .args(encodedABIArgs) + .accounts(foreignAccounts) + .foreignApps(foreignApps) + .foreignAssets(foreignAssets) + .boxReferences(boxReferences); Transaction tx = txBuilder.build(); @@ -228,7 +232,7 @@ public List createTransactions() { tx.localStateSchema = this.localStateSchema; if (this.extraPages != null) tx.extraPages = this.extraPages; - + TransactionWithSigner methodCall = new TransactionWithSigner(tx, this.signer); transactionArgs.add(methodCall); @@ -245,12 +249,12 @@ private static boolean checkTransactionType(TransactionWithSigner tws, String tx * and this function will return an index that can be used to reference `objectToBeAdded` in `objectArray`. * * @param objectToBeAdded - The value to add to the array. If this value is already present in the array, - * it will not be added again. Instead, the existing index will be returned. - * @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`. - * @param zerothObject - If provided, this value indicated two things: the 0 value is special for this - * array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals - * `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will - * be returned. + * it will not be added again. Instead, the existing index will be returned. + * @param objectArray - The existing foreign array. This input may be modified to append `valueToAdd`. + * @param zerothObject - If provided, this value indicated two things: the 0 value is special for this + * array, so all indexes into `objectArray` must start at 1; additionally, if `objectToBeAdded` equals + * `zerothValue`, then `objectToBeAdded` will not be added to the array, and instead the 0 indexes will + * be returned. * @return An index that can be used to reference `valueToAdd` in `array`. */ private static int populateForeignArrayIndex(T objectToBeAdded, List objectArray, T zerothObject) { diff --git a/src/main/java/com/algorand/algosdk/transaction/Transaction.java b/src/main/java/com/algorand/algosdk/transaction/Transaction.java index 98963413d..51c206763 100644 --- a/src/main/java/com/algorand/algosdk/transaction/Transaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/Transaction.java @@ -19,7 +19,7 @@ * A raw serializable transaction class, used to generate transactions to broadcast to the network. * This is distinct from algod.model.Transaction, which is only returned for GET requests to algod. */ -@JsonPropertyOrder(alphabetic=true) +@JsonPropertyOrder(alphabetic = true) @JsonInclude(JsonInclude.Include.NON_DEFAULT) public class Transaction implements Serializable { private static final byte[] TX_SIGN_PREFIX = ("TX").getBytes(StandardCharsets.UTF_8); @@ -117,7 +117,7 @@ public class Transaction implements Serializable { // account. @JsonProperty("aclose") public Address assetCloseTo = new Address(); - + /* asset freeze fields */ @JsonProperty("fadd") public Address freezeTarget = new Address(); @@ -147,6 +147,9 @@ public class Transaction implements Serializable { @JsonProperty("apas") public List foreignAssets = new ArrayList<>(); + @JsonProperty("apbx") + public List boxReferences = new ArrayList<>(); + @JsonProperty("apgs") public StateSchema globalStateSchema = new StateSchema(); @@ -164,12 +167,13 @@ public class Transaction implements Serializable { /** * Create a payment transaction - * @param fromAddr source address - * @param toAddr destination address - * @param fee transaction fee - * @param amount payment amount + * + * @param fromAddr source address + * @param toAddr destination address + * @param fee transaction fee + * @param amount payment amount * @param firstRound first valid round - * @param lastRound last valid round + * @param lastRound last valid round */ @Deprecated public Transaction(Address fromAddr, Address toAddr, BigInteger fee, BigInteger amount, BigInteger firstRound, @@ -186,12 +190,13 @@ public Transaction(Address fromAddr, Address toAddr, BigInteger fee, BigInteger /** * Create a payment transaction. Make sure to sign with a suggested fee. - * @param fromAddr source address - * @param toAddr destination address - * @param amount amount to send - * @param firstRound first valid round - * @param lastRound last valid round - * @param genesisID genesis id + * + * @param fromAddr source address + * @param toAddr destination address + * @param amount amount to send + * @param firstRound first valid round + * @param lastRound last valid round + * @param genesisID genesis id * @param genesisHash genesis hash */ @Deprecated @@ -227,9 +232,9 @@ public Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigInt */ @Deprecated public static Transaction createPaymentTransaction(Address sender, BigInteger fee, BigInteger firstValid, - BigInteger lastValid, byte[] note, String genesisID, - Digest genesisHash, BigInteger amount, Address receiver, - Address closeRemainderTo) { + BigInteger lastValid, byte[] note, String genesisID, + Digest genesisHash, BigInteger amount, Address receiver, + Address closeRemainderTo) { Objects.requireNonNull(sender, "sender is required."); Objects.requireNonNull(firstValid, "firstValid is required."); Objects.requireNonNull(lastValid, "lastValid is required."); @@ -287,20 +292,22 @@ public static Transaction createPaymentTransaction(Address sender, BigInteger fe null, null, null, + null, null); } /** * Create a key registration transaction. No field can be null except the note field. - * @param sender source address - * @param fee transaction fee - * @param firstValid first valid round - * @param lastValid last valid round - * @param note optional notes field (can be null) - * @param votePK the new participation key to register - * @param vrfPK the sortition key to register - * @param voteFirst key reg valid first round - * @param voteLast key reg valid last round + * + * @param sender source address + * @param fee transaction fee + * @param firstValid first valid round + * @param lastValid last valid round + * @param note optional notes field (can be null) + * @param votePK the new participation key to register + * @param vrfPK the sortition key to register + * @param voteFirst key reg valid first round + * @param voteLast key reg valid last round * @param voteKeyDilution key reg dilution */ @Deprecated @@ -315,7 +322,7 @@ public Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigInt if (lastValid != null) this.lastValid = lastValid; setNote(note); if (genesisID != null) this.genesisID = genesisID; - if (genesisHash != null) this.genesisHash = genesisHash; + if (genesisHash != null) this.genesisHash = genesisHash; if (votePK != null) this.votePK = votePK; if (vrfPK != null) this.selectionPK = vrfPK; if (voteFirst != null) this.voteFirst = voteFirst; @@ -390,35 +397,37 @@ public static Transaction createKeyRegistrationTransaction(Address sender, BigIn null, null, null, + null, null); } /** * Create an asset creation transaction. Note can be null. manager, reserve, freeze, and clawback can be zeroed. - * @param sender source address - * @param fee transaction fee - * @param firstValid first valid round - * @param lastValid last valid round - * @param note optional note field (can be null) + * + * @param sender source address + * @param fee transaction fee + * @param firstValid first valid round + * @param lastValid last valid round + * @param note optional note field (can be null) * @param genesisID * @param genesisHash - * @param assetTotal total asset issuance + * @param assetTotal total asset issuance * @param assetDecimals asset decimal precision * @param defaultFrozen whether accounts have this asset frozen by default * @param assetUnitName name of unit of the asset - * @param assetName name of the asset - * @param url where more information about the asset can be retrieved - * @param metadataHash specifies a commitment to some unspecified asset metadata. The format of this metadata is up to the application - * @param manager account which can reconfigure the asset - * @param reserve account whose asset holdings count as non-minted - * @param freeze account which can freeze or unfreeze holder accounts - * @param clawback account which can issue clawbacks against holder accounts + * @param assetName name of the asset + * @param url where more information about the asset can be retrieved + * @param metadataHash specifies a commitment to some unspecified asset metadata. The format of this metadata is up to the application + * @param manager account which can reconfigure the asset + * @param reserve account whose asset holdings count as non-minted + * @param freeze account which can freeze or unfreeze holder accounts + * @param clawback account which can issue clawbacks against holder accounts */ @Deprecated private Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigInteger lastValid, byte[] note, - String genesisID, Digest genesisHash, BigInteger assetTotal, Integer assetDecimals, boolean defaultFrozen, - String assetUnitName, String assetName, String url, byte[] metadataHash, - Address manager, Address reserve, Address freeze, Address clawback) { + String genesisID, Digest genesisHash, BigInteger assetTotal, Integer assetDecimals, boolean defaultFrozen, + String assetUnitName, String assetName, String url, byte[] metadataHash, + Address manager, Address reserve, Address freeze, Address clawback) { this.type = Type.AssetConfig; if (sender != null) this.sender = sender; setFee(fee); @@ -427,36 +436,37 @@ private Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigIn setNote(note); if (genesisID != null) this.genesisID = genesisID; if (genesisHash != null) this.genesisHash = genesisHash; - - this.assetParams = new AssetParams(assetTotal, assetDecimals, defaultFrozen, assetUnitName, assetName, url, metadataHash, manager, reserve, freeze, clawback); + + this.assetParams = new AssetParams(assetTotal, assetDecimals, defaultFrozen, assetUnitName, assetName, url, metadataHash, manager, reserve, freeze, clawback); } /** * Create an asset creation transaction. Note can be null. manager, reserve, freeze, and clawback can be zeroed. - * @param sender source address - * @param fee transaction fee - * @param firstValid first valid round - * @param lastValid last valid round - * @param note optional note field (can be null) + * + * @param sender source address + * @param fee transaction fee + * @param firstValid first valid round + * @param lastValid last valid round + * @param note optional note field (can be null) * @param genesisID * @param genesisHash - * @param assetTotal total asset issuance + * @param assetTotal total asset issuance * @param assetDecimals asset decimal precision * @param defaultFrozen whether accounts have this asset frozen by default * @param assetUnitName name of unit of the asset - * @param assetName name of the asset - * @param url where more information about the asset can be retrieved - * @param metadataHash specifies a commitment to some unspecified asset metadata. The format of this metadata is up to the application - * @param manager account which can reconfigure the asset - * @param reserve account whose asset holdings count as non-minted - * @param freeze account which can freeze or unfreeze holder accounts - * @param clawback account which can issue clawbacks against holder accounts + * @param assetName name of the asset + * @param url where more information about the asset can be retrieved + * @param metadataHash specifies a commitment to some unspecified asset metadata. The format of this metadata is up to the application + * @param manager account which can reconfigure the asset + * @param reserve account whose asset holdings count as non-minted + * @param freeze account which can freeze or unfreeze holder accounts + * @param clawback account which can issue clawbacks against holder accounts */ @Deprecated public static Transaction createAssetCreateTransaction(Address sender, BigInteger fee, BigInteger firstValid, BigInteger lastValid, byte[] note, - String genesisID, Digest genesisHash, BigInteger assetTotal, Integer assetDecimals, boolean defaultFrozen, - String assetUnitName, String assetName, String url, byte[] metadataHash, - Address manager, Address reserve, Address freeze, Address clawback) { + String genesisID, Digest genesisHash, BigInteger assetTotal, Integer assetDecimals, boolean defaultFrozen, + String assetUnitName, String assetName, String url, byte[] metadataHash, + Address manager, Address reserve, Address freeze, Address clawback) { Objects.requireNonNull(sender, "sender is required."); Objects.requireNonNull(firstValid, "firstValid is required."); @@ -515,28 +525,30 @@ public static Transaction createAssetCreateTransaction(Address sender, BigIntege null, null, null, + null, null); } - + /** * Create an asset configuration transaction. Note can be null. manager, reserve, freeze, and clawback can be zeroed. - * @param sender source address - * @param fee transaction fee - * @param firstValid first valid round - * @param lastValid last valid round - * @param note optional note field (can be null) + * + * @param sender source address + * @param fee transaction fee + * @param firstValid first valid round + * @param lastValid last valid round + * @param note optional note field (can be null) * @param genesisID * @param genesisHash - * @param index asset index - * @param manager account which can reconfigure the asset - * @param reserve account whose asset holdings count as non-minted - * @param freeze account which can freeze or unfreeze holder accounts - * @param clawback account which can issue clawbacks against holder accounts + * @param index asset index + * @param manager account which can reconfigure the asset + * @param reserve account whose asset holdings count as non-minted + * @param freeze account which can freeze or unfreeze holder accounts + * @param clawback account which can issue clawbacks against holder accounts */ private Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigInteger lastValid, byte[] note, - String genesisID, Digest genesisHash, BigInteger index, - Address manager, Address reserve, Address freeze, Address clawback) { - + String genesisID, Digest genesisHash, BigInteger index, + Address manager, Address reserve, Address freeze, Address clawback) { + this.type = Type.AssetConfig; if (sender != null) this.sender = sender; setFee(fee); @@ -547,62 +559,63 @@ private Transaction(Address sender, BigInteger fee, BigInteger firstValid, BigIn if (genesisHash != null) this.genesisHash = genesisHash; this.assetParams = new AssetParams(BigInteger.valueOf(0), 0, false, "", "", "", null, manager, reserve, freeze, clawback); assetIndex = index; - } + } /** * Create an asset configuration transaction. Note can be null. manager, reserve, freeze, and clawback can be zeroed. - * @param sender source address - * @param fee transaction fee - * @param firstValid first valid round - * @param lastValid last valid round - * @param note optional note field (can be null) + * + * @param sender source address + * @param fee transaction fee + * @param firstValid first valid round + * @param lastValid last valid round + * @param note optional note field (can be null) * @param genesisID * @param genesisHash - * @param index asset index - * @param manager account which can reconfigure the asset - * @param reserve account whose asset holdings count as non-minted - * @param freeze account which can freeze or unfreeze holder accounts - * @param clawback account which can issue clawbacks against holder accounts + * @param index asset index + * @param manager account which can reconfigure the asset + * @param reserve account whose asset holdings count as non-minted + * @param freeze account which can freeze or unfreeze holder accounts + * @param clawback account which can issue clawbacks against holder accounts * @param strictEmptyAddressChecking if true, disallow empty admin accounts from being set (preventing accidental disable of admin features) */ @Deprecated public static Transaction createAssetConfigureTransaction( - Address sender, - BigInteger fee, - BigInteger firstValid, - BigInteger lastValid, - byte[] note, - String genesisID, - Digest genesisHash, - BigInteger index, - Address manager, - Address reserve, - Address freeze, - Address clawback, - boolean strictEmptyAddressChecking) { + Address sender, + BigInteger fee, + BigInteger firstValid, + BigInteger lastValid, + byte[] note, + String genesisID, + Digest genesisHash, + BigInteger index, + Address manager, + Address reserve, + Address freeze, + Address clawback, + boolean strictEmptyAddressChecking) { Address defaultAddr = new Address(); - if (strictEmptyAddressChecking && ( - (manager == null || manager.equals(defaultAddr)) || - (reserve == null || reserve.equals(defaultAddr)) || - (freeze == null || freeze.equals(defaultAddr)) || - (clawback == null || clawback.equals(defaultAddr)) - )) { - throw new RuntimeException("strict empty address checking requested but " - + "empty or default address supplied to one or more manager addresses"); - } - return new Transaction( - sender, - fee, - firstValid, - lastValid, - note, - genesisID, - genesisHash, - index, - manager, - reserve, - freeze, - clawback); + if (strictEmptyAddressChecking && ( + (manager == null || manager.equals(defaultAddr)) || + (reserve == null || reserve.equals(defaultAddr)) || + (freeze == null || freeze.equals(defaultAddr)) || + (clawback == null || clawback.equals(defaultAddr)) + )) { + throw new RuntimeException("strict empty address checking requested but " + + "empty or default address supplied to one or more manager addresses"); + } + return new Transaction( + sender, + fee, + firstValid, + lastValid, + note, + genesisID, + genesisHash, + index, + manager, + reserve, + freeze, + clawback); } /** @@ -630,7 +643,7 @@ private Transaction(@JsonProperty("type") Type type, @JsonProperty("gh") byte[] genesisHash, @JsonProperty("lx") byte[] lease, @JsonProperty("rekey") byte[] rekeyTo, - @JsonProperty("grp") byte[] group, + @JsonProperty("grp") byte[] group, // payment fields @JsonProperty("amt") BigInteger amount, @JsonProperty("rcv") byte[] receiver, @@ -663,61 +676,63 @@ private Transaction(@JsonProperty("type") Type type, @JsonProperty("apat") List accounts, @JsonProperty("apfa") List foreignApps, @JsonProperty("apas") List foreignAssets, + @JsonProperty("apbx") List boxReferences, @JsonProperty("apgs") StateSchema globalStateSchema, @JsonProperty("apid") Long applicationId, @JsonProperty("apls") StateSchema localStateSchema, @JsonProperty("apsu") byte[] clearStateProgram, @JsonProperty("apep") Long extraPages - ) throws IOException { + ) throws IOException { this( - type, - //header fields - new Address(sender), - fee, - firstValid, - lastValid, - note, - genesisID, - new Digest(genesisHash), - lease, - new Address(rekeyTo), - new Digest(group), - // payment fields - amount, - new Address(receiver), - new Address(closeRemainderTo), - // keyreg fields - new ParticipationPublicKey(votePK), - new VRFPublicKey(vrfPK), - new MerkleVerifier(stateProofKey), - voteFirst, - voteLast, - voteKeyDilution, - nonpart, - // asset creation and configuration - assetParams, - assetIndex, - // asset transfer fields - xferAsset, - assetAmount, - new Address(assetSender), - new Address(assetReceiver), - new Address(assetCloseTo), - new Address(freezeTarget), - assetFreezeID, - freezeState, - // application fields - applicationArgs, - onCompletion, - approvalProgram == null ? null : new TEALProgram(approvalProgram), - convertToAddressList(accounts), - foreignApps, - foreignAssets, - globalStateSchema, - applicationId, - localStateSchema, - clearStateProgram == null ? null : new TEALProgram(clearStateProgram), - extraPages + type, + //header fields + new Address(sender), + fee, + firstValid, + lastValid, + note, + genesisID, + new Digest(genesisHash), + lease, + new Address(rekeyTo), + new Digest(group), + // payment fields + amount, + new Address(receiver), + new Address(closeRemainderTo), + // keyreg fields + new ParticipationPublicKey(votePK), + new VRFPublicKey(vrfPK), + new MerkleVerifier(stateProofKey), + voteFirst, + voteLast, + voteKeyDilution, + nonpart, + // asset creation and configuration + assetParams, + assetIndex, + // asset transfer fields + xferAsset, + assetAmount, + new Address(assetSender), + new Address(assetReceiver), + new Address(assetCloseTo), + new Address(freezeTarget), + assetFreezeID, + freezeState, + // application fields + applicationArgs, + onCompletion, + approvalProgram == null ? null : new TEALProgram(approvalProgram), + convertToAddressList(accounts), + foreignApps, + foreignAssets, + boxReferences, + globalStateSchema, + applicationId, + localStateSchema, + clearStateProgram == null ? null : new TEALProgram(clearStateProgram), + extraPages ); } @@ -770,6 +785,7 @@ public Transaction( List
accounts, List foreignApps, List foreignAssets, + List boxReferences, StateSchema globalStateSchema, Long applicationId, StateSchema localStateSchema, @@ -820,6 +836,7 @@ public Transaction( accounts, foreignApps, foreignAssets, + boxReferences, globalStateSchema, applicationId, localStateSchema, @@ -834,56 +851,57 @@ public Transaction( * https://developer.algorand.org/docs/reference/transactions/#asset-transfer-transaction */ public Transaction( - Type type, - //header fields - Address sender, - BigInteger fee, - BigInteger firstValid, - BigInteger lastValid, - byte[] note, - String genesisID, - Digest genesisHash, - byte[] lease, - Address rekeyTo, - Digest group, - // payment fields - BigInteger amount, - Address receiver, - Address closeRemainderTo, - // keyreg fields - ParticipationPublicKey votePK, - VRFPublicKey vrfPK, - MerkleVerifier stateProofKey, - BigInteger voteFirst, - BigInteger voteLast, - // voteKeyDilution - BigInteger voteKeyDilution, - boolean nonpart, - // asset creation and configuration - AssetParams assetParams, - BigInteger assetIndex, - // asset transfer fields - BigInteger xferAsset, - BigInteger assetAmount, - Address assetSender, - Address assetReceiver, - Address assetCloseTo, - Address freezeTarget, - BigInteger assetFreezeID, - boolean freezeState, - // application fields - List applicationArgs, - OnCompletion onCompletion, - TEALProgram approvalProgram, - List
accounts, - List foreignApps, - List foreignAssets, - StateSchema globalStateSchema, - Long applicationId, - StateSchema localStateSchema, - TEALProgram clearStateProgram, - Long extraPages - ) { + Type type, + //header fields + Address sender, + BigInteger fee, + BigInteger firstValid, + BigInteger lastValid, + byte[] note, + String genesisID, + Digest genesisHash, + byte[] lease, + Address rekeyTo, + Digest group, + // payment fields + BigInteger amount, + Address receiver, + Address closeRemainderTo, + // keyreg fields + ParticipationPublicKey votePK, + VRFPublicKey vrfPK, + MerkleVerifier stateProofKey, + BigInteger voteFirst, + BigInteger voteLast, + // voteKeyDilution + BigInteger voteKeyDilution, + boolean nonpart, + // asset creation and configuration + AssetParams assetParams, + BigInteger assetIndex, + // asset transfer fields + BigInteger xferAsset, + BigInteger assetAmount, + Address assetSender, + Address assetReceiver, + Address assetCloseTo, + Address freezeTarget, + BigInteger assetFreezeID, + boolean freezeState, + // application fields + List applicationArgs, + OnCompletion onCompletion, + TEALProgram approvalProgram, + List
accounts, + List foreignApps, + List foreignAssets, + List boxReferences, + StateSchema globalStateSchema, + Long applicationId, + StateSchema localStateSchema, + TEALProgram clearStateProgram, + Long extraPages + ) { if (type != null) this.type = type; if (sender != null) this.sender = sender; setFee(fee); @@ -921,6 +939,7 @@ public Transaction( if (accounts != null) this.accounts = accounts; if (foreignApps != null) this.foreignApps = foreignApps; if (foreignAssets != null) this.foreignAssets = foreignAssets; + if (boxReferences != null) this.boxReferences = boxReferences; if (globalStateSchema != null) this.globalStateSchema = globalStateSchema; if (applicationId != null) this.applicationId = applicationId; if (localStateSchema != null) this.localStateSchema = globalStateSchema; @@ -929,24 +948,26 @@ public Transaction( } // Used by Jackson to determine "default" values. - public Transaction() {} + public Transaction() { + } /** * Base constructor with flat fee for asset xfer/freeze/destroy transactions. - * @param flatFee is the transaction flat fee - * @param firstRound is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastRound is the last round this txn is valid + * + * @param flatFee is the transaction flat fee + * @param firstRound is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastRound is the last round this txn is valid * @param note * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network + * of the network **/ private Transaction( Type type, BigInteger flatFee, BigInteger firstRound, BigInteger lastRound, - byte [] note, + byte[] note, Digest genesisHash) { this.type = type; @@ -959,28 +980,29 @@ private Transaction( /** * Creates a tx to mark the account as willing to accept the asset. + * * @param acceptingAccount is a checksummed, human-readable address that - * will accept receiving the asset. - * @param flatFee is the transaction flat fee - * @param firstRound is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastRound is the last round this txn is valid + * will accept receiving the asset. + * @param flatFee is the transaction flat fee + * @param firstRound is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastRound is the last round this txn is valid * @param note - * @param genesisID corresponds to the id of the network - * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network - * @param assetIndex is the asset index + * @param genesisID corresponds to the id of the network + * @param genesisHash corresponds to the base64-encoded hash of the genesis + * of the network + * @param assetIndex is the asset index **/ @Deprecated public static Transaction createAssetAcceptTransaction( //AssetTransaction - Address acceptingAccount, - BigInteger flatFee, - BigInteger firstRound, - BigInteger lastRound, - byte [] note, - String genesisID, - Digest genesisHash, - BigInteger assetIndex) { + Address acceptingAccount, + BigInteger flatFee, + BigInteger firstRound, + BigInteger lastRound, + byte[] note, + String genesisID, + Digest genesisHash, + BigInteger assetIndex) { Transaction tx = createAssetTransferTransaction( acceptingAccount, @@ -1000,23 +1022,24 @@ public static Transaction createAssetAcceptTransaction( //AssetTransaction /** * Creates a tx to destroy the asset - * @param senderAccount is a checksummed, human-readable address of the sender - * @param flatFee is the transaction flat fee - * @param firstValid is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastValid is the last round this txn is valid + * + * @param senderAccount is a checksummed, human-readable address of the sender + * @param flatFee is the transaction flat fee + * @param firstValid is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastValid is the last round this txn is valid * @param note - * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network - * @param assetIndex is the asset ID to destroy + * @param genesisHash corresponds to the base64-encoded hash of the genesis + * of the network + * @param assetIndex is the asset ID to destroy **/ @Deprecated public static Transaction createAssetDestroyTransaction( - Address senderAccount, + Address senderAccount, BigInteger flatFee, BigInteger firstValid, BigInteger lastValid, - byte [] note, + byte[] note, Digest genesisHash, BigInteger assetIndex) { Transaction tx = new Transaction( @@ -1026,7 +1049,7 @@ public static Transaction createAssetDestroyTransaction( lastValid, note, genesisHash); - + if (assetIndex != null) tx.assetIndex = assetIndex; if (senderAccount != null) tx.sender = senderAccount; return tx; @@ -1034,25 +1057,26 @@ public static Transaction createAssetDestroyTransaction( /** * Creates a tx to freeze/unfreeze assets - * @param senderAccount is a checksummed, human-readable address of the sender - * @param flatFee is the transaction flat fee - * @param firstValid is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastValid is the last round this txn is valid + * + * @param senderAccount is a checksummed, human-readable address of the sender + * @param flatFee is the transaction flat fee + * @param firstValid is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastValid is the last round this txn is valid * @param note - * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network - * @param assetIndex is the asset ID to destroy + * @param genesisHash corresponds to the base64-encoded hash of the genesis + * of the network + * @param assetIndex is the asset ID to destroy **/ @Deprecated public static Transaction createAssetFreezeTransaction( - Address senderAccount, + Address senderAccount, Address accountToFreeze, boolean freezeState, BigInteger flatFee, BigInteger firstValid, BigInteger lastValid, - byte [] note, + byte[] note, Digest genesisHash, BigInteger assetIndex) { Transaction tx = new Transaction( @@ -1062,46 +1086,47 @@ public static Transaction createAssetFreezeTransaction( lastValid, note, genesisHash); - + if (senderAccount != null) tx.sender = senderAccount; if (accountToFreeze != null) tx.freezeTarget = accountToFreeze; if (assetIndex != null) tx.assetFreezeID = assetIndex; tx.freezeState = freezeState; return tx; - } - + } + /** * Creates a tx for revoking an asset from an account and sending it to another + * * @param transactionSender is a checksummed, human-readable address that will - * send the transaction - * @param assetRevokedFrom is a checksummed, human-readable address that will - * have assets taken from - * @param assetReceiver is a checksummed, human-readable address what will - * receive the assets - * @param assetAmount is the number of assets to send - * @param flatFee is the transaction flat fee - * @param firstRound is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastRound is the last round this txn is valid + * send the transaction + * @param assetRevokedFrom is a checksummed, human-readable address that will + * have assets taken from + * @param assetReceiver is a checksummed, human-readable address what will + * receive the assets + * @param assetAmount is the number of assets to send + * @param flatFee is the transaction flat fee + * @param firstRound is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastRound is the last round this txn is valid * @param note - * @param genesisID corresponds to the id of the network - * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network - * @param assetIndex is the asset index + * @param genesisID corresponds to the id of the network + * @param genesisHash corresponds to the base64-encoded hash of the genesis + * of the network + * @param assetIndex is the asset index **/ @Deprecated public static Transaction createAssetRevokeTransaction(// AssetTransaction - Address transactionSender, - Address assetRevokedFrom, - Address assetReceiver, - BigInteger assetAmount, - BigInteger flatFee, - BigInteger firstRound, - BigInteger lastRound, - byte [] note, - String genesisID, - Digest genesisHash, - BigInteger assetIndex) { + Address transactionSender, + Address assetRevokedFrom, + Address assetReceiver, + BigInteger assetAmount, + BigInteger flatFee, + BigInteger firstRound, + BigInteger lastRound, + byte[] note, + String genesisID, + Digest genesisHash, + BigInteger assetIndex) { Transaction tx = new Transaction( Type.AssetTransfer, @@ -1119,43 +1144,44 @@ public static Transaction createAssetRevokeTransaction(// AssetTransaction return tx; } - + /** * Creates a tx for sending some asset from an asset holder to another user. - * The asset receiver must have marked itself as willing to accept the - * asset. - * @param assetSender is a checksummed, human-readable address that will - * send the transaction and assets + * The asset receiver must have marked itself as willing to accept the + * asset. + * + * @param assetSender is a checksummed, human-readable address that will + * send the transaction and assets * @param assetReceiver is a checksummed, human-readable address what will - * receive the assets - * @param assetCloseTo is a checksummed, human-readable address that - * behaves as a close-to address for the asset transaction; the remaining - * assets not sent to assetReceiver will be sent to assetCloseTo. Leave - * blank for no close-to behavior. - * @param assetAmount is the number of assets to send - * @param flatFee is the transaction flat fee - * @param firstRound is the first round this txn is valid (txn semantics - * unrelated to asset management) - * @param lastRound is the last round this txn is valid + * receive the assets + * @param assetCloseTo is a checksummed, human-readable address that + * behaves as a close-to address for the asset transaction; the remaining + * assets not sent to assetReceiver will be sent to assetCloseTo. Leave + * blank for no close-to behavior. + * @param assetAmount is the number of assets to send + * @param flatFee is the transaction flat fee + * @param firstRound is the first round this txn is valid (txn semantics + * unrelated to asset management) + * @param lastRound is the last round this txn is valid * @param note - * @param genesisID corresponds to the id of the network - * @param genesisHash corresponds to the base64-encoded hash of the genesis - * of the network - * @param assetIndex is the asset index + * @param genesisID corresponds to the id of the network + * @param genesisHash corresponds to the base64-encoded hash of the genesis + * of the network + * @param assetIndex is the asset index **/ @Deprecated public static Transaction createAssetTransferTransaction(// AssetTransaction - Address assetSender, - Address assetReceiver, - Address assetCloseTo, - BigInteger assetAmount, - BigInteger flatFee, - BigInteger firstRound, - BigInteger lastRound, - byte [] note, - String genesisID, - Digest genesisHash, - BigInteger assetIndex) { + Address assetSender, + Address assetReceiver, + Address assetCloseTo, + BigInteger assetAmount, + BigInteger flatFee, + BigInteger firstRound, + BigInteger lastRound, + byte[] note, + String genesisID, + Digest genesisHash, + BigInteger assetIndex) { Transaction tx = new Transaction( Type.AssetTransfer, @@ -1181,15 +1207,15 @@ private void setNote(byte[] note) { /** * Set a transaction fee taking the minimum transaction fee into consideration. - * @param fee * + * @param fee * @Deprecated a transaction builder is coming. */ @Deprecated public void setFee(BigInteger fee) { if (fee != null) { this.fee = fee; - } else { + } else { this.fee = Account.MIN_TX_FEE_UALGOS; } @@ -1210,10 +1236,10 @@ public void setFee(BigInteger fee) { * the lease identified by the (Sender, Lease) pair of the * transaction until the LastValid round passes. While this * transaction possesses the lease, no other transaction - * specifying this lease can be confirmed. - * The Size is fixed at 32 bytes. - * @param lease 32 byte lease + * specifying this lease can be confirmed. + * The Size is fixed at 32 bytes. * + * @param lease 32 byte lease * @Deprecated use setLease(Lease) **/ @Deprecated @@ -1232,6 +1258,7 @@ public void setLease(byte[] lease) { * transaction possesses the lease, no other transaction * specifying this lease can be confirmed. * The Size is fixed at 32 bytes. + * * @param lease Lease object **/ public void setLease(Lease lease) { @@ -1255,19 +1282,21 @@ public enum Type { private static Map namesMap = new HashMap(6); private final String value; + Type(String value) { this.value = value; } /** * Return the enumeration for the given string value. Required for JSON serialization. + * * @param value string representation * @return enumeration type */ @JsonCreator public static Type forValue(String value) { - for (Type t : values()) { - if(t.value.equalsIgnoreCase(value)) { + for (Type t : values()) { + if (t.value.equalsIgnoreCase(value)) { return t; } } @@ -1276,6 +1305,7 @@ public static Type forValue(String value) { /** * Return the string value for this enumeration. Required for JSON serialization. + * * @return string value */ @JsonValue @@ -1301,7 +1331,7 @@ public enum OnCompletion { } public static OnCompletion String(String name) { - for(OnCompletion oc : values()) { + for (OnCompletion oc : values()) { if (oc.serializedName.equalsIgnoreCase(name)) { return oc; } @@ -1311,7 +1341,7 @@ public static OnCompletion String(String name) { @JsonCreator public static OnCompletion forValue(int value) { - for(OnCompletion oc : values()) { + for (OnCompletion oc : values()) { if (oc.serializedValue == value) { return oc; } @@ -1362,7 +1392,7 @@ public byte[] bytesToSign() throws IOException { */ public Digest rawTxID() throws IOException { try { - return new Digest(Digester.digest(this.bytesToSign())); + return new Digest(Digester.digest(this.bytesToSign())); } catch (IOException e) { throw new RuntimeException("tx computation failed", e); } catch (NoSuchAlgorithmException e) { @@ -1405,7 +1435,7 @@ public boolean equals(Object o) { voteFirst.equals(that.voteFirst) && voteLast.equals(that.voteLast) && voteKeyDilution.equals(that.voteKeyDilution) && - nonpart==that.nonpart && + nonpart == that.nonpart && assetParams.equals(that.assetParams) && assetIndex.equals(that.assetIndex) && xferAsset.equals(that.xferAsset) && @@ -1532,4 +1562,75 @@ public static ApplicationCallTransactionBuilder ApplicationCallTransactionBui public static ApplicationClearTransactionBuilder ApplicationClearTransactionBuilder() { return ApplicationClearTransactionBuilder.Builder(); } + + @JsonPropertyOrder(alphabetic = true) + @JsonInclude(JsonInclude.Include.NON_DEFAULT) + public static class BoxReference { + // the index in the foreign apps array of the app this box belongs to + @JsonProperty("i") + private final int appIndex; + + // the name of the box unique to the app it belongs to + @JsonProperty("n") + private final byte[] name; + + public BoxReference( + @JsonProperty("i") int appIndex, + @JsonProperty("n") byte[] name) { + this.appIndex = appIndex; + this.name = Arrays.copyOf(name, name.length); + } + + // Foreign apps start from index 1. Index 0 is the called App ID. + // Must apply offset to yield the foreign app index expected by algod. + private static final int FOREIGN_APPS_INDEX_OFFSET = 1; + private static final long NEW_APP_ID = 0L; + + public static BoxReference fromAppBoxReference(AppBoxReference abr, List foreignApps, Long currentApp) { + if (abr.getAppId() == NEW_APP_ID) + return new BoxReference(0, abr.getName()); + + if (foreignApps == null || !foreignApps.contains(abr.getAppId())) + // If the app references itself in foreign apps, then prefer foreign app index. + // Otherwise, fallback to comparing against the invoked app (`currentApp`). + if (Long.valueOf(abr.getAppId()).equals(currentApp)) + return new BoxReference(0, abr.getName()); + else + throw new RuntimeException( + String.format("Box app ID (%d) is not present in the foreign apps array: %d %s", abr.getAppId(), currentApp, foreignApps)); + else + return new BoxReference(foreignApps.indexOf(abr.getAppId()) + FOREIGN_APPS_INDEX_OFFSET, abr.getName()); + } + + public byte[] getName() { + return Arrays.copyOf(name, name.length); + } + + public int getAppIndex() { + return appIndex; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BoxReference that = (BoxReference) o; + return appIndex == that.appIndex && Arrays.equals(name, that.name); + } + + @Override + public int hashCode() { + int result = Objects.hash(appIndex); + result = 31 * result + Arrays.hashCode(name); + return result; + } + + @Override + public String toString() { + return "BoxReference{" + + "appIndex=" + appIndex + + ", name=" + Arrays.toString(name) + + '}'; + } + } } diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/Client.java b/src/main/java/com/algorand/algosdk/v2/client/common/Client.java index 37569dd92..fc5f28750 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/Client.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/Client.java @@ -48,7 +48,7 @@ public static HttpUrl getHttpUrl(QueryData qData, int port, String host) { httpUrlBuilder.port(port); for (String ps : qData.pathSegments) { - httpUrlBuilder.addPathSegment(ps); + httpUrlBuilder.addEncodedPathSegment(ps); } for (Entry kvp : qData.queries.entrySet()) { try { diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Box.java b/src/main/java/com/algorand/algosdk/v2/client/model/Box.java new file mode 100644 index 000000000..326593c4a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Box.java @@ -0,0 +1,50 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Box name and its content. + */ +public class Box extends PathResponse { + + /** + * (name) box name, base64 encoded + */ + @JsonProperty("name") + public void name(String base64Encoded) { + this.name = Encoder.decodeFromBase64(base64Encoded); + } + public String name() { + return Encoder.encodeToBase64(this.name); + } + public byte[] name; + + /** + * (value) box value, base64 encoded. + */ + @JsonProperty("value") + public void value(String base64Encoded) { + this.value = Encoder.decodeFromBase64(base64Encoded); + } + public String value() { + return Encoder.encodeToBase64(this.value); + } + public byte[] value; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + Box other = (Box) o; + if (!Objects.deepEquals(this.name, other.name)) return false; + if (!Objects.deepEquals(this.value, other.value)) return false; + + return true; + } +} diff --git a/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java b/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java index e44e68e9a..06634ddb4 100644 --- a/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java +++ b/src/test/java/com/algorand/algosdk/cucumber/shared/TransactionSteps.java @@ -47,8 +47,8 @@ public TransactionSteps(Base b) { this.base = b; } - @When("I build an application transaction with operation {string}, application-id {long}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {long}, global-ints {long}, local-bytes {long}, local-ints {long}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {long}, first-valid {long}, last-valid {long}, genesis-hash {string}, extra-pages {long}") - public void buildApplicationTransactions(String operation, Long applicationId, String sender, String approvalProgramFile, String clearProgramFile, Long globalBytes, Long globalInts, Long localBytes, Long localInts, String appArgs, String foreignApps, String foreignAssets, String appAccounts, Long fee, Long firstValid, Long lastValid, String genesisHash, Long extraPages) throws Exception { + @When("I build an application transaction with operation {string}, application-id {long}, sender {string}, approval-program {string}, clear-program {string}, global-bytes {long}, global-ints {long}, local-bytes {long}, local-ints {long}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, fee {long}, first-valid {long}, last-valid {long}, genesis-hash {string}, extra-pages {long}, boxes {string}") + public void buildApplicationTransactions(String operation, Long applicationId, String sender, String approvalProgramFile, String clearProgramFile, Long globalBytes, Long globalInts, Long localBytes, Long localInts, String appArgs, String foreignApps, String foreignAssets, String appAccounts, Long fee, Long firstValid, Long lastValid, String genesisHash, Long extraPages, String boxesStr) throws Exception { ApplicationBaseTransactionBuilder builder = null; // Create builder and apply builder-specific parameters @@ -116,6 +116,9 @@ public void buildApplicationTransactions(String operation, Long applicationId, S if (StringUtils.isNotEmpty(genesisHash)) { builder.genesisHashB64(genesisHash); } + if (StringUtils.isNotEmpty(boxesStr)) { + builder.boxReferences(convertBoxes(boxesStr)); + } builtTransaction = builder.build(); } diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index de7206de3..b32203c4f 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -42,8 +42,8 @@ public Applications(TransientAccount transientAccount, Clients clients, Stepdefs this.base = base; } - @Given("I build an application transaction with the transient account, the current application, suggested params, operation {string}, approval-program {string}, clear-program {string}, global-bytes {long}, global-ints {long}, local-bytes {long}, local-ints {long}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, extra-pages {long}") - public void buildAnApplicationTransactions(String operation, String approvalProgramFile, String clearProgramFile, Long globalBytes, Long globalInts, Long localBytes, Long localInts, String appArgs, String foreignApps, String foreignAssets, String appAccounts, Long extraPages) throws Exception { + @Given("I build an application transaction with the transient account, the current application, suggested params, operation {string}, approval-program {string}, clear-program {string}, global-bytes {long}, global-ints {long}, local-bytes {long}, local-ints {long}, app-args {string}, foreign-apps {string}, foreign-assets {string}, app-accounts {string}, extra-pages {long}, boxes {string}") + public void buildAnApplicationTransactions(String operation, String approvalProgramFile, String clearProgramFile, Long globalBytes, Long globalInts, Long localBytes, Long localInts, String appArgs, String foreignApps, String foreignAssets, String appAccounts, Long extraPages, String boxesStr) throws Exception { ApplicationBaseTransactionBuilder builder = null; // Create builder and apply builder-specific parameters @@ -101,6 +101,9 @@ public void buildAnApplicationTransactions(String operation, String approvalProg if (StringUtils.isNotEmpty(appAccounts)) { builder.accounts(convertAccounts(appAccounts)); } + if (StringUtils.isNotEmpty(boxesStr)) { + builder.boxReferences(convertBoxes(boxesStr)); + } // Send with transient account, suggested params and current application builder.sender(this.transientAccount.transientAccount.getAddress()); diff --git a/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java b/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java new file mode 100644 index 000000000..becfc9edc --- /dev/null +++ b/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java @@ -0,0 +1,23 @@ +package com.algorand.algosdk.transaction; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class TestAppBoxReference { + + private AppBoxReference genConstant() { + return new AppBoxReference(Long.valueOf(5), "example".getBytes(StandardCharsets.US_ASCII)); + } + + @Test + public void testEqualsByReference() { + Assert.assertEquals(genConstant(), genConstant()); + } + + @Test + public void testHashCode() { + Assert.assertEquals(genConstant().hashCode(), genConstant().hashCode()); + } +} diff --git a/src/test/java/com/algorand/algosdk/transaction/TestBoxReference.java b/src/test/java/com/algorand/algosdk/transaction/TestBoxReference.java new file mode 100644 index 000000000..4594deea7 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/transaction/TestBoxReference.java @@ -0,0 +1,77 @@ +package com.algorand.algosdk.transaction; + +import com.google.common.collect.Lists; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class TestBoxReference { + + private AppBoxReference genWithAppId(long appId) { + return new AppBoxReference(appId, "example".getBytes(StandardCharsets.US_ASCII)); + } + + private AppBoxReference genWithNewAppId() { + return new AppBoxReference(0L, "example".getBytes(StandardCharsets.US_ASCII)); + } + + @Test + public void testAppIndexExists() { + long appId = 7; + AppBoxReference abr = genWithAppId(appId); + + Assert.assertEquals( + new Transaction.BoxReference(4, abr.getName()), + Transaction.BoxReference.fromAppBoxReference( + abr, + Lists.newArrayList(1L, 3L, 4L, appId), + appId - 1 + ) + ); + } + + @Test(expected = RuntimeException.class) + public void testAppIndexDoesNotExist() { + long appId = 7; + AppBoxReference abr = genWithAppId(appId); + + Assert.assertEquals( + new Transaction.BoxReference(4, abr.getName()), + Transaction.BoxReference.fromAppBoxReference( + abr, + Lists.newArrayList(1L, 3L, 4L), + appId - 1 + ) + ); + } + + @Test + public void testNewAppId() { + AppBoxReference abr = genWithNewAppId(); + Assert.assertEquals( + new Transaction.BoxReference(0, abr.getName()), + Transaction.BoxReference.fromAppBoxReference( + abr, Lists.newArrayList(), 1L)); + } + + @Test + public void testFallbackToCurrentApp() { + // Mirrors priority search in goal from `cmd/goal/application.go::translateBoxRefs`. + long appId = 7; + AppBoxReference abr = genWithAppId(appId); + + // Prefer foreign apps index when present. + Assert.assertEquals( + new Transaction.BoxReference(4, abr.getName()), + Transaction.BoxReference.fromAppBoxReference( + abr, Lists.newArrayList(1L, 3L, 4L, appId), appId)); + + // Fallback to current app when absent from foreign apps. + Assert.assertEquals( + new Transaction.BoxReference(0, abr.getName()), + Transaction.BoxReference.fromAppBoxReference( + abr, Lists.newArrayList(1L, 3L, 4L), appId)); + } + +} diff --git a/src/test/java/com/algorand/algosdk/transaction/TestTransaction.java b/src/test/java/com/algorand/algosdk/transaction/TestTransaction.java index 1a63b67f7..88801ef18 100644 --- a/src/test/java/com/algorand/algosdk/transaction/TestTransaction.java +++ b/src/test/java/com/algorand/algosdk/transaction/TestTransaction.java @@ -5,10 +5,8 @@ import com.algorand.algosdk.mnemonic.Mnemonic; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.util.TestUtil; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.junit.Assert; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -18,7 +16,6 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Base64; -import java.util.Objects; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -103,6 +100,8 @@ public void testApplicationTransactionJsonSerialization() throws Exception { .firstValid(301) .lastValid(1300) .genesisHash(new Digest()) + .foreignApps(Arrays.asList(10L)) + .boxReferences(Arrays.asList(new AppBoxReference(10L, "name".getBytes()))) .build(); ObjectMapper objectMapper = new ObjectMapper(); @@ -113,6 +112,32 @@ public void testApplicationTransactionJsonSerialization() throws Exception { assertThat(transactionJson).isEqualTo(transactionJson1); } + @Test + public void testApplicationTransactionWithBoxes() throws Exception { + Address from = new Address("VKM6KSCTDHEM6KGEAMSYCNEGIPFJMHDSEMIRAQLK76CJDIRMMDHKAIRMFQ"); + Transaction tx = Transaction.ApplicationUpdateTransactionBuilder() + .sender(from) + .applicationId(100000L) + .firstValid(301) + .lastValid(1300) + .genesisHash(new Digest()) + .foreignApps(Arrays.asList(10L, 100000L)) + .boxReferences(Arrays.asList( + new AppBoxReference(10L, "name".getBytes(StandardCharsets.US_ASCII)), + new AppBoxReference(100000L, "name2".getBytes(StandardCharsets.US_ASCII)), + new AppBoxReference(0L, "name3".getBytes(StandardCharsets.US_ASCII)))) + .build(); + + Assert.assertEquals(3, tx.boxReferences.size()); + Assert.assertArrayEquals( + new int[]{1, 2, 0}, + new int[]{ + tx.boxReferences.get(0).getAppIndex(), + tx.boxReferences.get(1).getAppIndex(), + tx.boxReferences.get(2).getAppIndex(), + }); + } + @Test public void testSerializationMsgpack() throws Exception { Address from = new Address("VKM6KSCTDHEM6KGEAMSYCNEGIPFJMHDSEMIRAQLK76CJDIRMMDHKAIRMFQ"); @@ -139,7 +164,7 @@ public void testMetadaHashBuilderMethods() throws Exception { // when given as input the same metadata hash // and that it is different when the input is different - String metadataHashUTF8 = "Hello! This is the metadata hash"; + String metadataHashUTF8 = "Hello! This is the metadata hash"; String metadataHashUTF8Different = "Hi! I am another metadata hash.."; byte[] metadataHashBytes = metadataHashUTF8.getBytes(StandardCharsets.UTF_8); // The value below is the base64 of metadataHashUTF8 diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index 611f622a1..d0fd95a1c 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -3,7 +3,9 @@ import com.algorand.algosdk.crypto.Address; import com.algorand.algosdk.unit.utils.QueryMapper; import com.algorand.algosdk.unit.utils.TestingUtils; -import com.algorand.algosdk.v2.client.algod.*; +import com.algorand.algosdk.v2.client.algod.AccountInformation; +import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; +import com.algorand.algosdk.v2.client.algod.GetPendingTransactionsByAddress; import com.algorand.algosdk.v2.client.common.AlgodClient; import io.cucumber.java.en.When; @@ -67,14 +69,14 @@ public void getApplicationByID(Long id) { @When("we make an Account Application Information call against account {string} applicationID {int}") public void accountApplicationInformation(String string, Integer int1) throws NoSuchAlgorithmException { - ps.q = algodClient.AccountApplicationInformation(new Address(string), (long)int1.intValue()); + ps.q = algodClient.AccountApplicationInformation(new Address(string), (long) int1.intValue()); } - + @When("we make an Account Asset Information call against account {string} assetID {int}") public void accountAssetInformation(String string, Integer int1) throws NoSuchAlgorithmException { - ps.q = algodClient.AccountAssetInformation(new Address(string), (long)int1.intValue()); + ps.q = algodClient.AccountAssetInformation(new Address(string), (long) int1.intValue()); } - + @When("we make an Account Information call against account {string} with exclude {string}") public void accountInformation(String string, String string2) throws NoSuchAlgorithmException { AccountInformation aiq = algodClient.AccountInformation(new Address(string)); diff --git a/src/test/java/com/algorand/algosdk/util/ConversionUtils.java b/src/test/java/com/algorand/algosdk/util/ConversionUtils.java index 604323cc3..60a217885 100644 --- a/src/test/java/com/algorand/algosdk/util/ConversionUtils.java +++ b/src/test/java/com/algorand/algosdk/util/ConversionUtils.java @@ -1,11 +1,16 @@ package com.algorand.algosdk.util; import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.transaction.AppBoxReference; + import org.assertj.core.api.Assertions; import org.bouncycastle.util.Strings; +import okio.ByteString; + import java.math.BigInteger; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -20,7 +25,7 @@ public static List convertArgs(String args) { .map(s -> { String[] parts = Strings.split(s, ':'); byte[] converted = null; - switch(parts[0]) { + switch (parts[0]) { case "str": converted = parts[1].getBytes(); break; @@ -65,4 +70,29 @@ public static List
convertAccounts(String accounts) { .collect(Collectors.toList()); } + public static List convertBoxes(String boxesStr) { + if (boxesStr.equals("")) { + return null; + } + + ArrayList boxReferences = new ArrayList<>(); + String[] boxesArray = Strings.split(boxesStr, ','); + for (int i = 0; i < boxesArray.length; i += 2) { + Long appID = Long.parseLong(boxesArray[i]); + byte[] name = null; + String enc = Strings.split(boxesArray[i + 1], ':')[0]; + String strName = Strings.split(boxesArray[i + 1], ':')[1]; + if (enc.equals("str")) { + name = strName.getBytes(); + } else { + // b64 encoding + name = ByteString.decodeBase64(strName).toByteArray(); + } + + boxReferences.add(new AppBoxReference(appID, name)); + } + + return boxReferences; + } + } From f181828544887bdefc8ca43c44814794c4b5dec4 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 6 Jul 2022 17:28:40 -0400 Subject: [PATCH 04/20] Boxes: Query algod Box by application ID and Box name (#343) --- Makefile | 4 +- .../client/algod/GetApplicationBoxByName.java | 82 +++++++++++++++++++ .../algosdk/v2/client/common/AlgodClient.java | 31 +++++-- .../algosdk/integration/Applications.java | 14 ++++ .../com/algorand/algosdk/unit/AlgodPaths.java | 5 ++ 5 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxByName.java diff --git a/Makefile b/Makefile index c9fce9856..726f8c30b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ unit: - mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" + mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" integration: - mvn test -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey or @applications.verified or @applications or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c" + mvn test -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c" docker-test: ./run_integration_tests.sh diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxByName.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxByName.java new file mode 100644 index 000000000..2eab16704 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxByName.java @@ -0,0 +1,82 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.Box; + + +/** + * Given an application ID and box name, it returns the box name and value (each + * base64 encoded). Box names must be in the goal app call arg encoding form + * 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form + * 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use + * the form 'addr:XYZ...'. + * /v2/applications/{application-id}/box + */ +public class GetApplicationBoxByName extends Query { + + private Long applicationId; + + /** + * @param applicationId An application identifier + */ + public GetApplicationBoxByName(Client client, Long applicationId) { + super(client, new HttpMethod("get")); + this.applicationId = applicationId; + } + + /** + * A box name, in the goal app call arg form 'encoding:value'. For ints, use the + * form 'int:1234'. For raw bytes, use the form 'b64:A=='. For printable strings, + * use the form 'str:hello'. For addresses, use the form 'addr:XYZ...'. + */ + public GetApplicationBoxByName name(String name) { + addQuery("name", String.valueOf(name)); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(Box.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(Box.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.applicationId == null) { + throw new RuntimeException("application-id is not set. It is a required parameter."); + } + if (!qd.queries.containsKey("name")) { + throw new RuntimeException("name is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("applications")); + addPathSegment(String.valueOf(applicationId)); + addPathSegment(String.valueOf("box")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index 4d2909782..453e9617c 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -19,6 +19,7 @@ import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation; import com.algorand.algosdk.v2.client.algod.GetApplicationByID; +import com.algorand.algosdk.v2.client.algod.GetApplicationBoxByName; import com.algorand.algosdk.v2.client.algod.GetAssetByID; import com.algorand.algosdk.v2.client.algod.TealCompile; import com.algorand.algosdk.v2.client.algod.TealDisassemble; @@ -29,8 +30,9 @@ public class AlgodClient extends Client { /** * Construct an AlgodClient for communicating with the REST API. - * @param host using a URI format. If the scheme is not supplied the client will use HTTP. - * @param port REST server port. + * + * @param host using a URI format. If the scheme is not supplied the client will use HTTP. + * @param port REST server port. * @param token authentication token. */ public AlgodClient(String host, int port, String token) { @@ -39,9 +41,10 @@ public AlgodClient(String host, int port, String token) { /** * Construct an AlgodClient with custom token key for communicating with the REST API. - * @param host using a URI format. If the scheme is not supplied the client will use HTTP. - * @param port REST server port. - * @param token authentication token. + * + * @param host using a URI format. If the scheme is not supplied the client will use HTTP. + * @param port REST server port. + * @param token authentication token. * @param tokenKey authentication token key. */ public AlgodClient(String host, int port, String token, String tokenKey) { @@ -105,7 +108,7 @@ public AccountInformation AccountInformation(Address address) { * /v2/accounts/{address}/assets/{asset-id} */ public AccountAssetInformation AccountAssetInformation(Address address, - Long assetId) { + Long assetId) { return new AccountAssetInformation((Client) this, address, assetId); } @@ -117,7 +120,7 @@ public AccountAssetInformation AccountAssetInformation(Address address, * /v2/accounts/{address}/applications/{application-id} */ public AccountApplicationInformation AccountApplicationInformation(Address address, - Long applicationId) { + Long applicationId) { return new AccountApplicationInformation((Client) this, address, applicationId); } @@ -144,7 +147,7 @@ public GetBlock GetBlock(Long round) { * /v2/blocks/{round}/transactions/{txid}/proof */ public GetProof GetProof(Long round, - String txid) { + String txid) { return new GetProof((Client) this, round, txid); } @@ -222,6 +225,18 @@ public GetApplicationByID GetApplicationByID(Long applicationId) { return new GetApplicationByID((Client) this, applicationId); } + /** + * Given an application ID and box name, it returns the box name and value (each + * base64 encoded). Box names must be in the goal app call arg encoding form + * 'encoding:value'. For ints, use the form 'int:1234'. For raw bytes, use the form + * 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use + * the form 'addr:XYZ...'. + * /v2/applications/{application-id}/box + */ + public GetApplicationBoxByName GetApplicationBoxByName(Long applicationId) { + return new GetApplicationBoxByName((Client) this, applicationId); + } + /** * Given a asset ID, it returns asset information including creator, name, total * supply and special addresses. diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index b32203c4f..43159e8e7 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -250,4 +250,18 @@ public void checkAccountData( assertThat(found).as("Couldn't find key '%s'", hasKey).isTrue(); } + + @Then("the contents of the box with name {string} should be {string}. If there is an error it is {string}.") + public void contentsOfBoxShouldBe(String encodedBoxName, String boxContents, String errStr) throws Exception { + Response boxResp = clients.v2Client.GetApplicationBoxByName(this.appId).name(encodedBoxName).execute(); + + // If an error was expected, make sure it is set correctly. + if (StringUtils.isNotEmpty(errStr)) { + assertThat(boxResp.isSuccessful()).isFalse(); + assertThat(boxResp.message()).containsIgnoringCase(errStr); + return; + } + + assertThat(boxResp.body().value().equals(boxContents)); + } } diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index d0fd95a1c..ccb14b093 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -83,4 +83,9 @@ public void accountInformation(String string, String string2) throws NoSuchAlgor if (TestingUtils.notEmpty(string2)) aiq.exclude(QueryMapper.getExclude(string2)); ps.q = aiq; } + + @When("we make a GetApplicationBoxByName call for applicationID {int} with encoded box name {string}") + public void getBoxByName(Integer appID, String encodedBoxName) { + ps.q = algodClient.GetApplicationBoxByName(Long.valueOf(appID)).name(encodedBoxName); + } } From c478da8b269477fe3be0d4f47384fcce6a8e737e Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Tue, 12 Jul 2022 13:20:04 -0400 Subject: [PATCH 05/20] Boxes: Add convenience methods for encoding Box names (#346) --- .../algosdk/transaction/AppBoxReference.java | 6 +++ .../algosdk/util/BoxQueryEncoding.java | 25 +++++++++ .../transaction/TestAppBoxReference.java | 15 +++++- .../algosdk/util/TestBoxQueryEncoding.java | 53 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java create mode 100644 src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java diff --git a/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java index ea93c3186..57c13f6f0 100644 --- a/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java +++ b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java @@ -1,5 +1,7 @@ package com.algorand.algosdk.transaction; +import com.algorand.algosdk.util.BoxQueryEncoding; + import java.util.Arrays; import java.util.Objects; @@ -46,4 +48,8 @@ public String toString() { ", name=" + Arrays.toString(name) + '}'; } + + public String nameQueryEncoded() { + return BoxQueryEncoding.encodeBytes(this.getName()); + } } diff --git a/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java b/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java new file mode 100644 index 000000000..0d6a3733a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java @@ -0,0 +1,25 @@ +package com.algorand.algosdk.util; + +import com.algorand.algosdk.transaction.AppBoxReference; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.v2.client.model.Box; + +/** + * BoxQueryEncoding provides convenience methods to String encode box names for use with Box search APIs (e.g. GetApplicationBoxByName). + */ +public final class BoxQueryEncoding { + + private static final String ENCODING_BASE64_PREFIX = "b64:"; + + public static String encodeBytes(byte[] xs) { + return ENCODING_BASE64_PREFIX + Encoder.encodeToBase64(xs); + } + + public static String encodeBox(Box b) { + return encodeBytes(b.name); + } + + public static String encodeBoxReference(Transaction.BoxReference br) { + return encodeBytes(br.getName()); + } +} diff --git a/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java b/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java index becfc9edc..300ddb208 100644 --- a/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java +++ b/src/test/java/com/algorand/algosdk/transaction/TestAppBoxReference.java @@ -8,7 +8,7 @@ public class TestAppBoxReference { private AppBoxReference genConstant() { - return new AppBoxReference(Long.valueOf(5), "example".getBytes(StandardCharsets.US_ASCII)); + return new AppBoxReference(5, "example".getBytes(StandardCharsets.US_ASCII)); } @Test @@ -20,4 +20,17 @@ public void testEqualsByReference() { public void testHashCode() { Assert.assertEquals(genConstant().hashCode(), genConstant().hashCode()); } + + @Test + public void testNameQueryEncoding() { + String example = "tkÿÿ"; + String expectedEncoding = "b64:dGvDv8O/"; + + AppBoxReference abr = new AppBoxReference(0, example.getBytes(StandardCharsets.UTF_8)); + + Assert.assertEquals( + expectedEncoding, + abr.nameQueryEncoded() + ); + } } diff --git a/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java b/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java new file mode 100644 index 000000000..191cb0105 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java @@ -0,0 +1,53 @@ +package com.algorand.algosdk.util; + +import com.algorand.algosdk.transaction.AppBoxReference; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.v2.client.model.Box; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class TestBoxQueryEncoding { + + private static class Example { + final String source; + final String expectedEncoding; + + public Example(String source, String expectedEncoding) { + this.source = source; + this.expectedEncoding = "b64:" + expectedEncoding; + } + } + + private final Example e = new Example("tkÿÿ", "dGvDv8O/"); + + @Test + public void testEncodeBytes() { + Assert.assertEquals( + e.expectedEncoding, + BoxQueryEncoding.encodeBytes(e.source.getBytes(StandardCharsets.UTF_8)) + ); + } + + @Test + public void testEncodeBox() { + Box b = new Box(); + b.name(Encoder.encodeToBase64(e.source.getBytes(StandardCharsets.UTF_8))); + + Assert.assertEquals( + e.expectedEncoding, + BoxQueryEncoding.encodeBox(b) + ); + } + + @Test + public void testEncodeBoxReference() { + Transaction.BoxReference br = new Transaction.BoxReference(0, e.source.getBytes(StandardCharsets.UTF_8)); + + Assert.assertEquals( + e.expectedEncoding, + BoxQueryEncoding.encodeBoxReference(br) + ); + } +} From 5a9d07766f74f1baa53591e7fd16695b691b1539 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 15 Jul 2022 13:47:07 -0400 Subject: [PATCH 06/20] Boxes: Support GetApplicationBoxes (#347) --- .../algosdk/util/BoxQueryEncoding.java | 6 +- .../v2/client/algod/GetApplicationBoxes.java | 75 +++++++++++++++++++ .../algosdk/v2/client/common/AlgodClient.java | 10 +++ .../algosdk/v2/client/common/Query.java | 5 +- .../v2/client/model/BoxDescriptor.java | 37 +++++++++ .../v2/client/model/BoxesResponse.java | 29 +++++++ .../algosdk/integration/Applications.java | 56 +++++++++++++- .../com/algorand/algosdk/unit/AlgodPaths.java | 16 +++- .../algosdk/util/ConversionUtils.java | 32 +++++--- .../algosdk/util/TestBoxQueryEncoding.java | 13 +++- 10 files changed, 259 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java diff --git a/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java b/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java index 0d6a3733a..d1689d9d4 100644 --- a/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java +++ b/src/main/java/com/algorand/algosdk/util/BoxQueryEncoding.java @@ -1,8 +1,8 @@ package com.algorand.algosdk.util; -import com.algorand.algosdk.transaction.AppBoxReference; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.v2.client.model.Box; +import com.algorand.algosdk.v2.client.model.BoxDescriptor; /** * BoxQueryEncoding provides convenience methods to String encode box names for use with Box search APIs (e.g. GetApplicationBoxByName). @@ -19,6 +19,10 @@ public static String encodeBox(Box b) { return encodeBytes(b.name); } + public static String encodeBoxDescriptor(BoxDescriptor b) { + return encodeBytes(b.name); + } + public static String encodeBoxReference(Transaction.BoxReference br) { return encodeBytes(br.getName()); } diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java new file mode 100644 index 000000000..5dd27a9a5 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java @@ -0,0 +1,75 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.BoxesResponse; + + +/** + * Given an application ID, it returns the box names of that application. No + * particular ordering is guaranteed. + * /v2/applications/{application-id}/boxes + */ +public class GetApplicationBoxes extends Query { + + private Long applicationId; + + /** + * @param applicationId An application identifier + */ + public GetApplicationBoxes(Client client, Long applicationId) { + super(client, new HttpMethod("get")); + this.applicationId = applicationId; + } + + /** + * Max number of box names to return. If max is not set, or max == 0, returns all + * box-names. + */ + public GetApplicationBoxes max(Long max) { + addQuery("max", String.valueOf(max)); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(BoxesResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(BoxesResponse.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.applicationId == null) { + throw new RuntimeException("application-id is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("applications")); + addPathSegment(String.valueOf(applicationId)); + addPathSegment(String.valueOf("boxes")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index 453e9617c..825343069 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -19,6 +19,7 @@ import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation; import com.algorand.algosdk.v2.client.algod.GetApplicationByID; +import com.algorand.algosdk.v2.client.algod.GetApplicationBoxes; import com.algorand.algosdk.v2.client.algod.GetApplicationBoxByName; import com.algorand.algosdk.v2.client.algod.GetAssetByID; import com.algorand.algosdk.v2.client.algod.TealCompile; @@ -225,6 +226,15 @@ public GetApplicationByID GetApplicationByID(Long applicationId) { return new GetApplicationByID((Client) this, applicationId); } + /** + * Given an application ID, it returns the box names of that application. No + * particular ordering is guaranteed. + * /v2/applications/{application-id}/boxes + */ + public GetApplicationBoxes GetApplicationBoxes(Long applicationId) { + return new GetApplicationBoxes((Client) this, applicationId); + } + /** * Given an application ID and box name, it returns the box name and value (each * base64 encoded). Box names must be in the goal app call arg encoding form diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/Query.java b/src/main/java/com/algorand/algosdk/v2/client/common/Query.java index e66a10d90..20901e05f 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/Query.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/Query.java @@ -17,11 +17,11 @@ protected Query(Client client, HttpMethod httpMethod) { protected abstract QueryData getRequestString(); - protected Response baseExecute() throws Exception { + protected Response baseExecute() throws Exception { return baseExecute(null, null); } - protected Response baseExecute(String[] headers, String[] values) throws Exception { + protected Response baseExecute(String[] headers, String[] values) throws Exception { QueryData qData = this.getRequestString(); com.squareup.okhttp.Response resp = this.client.executeCall(qData, httpMethod, headers, values); @@ -69,5 +69,6 @@ protected void addToBody(Object content) { } public abstract Response execute() throws Exception; + public abstract Response execute(String[] headers, String[] values) throws Exception; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java b/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java new file mode 100644 index 000000000..9a4a4335b --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java @@ -0,0 +1,37 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Box descriptor describes a Box. + */ +public class BoxDescriptor extends PathResponse { + + /** + * Base64 encoded box name + */ + @JsonProperty("name") + public void name(String base64Encoded) { + this.name = Encoder.decodeFromBase64(base64Encoded); + } + public String name() { + return Encoder.encodeToBase64(this.name); + } + public byte[] name; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + BoxDescriptor other = (BoxDescriptor) o; + if (!Objects.deepEquals(this.name, other.name)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java new file mode 100644 index 000000000..3b8740b4b --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java @@ -0,0 +1,29 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Box names of an application + */ +public class BoxesResponse extends PathResponse { + + @JsonProperty("boxes") + public List boxes = new ArrayList(); + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + BoxesResponse other = (BoxesResponse) o; + if (!Objects.deepEquals(this.boxes, other.boxes)) return false; + + return true; + } +} diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index 43159e8e7..9e176c0f1 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -14,11 +14,16 @@ import io.cucumber.java.en.Then; import org.apache.commons.lang3.StringUtils; import org.assertj.core.api.Assertions; +import org.assertj.core.util.Lists; +import org.bouncycastle.util.Strings; +import org.junit.Assert; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -251,7 +256,7 @@ public void checkAccountData( assertThat(found).as("Couldn't find key '%s'", hasKey).isTrue(); } - @Then("the contents of the box with name {string} should be {string}. If there is an error it is {string}.") + @Then("the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.") public void contentsOfBoxShouldBe(String encodedBoxName, String boxContents, String errStr) throws Exception { Response boxResp = clients.v2Client.GetApplicationBoxByName(this.appId).name(encodedBoxName).execute(); @@ -264,4 +269,53 @@ public void contentsOfBoxShouldBe(String encodedBoxName, String boxContents, Str assertThat(boxResp.body().value().equals(boxContents)); } + + private static byte[] decodeBoxName(String encodedBoxName) { + String[] split = Strings.split(encodedBoxName, ':'); + if (split.length != 2) + throw new RuntimeException("encodedBoxName (" + encodedBoxName + ") does not match expected format"); + + String encoding = split[0]; + String encoded = split[1]; + switch (encoding) { + case "str": + return encoded.getBytes(StandardCharsets.US_ASCII); + case "b64": + return Encoder.decodeFromBase64(encoded); + default: + throw new RuntimeException("Unsupported encoding = " + encoding); + } + } + + private static boolean contains(byte[] elem, List xs) { + for (byte[] e : xs) { + if (Arrays.equals(e, elem)) + return true; + } + return false; + } + + @Then("the current application should have the following boxes {string}.") + public void checkAppBoxes(String encodedBoxesRaw) throws Exception { + final List expectedNames = Lists.newArrayList(); + if (!encodedBoxesRaw.isEmpty()) { + for (String s : Strings.split(encodedBoxesRaw, ',')) { + expectedNames.add(decodeBoxName(s)); + } + } + + final Response r = clients.v2Client.GetApplicationBoxes(this.appId).execute(); + Assert.assertTrue(r.isSuccessful()); + + final List actualNames = Lists.newArrayList(); + for (BoxDescriptor b : r.body().boxes) { + actualNames.add(b.name); + } + + Assert.assertEquals("expected and actual box names length do not match", expectedNames.size(), actualNames.size()); + for (byte[] e : expectedNames) { + if (!contains(e, actualNames)) + throw new RuntimeException("expected and actual box names do not match: " + expectedNames + " != " + actualNames); + } + } } diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index ccb14b093..679ec3a33 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -4,6 +4,7 @@ import com.algorand.algosdk.unit.utils.QueryMapper; import com.algorand.algosdk.unit.utils.TestingUtils; import com.algorand.algosdk.v2.client.algod.AccountInformation; +import com.algorand.algosdk.v2.client.algod.GetApplicationBoxes; import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.GetPendingTransactionsByAddress; import com.algorand.algosdk.v2.client.common.AlgodClient; @@ -84,8 +85,17 @@ public void accountInformation(String string, String string2) throws NoSuchAlgor ps.q = aiq; } - @When("we make a GetApplicationBoxByName call for applicationID {int} with encoded box name {string}") - public void getBoxByName(Integer appID, String encodedBoxName) { - ps.q = algodClient.GetApplicationBoxByName(Long.valueOf(appID)).name(encodedBoxName); + @When("we make a GetApplicationBoxByName call for applicationID {long} with encoded box name {string}") + public void getBoxByName(Long appID, String encodedBoxName) { + ps.q = algodClient.GetApplicationBoxByName(appID).name(encodedBoxName); + } + + @When("we make a GetApplicationBoxes call for applicationID {long} with max {long}") + public void getBoxes(Long appId, Long max) { + GetApplicationBoxes q = algodClient.GetApplicationBoxes(appId); + + if (TestingUtils.notEmpty(max)) q.max(max); + + ps.q = q; } } diff --git a/src/test/java/com/algorand/algosdk/util/ConversionUtils.java b/src/test/java/com/algorand/algosdk/util/ConversionUtils.java index 60a217885..858d03bf9 100644 --- a/src/test/java/com/algorand/algosdk/util/ConversionUtils.java +++ b/src/test/java/com/algorand/algosdk/util/ConversionUtils.java @@ -6,9 +6,8 @@ import org.assertj.core.api.Assertions; import org.bouncycastle.util.Strings; -import okio.ByteString; - import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; @@ -32,8 +31,11 @@ public static List convertArgs(String args) { case "int": converted = BigInteger.valueOf(Integer.parseInt(parts[1])).toByteArray(); break; + case "b64": + converted = Encoder.decodeFromBase64(parts[1]); + break; default: - Assertions.fail("Doesn't currently support '" + parts[0] + "' convertion."); + Assertions.fail("Doesn't currently support '" + parts[0] + "' conversion."); } return converted; }) @@ -75,21 +77,27 @@ public static List convertBoxes(String boxesStr) { return null; } - ArrayList boxReferences = new ArrayList<>(); + List boxReferences = new ArrayList<>(); String[] boxesArray = Strings.split(boxesStr, ','); for (int i = 0; i < boxesArray.length; i += 2) { - Long appID = Long.parseLong(boxesArray[i]); - byte[] name = null; + long appId = Long.parseLong(boxesArray[i]); + String enc = Strings.split(boxesArray[i + 1], ':')[0]; String strName = Strings.split(boxesArray[i + 1], ':')[1]; - if (enc.equals("str")) { - name = strName.getBytes(); - } else { - // b64 encoding - name = ByteString.decodeBase64(strName).toByteArray(); + + byte[] name; + switch (enc) { + case "str": + name = strName.getBytes(StandardCharsets.US_ASCII); + break; + case "b64": + name = Encoder.decodeFromBase64(strName); + break; + default: + throw new RuntimeException("Unsupported encoding = " + enc); } - boxReferences.add(new AppBoxReference(appID, name)); + boxReferences.add(new AppBoxReference(appId, name)); } return boxReferences; diff --git a/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java b/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java index 191cb0105..01caef05f 100644 --- a/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java +++ b/src/test/java/com/algorand/algosdk/util/TestBoxQueryEncoding.java @@ -1,8 +1,8 @@ package com.algorand.algosdk.util; -import com.algorand.algosdk.transaction.AppBoxReference; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.v2.client.model.Box; +import com.algorand.algosdk.v2.client.model.BoxDescriptor; import org.junit.Assert; import org.junit.Test; @@ -41,6 +41,17 @@ public void testEncodeBox() { ); } + @Test + public void testEncodeBoxDescriptor() { + BoxDescriptor b = new BoxDescriptor(); + b.name(Encoder.encodeToBase64(e.source.getBytes(StandardCharsets.UTF_8))); + + Assert.assertEquals( + e.expectedEncoding, + BoxQueryEncoding.encodeBoxDescriptor(b) + ); + } + @Test public void testEncodeBoxReference() { Transaction.BoxReference br = new Transaction.BoxReference(0, e.source.getBytes(StandardCharsets.UTF_8)); From 83fd3eb9b9fe32acad8f2afc8a75dedc5b005b88 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 11 Aug 2022 09:43:25 -0400 Subject: [PATCH 07/20] Merge develop branch to boxes feature branch (#354) * Ignore copied over txt test resource files (#342) * Github-Actions: Adding pr title and label checks (#339) * Enhancement: Add UNKNOWN enum type to HTTP client enums. (#351) * AVM: Consolidate TEAL and AVM versions (#348) * Testing: Modify cucumber steps to use dev mode network (#350) * DevTools: adding source map decoder (#352) * adding source map decoder * Add back line in makefile * Remove redundant mvn test Co-authored-by: Michael Diamant Co-authored-by: Jack <87339414+algojack@users.noreply.github.com> Co-authored-by: Will Winder Co-authored-by: Ben Guidarelli --- .github/release.yml | 20 ++ .github/workflows/pr-type-category.yml | 24 +++ Makefile | 6 +- .../com/algorand/algosdk/logic/Logic.java | 6 +- .../com/algorand/algosdk/logic/SourceMap.java | 121 +++++++++++++ .../algosdk/v2/client/model/Enums.java | 85 ++++++++- .../algosdk/integration/Applications.java | 4 +- .../algosdk/integration/Stepdefs.java | 171 ++++++++++++++++-- .../algosdk/integration/TransientAccount.java | 2 +- .../com/algorand/algosdk/logic/TestLogic.java | 12 +- .../algorand/algosdk/unit/TestSourceMap.java | 60 ++++++ .../algorand/algosdk/util/TestEncoder.java | 44 +++++ 12 files changed, 520 insertions(+), 35 deletions(-) create mode 100644 .github/release.yml create mode 100644 .github/workflows/pr-type-category.yml create mode 100644 src/main/java/com/algorand/algosdk/logic/SourceMap.java create mode 100644 src/test/java/com/algorand/algosdk/unit/TestSourceMap.java diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000..1ea816c33 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,20 @@ +changelog: + exclude: + labels: + - Skip-Release-Notes + categories: + - title: Bugfixes + labels: + - Bug-Fix + - title: New Features + labels: + - New Feature + - title: Enhancements + labels: + - Enhancement + - title: Not Yet Enabled + labels: + - Not-Yet-Enabled + - title: Other + labels: + - "*" diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml new file mode 100644 index 000000000..0ae6369ca --- /dev/null +++ b/.github/workflows/pr-type-category.yml @@ -0,0 +1,24 @@ +name: Check PR category and type +on: + pull_request: + branches: + - develop + types: [opened, synchronize, reopened, labeled, unlabeled, edited] +jobs: + check_label: + runs-on: ubuntu-latest + name: Check PR Category and Type + steps: + - name: Checking for correct number of required github pr labels + uses: mheap/github-action-required-labels@v2 + with: + mode: exactly + count: 1 + labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" + + - name: "Checking for PR Category in PR title. Should be like ': '." + run: | + if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then + echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY + exit 1 + fi diff --git a/Makefile b/Makefile index 726f8c30b..d1f6cdf21 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ unit: - mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application" + mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" integration: - mvn test -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c" + mvn test \ + -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \ + -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c or @compile.sourcemap" docker-test: ./run_integration_tests.sh diff --git a/src/main/java/com/algorand/algosdk/logic/Logic.java b/src/main/java/com/algorand/algosdk/logic/Logic.java index b8547b0f1..ef4202e08 100644 --- a/src/main/java/com/algorand/algosdk/logic/Logic.java +++ b/src/main/java/com/algorand/algosdk/logic/Logic.java @@ -250,14 +250,14 @@ public static ProgramData readProgram(byte[] program, List args) throws } // costs calculated dynamically starting in v4 if (version < 4 && cost > MAX_COST) { - throw new IllegalArgumentException("program too costly for Teal version < 4. consider using v4."); + throw new IllegalArgumentException("program too costly for version < 4. consider using v4."); } return new ProgramData(true, ints, bytes); } /** - * Retrieves TEAL supported version + * Retrieves supported program version * @return int * @throws IOException */ @@ -269,7 +269,7 @@ public static int getLogicSigVersion() throws IOException { } /** - * Retrieves max supported version of TEAL evaluator + * Retrieves max supported program version of evaluator * @return int * @throws IOException */ diff --git a/src/main/java/com/algorand/algosdk/logic/SourceMap.java b/src/main/java/com/algorand/algosdk/logic/SourceMap.java new file mode 100644 index 000000000..5883b5fa9 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/logic/SourceMap.java @@ -0,0 +1,121 @@ +package com.algorand.algosdk.logic; + +import java.lang.Integer; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * SourceMap class provides parser for source map from + * algod compile endpoint + */ +public class SourceMap { + + public int version; + public String file; + public String[] sources; + public String[] names; + public String mappings; + + public HashMap pcToLine; + public HashMap> lineToPc; + + public SourceMap(HashMap sourceMap) { + int version = (int) sourceMap.get("version"); + if(version != 3){ + throw new IllegalArgumentException("Only source map version 3 is supported"); + } + this.version = version; + + this.file = (String) sourceMap.get("file"); + this.mappings = (String) sourceMap.get("mappings"); + + this.lineToPc = new HashMap<>(); + this.pcToLine = new HashMap<>(); + + Integer lastLine = 0; + String[] vlqs = this.mappings.split(";"); + for(int i=0; i vals = VLQDecoder.decodeSourceMapLine(vlqs[i]); + + // If the vals length >= 3 the lineDelta + if(vals.size() >= 3){ + lastLine = lastLine + vals.get(2); + } + + if(!this.lineToPc.containsKey(lastLine)){ + this.lineToPc.put(lastLine, new ArrayList()); + } + + ArrayList currList = this.lineToPc.get(lastLine); + currList.add(i); + this.lineToPc.put(lastLine, currList); + + this.pcToLine.put(i, lastLine); + } + + } + + /** + * Returns the Integer line number for the passed PC or null if not found + * @param pc the pc (program counter) of the assembled file + * @return the line number or null if not found + */ + public Integer getLineForPc(Integer pc) { + return this.pcToLine.get(pc); + } + + /** + * Returns the List of PCs for the passed line number + * @param line the line number of the source file + * @return the list of PCs that line generated or empty array if not found + */ + public ArrayList getPcsForLine(Integer line) { + if(!this.pcToLine.containsKey(line)){ + return new ArrayList(); + } + return this.lineToPc.get(line); + } + + private static class VLQDecoder { + // VLQDecoder for decoding the VLQ values returned for source map + // based on the decoder implementation here: https://github.com/algorand/go-algorand-sdk/blob/develop/logic/source_map.go + + private static final String b64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final int vlqShiftSize = 5; + private static final int vlqFlag = 1 << vlqShiftSize; + private static final int vlqMask = vlqFlag - 1; + + public static ArrayList decodeSourceMapLine(String vlq) { + + ArrayList results = new ArrayList<>(); + int value = 0; + int shift = 0; + + for(int i=0; i 0) { + shift += vlqShiftSize; + continue; + } + + if((value&1)>0){ + value = (value >> 1) * -1; + }else{ + value = value >> 1; + } + + results.add(value); + + // Reset + value = 0; + shift = 0; + } + + return results; + } + } + +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java index 04ff6e685..8fc2fec9e 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java @@ -1,5 +1,6 @@ package com.algorand.algosdk.v2.client.model; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; public class Enums { @@ -10,7 +11,8 @@ public class Enums { public enum AddressRole { @JsonProperty("sender") SENDER("sender"), @JsonProperty("receiver") RECEIVER("receiver"), - @JsonProperty("freeze-target") FREEZETARGET("freeze-target"); + @JsonProperty("freeze-target") FREEZETARGET("freeze-target"), + @JsonProperty("") UNKNOWN(""); final String serializedName; AddressRole(String name) { @@ -21,6 +23,17 @@ public enum AddressRole { public String toString() { return this.serializedName; } + + @JsonCreator + public static AddressRole forValue(String value) { + for (AddressRole t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -34,7 +47,8 @@ public enum Exclude { @JsonProperty("created-assets") CREATEDASSETS("created-assets"), @JsonProperty("apps-local-state") APPSLOCALSTATE("apps-local-state"), @JsonProperty("created-apps") CREATEDAPPS("created-apps"), - @JsonProperty("none") NONE("none"); + @JsonProperty("none") NONE("none"), + @JsonProperty("") UNKNOWN(""); final String serializedName; Exclude(String name) { @@ -45,6 +59,17 @@ public enum Exclude { public String toString() { return this.serializedName; } + + @JsonCreator + public static Exclude forValue(String value) { + for (Exclude t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -54,7 +79,8 @@ public String toString() { */ public enum Hashtype { @JsonProperty("sha512_256") SHA512_256("sha512_256"), - @JsonProperty("sha256") SHA256("sha256"); + @JsonProperty("sha256") SHA256("sha256"), + @JsonProperty("") UNKNOWN(""); final String serializedName; Hashtype(String name) { @@ -65,6 +91,17 @@ public enum Hashtype { public String toString() { return this.serializedName; } + + @JsonCreator + public static Hashtype forValue(String value) { + for (Hashtype t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -84,7 +121,8 @@ public enum OnCompletion { @JsonProperty("closeout") CLOSEOUT("closeout"), @JsonProperty("clear") CLEAR("clear"), @JsonProperty("update") UPDATE("update"), - @JsonProperty("delete") DELETE("delete"); + @JsonProperty("delete") DELETE("delete"), + @JsonProperty("") UNKNOWN(""); final String serializedName; OnCompletion(String name) { @@ -95,6 +133,17 @@ public enum OnCompletion { public String toString() { return this.serializedName; } + + @JsonCreator + public static OnCompletion forValue(String value) { + for (OnCompletion t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -107,7 +156,8 @@ public String toString() { public enum SigType { @JsonProperty("sig") SIG("sig"), @JsonProperty("msig") MSIG("msig"), - @JsonProperty("lsig") LSIG("lsig"); + @JsonProperty("lsig") LSIG("lsig"), + @JsonProperty("") UNKNOWN(""); final String serializedName; SigType(String name) { @@ -118,6 +168,17 @@ public enum SigType { public String toString() { return this.serializedName; } + + @JsonCreator + public static SigType forValue(String value) { + for (SigType t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } /** @@ -137,7 +198,8 @@ public enum TxType { @JsonProperty("acfg") ACFG("acfg"), @JsonProperty("axfer") AXFER("axfer"), @JsonProperty("afrz") AFRZ("afrz"), - @JsonProperty("appl") APPL("appl"); + @JsonProperty("appl") APPL("appl"), + @JsonProperty("") UNKNOWN(""); final String serializedName; TxType(String name) { @@ -148,6 +210,17 @@ public enum TxType { public String toString() { return this.serializedName; } + + @JsonCreator + public static TxType forValue(String value) { + for (TxType t : values()) { + if (t.serializedName.equalsIgnoreCase(value)) { + return t; + } + } + return UNKNOWN; + } + } } diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index 9e176c0f1..1970e3af9 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -143,7 +143,7 @@ public void sendTransactionWithTransientAccountAndCheckForError(String error) th @Given("I wait for the transaction to be confirmed.") public void waitForTransactionToBeConfirmed() throws Exception { - Utils.waitForConfirmation(clients.v2Client, txId, 5); + Utils.waitForConfirmation(clients.v2Client, txId, 1); } // TODO: Use V2 Pending Transaction endpoint when it is available. @@ -173,7 +173,7 @@ public void fundAppAccount(Integer amount) throws Exception { SignedTransaction stx = base.signWithAddress(tx, sender); Response rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute(); - Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5); + Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1); } @Then("I get the account address for the current application and see that it matches the app id's hash") diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index ad00827f5..278878e03 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -17,6 +17,7 @@ import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.util.AlgoConverter; import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.util.ResourceUtils; import com.algorand.algosdk.v2.client.common.Response; import com.algorand.algosdk.v2.client.model.CompileResponse; import com.algorand.algosdk.v2.client.model.DryrunResponse; @@ -24,6 +25,7 @@ import com.algorand.algosdk.v2.client.model.DryrunSource; import com.fasterxml.jackson.core.JsonProcessingException; + import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; @@ -32,6 +34,7 @@ import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.security.GeneralSecurityException; @@ -40,8 +43,11 @@ import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import static com.algorand.algosdk.util.ResourceUtils.loadResource; import static org.assertj.core.api.Assertions.assertThat; @@ -118,6 +124,31 @@ public class Stepdefs { Response compileResponse; Response dryrunResponse; + private static class DevModeState { + static final long ACCOUNT_FUNDING_MICROALGOS = 100_000_000; + private Account advanceRounds; + + /** + * randomAmount minimizes the chance `advanceRounds` issues duplicate transactions by randomizing the payment amount. + */ + private long randomAmount() { + return ThreadLocalRandom.current().nextLong(1, (long) (ACCOUNT_FUNDING_MICROALGOS * .01)); + } + + public SignedTransaction selfPay(TransactionParams tp) throws Exception { + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(advanceRounds.getAddress()) + .suggestedParams(tp) + .amount(randomAmount()) + .receiver(advanceRounds.getAddress()) + .build(); + return advanceRounds.signTransaction(tx); + } + } + + private final DevModeState dms = new DevModeState(); + protected Address getAddress(int i) { if (addresses == null) { throw new RuntimeException("Addresses not initialized, must use given 'wallet information'"); @@ -158,6 +189,48 @@ public SignedTransaction signWithAddress(Transaction tx, Address addr) throws co return acct.signTransaction(tx); } + /** + * advanceRound is a convenience method intended for testing with DevMode networks. + *

+ * Since DevMode block generation requires a transaction rather than time passing, test assertions may require advancing rounds. advanceRound issues advanceCount payments to advance rounds. + */ + private void advanceRoundsV1(int advanceCount) { + initializeDevModeAccount(); + for (int i = 0; i < advanceCount; i++) { + try { + acl.rawTransaction(Encoder.encodeToMsgPack(dms.selfPay(acl.transactionParams()))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * initializeDevModeAccount performs a one-time account initialization per inclusion in a scenario outline. No attempt is made to delete the account. + */ + public void initializeDevModeAccount() { + if (dms.advanceRounds != null) { + return; + } + + try { + getParams(); + dms.advanceRounds = new Account(); + Address sender = getAddress(0); + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(sender) + .suggestedParams(acl.transactionParams()) + .amount(DevModeState.ACCOUNT_FUNDING_MICROALGOS) + .receiver(dms.advanceRounds.getAddress()) + .build(); + SignedTransaction st = signWithAddress(tx, sender); + acl.rawTransaction(Encoder.encodeToMsgPack(st)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * Convenience method to export a key and initialize an account to use for signing. */ @@ -447,8 +520,8 @@ public void status() throws ApiException{ } @When("I get status after this block") - public void statusBlock() throws ApiException, InterruptedException { - Thread.sleep(4000); + public void statusBlock() throws Exception { + advanceRoundsV1(1); statusAfter = acl.waitForBlock(status.getLastRound()); } @@ -526,21 +599,48 @@ public void msigNotInWallet()throws com.algorand.algosdk.kmd.client.ApiException } @When("I generate a key using kmd") - public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException{ + public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException { GenerateKeyRequest req = new GenerateKeyRequest(); req.setDisplayMnemonic(false); req.setWalletHandleToken(handle); pk = new Address(kcl.generateKey(req).getAddress()); } + @When("I generate a key using kmd for rekeying and fund it") + public void genKeyKmdRekey() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException { + GenerateKeyRequest req = new GenerateKeyRequest(); + req.setDisplayMnemonic(false); + req.setWalletHandleToken(handle); + rekey = new Address(kcl.generateKey(req).getAddress()); + + // Fund rekey address + try { + getParams(); + Address sender = getAddress(0); + Transaction tx = + Transaction.PaymentTransactionBuilder() + .sender(sender) + .suggestedParams(acl.transactionParams()) + .amount(100_000_000) + .receiver(rekey) + .build(); + SignedTransaction st = signWithAddress(tx, sender); + acl.rawTransaction(Encoder.encodeToMsgPack(st)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + Address rekey; + @Then("the key should be in the wallet") public void keyInWallet() throws com.algorand.algosdk.kmd.client.ApiException { ListKeysRequest req = new ListKeysRequest(); req.setWalletHandleToken(handle); List keys = kcl.listKeysInWallet(req).getAddresses(); boolean exists = false; - for (String k : keys){ - if (k.equals(pk.toString())){ + for (String k : keys) { + if (k.equals(pk.toString())) { exists = true; } } @@ -632,6 +732,7 @@ public void aClient() throws FileNotFoundException, IOException{ algodClient.setBasePath("http://localhost:" + algodPort); acl = new AlgodApi(algodClient); } + @Given("an algod v2 client") public void aClientv2() throws FileNotFoundException, IOException{ aclv2 = new com.algorand.algosdk.v2.client.common.AlgodClient( @@ -660,21 +761,30 @@ public void walletInfo() throws com.algorand.algosdk.kmd.client.ApiException, No } @Given("default transaction with parameters {int} {string}") - public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException{ + public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException { + defaultTxnWithAddress(amt, note, getAddress(0)); + } + + @Given("default transaction with parameters {int} {string} and rekeying key") + public void defaultTxnForRekeying(int amt, String note) { + defaultTxnWithAddress(amt, note, rekey); + } + + private void defaultTxnWithAddress(int amt, String note, Address sender) { getParams(); - if (note.equals("none")){ + if (note.equals("none")) { this.note = null; - } else{ + } else { this.note = Encoder.decodeFromBase64(note); } txnBuilder = Transaction.PaymentTransactionBuilder() - .sender(getAddress(0)) + .sender(sender) .suggestedParams(params) .note(this.note) .amount(amt) .receiver(getAddress(1)); txn = txnBuilder.build(); - pk = getAddress(0); + pk = sender; } @Given("default multisig transaction with parameters {int} {string}") @@ -715,23 +825,32 @@ public void sendMsigTxn() throws JsonProcessingException, ApiException{ } @Then("the transaction should go through") - public void checkTxn() throws ApiException, InterruptedException{ + public void checkTxn() throws Exception { String ans = acl.pendingTransactionInformation(txid).getFrom(); assertThat(this.txn.sender.toString()).isEqualTo(ans); - acl.waitForBlock(lastRound.add(BigInteger.valueOf(2))); + waitForAlgodTransactionProcessingToComplete(); String senderFromResponse = acl.transactionInformation(txn.sender.toString(), txid).getFrom(); assertThat(senderFromResponse).isEqualTo(txn.sender.toString()); assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); } + /** + * waitForAlgodTransactionProcessingToComplete is a Dev mode helper method that's a rough analog to `acl.waitForBlock(lastRound.add(BigInteger.valueOf(2)));`. + *

+ * Since Dev mode produces blocks on a per transaction basis, it's possible algod generates a block _before_ the corresponding SDK call to wait for a block. Without _any_ wait, it's possible the SDK looks for the transaction before algod completes processing. So, the method performs a local sleep to simulate waiting for a block. + */ + private static void waitForAlgodTransactionProcessingToComplete() throws Exception { + Thread.sleep(500); + } + @Then("I can get the transaction by ID") - public void txnbyID() throws ApiException, InterruptedException{ - acl.waitForBlock(lastRound.add(BigInteger.valueOf(2))); + public void txnByID() throws Exception { + waitForAlgodTransactionProcessingToComplete(); assertThat(acl.transaction(txid).getFrom()).isEqualTo(pk.toString()); } @Then("the transaction should not go through") - public void txnFail(){ + public void txnFail() { assertThat(err).isTrue(); } @@ -1361,4 +1480,26 @@ public void i_get_execution_result(String result) throws Exception { assertThat(msgs.size()).isGreaterThan(0); assertThat(msgs.get(msgs.size() - 1)).isEqualTo(result); } + + + @When("I compile a teal program {string} with mapping enabled") + public void i_compile_a_teal_program_with_mapping_enabled(String tealPath) throws Exception { + byte[] tealProgram = ResourceUtils.loadResource(tealPath); + this.compileResponse = aclv2.TealCompile().source(tealProgram).sourcemap(true).execute(); + } + + + @Then("the resulting source map is the same as the json {string}") + public void the_resulting_source_map_is_the_same_as_the_json(String jsonPath) throws Exception { + String[] fields = {"version", "sources", "names", "mapping", "mappings"}; + String srcMapStr = new String(ResourceUtils.readResource(jsonPath), StandardCharsets.UTF_8); + + HashMap expectedMap = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class)); + HashMap actualMap = this.compileResponse.body().sourcemap; + + for(String field: fields){ + assertThat(actualMap.get(field)).isEqualTo(expectedMap.get(field)); + } + } + } diff --git a/src/test/java/com/algorand/algosdk/integration/TransientAccount.java b/src/test/java/com/algorand/algosdk/integration/TransientAccount.java index dfc3e48fa..a571e617b 100644 --- a/src/test/java/com/algorand/algosdk/integration/TransientAccount.java +++ b/src/test/java/com/algorand/algosdk/integration/TransientAccount.java @@ -38,7 +38,7 @@ public void createAndFundTransientAccount(Long amount) throws Exception { SignedTransaction stx = base.signWithAddress(tx, sender); Response rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute(); - Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5); + Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1); } } diff --git a/src/test/java/com/algorand/algosdk/logic/TestLogic.java b/src/test/java/com/algorand/algosdk/logic/TestLogic.java index bcf3ca9e7..64abd28fc 100644 --- a/src/test/java/com/algorand/algosdk/logic/TestLogic.java +++ b/src/test/java/com/algorand/algosdk/logic/TestLogic.java @@ -213,7 +213,7 @@ public void testCheckProgramCostly() throws Exception { program2[0] = v; assertThatThrownBy(() -> readProgram(program2, args)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("program too costly for Teal version < 4. consider using v4."); + .hasMessage("program too costly for version < 4. consider using v4."); } for (byte v : versions) { @@ -236,7 +236,7 @@ public void testCheckProgramInvalidOpcode() throws Exception { } @Test - public void testCheckProgramTealV2() throws Exception { + public void testCheckProgramV2() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(2); assertThat(getLogicSigVersion()).isGreaterThanOrEqualTo(2); @@ -269,7 +269,7 @@ public void testCheckProgramTealV2() throws Exception { } @Test - public void testCheckProgramTealV3() throws Exception { + public void testCheckProgramV3() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(3); assertThat(getLogicSigVersion()).isGreaterThanOrEqualTo(3); @@ -311,7 +311,7 @@ public void testCheckProgramTealV3() throws Exception { } @Test - public void testCheckProgramTealV4() throws Exception { + public void testCheckProgramV4() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(4); { @@ -379,7 +379,7 @@ public void testCheckProgramTealV4() throws Exception { } @Test - public void testCheckProgramTealV5() throws Exception { + public void testCheckProgramV5() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(5); { @@ -414,7 +414,7 @@ public void testCheckProgramTealV5() throws Exception { } @Test - public void testCheckProgramTealV6() throws Exception { + public void testCheckProgramV6() throws Exception { assertThat(getEvalMaxVersion()).isGreaterThanOrEqualTo(6); { diff --git a/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java b/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java new file mode 100644 index 000000000..7c51e9862 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/unit/TestSourceMap.java @@ -0,0 +1,60 @@ +package com.algorand.algosdk.unit; + +import com.algorand.algosdk.logic.SourceMap; +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.util.ResourceUtils; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import static org.assertj.core.api.Assertions.assertThat; + + +public class TestSourceMap { + SourceMap srcMap; + + @Given("a source map json file {string}") + public void a_source_map_json_file(String srcMapPath) throws IOException { + String srcMapStr = new String(ResourceUtils.readResource(srcMapPath), StandardCharsets.UTF_8); + HashMap map = new HashMap<>(Encoder.decodeFromJson(srcMapStr, Map.class)); + this.srcMap = new SourceMap(map); + } + + @Then("the string composed of pc:line number equals {string}") + public void the_string_composed_of_pc_line_number_equals(String expected) { + List strs = new ArrayList<>(); + for(Map.Entry entry: this.srcMap.pcToLine.entrySet()){ + strs.add(String.format("%d:%d", entry.getKey(), entry.getValue())); + } + + String actual = StringUtils.join(strs, ";"); + assertThat(actual).isEqualTo(expected); + } + + @Then("getting the last pc associated with a line {string} equals {string}") + public void getting_the_last_pc_associated_with_a_line_equals(String lineStr, String pcStr) { + Integer line = Integer.valueOf(lineStr); + Integer pc = Integer.valueOf(pcStr); + + ArrayList actualPcs = this.srcMap.getPcsForLine(line); + assertThat(actualPcs.get(actualPcs.size() - 1)).isEqualTo(pc); + } + + + @Then("getting the line associated with a pc {string} equals {string}") + public void getting_the_line_associated_with_a_pc_equals(String pcStr, String lineStr) { + Integer line = Integer.valueOf(lineStr); + Integer pc = Integer.valueOf(pcStr); + + Integer actualLine = this.srcMap.getLineForPc(pc); + assertThat(actualLine).isEqualTo(line); + } + +} diff --git a/src/test/java/com/algorand/algosdk/util/TestEncoder.java b/src/test/java/com/algorand/algosdk/util/TestEncoder.java index 5bfb0e95c..15e4f726f 100644 --- a/src/test/java/com/algorand/algosdk/util/TestEncoder.java +++ b/src/test/java/com/algorand/algosdk/util/TestEncoder.java @@ -1,11 +1,17 @@ package com.algorand.algosdk.util; +import com.algorand.algosdk.algod.client.model.PaymentTransactionType; +import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.v2.client.model.Account; +import com.algorand.algosdk.v2.client.model.Enums; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; +import java.io.IOException; import java.math.BigInteger; import static org.assertj.core.api.Assertions.*; @@ -131,4 +137,42 @@ public void testDecodeUint64() { .isInstanceOf(IllegalArgumentException.class) .hasMessage("Length of byte array is invalid"); } + + @ParameterizedTest + @CsvSource({ + "pay, pay", + "blah, ", + "blah, ''" + }) + public void testTransactionEnum(String type, String expected) throws IOException { + String txnString = "{\"type\": \"" + type + "\"}"; + Transaction txn = Encoder.decodeFromJson(txnString, Transaction.class); + if (expected != null) { + Transaction.Type enumValue = Transaction.Type.forValue(expected); + assertThat(enumValue).isEqualTo(txn.type); + } else { + assertThat(Transaction.Type.Default).isEqualTo(txn.type); + } + + TestUtil.serializeDeserializeCheck(txn); + } + + @ParameterizedTest + @CsvSource({ + "sig, sig", + "blah, ", + "blah, ''" + }) + public void testHttpModelEnum(String type, String expected) throws IOException { + String acctString = "{\"sig-type\": \"" + type + "\"}"; + Account acct = Encoder.decodeFromJson(acctString, Account.class); + if (expected != null) { + Enums.SigType enumValue = Enums.SigType.forValue(expected); + assertThat(enumValue).isEqualTo(acct.sigType); + } else { + assertThat(Enums.SigType.UNKNOWN).isEqualTo(acct.sigType); + } + + TestUtil.serializeDeserializeCheck(acct); + } } From c09d9afe6ca4f06cf442d8688e2676abe3a0e554 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Fri, 2 Sep 2022 16:33:06 -0400 Subject: [PATCH 08/20] Enhancement: Merge `develop` to `feature/box-storage` (#381) * Ignore copied over txt test resource files (#342) * Github-Actions: Adding pr title and label checks (#339) * Enhancement: Add UNKNOWN enum type to HTTP client enums. (#351) * AVM: Consolidate TEAL and AVM versions (#348) * Testing: Modify cucumber steps to use dev mode network (#350) * DevTools: adding source map decoder (#352) * adding source map decoder * bump to 1.17.0 * Enhancement: Use Sandbox for Testing (#363) * Bugfix: Pass verbosity to the harness and sandbox (#371) * Enhancement: Deprecating use of langspec (#367) Co-authored-by: Michael Diamant * StateProofs: Add State Proof support. (#360) * Regenerate client. * Implement cucumber response tests. * Implement path tests, update Makefile, update README. * Revert * Add state proof fields to Transaction. * Update README.md * Update src/main/java/com/algorand/algosdk/transaction/Transaction.java * publish results. * Add transaction-root-256 * dummy commit to avoid check pr category * dummy commit to kill previous dummy commit Co-authored-by: Michael Diamant Co-authored-by: Jack <87339414+algojack@users.noreply.github.com> Co-authored-by: Will Winder Co-authored-by: Ben Guidarelli Co-authored-by: Barbara Poon Co-authored-by: Zeph Grunschlag --- .test-env | 14 + CHANGELOG.md | 11 + Makefile | 26 +- README.md | 19 +- pom.xml | 2 +- run_integration_tests.sh | 82 --- .../algosdk/crypto/LogicsigSignature.java | 68 +- .../com/algorand/algosdk/logic/Logic.java | 9 +- .../algosdk/transaction/Transaction.java | 15 +- .../algod/GetLightBlockHeaderProof.java | 66 ++ .../v2/client/algod/GetStateProof.java | 64 ++ ...GetProof.java => GetTransactionProof.java} | 22 +- .../algosdk/v2/client/common/AlgodClient.java | 28 +- .../algosdk/v2/client/model/Block.java | 23 + .../algosdk/v2/client/model/Enums.java | 2 + .../algosdk/v2/client/model/HashFactory.java | 27 + .../model/IndexerStateProofMessage.java | 68 ++ .../client/model/LightBlockHeaderProof.java | 52 ++ .../v2/client/model/MerkleArrayProof.java | 57 ++ .../algosdk/v2/client/model/StateProof.java | 44 ++ .../v2/client/model/StateProofFields.java | 84 +++ .../v2/client/model/StateProofMessage.java | 73 ++ .../client/model/StateProofParticipant.java | 34 + .../v2/client/model/StateProofReveal.java | 42 ++ .../v2/client/model/StateProofSigSlot.java | 31 + .../v2/client/model/StateProofSignature.java | 52 ++ .../v2/client/model/StateProofTracking.java | 57 ++ .../v2/client/model/StateProofVerifier.java | 41 ++ .../algosdk/v2/client/model/Transaction.java | 10 + ...nse.java => TransactionProofResponse.java} | 6 +- .../client/model/TransactionStateProof.java | 49 ++ .../algosdk/v2/client/model/VersionBuild.java | 55 -- src/test/integration.tags | 14 + .../algosdk/crypto/TestLogicsigSignature.java | 10 - .../algorand/algosdk/integration/Clients.java | 5 - .../algosdk/integration/EvalDelta.java | 117 --- .../algorand/algosdk/integration/Indexer.java | 492 ------------- .../algosdk/integration/Stepdefs.java | 148 +--- .../com/algorand/algosdk/unit/AlgodPaths.java | 22 +- .../algorand/algosdk/unit/IndexerPaths.java | 21 +- .../algosdk/unit/ProgramSanityCheck.java | 37 + .../algosdk/unit/ResponsesShared.java | 17 +- .../algosdk/unit/RunCucumberUnitTest.java | 2 +- .../algosdk/unit/utils/QueryMapper.java | 689 ------------------ src/test/unit.tags | 29 + test-harness.sh | 80 ++ 46 files changed, 1261 insertions(+), 1655 deletions(-) create mode 100644 .test-env delete mode 100755 run_integration_tests.sh create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetLightBlockHeaderProof.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetStateProof.java rename src/main/java/com/algorand/algosdk/v2/client/algod/{GetProof.java => GetTransactionProof.java} (73%) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/HashFactory.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/IndexerStateProofMessage.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/LightBlockHeaderProof.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/MerkleArrayProof.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProof.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofFields.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofMessage.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofParticipant.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofReveal.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofSigSlot.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofSignature.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofTracking.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/StateProofVerifier.java rename src/main/java/com/algorand/algosdk/v2/client/model/{ProofResponse.java => TransactionProofResponse.java} (91%) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/TransactionStateProof.java delete mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/VersionBuild.java create mode 100644 src/test/integration.tags delete mode 100644 src/test/java/com/algorand/algosdk/integration/EvalDelta.java delete mode 100644 src/test/java/com/algorand/algosdk/integration/Indexer.java create mode 100644 src/test/java/com/algorand/algosdk/unit/ProgramSanityCheck.java delete mode 100644 src/test/java/com/algorand/algosdk/unit/utils/QueryMapper.java create mode 100644 src/test/unit.tags create mode 100755 test-harness.sh diff --git a/.test-env b/.test-env new file mode 100644 index 000000000..eff6690c0 --- /dev/null +++ b/.test-env @@ -0,0 +1,14 @@ +# Configs for testing repo download: +SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" +SDK_TESTING_BRANCH="feature/box-storage" +SDK_TESTING_HARNESS="test-harness" + +VERBOSE_HARNESS=0 + +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +REMOVE_LOCAL_FEATURES=0 + +# WARNING: Be careful when turning on the next variable. +# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` +OVERWRITE_TESTING_ENVIRONMENT=0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 749808d10..55cb252fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 1.17.0 +### New Features +* DevTools: adding source map decoder by @barnjamin in https://github.com/algorand/java-algorand-sdk/pull/352 +### Enhancements +* Github-Actions: Adding pr title and label checks by @algojack in https://github.com/algorand/java-algorand-sdk/pull/339 +* Enhancement: Add UNKNOWN enum type to HTTP client enums. by @winder in https://github.com/algorand/java-algorand-sdk/pull/351 +* AVM: Consolidate TEAL and AVM versions by @michaeldiamant in https://github.com/algorand/java-algorand-sdk/pull/348 +* Testing: Modify cucumber steps to use dev mode network by @michaeldiamant in https://github.com/algorand/java-algorand-sdk/pull/350 +### Other +* Ignore copied over txt test resource files by @michaeldiamant in https://github.com/algorand/java-algorand-sdk/pull/342 + # 1.16.0 ## What's Changed diff --git a/Makefile b/Makefile index d1f6cdf21..be60d45d2 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,24 @@ +UNIT_TAGS := "$(subst :, or ,$(shell awk '{print $2}' src/test/unit.tags | paste -s -d: -))" +INTEGRATION_TAGS := "$(subst :, or ,$(shell awk '{print $2}' src/test/integration.tags | paste -s -d: -))" + unit: - mvn test -Dcucumber.filter.tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.indexer.rekey or @unit.transactions or @unit.transactions.keyreg or @unit.responses or @unit.applications or @unit.applications.boxes or @unit.dryrun or @unit.tealsign or @unit.responses.messagepack or @unit.responses.231 or @unit.responses.messagepack.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.abijson.byname or @unit.atomic_transaction_composer or @unit.transactions.payment or @unit.responses.unlimited_assets or @unit.algod.ledger_refactoring or @unit.indexer.ledger_refactoring or @unit.dryrun.trace.application or @unit.sourcemap" + mvn test -Dcucumber.filter.tags=$(UNIT_TAGS) integration: - mvn test \ - -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \ - -Dcucumber.filter.tags="@algod or @assets or @auction or @kmd or @send or @send.keyregtxn or @indexer or @rekey_v1 or @applications.verified or @applications or @applications.boxes or @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c or @compile.sourcemap" + mvn test -Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest -Dcucumber.filter.tags=$(INTEGRATION_TAGS) + +display-all-java-steps: + find src/test/java/com/algorand/algosdk -name "*.java" | xargs grep "io.cucumber.java.en" 2>/dev/null | grep -v Binary | cut -d: -f1 | sort | uniq | xargs grep -E "@(Given|Then|When)" + +harness: + ./test-harness.sh + +docker-javasdk-build: + # Build SDK testing environment + docker build -t java-sdk-testing . + +docker-javasdk-run: + # Launch SDK testing + docker run -it --network host java-sdk-testing:latest -docker-test: - ./run_integration_tests.sh +docker-test: harness docker-javasdk-build docker-javasdk-run diff --git a/README.md b/README.md index 27b13aa52..f44389c10 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Maven: com.algorand algosdk - 1.16.0 + 1.17.0 ``` @@ -219,6 +219,13 @@ There is also a special integration test environment, and shared tests. To run t ~$ make docker-test ``` +To stand up the test harness, without running the entire test suite use the Makefile: +``` +~$ make harness +``` +You can then run specific cucumber-based unit and integration tests directly. + + ## deploying artifacts The generated pom file provides maven compatibility and deploy capabilities. @@ -229,6 +236,14 @@ mvn clean site -P github,default # for javadoc mvn clean deploy -P release,default ``` +# Testing + +Many cross-SDK tests are defined in [algorand-sdk-testing](https://github.com/algorand/algorand-sdk-testing/). Some are integration tests with additional dependencies. These dependencies are containerized in a docker file, which can be executed with `make docker-test`. + +It is occasionally useful to run locally, or against alternate integration branches. To do this: +1. Install feature files for your test branch "./run_integration_tests.sh -feature-only -test-branch " +2. Run locally with `make integration` and `make unit`, or from the IDE by running "RunCucumberUnitTest.java" + # Android Support Significant work has been taken to ensure Android compatibility (in particular for `minSdkVersion` 16). Note that the @@ -245,7 +260,7 @@ A testing framework can also be generated with: `com.algorand.sdkutils.RunQueryM ## Regenerate the Client Code -To actually regenerate the code, use `run_generator.sh` with paths to the `*.oas2.json` files mentioned above. +The actual generation is done using the `generate_java.sh` script in the [generator](https://github.com/algorand/generator/) repo. # Updating the `kmd` REST client The `kmd` REST client has not been upgraded to use the new code generation, it is still largely autogenerated by `swagger-codegen`. [https://github.com/swagger-api/swagger-codegen] diff --git a/pom.xml b/pom.xml index 07df6268e..f2a183f17 100755 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.algorand algosdk - 1.16.0 + 1.17.0 jar ${project.groupId}:${project.artifactId} diff --git a/run_integration_tests.sh b/run_integration_tests.sh deleted file mode 100755 index fbba8cf10..000000000 --- a/run_integration_tests.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env bash -set -e - -rootdir=`dirname $0` -pushd $rootdir - -SKIP_TEST_CONTAINER=0 -UPDATE_FEATURE_FILES_ONLY=0 -TEST_BRANCH=feature/box-storage - -function help { - echo "Options:" - echo " -local skip launching the test container." - echo " -feature-only don't bring up test environment or launch test container." - echo " -test-branch install feature files from this branch instead of the default branch." -} - -function my_exit { - popd - exit $1 -} - -while [ "$1" != "" ]; do - case "$1" in - -local) - SKIP_TEST_CONTAINER=1 - ;; - -feature-only) - UPDATE_FEATURE_FILES_ONLY=1 - ;; - -test-branch) - shift - TEST_BRANCH=$1 - ;; - *) - echo "Unknown option $1" - help - my_exit 0 - ;; - esac - shift -done - -# Reset test harness -rm -rf test-harness -git clone --single-branch --branch ${TEST_BRANCH} https://github.com/algorand/algorand-sdk-testing.git test-harness - -## Copy feature files into the project resources - -rm -rf src/test/resources/com/algorand/algosdk/integration -rm -rf src/test/resources/com/algorand/algosdk/unit -mkdir -p src/test/resources/com/algorand/algosdk/integration -mkdir -p src/test/resources/com/algorand/algosdk/unit - -# The Java implementation of these is too tightly coupled with the -# integration tests, so add them to the integration tests instead. -mv test-harness/features/unit/offline.feature test-harness/features/integration/ - -cp -r test-harness/features/integration/* src/test/resources/com/algorand/algosdk/integration -cp -r test-harness/features/unit/* src/test/resources/com/algorand/algosdk/unit -cp -r test-harness/features/resources/* src/test/resources/ - -if [ "${UPDATE_FEATURE_FILES_ONLY}" == "1" ]; then - my_exit 1 -fi - -# Start test harness environment -./test-harness/scripts/up.sh - -if [ "${SKIP_TEST_CONTAINER}" == "1" ]; then - my_exit 1 -fi - -# Build SDK testing environment -docker build -t java-sdk-testing -f Dockerfile "$(pwd)" - -# Launch SDK testing -docker run -it \ - --network host \ - java-sdk-testing:latest - -my_exit 0 diff --git a/src/main/java/com/algorand/algosdk/crypto/LogicsigSignature.java b/src/main/java/com/algorand/algosdk/crypto/LogicsigSignature.java index 91c74de45..7a3b92ffc 100644 --- a/src/main/java/com/algorand/algosdk/crypto/LogicsigSignature.java +++ b/src/main/java/com/algorand/algosdk/crypto/LogicsigSignature.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.apache.commons.codec.binary.Base64; /** * Serializable logicsig class. @@ -39,6 +40,59 @@ public class LogicsigSignature { @JsonProperty("msig") public MultisigSignature msig; + + private static boolean isAsciiPrintable(final byte symbol) { + // linebreak existence check in program byte + boolean isBreakLine = symbol == '\n'; + // printable ascii between range 32 (space) and 126 (tilde ~) + boolean isStdPrintable = symbol >= ' ' && symbol <= '~'; + return isBreakLine || isStdPrintable; + } + + private static boolean isAsciiPrintable(final byte[] program) { + for (byte b : program) { + if (!isAsciiPrintable(b)) + return false; + } + return true; + } + + /** + * Performs heuristic program validation: + * check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes + * @param program + */ + private static void sanityCheckProgram(final byte[] program) { + if (program == null || program.length == 0) + throw new IllegalArgumentException("empty program"); + // in any case, if a slice of "program-bytes" is full of ASCII printable, + // then the slice of bytes can't be Teal program bytes. + // need to check what possible kind of bytes are passed in. + if (isAsciiPrintable(program)) { + // maybe the bytes passed in are representing an Algorand address + boolean isAddress = false; + try { + new Address(new String(program)); + isAddress = true; + } catch (NoSuchAlgorithmException | IllegalArgumentException e) { + // if exception is IllegalArgException, it means bytes are not Algorand address + if (e instanceof NoSuchAlgorithmException) + throw new IllegalArgumentException("cannot check if program bytes are Algorand address" + e); + } + if (isAddress) + throw new IllegalArgumentException("requesting program bytes, get Algorand address"); + + // or maybe these bytes are some B64 encoded bytes representation + if (Base64.isBase64(program)) + throw new IllegalArgumentException("program should not be b64 encoded"); + + // can't further analyze, but it is more than just B64 encoding at this point + throw new IllegalArgumentException( + "program bytes are all ASCII printable characters, not looking like Teal byte code" + ); + } + } + @JsonCreator public LogicsigSignature( @JsonProperty("l") byte[] logic, @@ -48,14 +102,8 @@ public LogicsigSignature( ) { this.logic = Objects.requireNonNull(logic, "program must not be null"); this.args = args; - boolean verified = false; - try { - verified = Logic.checkProgram(this.logic, this.args); - } catch (IOException ex) { - throw new IllegalArgumentException("invalid program", ex); - } - assert verified; + sanityCheckProgram(this.logic); if (sig != null) this.sig = new Signature(sig); this.msig = msig; @@ -124,11 +172,7 @@ public boolean verify(Address singleSigner) throws NoSuchAlgorithmException { return false; } - try { - Logic.checkProgram(this.logic, this.args); - } catch (Exception ex) { - return false; - } + sanityCheckProgram(this.logic); PublicKey pk; try { diff --git a/src/main/java/com/algorand/algosdk/logic/Logic.java b/src/main/java/com/algorand/algosdk/logic/Logic.java index ef4202e08..42965e295 100644 --- a/src/main/java/com/algorand/algosdk/logic/Logic.java +++ b/src/main/java/com/algorand/algosdk/logic/Logic.java @@ -14,7 +14,11 @@ /** * Logic class provides static checkProgram function * that can be used for client-side program validation for size and execution cost. + * + * @deprecated this class is deprecated for relying on metadata (`langspec.json`) that + * does not accurately represent opcode behavior across program versions. */ +@Deprecated public class Logic { private static final int MAX_COST = 20000; @@ -103,7 +107,7 @@ protected static class ByteConstBlock { * Each byte in a varint, except the last byte, has the most significant * bit (msb) set – this indicates that there are further bytes to come. * The lower 7 bits of each byte are used to store the two's complement - * representation of the number in groups of 7 bits, least significant + * representation of the number in groups of 7 bits, the least significant * group first. * https://developers.google.com/protocol-buffers/docs/encoding * @param value being serialized @@ -128,7 +132,7 @@ public static byte[] putUVarint(int value) { * Given a varint, get the integer value * @param buffer serialized varint * @param bufferOffset position in the buffer to start reading from - * @return pair of values in in array: value, read size + * @return pair of values in an array: value, read size */ public static VarintResult getUVarint(byte [] buffer, int bufferOffset) { int x = 0; @@ -164,6 +168,7 @@ public static boolean checkProgram(byte[] program, List args) throws IOE /** * Performs basic program validation: instruction count and program cost + * * @param program Program to validate * @param args Program arguments to validate * @return boolean diff --git a/src/main/java/com/algorand/algosdk/transaction/Transaction.java b/src/main/java/com/algorand/algosdk/transaction/Transaction.java index 51c206763..c352b0f39 100644 --- a/src/main/java/com/algorand/algosdk/transaction/Transaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/Transaction.java @@ -165,6 +165,16 @@ public class Transaction implements Serializable { @JsonProperty("apep") public Long extraPages = 0L; + /* state proof fields */ + @JsonProperty("sptype") + public Integer stateProofType = null; + + @JsonProperty("sp") + public Map stateProof = null; + + @JsonProperty("spmsg") + public Map stateProofMessage = null; + /** * Create a payment transaction * @@ -737,7 +747,7 @@ private Transaction(@JsonProperty("type") Type type, } /** - * Constructor which takes all the fields of Transaction except for nonpart and state proof. + * Constructor which takes all the fields of Transaction except for nonpart. * For details about which fields to use with different transaction types, refer to the developer documentation: * https://developer.algorand.org/docs/reference/transactions/#asset-transfer-transaction */ @@ -1277,7 +1287,8 @@ public enum Type { AssetConfig("acfg"), AssetTransfer("axfer"), AssetFreeze("afrz"), - ApplicationCall("appl"); + ApplicationCall("appl"), + StateProof("stpf"); private static Map namesMap = new HashMap(6); diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetLightBlockHeaderProof.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetLightBlockHeaderProof.java new file mode 100644 index 000000000..ecf9cdf87 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetLightBlockHeaderProof.java @@ -0,0 +1,66 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.LightBlockHeaderProof; + + +/** + * Gets a proof for a given light block header inside a state proof commitment + * /v2/blocks/{round}/lightheader/proof + */ +public class GetLightBlockHeaderProof extends Query { + + private Long round; + + /** + * @param round The round to which the light block header belongs. + */ + public GetLightBlockHeaderProof(Client client, Long round) { + super(client, new HttpMethod("get")); + this.round = round; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(LightBlockHeaderProof.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(LightBlockHeaderProof.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.round == null) { + throw new RuntimeException("round is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("blocks")); + addPathSegment(String.valueOf(round)); + addPathSegment(String.valueOf("lightheader")); + addPathSegment(String.valueOf("proof")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetStateProof.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetStateProof.java new file mode 100644 index 000000000..dc35ba3cd --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetStateProof.java @@ -0,0 +1,64 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.StateProof; + + +/** + * Get a state proof that covers a given round + * /v2/stateproofs/{round} + */ +public class GetStateProof extends Query { + + private Long round; + + /** + * @param round The round for which a state proof is desired. + */ + public GetStateProof(Client client, Long round) { + super(client, new HttpMethod("get")); + this.round = round; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(StateProof.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(StateProof.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.round == null) { + throw new RuntimeException("round is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("stateproofs")); + addPathSegment(String.valueOf(round)); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetTransactionProof.java similarity index 73% rename from src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java rename to src/main/java/com/algorand/algosdk/v2/client/algod/GetTransactionProof.java index 6824d0f4c..edde98b7e 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/algod/GetProof.java +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetTransactionProof.java @@ -6,14 +6,14 @@ import com.algorand.algosdk.v2.client.common.QueryData; import com.algorand.algosdk.v2.client.common.Response; import com.algorand.algosdk.v2.client.model.Enums; -import com.algorand.algosdk.v2.client.model.ProofResponse; +import com.algorand.algosdk.v2.client.model.TransactionProofResponse; /** - * Get a Merkle proof for a transaction in a block. + * Get a proof for a transaction in a block. * /v2/blocks/{round}/transactions/{txid}/proof */ -public class GetProof extends Query { +public class GetTransactionProof extends Query { private Long round; private String txid; @@ -22,7 +22,7 @@ public class GetProof extends Query { * @param round The round in which the transaction appears. * @param txid The transaction ID for which to generate a proof. */ - public GetProof(Client client, Long round, String txid) { + public GetTransactionProof(Client client, Long round, String txid) { super(client, new HttpMethod("get")); addQuery("format", "msgpack"); this.round = round; @@ -34,7 +34,7 @@ public GetProof(Client client, Long round, String txid) { * sha512_256 * sha256 */ - public GetProof hashtype(Enums.Hashtype hashtype) { + public GetTransactionProof hashtype(Enums.Hashtype hashtype) { addQuery("hashtype", String.valueOf(hashtype)); return this; } @@ -45,9 +45,9 @@ public GetProof hashtype(Enums.Hashtype hashtype) { * @throws Exception */ @Override - public Response execute() throws Exception { - Response resp = baseExecute(); - resp.setValueType(ProofResponse.class); + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(TransactionProofResponse.class); return resp; } @@ -60,9 +60,9 @@ public Response execute() throws Exception { * @throws Exception */ @Override - public Response execute(String[] headers, String[] values) throws Exception { - Response resp = baseExecute(headers, values); - resp.setValueType(ProofResponse.class); + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(TransactionProofResponse.class); return resp; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index 825343069..dcbad0340 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -10,7 +10,7 @@ import com.algorand.algosdk.v2.client.algod.AccountApplicationInformation; import com.algorand.algosdk.v2.client.algod.GetPendingTransactionsByAddress; import com.algorand.algosdk.v2.client.algod.GetBlock; -import com.algorand.algosdk.v2.client.algod.GetProof; +import com.algorand.algosdk.v2.client.algod.GetTransactionProof; import com.algorand.algosdk.v2.client.algod.GetSupply; import com.algorand.algosdk.v2.client.algod.GetStatus; import com.algorand.algosdk.v2.client.algod.WaitForBlock; @@ -18,6 +18,8 @@ import com.algorand.algosdk.v2.client.algod.TransactionParams; import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.PendingTransactionInformation; +import com.algorand.algosdk.v2.client.algod.GetStateProof; +import com.algorand.algosdk.v2.client.algod.GetLightBlockHeaderProof; import com.algorand.algosdk.v2.client.algod.GetApplicationByID; import com.algorand.algosdk.v2.client.algod.GetApplicationBoxes; import com.algorand.algosdk.v2.client.algod.GetApplicationBoxByName; @@ -144,12 +146,12 @@ public GetBlock GetBlock(Long round) { } /** - * Get a Merkle proof for a transaction in a block. + * Get a proof for a transaction in a block. * /v2/blocks/{round}/transactions/{txid}/proof */ - public GetProof GetProof(Long round, - String txid) { - return new GetProof((Client) this, round, txid); + public GetTransactionProof GetTransactionProof(Long round, + String txid) { + return new GetTransactionProof((Client) this, round, txid); } /** @@ -217,6 +219,22 @@ public PendingTransactionInformation PendingTransactionInformation(String txid) return new PendingTransactionInformation((Client) this, txid); } + /** + * Get a state proof that covers a given round + * /v2/stateproofs/{round} + */ + public GetStateProof GetStateProof(Long round) { + return new GetStateProof((Client) this, round); + } + + /** + * Gets a proof for a given light block header inside a state proof commitment + * /v2/blocks/{round}/lightheader/proof + */ + public GetLightBlockHeaderProof GetLightBlockHeaderProof(Long round) { + return new GetLightBlockHeaderProof((Client) this, round); + } + /** * Given a application ID, it returns application information including creator, * approval and clear programs, global and local schemas, and global state. diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Block.java b/src/main/java/com/algorand/algosdk/v2/client/model/Block.java index 442ab604c..83078c5ee 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Block.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Block.java @@ -69,6 +69,12 @@ public String seed() { } public byte[] seed; + /** + * Tracks the status of state proofs. + */ + @JsonProperty("state-proof-tracking") + public List stateProofTracking = new ArrayList(); + /** * (ts) Block creation timestamp in seconds since eposh */ @@ -98,6 +104,21 @@ public String transactionsRoot() { } public byte[] transactionsRoot; + /** + * (txn256) TransactionsRootSHA256 is an auxiliary TransactionRoot, built using a + * vector commitment instead of a merkle tree, and SHA256 hash function instead of + * the default SHA512_256. This commitment can be used on environments where only + * the SHA256 function exists. + */ + @JsonProperty("transactions-root-sha256") + public void transactionsRootSha256(String base64Encoded) { + this.transactionsRootSha256 = Encoder.decodeFromBase64(base64Encoded); + } + public String transactionsRootSha256() { + return Encoder.encodeToBase64(this.transactionsRootSha256); + } + public byte[] transactionsRootSha256; + /** * (tc) TxnCounter counts the number of transactions committed in the ledger, from * the time at which support for this feature was introduced. @@ -133,9 +154,11 @@ public boolean equals(Object o) { if (!Objects.deepEquals(this.rewards, other.rewards)) return false; if (!Objects.deepEquals(this.round, other.round)) return false; if (!Objects.deepEquals(this.seed, other.seed)) return false; + if (!Objects.deepEquals(this.stateProofTracking, other.stateProofTracking)) return false; if (!Objects.deepEquals(this.timestamp, other.timestamp)) return false; if (!Objects.deepEquals(this.transactions, other.transactions)) return false; if (!Objects.deepEquals(this.transactionsRoot, other.transactionsRoot)) return false; + if (!Objects.deepEquals(this.transactionsRootSha256, other.transactionsRootSha256)) return false; if (!Objects.deepEquals(this.txnCounter, other.txnCounter)) return false; if (!Objects.deepEquals(this.upgradeState, other.upgradeState)) return false; if (!Objects.deepEquals(this.upgradeVote, other.upgradeVote)) return false; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java index 8fc2fec9e..df6b7c775 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Enums.java @@ -191,6 +191,7 @@ public static SigType forValue(String value) { * (axfer) asset-transfer-transaction * (afrz) asset-freeze-transaction * (appl) application-transaction + * (stpf) state-proof-transaction */ public enum TxType { @JsonProperty("pay") PAY("pay"), @@ -199,6 +200,7 @@ public enum TxType { @JsonProperty("axfer") AXFER("axfer"), @JsonProperty("afrz") AFRZ("afrz"), @JsonProperty("appl") APPL("appl"), + @JsonProperty("stpf") STPF("stpf"), @JsonProperty("") UNKNOWN(""); final String serializedName; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/HashFactory.java b/src/main/java/com/algorand/algosdk/v2/client/model/HashFactory.java new file mode 100644 index 000000000..56e436772 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/HashFactory.java @@ -0,0 +1,27 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class HashFactory extends PathResponse { + + /** + * (t) + */ + @JsonProperty("hash-type") + public Long hashType; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + HashFactory other = (HashFactory) o; + if (!Objects.deepEquals(this.hashType, other.hashType)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/IndexerStateProofMessage.java b/src/main/java/com/algorand/algosdk/v2/client/model/IndexerStateProofMessage.java new file mode 100644 index 000000000..9358b4168 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/IndexerStateProofMessage.java @@ -0,0 +1,68 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class IndexerStateProofMessage extends PathResponse { + + /** + * (b) + */ + @JsonProperty("block-headers-commitment") + public void blockHeadersCommitment(String base64Encoded) { + this.blockHeadersCommitment = Encoder.decodeFromBase64(base64Encoded); + } + public String blockHeadersCommitment() { + return Encoder.encodeToBase64(this.blockHeadersCommitment); + } + public byte[] blockHeadersCommitment; + + /** + * (f) + */ + @JsonProperty("first-attested-round") + public java.math.BigInteger firstAttestedRound; + + /** + * (l) + */ + @JsonProperty("latest-attested-round") + public java.math.BigInteger latestAttestedRound; + + /** + * (P) + */ + @JsonProperty("ln-proven-weight") + public java.math.BigInteger lnProvenWeight; + + /** + * (v) + */ + @JsonProperty("voters-commitment") + public void votersCommitment(String base64Encoded) { + this.votersCommitment = Encoder.decodeFromBase64(base64Encoded); + } + public String votersCommitment() { + return Encoder.encodeToBase64(this.votersCommitment); + } + public byte[] votersCommitment; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + IndexerStateProofMessage other = (IndexerStateProofMessage) o; + if (!Objects.deepEquals(this.blockHeadersCommitment, other.blockHeadersCommitment)) return false; + if (!Objects.deepEquals(this.firstAttestedRound, other.firstAttestedRound)) return false; + if (!Objects.deepEquals(this.latestAttestedRound, other.latestAttestedRound)) return false; + if (!Objects.deepEquals(this.lnProvenWeight, other.lnProvenWeight)) return false; + if (!Objects.deepEquals(this.votersCommitment, other.votersCommitment)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/LightBlockHeaderProof.java b/src/main/java/com/algorand/algosdk/v2/client/model/LightBlockHeaderProof.java new file mode 100644 index 000000000..f99af414f --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/LightBlockHeaderProof.java @@ -0,0 +1,52 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Proof of membership and position of a light block header. + */ +public class LightBlockHeaderProof extends PathResponse { + + /** + * The index of the light block header in the vector commitment tree + */ + @JsonProperty("index") + public Long index; + + /** + * The encoded proof. + */ + @JsonProperty("proof") + public void proof(String base64Encoded) { + this.proof = Encoder.decodeFromBase64(base64Encoded); + } + public String proof() { + return Encoder.encodeToBase64(this.proof); + } + public byte[] proof; + + /** + * Represents the depth of the tree that is being proven, i.e. the number of edges + * from a leaf to the root. + */ + @JsonProperty("treedepth") + public Long treedepth; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + LightBlockHeaderProof other = (LightBlockHeaderProof) o; + if (!Objects.deepEquals(this.index, other.index)) return false; + if (!Objects.deepEquals(this.proof, other.proof)) return false; + if (!Objects.deepEquals(this.treedepth, other.treedepth)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/MerkleArrayProof.java b/src/main/java/com/algorand/algosdk/v2/client/model/MerkleArrayProof.java new file mode 100644 index 000000000..454d526d8 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/MerkleArrayProof.java @@ -0,0 +1,57 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class MerkleArrayProof extends PathResponse { + + @JsonProperty("hash-factory") + public HashFactory hashFactory; + + /** + * (pth) + */ + @JsonProperty("path") + public List path = new ArrayList(); + @JsonIgnore + public void path(List base64Encoded) { + this.path = new ArrayList(); + for (String val : base64Encoded) { + this.path.add(Encoder.decodeFromBase64(val)); + } + } + @JsonIgnore + public List path() { + ArrayList ret = new ArrayList(); + for (byte[] val : this.path) { + ret.add(Encoder.encodeToBase64(val)); + } + return ret; + } + + /** + * (td) + */ + @JsonProperty("tree-depth") + public Long treeDepth; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + MerkleArrayProof other = (MerkleArrayProof) o; + if (!Objects.deepEquals(this.hashFactory, other.hashFactory)) return false; + if (!Objects.deepEquals(this.path, other.path)) return false; + if (!Objects.deepEquals(this.treeDepth, other.treeDepth)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProof.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProof.java new file mode 100644 index 000000000..7fd5a2301 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProof.java @@ -0,0 +1,44 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a state proof and its corresponding message + */ +public class StateProof extends PathResponse { + + /** + * Represents the message that the state proofs are attesting to. + */ + @JsonProperty("Message") + public StateProofMessage message; + + /** + * The encoded StateProof for the message. + */ + @JsonProperty("StateProof") + public void stateProof(String base64Encoded) { + this.stateProof = Encoder.decodeFromBase64(base64Encoded); + } + public String stateProof() { + return Encoder.encodeToBase64(this.stateProof); + } + public byte[] stateProof; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProof other = (StateProof) o; + if (!Objects.deepEquals(this.message, other.message)) return false; + if (!Objects.deepEquals(this.stateProof, other.stateProof)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofFields.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofFields.java new file mode 100644 index 000000000..049ea5a9b --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofFields.java @@ -0,0 +1,84 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * (sp) represents a state proof. + * Definition: + * crypto/stateproof/structs.go : StateProof + */ +public class StateProofFields extends PathResponse { + + /** + * (P) + */ + @JsonProperty("part-proofs") + public MerkleArrayProof partProofs; + + /** + * (pr) Sequence of reveal positions. + */ + @JsonProperty("positions-to-reveal") + public List positionsToReveal = new ArrayList(); + + /** + * (r) Note that this is actually stored as a map[uint64] - Reveal in the actual + * msgp + */ + @JsonProperty("reveals") + public List reveals = new ArrayList(); + + /** + * (v) Salt version of the merkle signature. + */ + @JsonProperty("salt-version") + public Long saltVersion; + + /** + * (c) + */ + @JsonProperty("sig-commit") + public void sigCommit(String base64Encoded) { + this.sigCommit = Encoder.decodeFromBase64(base64Encoded); + } + public String sigCommit() { + return Encoder.encodeToBase64(this.sigCommit); + } + public byte[] sigCommit; + + /** + * (S) + */ + @JsonProperty("sig-proofs") + public MerkleArrayProof sigProofs; + + /** + * (w) + */ + @JsonProperty("signed-weight") + public java.math.BigInteger signedWeight; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofFields other = (StateProofFields) o; + if (!Objects.deepEquals(this.partProofs, other.partProofs)) return false; + if (!Objects.deepEquals(this.positionsToReveal, other.positionsToReveal)) return false; + if (!Objects.deepEquals(this.reveals, other.reveals)) return false; + if (!Objects.deepEquals(this.saltVersion, other.saltVersion)) return false; + if (!Objects.deepEquals(this.sigCommit, other.sigCommit)) return false; + if (!Objects.deepEquals(this.sigProofs, other.sigProofs)) return false; + if (!Objects.deepEquals(this.signedWeight, other.signedWeight)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofMessage.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofMessage.java new file mode 100644 index 000000000..1ee30534a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofMessage.java @@ -0,0 +1,73 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents the message that the state proofs are attesting to. + */ +public class StateProofMessage extends PathResponse { + + /** + * The vector commitment root on all light block headers within a state proof + * interval. + */ + @JsonProperty("BlockHeadersCommitment") + public void blockHeadersCommitment(String base64Encoded) { + this.blockHeadersCommitment = Encoder.decodeFromBase64(base64Encoded); + } + public String blockHeadersCommitment() { + return Encoder.encodeToBase64(this.blockHeadersCommitment); + } + public byte[] blockHeadersCommitment; + + /** + * The first round the message attests to. + */ + @JsonProperty("FirstAttestedRound") + public java.math.BigInteger firstAttestedRound; + + /** + * The last round the message attests to. + */ + @JsonProperty("LastAttestedRound") + public java.math.BigInteger lastAttestedRound; + + /** + * An integer value representing the natural log of the proven weight with 16 bits + * of precision. This value would be used to verify the next state proof. + */ + @JsonProperty("LnProvenWeight") + public java.math.BigInteger lnProvenWeight; + + /** + * The vector commitment root of the top N accounts to sign the next StateProof. + */ + @JsonProperty("VotersCommitment") + public void votersCommitment(String base64Encoded) { + this.votersCommitment = Encoder.decodeFromBase64(base64Encoded); + } + public String votersCommitment() { + return Encoder.encodeToBase64(this.votersCommitment); + } + public byte[] votersCommitment; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofMessage other = (StateProofMessage) o; + if (!Objects.deepEquals(this.blockHeadersCommitment, other.blockHeadersCommitment)) return false; + if (!Objects.deepEquals(this.firstAttestedRound, other.firstAttestedRound)) return false; + if (!Objects.deepEquals(this.lastAttestedRound, other.lastAttestedRound)) return false; + if (!Objects.deepEquals(this.lnProvenWeight, other.lnProvenWeight)) return false; + if (!Objects.deepEquals(this.votersCommitment, other.votersCommitment)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofParticipant.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofParticipant.java new file mode 100644 index 000000000..e48d99581 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofParticipant.java @@ -0,0 +1,34 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofParticipant extends PathResponse { + + /** + * (p) + */ + @JsonProperty("verifier") + public StateProofVerifier verifier; + + /** + * (w) + */ + @JsonProperty("weight") + public java.math.BigInteger weight; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofParticipant other = (StateProofParticipant) o; + if (!Objects.deepEquals(this.verifier, other.verifier)) return false; + if (!Objects.deepEquals(this.weight, other.weight)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofReveal.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofReveal.java new file mode 100644 index 000000000..4db51d66c --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofReveal.java @@ -0,0 +1,42 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofReveal extends PathResponse { + + /** + * (p) + */ + @JsonProperty("participant") + public StateProofParticipant participant; + + /** + * The position in the signature and participants arrays corresponding to this + * entry. + */ + @JsonProperty("position") + public java.math.BigInteger position; + + /** + * (s) + */ + @JsonProperty("sig-slot") + public StateProofSigSlot sigSlot; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofReveal other = (StateProofReveal) o; + if (!Objects.deepEquals(this.participant, other.participant)) return false; + if (!Objects.deepEquals(this.position, other.position)) return false; + if (!Objects.deepEquals(this.sigSlot, other.sigSlot)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSigSlot.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSigSlot.java new file mode 100644 index 000000000..7f9a7cd69 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSigSlot.java @@ -0,0 +1,31 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofSigSlot extends PathResponse { + + /** + * (l) The total weight of signatures in the lower-numbered slots. + */ + @JsonProperty("lower-sig-weight") + public java.math.BigInteger lowerSigWeight; + + @JsonProperty("signature") + public StateProofSignature signature; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofSigSlot other = (StateProofSigSlot) o; + if (!Objects.deepEquals(this.lowerSigWeight, other.lowerSigWeight)) return false; + if (!Objects.deepEquals(this.signature, other.signature)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSignature.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSignature.java new file mode 100644 index 000000000..4491515f9 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofSignature.java @@ -0,0 +1,52 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofSignature extends PathResponse { + + @JsonProperty("falcon-signature") + public void falconSignature(String base64Encoded) { + this.falconSignature = Encoder.decodeFromBase64(base64Encoded); + } + public String falconSignature() { + return Encoder.encodeToBase64(this.falconSignature); + } + public byte[] falconSignature; + + @JsonProperty("merkle-array-index") + public Long merkleArrayIndex; + + @JsonProperty("proof") + public MerkleArrayProof proof; + + /** + * (vkey) + */ + @JsonProperty("verifying-key") + public void verifyingKey(String base64Encoded) { + this.verifyingKey = Encoder.decodeFromBase64(base64Encoded); + } + public String verifyingKey() { + return Encoder.encodeToBase64(this.verifyingKey); + } + public byte[] verifyingKey; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofSignature other = (StateProofSignature) o; + if (!Objects.deepEquals(this.falconSignature, other.falconSignature)) return false; + if (!Objects.deepEquals(this.merkleArrayIndex, other.merkleArrayIndex)) return false; + if (!Objects.deepEquals(this.proof, other.proof)) return false; + if (!Objects.deepEquals(this.verifyingKey, other.verifyingKey)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofTracking.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofTracking.java new file mode 100644 index 000000000..aac966d25 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofTracking.java @@ -0,0 +1,57 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofTracking extends PathResponse { + + /** + * (n) Next round for which we will accept a state proof transaction. + */ + @JsonProperty("next-round") + public Long nextRound; + + /** + * (t) The total number of microalgos held by the online accounts during the + * StateProof round. + */ + @JsonProperty("online-total-weight") + public Long onlineTotalWeight; + + /** + * State Proof Type. Note the raw object uses map with this as key. + */ + @JsonProperty("type") + public java.math.BigInteger type; + + /** + * (v) Root of a vector commitment containing online accounts that will help sign + * the proof. + */ + @JsonProperty("voters-commitment") + public void votersCommitment(String base64Encoded) { + this.votersCommitment = Encoder.decodeFromBase64(base64Encoded); + } + public String votersCommitment() { + return Encoder.encodeToBase64(this.votersCommitment); + } + public byte[] votersCommitment; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofTracking other = (StateProofTracking) o; + if (!Objects.deepEquals(this.nextRound, other.nextRound)) return false; + if (!Objects.deepEquals(this.onlineTotalWeight, other.onlineTotalWeight)) return false; + if (!Objects.deepEquals(this.type, other.type)) return false; + if (!Objects.deepEquals(this.votersCommitment, other.votersCommitment)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/StateProofVerifier.java b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofVerifier.java new file mode 100644 index 000000000..4806a63d6 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/StateProofVerifier.java @@ -0,0 +1,41 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.util.Encoder; +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class StateProofVerifier extends PathResponse { + + /** + * (cmt) Represents the root of the vector commitment tree. + */ + @JsonProperty("commitment") + public void commitment(String base64Encoded) { + this.commitment = Encoder.decodeFromBase64(base64Encoded); + } + public String commitment() { + return Encoder.encodeToBase64(this.commitment); + } + public byte[] commitment; + + /** + * (lf) Key lifetime. + */ + @JsonProperty("key-lifetime") + public java.math.BigInteger keyLifetime; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + StateProofVerifier other = (StateProofVerifier) o; + if (!Objects.deepEquals(this.commitment, other.commitment)) return false; + if (!Objects.deepEquals(this.keyLifetime, other.keyLifetime)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Transaction.java b/src/main/java/com/algorand/algosdk/v2/client/model/Transaction.java index 345e6b61f..1ac8a4423 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Transaction.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Transaction.java @@ -302,6 +302,14 @@ public String rekeyTo() throws NoSuchAlgorithmException { @JsonProperty("signature") public TransactionSignature signature; + /** + * Fields for a state proof transaction. + * Definition: + * data/transactions/stateproof.go : StateProofTxnFields + */ + @JsonProperty("state-proof-transaction") + public TransactionStateProof stateProofTransaction; + /** * (type) Indicates what type of transaction this is. Different types have * different fields. @@ -312,6 +320,7 @@ public String rekeyTo() throws NoSuchAlgorithmException { * (axfer) asset-transfer-transaction * (afrz) asset-freeze-transaction * (appl) application-transaction + * (stpf) state-proof-transaction */ @JsonProperty("tx-type") public Enums.TxType txType; @@ -355,6 +364,7 @@ public boolean equals(Object o) { if (!Objects.deepEquals(this.sender, other.sender)) return false; if (!Objects.deepEquals(this.senderRewards, other.senderRewards)) return false; if (!Objects.deepEquals(this.signature, other.signature)) return false; + if (!Objects.deepEquals(this.stateProofTransaction, other.stateProofTransaction)) return false; if (!Objects.deepEquals(this.txType, other.txType)) return false; return true; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/TransactionProofResponse.java similarity index 91% rename from src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java rename to src/main/java/com/algorand/algosdk/v2/client/model/TransactionProofResponse.java index 6318e77a3..e74827b8e 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/ProofResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/TransactionProofResponse.java @@ -9,7 +9,7 @@ /** * Proof of transaction in a block. */ -public class ProofResponse extends PathResponse { +public class TransactionProofResponse extends PathResponse { /** * The type of hash function used to create the proof, must be one of: @@ -26,7 +26,7 @@ public class ProofResponse extends PathResponse { public Long idx; /** - * Merkle proof of transaction membership. + * Proof of transaction membership. */ @JsonProperty("proof") public void proof(String base64Encoded) { @@ -62,7 +62,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - ProofResponse other = (ProofResponse) o; + TransactionProofResponse other = (TransactionProofResponse) o; if (!Objects.deepEquals(this.hashtype, other.hashtype)) return false; if (!Objects.deepEquals(this.idx, other.idx)) return false; if (!Objects.deepEquals(this.proof, other.proof)) return false; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/TransactionStateProof.java b/src/main/java/com/algorand/algosdk/v2/client/model/TransactionStateProof.java new file mode 100644 index 000000000..7ffa0868d --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/TransactionStateProof.java @@ -0,0 +1,49 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Fields for a state proof transaction. + * Definition: + * data/transactions/stateproof.go : StateProofTxnFields + */ +public class TransactionStateProof extends PathResponse { + + /** + * (spmsg) + */ + @JsonProperty("message") + public IndexerStateProofMessage message; + + /** + * (sp) represents a state proof. + * Definition: + * crypto/stateproof/structs.go : StateProof + */ + @JsonProperty("state-proof") + public StateProofFields stateProof; + + /** + * (sptype) Type of the state proof. Integer representing an entry defined in + * protocol/stateproof.go + */ + @JsonProperty("state-proof-type") + public java.math.BigInteger stateProofType; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + TransactionStateProof other = (TransactionStateProof) o; + if (!Objects.deepEquals(this.message, other.message)) return false; + if (!Objects.deepEquals(this.stateProof, other.stateProof)) return false; + if (!Objects.deepEquals(this.stateProofType, other.stateProofType)) return false; + + return true; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/VersionBuild.java b/src/main/java/com/algorand/algosdk/v2/client/model/VersionBuild.java deleted file mode 100644 index 38dd12457..000000000 --- a/src/main/java/com/algorand/algosdk/v2/client/model/VersionBuild.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.algorand.algosdk.v2.client.model; - -import java.util.Objects; - -import com.algorand.algosdk.util.Encoder; -import com.algorand.algosdk.v2.client.common.PathResponse; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * the current algod build version information. - */ -public class VersionBuild extends PathResponse { - - @JsonProperty("branch") - public String branch; - - @JsonProperty("build-number") - public Long buildNumber; - - @JsonProperty("channel") - public String channel; - - @JsonProperty("commit-hash") - public void commitHash(String base64Encoded) { - this.commitHash = Encoder.decodeFromBase64(base64Encoded); - } - @JsonProperty("commit-hash") - public String commitHash() { - return Encoder.encodeToBase64(this.commitHash); - } - public byte[] commitHash; - - @JsonProperty("major") - public Long major; - - @JsonProperty("minor") - public Long minor; - - @Override - public boolean equals(Object o) { - - if (this == o) return true; - if (o == null) return false; - - VersionBuild other = (VersionBuild) o; - if (!Objects.deepEquals(this.branch, other.branch)) return false; - if (!Objects.deepEquals(this.buildNumber, other.buildNumber)) return false; - if (!Objects.deepEquals(this.channel, other.channel)) return false; - if (!Objects.deepEquals(this.commitHash, other.commitHash)) return false; - if (!Objects.deepEquals(this.major, other.major)) return false; - if (!Objects.deepEquals(this.minor, other.minor)) return false; - - return true; - } -} diff --git a/src/test/integration.tags b/src/test/integration.tags new file mode 100644 index 000000000..c9421bf47 --- /dev/null +++ b/src/test/integration.tags @@ -0,0 +1,14 @@ +@abi +@algod +@applications +@applications.verified +@assets +@auction +@c2c +@compile +@compile.sourcemap +@dryrun +@kmd +@rekey_v1 +@send +@send.keyregtxn diff --git a/src/test/java/com/algorand/algosdk/crypto/TestLogicsigSignature.java b/src/test/java/com/algorand/algosdk/crypto/TestLogicsigSignature.java index e31721bb3..673fbe82e 100644 --- a/src/test/java/com/algorand/algosdk/crypto/TestLogicsigSignature.java +++ b/src/test/java/com/algorand/algosdk/crypto/TestLogicsigSignature.java @@ -62,16 +62,6 @@ public void testLogicsigCreation() throws Exception { assertThat(lsig).isEqualTo(lsig1); } - @Test - public void testLogicsigInvalidProgramCreation() throws Exception { - byte[] program = { - 0x7F, 0x20, 0x01, 0x01, 0x22 - }; - assertThatThrownBy(() -> new LogicsigSignature(program)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("unsupported version"); - } - @Test public void testLogicsigSignature() throws Exception { byte[] program = { diff --git a/src/test/java/com/algorand/algosdk/integration/Clients.java b/src/test/java/com/algorand/algosdk/integration/Clients.java index 89a94cfff..4f4b61b22 100644 --- a/src/test/java/com/algorand/algosdk/integration/Clients.java +++ b/src/test/java/com/algorand/algosdk/integration/Clients.java @@ -15,9 +15,4 @@ public class Clients { public void an_algod_v2_client_connected_to_port_with_token(String host, Integer port, String token) { v2Client = new AlgodClient(host, port, token); } - - @Given("indexer client {int} at {string} port {int} with token {string}") - public void indexer_client_at_port_with_token(Integer index, String uri, Integer port, String token) { - indexerClients.put(index, new IndexerClient(uri, port, "")); - } } diff --git a/src/test/java/com/algorand/algosdk/integration/EvalDelta.java b/src/test/java/com/algorand/algosdk/integration/EvalDelta.java deleted file mode 100644 index 7bc9f8fd2..000000000 --- a/src/test/java/com/algorand/algosdk/integration/EvalDelta.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.algorand.algosdk.integration; - -import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.v2.client.model.*; -import io.cucumber.java.en.Then; -import org.assertj.core.api.Assertions; - -import java.math.BigInteger; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; - -public class EvalDelta { - private final Clients clients; - private final TransientAccount transientAccount; - private final Applications applications; - - public EvalDelta(Clients clients, TransientAccount transientAccount, Applications applications) { - this.clients = clients; - this.transientAccount = transientAccount; - this.applications = applications; - } - - @Then("the unconfirmed pending transaction by ID should have no apply data fields.") - public void the_unconfirmed_pending_transaction_by_ID_should_have_no_apply_data_fields() throws Exception { - PendingTransactionResponse r = clients.v2Client - .PendingTransactionInformation(applications.txId) - .execute() - .body(); - - // Just in case we missed the boat and the tx is now confirmed. - if (r.confirmedRound == null) { - assertThat(r.globalStateDelta).isEmpty(); - assertThat(r.localStateDelta).isEmpty(); - } - } - - private List getAccountDelta(Address addr, List data) { - return data.stream() - .filter(ad -> ad.address.equals(addr)) - .map(ad -> ad.delta) - .findAny() - .orElse(null); - } - - @Then("the confirmed pending transaction by ID should have a {string} state change for {string} to {string}, indexer {int} should also confirm this.") - public void checkConfirmedTransaction(String stateLocation, String key, String newValue, int indexer) throws Exception { - PendingTransactionResponse r = clients.v2Client - .PendingTransactionInformation(applications.txId) - .execute() - .body(); - - // Try fetching the transaction from indexer a few times to give indexer a chance to process the new block. - Transaction tx = null; - for (int i = 0; i < 5 && tx == null; i++) { - TransactionsResponse indexerResponse = clients.indexerClients.get(indexer) - .searchForTransactions() - .txid(applications.txId) - .execute() - .body(); - if (! indexerResponse.transactions.isEmpty()) { - tx = indexerResponse.transactions.get(0); - } else { - Thread.sleep(TimeUnit.SECONDS.toMillis(1)); - } - } - - assertThat(tx).as("Indexer was unable to return txid %s.", applications.txId).isNotNull(); - - // Grab local or global key/value deltas. - // Algod - List keyValuesAlgod = null; - // Indexer - List keyValuesIndexer = null; - switch(stateLocation) { - case "local": - Address localAccountKey = r.txn.tx.sender; - keyValuesAlgod = getAccountDelta(localAccountKey, r.localStateDelta); - keyValuesIndexer = getAccountDelta(localAccountKey, tx.localStateDelta); - break; - case "global": - keyValuesAlgod = r.globalStateDelta; - keyValuesIndexer = tx.globalStateDelta; - break; - default: - Assertions.fail("Unknown state location: %s", stateLocation); - } - - // Algod - assertThat(keyValuesAlgod).hasSize(1); - EvalDeltaKeyValue keyValueAlgod = keyValuesAlgod.get(0); - assertThat(keyValueAlgod.key).isEqualTo(key); - - // Indexer - assertThat(keyValuesIndexer).hasSize(1); - EvalDeltaKeyValue keyValueIndexer = keyValuesIndexer.get(0); - assertThat(keyValueIndexer.key).isEqualTo(key); - - // They should have the same action - assertThat(keyValueAlgod.value.action).isEqualTo(keyValueIndexer.value.action); - - // And the values should be equal, and should equal the expected value - switch(keyValueAlgod.value.action.toString()) { - case "1": - assertThat(keyValueAlgod.value.bytes).isEqualTo(newValue); - assertThat(keyValueAlgod.value.bytes).isEqualTo(keyValueIndexer.value.bytes); - break; - case "2": - assertThat(keyValueAlgod.value.uint).isEqualTo(BigInteger.valueOf(Long.parseLong(newValue))); - assertThat(keyValueAlgod.value.uint).isEqualTo(keyValueIndexer.value.uint); - break; - default: - Assertions.fail("Unknown value action %d.", keyValueAlgod.value.action); - } - } -} diff --git a/src/test/java/com/algorand/algosdk/integration/Indexer.java b/src/test/java/com/algorand/algosdk/integration/Indexer.java deleted file mode 100644 index 706af3716..000000000 --- a/src/test/java/com/algorand/algosdk/integration/Indexer.java +++ /dev/null @@ -1,492 +0,0 @@ -package com.algorand.algosdk.integration; - -import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.unit.utils.TestingUtils; -import com.algorand.algosdk.util.Encoder; -import com.algorand.algosdk.util.ResourceUtils; -import com.algorand.algosdk.v2.client.common.*; -import com.algorand.algosdk.v2.client.indexer.*; -import com.algorand.algosdk.v2.client.model.*; -import io.cucumber.java.en.And; -import io.cucumber.java.en.Given; -import io.cucumber.java.en.Then; -import io.cucumber.java.en.When; -import org.apache.commons.lang3.StringUtils; -import org.assertj.core.api.Assertions; - -import java.io.File; -import java.io.IOException; -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; - -import static com.algorand.algosdk.unit.utils.TestingUtils.searchEnum; -import static org.assertj.core.api.Assertions.assertThat; - -public class Indexer { - Response healthResponse; - Response blockResponse; - Response accountResponse; - Response accountsResponse; - Response assetResponse; - Response assetBalancesResponse; - Response transactionsResponse; - Response assetsResponse; - Response applicationsResponse; - - Response response; - - private final Clients clients; - - public Indexer(Clients clients) { - this.clients = clients; - } - - @When("I use {int} to check the services health") - public void i_use_to_check_the_services_health(Integer index) throws Exception { - healthResponse = clients.indexerClients.get(index).makeHealthCheck().execute(); - } - - @Then("I receive status code {int}") - public void i_receive_status_code(Integer code) { - assertThat(healthResponse.code()).isEqualTo(code); - } - - @When("I use {int} to lookup block {long}") - public void i_request_block_with_indexer(Integer indexer, Long block) throws Exception { - blockResponse = clients.indexerClients.get(indexer).lookupBlock(block).execute(); - } - - @Then("The block was confirmed at {long}, contains {int} transactions, has the previous block hash {string}") - public void the_block_was_confirmed_at_contains_transactions_has_the_previous_block_hash(Long unixTimestamp, Integer numTransactions, String previousBlockHash) { - Block block = blockResponse.body(); - - assertThat(block.timestamp).isEqualTo(unixTimestamp); - assertThat(block.transactions).hasSize(numTransactions); - assertThat(block.previousBlockHash).isEqualTo(Encoder.decodeFromBase64(previousBlockHash)); - } - - @When("I use {int} to lookup account {string} at round {long}") - public void i_lookup_account_with(Integer indexer, String account, Long round) throws Exception { - LookupAccountByID query = clients.indexerClients.get(indexer).lookupAccountByID(new Address(account)); - if (round != 0) { - query.round(round); - } - accountResponse = query.execute(); - } - - @Then("The account has {int} assets, the first is asset {long} has a frozen status of {string} and amount {biginteger}.") - public void the_account_has_num_assets_asset_has_a_frozen_status_of_and_amount(Integer numAssets, Long assetId, String frozenStatus, BigInteger amount) { - AccountResponse response = accountResponse.body(); - - assertThat(response.account.assets).hasSize(numAssets); - AssetHolding holding = response.account.assets.get(0); - assertThat(holding.assetId).isEqualTo(assetId); - assertThat(holding.isFrozen).isEqualTo(Boolean.parseBoolean(frozenStatus)); - assertThat(holding.amount).isEqualTo(amount); - } - - @Then("The account has {long} μalgos and {int} assets, {long} has {biginteger}") - public void the_account_has_μalgos_and_assets(Long amount, Integer numAssets, Long assetId, BigInteger assetAmount) { - AccountResponse response = accountResponse.body(); - assertThat(response.account.assets).hasSize(numAssets); - assertThat(response.account.amount).as("μalgos").isEqualTo(amount); - - if (assetId != 0) { - response.account.assets.forEach(a -> { - if (a.assetId == assetId) { - assertThat(a.amount).as("assets").isEqualTo(assetAmount); - } - }); - } - } - - @Then("The account created {int} assets, the first is asset {long} is named {string} with a total amount of {biginteger} {string}") - public void the_account_created_assets_the_first_is_asset_is_named_with_a_total_amount_of(Integer numAssetsCreated, Long assetId, String assetName, BigInteger assetTotal, String assetUnit) { - AccountResponse response = accountResponse.body(); - assertThat(response.account.createdAssets).hasSize(numAssetsCreated); - Asset asset = response.account.createdAssets.get(0); - assertThat(asset.params.name).isEqualTo(assetName); - assertThat(asset.params.unitName).isEqualTo(assetUnit); - assertThat(asset.params.total).isEqualTo(assetTotal); - } - - @When("I use {int} to lookup asset {long}") - public void i_lookup_asset_with(Integer indexer, Long assetId) throws Exception { - assetResponse = clients.indexerClients.get(indexer).lookupAssetByID(assetId).execute(); - } - - @Then("The asset found has: {string}, {string}, {string}, {long}, {string}, {biginteger}, {string}") - public void the_asset_found_has(String name, String units, String creatorAddress, Long decimals, String defaultFrozen, BigInteger total, String clawbackAddress) { - AssetResponse response = assetResponse.body(); - AssetParams params = response.asset.params; - - assertThat(params.name).isEqualTo(name); - assertThat(params.unitName).isEqualTo(units); - assertThat(params.creator).isEqualTo(creatorAddress); - assertThat(params.decimals).isEqualTo(decimals); - assertThat(params.defaultFrozen).isEqualTo(Boolean.parseBoolean(defaultFrozen)); - assertThat(params.total).isEqualTo(total); - assertThat(params.clawback).isEqualTo(clawbackAddress); - } - - @When("I use {int} to lookup asset balances for {long} with {long}, {long}, {long} and token {string}") - public void i_lookup_asset_balances_for_with_with(Integer indexer, Long assetId, Long currencyGT, Long currencyLT, Long limit, String next) throws Exception { - LookupAssetBalances query = clients.indexerClients.get(indexer).lookupAssetBalances(assetId); - if (currencyGT != 0) { - query.currencyGreaterThan(currencyGT); - } - if (currencyLT != 0) { - query.currencyLessThan(currencyLT); - } - if (limit != 0) { - query.limit(limit); - } - if (StringUtils.isNotEmpty(next)) { - query.next(next); - } - assetBalancesResponse = query.execute(); - } - - @Then("There are {int} with the asset, the first is {string} has {string} and {biginteger}") - public void there_are_with_the_asset_the_first_is_has_and(Integer numResults, String account, String frozenState, BigInteger amount) { - AssetBalancesResponse response = assetBalancesResponse.body(); - - assertThat(response.balances).hasSize(numResults); - - MiniAssetHolding holding = response.balances.get(0); - - assertThat(holding.isFrozen).isEqualTo(Boolean.parseBoolean(frozenState)); - assertThat(holding.amount).isEqualTo(amount); - } - - @When("I get the next page using {int} to lookup asset balances for {long} with {long}, {long}, {long}") - public void i_get_the_next_page_using_to_search_for_asset_balances_with(Integer indexer, Long assetId, Long currencyGT, Long currencyLT, Long limit) throws Exception { - String token = assetBalancesResponse.body().nextToken; - i_lookup_asset_balances_for_with_with(indexer, assetId, currencyGT, currencyLT, limit, token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long} and token {string}") - public void oldSearchForAccounts(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String token) throws Exception { - searchForAccounts(indexer, assetId, limit, gt, lt, "", 0L, "false", token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long}, {string}, {long} and token {string}") - public void oldSearchForAccounts2(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String authAddr, Long applicationId, String token) throws Exception { - searchForAccounts(indexer, assetId, limit, gt, lt, authAddr, applicationId, "false", token); - } - - @When("I use {int} to search for an account with {long}, {long}, {long}, {long}, {string}, {long}, {string} and token {string}") - public void searchForAccounts(Integer indexer, Long assetId, Long limit, Long gt, Long lt, String authAddr, Long applicationId, String includeAll, String token) throws Exception { - SearchForAccounts query = clients.indexerClients.get(indexer).searchForAccounts(); - - if (assetId != 0) query.assetId(assetId); - if (limit != 0) query.limit(limit); - if (gt != 0) query.currencyGreaterThan(gt); - if (lt != 0) query.currencyLessThan(lt); - if (StringUtils.isNotEmpty(token)) query.next(token); - if (StringUtils.isNotEmpty(authAddr)) query.authAddr(new Address(authAddr)); - if (applicationId != 0) query.applicationId(applicationId); - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - - accountsResponse = query.execute(); - response = accountsResponse; - } - - @Then("There are {int}, the first has {long}, {long}, {long}, {long}, {string}, {long}, {string}, {string}") - public void there_are_the_first_has(Integer numAccounts, Long pendingRewards, Long rewardsBase, Long rewards, Long amountWithoutPendingRewards, String address, Long amount, String status, String type) { - AccountsResponse response = accountsResponse.body(); - - assertThat(response.accounts).hasSize(numAccounts); - - Account account = response.accounts.get(0); - assertThat(account.address.toString()).isEqualTo(address); - assertThat(account.pendingRewards).isEqualTo(pendingRewards); - assertThat(account.rewardBase).isEqualTo(rewardsBase); - assertThat(account.rewards).isEqualTo(rewards); - assertThat(account.amountWithoutPendingRewards).isEqualTo(amountWithoutPendingRewards); - assertThat(account.amount).isEqualTo(amount); - assertThat(account.status).isEqualTo(status); - if (account.sigType != null) { - assertThat(account.sigType).isEqualTo(searchEnum(Enums.SigType.class, type)); - } - } - - @Then("The first account is online and has {string}, {long}, {long}, {long}, {string}, {string}") - public void the_first_account_is_online_and_has(String address, Long keyDilution, Long firstValid, Long lastValid, String voteKey, String selectionKey) { - Account account = accountsResponse.body().accounts.get(0); - assertThat(account.address.toString()).isEqualTo(address); - - AccountParticipation participation = account.participation; - assertThat(participation).isNotNull(); - assertThat(participation.voteKeyDilution).isEqualTo(keyDilution); - assertThat(participation.voteFirstValid).isEqualTo(firstValid); - assertThat(participation.voteLastValid).isEqualTo(lastValid); - assertThat(participation.voteParticipationKey).isEqualTo(Encoder.decodeFromBase64(voteKey)); - assertThat(participation.selectionParticipationKey).isEqualTo(Encoder.decodeFromBase64(selectionKey)); - } - - @Then("I get the next page using {int} to search for an account with {long}, {long}, {long} and {long}") - public void i_get_the_next_page_using_to_search_for_an_account_with_and(Integer indexer, Long assetId, Long limit, Long gt, Long lt) throws Exception { - AccountsResponse response = accountsResponse.body(); - searchForAccounts(indexer, assetId, limit, gt, lt, "", 0L, "false", response.nextToken); - } - - @When("I use {int} to search for transactions with {long}, {string}, {string}, {string}, {string}, {long}, {long}, {long}, {long}, {string}, {string}, {long}, {long}, {string}, {string}, {string} and token {string}") - public void oldSearchForTransactions(Integer indexer, Long limit, String notePrefix, - String txType, String sigType, String txId, Long round, - Long minRound, Long maxRound, Long assetId, - String beforeTime, String afterTime, Long currencyGT, - Long currencyLT, String address, String addressRole, - String excludeCloseTo, String token - ) throws Exception { - searchForTransactions(indexer, limit, notePrefix, txType, sigType, txId, round, minRound, maxRound, assetId, - beforeTime, afterTime, currencyGT, currencyLT, address, addressRole, excludeCloseTo, 0L, - token); - } - - @When("I use {int} to search for transactions with {long}, {string}, {string}, {string}, {string}, {long}, {long}, {long}, {long}, {string}, {string}, {long}, {long}, {string}, {string}, {string}, {long} and token {string}") - public void searchForTransactions(Integer indexer, Long limit, String notePrefix, - String txType, String sigType, String txId, Long round, - Long minRound, Long maxRound, Long assetId, - String beforeTime, String afterTime, Long currencyGT, - Long currencyLT, String address, String addressRole, - String excludeCloseTo, Long applicaitonId, String token - ) throws Exception { - SearchForTransactions query = clients.indexerClients.get(indexer).searchForTransactions(); - - if (limit != 0) query.limit(limit); - if (StringUtils.isNotEmpty(notePrefix)) query.notePrefix(Encoder.decodeFromBase64(notePrefix)); - if (StringUtils.isNotEmpty(txType)) query.txType(searchEnum(Enums.TxType.class, txType)); - if (StringUtils.isNotEmpty(sigType)) query.sigType(searchEnum(Enums.SigType.class, sigType)); - if (StringUtils.isNotEmpty(txId)) query.txid(txId); - if (round != 0) query.round(round); - if (minRound != 0) query.minRound(minRound); - if (maxRound != 0) query.maxRound(maxRound); - if (assetId != 0) query.assetId(assetId); - if (StringUtils.isNotEmpty(beforeTime)) query.beforeTime(Utils.parseDate(beforeTime)); - if (StringUtils.isNotEmpty(afterTime)) query.afterTime(Utils.parseDate(afterTime)); - if (currencyGT != 0) query.currencyGreaterThan(currencyGT); - if (currencyLT != 0) query.currencyLessThan(currencyLT); - if (StringUtils.isNotEmpty(address)) query.address(new Address(address)); - if (StringUtils.isNotEmpty(addressRole)) query.addressRole(Enums.AddressRole.valueOf(addressRole.toUpperCase())); - if (StringUtils.isNotEmpty(excludeCloseTo)) query.excludeCloseTo(Boolean.parseBoolean(excludeCloseTo)); - if (StringUtils.isNotEmpty(token)) query.next(token); - if (applicaitonId != 0) query.applicationId(applicaitonId); - - // This step is followed by multiple 'then' steps, so save the state in two places. - transactionsResponse = query.execute(); - response = transactionsResponse; - } - - @Then("there are {int} transactions in the response, the first is {string}.") - public void there_are_transactions_in_the_response_the_first_is(Integer num, String txid) throws NoSuchAlgorithmException { - TransactionsResponse transactions = transactionsResponse.body(); - - assertThat(transactions.transactions).hasSize(num); - - // Don't check the txid if there weren't supposed to be any results. - if (num == 0) return; - - assertThat(transactions.transactions) - .first() - .hasFieldOrPropertyWithValue("id", txid); - } - - @And("Every transaction has tx-type {string}") - public void every_transaction_has_txtype(String type) { - TransactionsResponse transactions = transactionsResponse.body(); - - transactions.transactions.forEach(tx -> { - assertThat(tx.txType).isEqualTo(Enums.TxType.valueOf(type.toUpperCase())); - }); - } - - @And("Every transaction has sig-type {string}") - public void every_transaction_has_sigtype(String type) { - TransactionsResponse transactions = transactionsResponse.body(); - - transactions.transactions.forEach(tx -> { - switch (type) { - case "sig": - assertThat(tx.signature.sig).isNotEmpty(); - break; - case "msig": - assertThat(tx.signature.multisig).isNotNull(); - break; - case "lsig": - assertThat(tx.signature.logicsig).isNotNull(); - break; - } - ; - } - ); - } - - @And("Every transaction has round {long}") - public void every_transaction_has_round(Long round) { - TransactionsResponse transactions = transactionsResponse.body(); - - // Nothing to check for these results. - if (transactions.transactions.size() == 0) return; - - assertThat(transactions.transactions) - .extracting("confirmedRound") - .containsOnly(round); - } - - @And("Every transaction has round >= {long}") - public void every_transaction_has_round_greater_than(Long minRound) { - transactionsResponse.body().transactions.forEach(tx -> assertThat(tx.confirmedRound).isGreaterThanOrEqualTo(minRound)); - } - - @And("Every transaction has round <= {long}") - public void every_transaction_has_round_less_than(Long maxRound) { - transactionsResponse.body().transactions.forEach(tx -> assertThat(tx.confirmedRound).isLessThanOrEqualTo(maxRound)); - } - - @And("Every transaction works with asset-id {long}") - public void every_transaction_works_with_asset_id(Long assetId) { - transactionsResponse.body().transactions.forEach(tx -> { - if (tx.createdAssetIndex != null) { - assertThat(tx.createdAssetIndex).isEqualTo(assetId); - } - if (tx.assetTransferTransaction != null) { - assertThat(tx.assetTransferTransaction.assetId).isEqualTo(assetId); - } - }); - } - - @And("Every transaction is newer than {string}") - public void every_transaction_is_newer_than(String dateString) { - Instant i = Instant.parse(dateString); - transactionsResponse.body().transactions.forEach(tx -> { - assertThat(tx.roundTime).isGreaterThan(i.getEpochSecond()); - }); - } - - @And("Every transaction is older than {string}") - public void every_transaction_is_older_than(String dateString) { - Instant i = Instant.parse(dateString); - transactionsResponse.body().transactions.forEach(tx -> { - assertThat(tx.roundTime).isLessThan(i.getEpochSecond()); - }); - } - - @And("Every transaction moves between {biginteger} and {biginteger} currency") - public void every_transaction_moves_between_and_currency(BigInteger min, BigInteger max) { - // Normalize in case max isn't set. - BigInteger maxUnsignedLong = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf(2)); - final BigInteger normalizedMax = (max.longValue() == 0) ? maxUnsignedLong : max; - - transactionsResponse.body().transactions.forEach(tx -> { - switch (tx.txType) { - case PAY: - assertThat(BigInteger.valueOf(tx.paymentTransaction.amount)).isBetween(min, normalizedMax); - break; - case AXFER: - assertThat(tx.assetTransferTransaction.amount).isBetween(min, normalizedMax); - break; - default: - Assertions.fail("Only transactions that move currency should match this."); - } - }); - } - - @When("I use {int} to search for all {string} transactions") - public void i_use_to_search_for_all_transactions(Integer indexer, String account) throws Exception { - LookupAccountTransactions query = clients.indexerClients.get(indexer).lookupAccountTransactions(new Address(account)); - transactionsResponse = query.execute(); - } - - @When("I use {int} to search for all {long} asset transactions") - public void i_use_to_search_for_all_asset_transactions(Integer indexer, Long assetId) throws Exception { - LookupAssetTransactions query = clients.indexerClients.get(indexer).lookupAssetTransactions(assetId); - transactionsResponse = query.execute(); - } - - @And("I get the next page using {int} to search for transactions with {long} and {long}") - public void i_get_the_next_page_using_to_search_for_transactions_with_and(Integer indexer, Long limit, Long maxRound) throws Exception { - String next = transactionsResponse.body().nextToken; - - // Reuse the all-args wrapper, injecting the next token - searchForTransactions(indexer, limit, "", "", "", "", - 0L, 0L, maxRound, 0L, "", "", 0L, 0L, - "", "", "", 0L, next); - } - - @When("I use {int} to search for assets with {long}, {long}, {string}, {string}, {string}, and token {string}") - public void i_use_to_search_for_assets_with_and_token(Integer indexer, Long limit, Long assetId, String creator, String name, String unit, String token) throws Exception { - SearchForAssets query = clients.indexerClients.get(indexer).searchForAssets(); - - if (limit != 0) { - query.limit(limit); - } - if (assetId != 0) { - query.assetId(assetId); - } - if (StringUtils.isNotEmpty(creator)) { - query.creator(creator); - } - if (StringUtils.isNotEmpty(name)) { - query.name(name); - } - if (StringUtils.isNotEmpty(unit)) { - query.unit(unit); - } - if (StringUtils.isNotEmpty(token)) { - query.next(token); - } - assetsResponse = query.execute(); - } - - @Then("there are {int} assets in the response, the first is {long}.") - public void there_are_assets_in_the_response_the_first_is(Integer num, Long assetId) { - AssetsResponse response = assetsResponse.body(); - assertThat(response.assets).hasSize(num); - response.assets.forEach(a -> assertThat(a.index).isEqualTo(assetId)); - } - - - @When("I use {int} to search for applications with {long}, {long}, and token {string}") - public void i_use_to_search_for_applications_with_and_token_deprecated(Integer indexer, Long limit, Long applicationId, String token) throws Exception { - i_use_to_search_for_applications_with_and_token(indexer, limit, applicationId, "false", token); - } - - @When("I use {int} to search for applications with {long}, {long}, {string} and token {string}") - public void i_use_to_search_for_applications_with_and_token(Integer indexer, Long limit, Long applicationId, String includeAll, String token) throws Exception { - SearchForApplications query = clients.indexerClients.get(indexer).searchForApplications(); - - if (limit != 0) query.limit(limit); - if (applicationId != 0) query.applicationId(applicationId); - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - if (StringUtils.isNotEmpty(token)) query.next(token); - - response = query.execute(); - } - - @When("I use {int} to lookup application with {long}") - public void i_use_to_lookup_application_with(Integer indexer, Long applicationId) throws Exception { - i_use_to_lookup_application_with_and(indexer, applicationId, "false"); - } - - @When("I use {int} to lookup application with {long} and {string}") - public void i_use_to_lookup_application_with_and(Integer indexer, Long applicationId, String includeAll) throws Exception { - LookupApplicationByID query = clients.indexerClients.get(indexer).lookupApplicationByID(applicationId); - - if (Boolean.parseBoolean(includeAll)) query.includeAll(Boolean.parseBoolean(includeAll)); - - response = query.execute(); - } - - @Then("the parsed response should equal {string}.") - public void the_parsed_response_should_equal(String jsonFile) throws IOException { - File f = new File("src/test/resources/" + jsonFile); - assertThat(f).canRead(); - String json = response.toString(); - TestingUtils.verifyResponse(response, f); - } - -} diff --git a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java index 278878e03..2b4a30399 100644 --- a/src/test/java/com/algorand/algosdk/integration/Stepdefs.java +++ b/src/test/java/com/algorand/algosdk/integration/Stepdefs.java @@ -29,14 +29,11 @@ import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; -import org.threeten.bp.LocalDate; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; @@ -348,65 +345,12 @@ public void transactionParameters(int fee, int fv, int lv, String gh, String to, } } - @Given("key registration transaction parameters {int} {int} {int} {string} {string} {string} {int} {int} {int} {string} {string}") - public void keyregTxnParameters(int fee, int fv, int lv, String gh, String votepk, String vrfpk, int votefst, int votelst, int votekd, String gen, String note) throws GeneralSecurityException, NoSuchAlgorithmException{ - this.fee = BigInteger.valueOf(fee); - this.fv = BigInteger.valueOf(fv); - this.lv = BigInteger.valueOf(lv); - this.gh = new Digest(Encoder.decodeFromBase64(gh)); - this.votepk = new ParticipationPublicKey(Encoder.decodeFromBase64(votepk)); - this.vrfpk = new VRFPublicKey(Encoder.decodeFromBase64(vrfpk)); - this.votefst = BigInteger.valueOf(votefst); - this.votelst = BigInteger.valueOf(votelst); - this.votekd = BigInteger.valueOf(votekd); - if (!gen.equals("none")) { - this.gen = gen; - } - if (!note.equals("none")) { - this.note = Encoder.decodeFromBase64(note); - } - } - @Given("mnemonic for private key {string}") public void mn_for_sk(String mn) throws GeneralSecurityException{ account = new Account(mn); pk = account.getAddress(); } - @When("I create the payment transaction") - public void createPaytxn() throws NoSuchAlgorithmException, JsonProcessingException, IOException{ - txn = Transaction.PaymentTransactionBuilder() - .sender(pk) - .fee(fee) - .firstValid(fv) - .lastValid(lv) - .note(note) - .genesisID(gen) - .genesisHash(gh) - .amount(amt) - .receiver(to) - .closeRemainderTo(close) - .build(); - } - - @When("I create the key registration transaction") - public void createKeyregTxn() throws NoSuchAlgorithmException, JsonProcessingException, IOException{ - txn = Transaction.KeyRegistrationTransactionBuilder() - .sender(pk) - .fee(fee) - .firstValid(fv) - .lastValid(lv) - .note(note) - .genesisID(gen) - .genesisHash(gh) - .participationPublicKey(votepk) - .selectionPublicKey(vrfpk) - .voteFirst(votefst) - .voteLast(votelst) - .voteKeyDilution(votekd) - .build(); - } - @Given("default V2 key registration transaction {string}") public void default_v2_key_registration_transaction(String type) throws NoSuchAlgorithmException, JsonProcessingException, IOException{ getParams(); @@ -824,6 +768,7 @@ public void sendMsigTxn() throws JsonProcessingException, ApiException{ } } + // TODO: this needs to be modified/removed when v1 is no longer supported!!! @Then("the transaction should go through") public void checkTxn() throws Exception { String ans = acl.pendingTransactionInformation(txid).getFrom(); @@ -831,7 +776,7 @@ public void checkTxn() throws Exception { waitForAlgodTransactionProcessingToComplete(); String senderFromResponse = acl.transactionInformation(txn.sender.toString(), txid).getFrom(); assertThat(senderFromResponse).isEqualTo(txn.sender.toString()); - assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); + // assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse); } /** @@ -894,74 +839,6 @@ public void signMsigBothEqual() throws JsonProcessingException, com.algorand.alg kcl.deleteMultisig(req); } - @When("I read a transaction {string} from file {string}") - public void readTxn(String encodedTxn, String num) throws IOException { - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - this.num = num; - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - stx = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - } - - @When("I write the transaction to file") - public void writeTxn() throws JsonProcessingException, IOException{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - byte[] data = Encoder.encodeToMsgPack(stx); - FileOutputStream out = new FileOutputStream(path); - out.write(data); - out.close(); - } - - @Then("the transaction should still be the same") - public void checkEnc() throws IOException{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/raw" + this.num + ".tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - SignedTransaction stxnew = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - - path = p.getParent() + "/temp/old" + this.num + ".tx"; - inputStream = new FileInputStream(path); - file = new File(path); - data = new byte[(int) file.length()]; - inputStream.read(data); - SignedTransaction stxold = Encoder.decodeFromMsgPack(data, SignedTransaction.class); - inputStream.close(); - assertThat(stxnew).isEqualTo(stxold); - } - - @Then("I do my part") - public void signSaveTxn() throws IOException, JsonProcessingException, NoSuchAlgorithmException, com.algorand.algosdk.kmd.client.ApiException, Exception{ - String path = System.getProperty("user.dir"); - Path p = Paths.get(path); - path = p.getParent() + "/temp/txn.tx"; - FileInputStream inputStream = new FileInputStream(path); - File file = new File(path); - byte[] data = new byte[(int) file.length()]; - inputStream.read(data); - inputStream.close(); - - txn = Encoder.decodeFromMsgPack(data, Transaction.class); - exportKeyAndSetAccount(txn.sender); - - stx = account.signTransaction(txn); - data = Encoder.encodeToMsgPack(stx); - FileOutputStream out = new FileOutputStream(path); - out.write(data); - out.close(); - } - @Then("the node should be healthy") public void nodeHealth() throws ApiException{ acl.healthCheck(); @@ -979,20 +856,6 @@ public void txnsByAddrRound() throws ApiException{ //Assert.assertTrue(acl.transactions(addresses.get(0), BigInteger.valueOf(1), acl.getStatus().getLastRound(), null, null, BigInteger.valueOf(10)).getTransactions() instanceof List); } - @Then("I get transactions by address only") - public void txnsByAddrOnly() throws ApiException{ - assertThat(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(10)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(10)).getTransactions() instanceof List); - } - - @Then("I get transactions by address and date") - public void txnsByAddrDate() throws ApiException{ - assertThat(acl.transactions(addresses.get(0), null, null, LocalDate.now(), LocalDate.now(), BigInteger.valueOf(10)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, LocalDate.now(), LocalDate.now(), BigInteger.valueOf(10)).getTransactions() instanceof List); - } - @Then("I get pending transactions") public void pendingTxns() throws ApiException{ assertThat(acl.getPendingTransactions(BigInteger.valueOf(10)).getTruncatedTxns()) @@ -1147,13 +1010,6 @@ public void newAccInfo() throws ApiException, NoSuchAlgorithmException, com.algo kcl.deleteKey(req); } - @When("I get recent transactions, limited by {int} transactions") - public void i_get_recent_transactions_limited_by_count(int cnt) throws ApiException { - assertThat(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(cnt)).getTransactions()) - .isInstanceOf(List.class); - //Assert.assertTrue(acl.transactions(addresses.get(0), null, null, null, null, BigInteger.valueOf(cnt)).getTransactions() instanceof List); - } - @Given("asset test fixture") public void asset_test_fixture() { // Implemented by the construction of Stepdefs; diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index 679ec3a33..f3f92e423 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -1,13 +1,14 @@ package com.algorand.algosdk.unit; import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.unit.utils.QueryMapper; import com.algorand.algosdk.unit.utils.TestingUtils; import com.algorand.algosdk.v2.client.algod.AccountInformation; import com.algorand.algosdk.v2.client.algod.GetApplicationBoxes; import com.algorand.algosdk.v2.client.algod.GetPendingTransactions; import com.algorand.algosdk.v2.client.algod.GetPendingTransactionsByAddress; +import com.algorand.algosdk.v2.client.algod.GetTransactionProof; import com.algorand.algosdk.v2.client.common.AlgodClient; +import com.algorand.algosdk.v2.client.model.Enums; import io.cucumber.java.en.When; import java.security.NoSuchAlgorithmException; @@ -81,7 +82,7 @@ public void accountAssetInformation(String string, Integer int1) throws NoSuchAl @When("we make an Account Information call against account {string} with exclude {string}") public void accountInformation(String string, String string2) throws NoSuchAlgorithmException { AccountInformation aiq = algodClient.AccountInformation(new Address(string)); - if (TestingUtils.notEmpty(string2)) aiq.exclude(QueryMapper.getExclude(string2)); + if (TestingUtils.notEmpty(string2)) aiq.exclude(Enums.Exclude.forValue(string2)); ps.q = aiq; } @@ -98,4 +99,21 @@ public void getBoxes(Long appId, Long max) { ps.q = q; } + + @When("we make a GetTransactionProof call for round {long} txid {string} and hashtype {string}") + public void getTransactionProof(Long round, String txid, String hashType) { + GetTransactionProof gtp = algodClient.GetTransactionProof(round, txid); + if (TestingUtils.notEmpty(hashType)) gtp.hashtype(Enums.Hashtype.forValue(hashType)); + ps.q = gtp; + } + + @When("we make a GetLightBlockHeaderProof call for round {long}") + public void getLightBlockHeaderProof(Long round) { + ps.q = algodClient.GetLightBlockHeaderProof(round); + } + + @When("we make a GetStateProof call for round {long}") + public void getStateProof(Long round) { + ps.q = algodClient.GetStateProof(round); + } } diff --git a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java index bdcec1fd9..8c9cb43e8 100644 --- a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java @@ -1,7 +1,6 @@ package com.algorand.algosdk.unit; import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.unit.utils.QueryMapper; import com.algorand.algosdk.unit.utils.TestingUtils; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.v2.client.common.IndexerClient; @@ -155,8 +154,8 @@ public void lookupAssetTransactions(Long assetId, String notePrefix, String txTy LookupAssetTransactions q = this.indexerClient.lookupAssetTransactions(assetId); if (TestingUtils.notEmpty(address)) q.address(new Address(address)); if (TestingUtils.notEmpty(notePrefix)) q.notePrefix(Encoder.decodeFromBase64(notePrefix)); - if (TestingUtils.notEmpty(txType)) q.txType(QueryMapper.getTxType(txType)); - if (TestingUtils.notEmpty(sigType)) q.sigType(QueryMapper.getSigType(sigType)); + if (TestingUtils.notEmpty(txType)) q.txType(Enums.TxType.forValue(txType)); + if (TestingUtils.notEmpty(sigType)) q.sigType(Enums.SigType.forValue(sigType)); if (TestingUtils.notEmpty(txid)) q.txid(txid); if (TestingUtils.notEmpty(round)) q.round(round); if (TestingUtils.notEmpty(minRound)) q.minRound(minRound); @@ -166,7 +165,7 @@ public void lookupAssetTransactions(Long assetId, String notePrefix, String txTy if (TestingUtils.notEmpty(afterTime)) q.afterTime(Utils.parseDate(afterTime)); if (TestingUtils.notEmpty(currencyGT)) q.currencyGreaterThan(currencyGT); if (TestingUtils.notEmpty(currencyLT)) q.currencyLessThan(currencyLT); - if (TestingUtils.notEmpty(addressRole)) q.addressRole(QueryMapper.getAddressRole(addressRole)); + if (TestingUtils.notEmpty(addressRole)) q.addressRole(Enums.AddressRole.forValue(addressRole)); if (TestingUtils.notEmpty(excludeCloseTo)) q.excludeCloseTo(excludeCloseTo.equals("true")); if (TestingUtils.notEmpty(rekeyTo)) q.rekeyTo(rekeyTo.equals("true")); ps.q = q; @@ -176,8 +175,8 @@ public void lookupAssetTransactions(Long assetId, String notePrefix, String txTy public void lookupAccountTransactions(String account, String notePrefix, String txType, String sigType, String txid, Long round, Long minRound, Long maxRound, Long limit, String beforeTime, String afterTime, Long currencyGT, Long currencyLT, Long assetId, String rekeyTo) throws NoSuchAlgorithmException, ParseException { LookupAccountTransactions q = this.indexerClient.lookupAccountTransactions(new Address(account)); if (TestingUtils.notEmpty(notePrefix)) q.notePrefix(Encoder.decodeFromBase64(notePrefix)); - if (TestingUtils.notEmpty(txType)) q.txType(QueryMapper.getTxType(txType)); - if (TestingUtils.notEmpty(sigType)) q.sigType(QueryMapper.getSigType(sigType)); + if (TestingUtils.notEmpty(txType)) q.txType(Enums.TxType.forValue(txType)); + if (TestingUtils.notEmpty(sigType)) q.sigType(Enums.SigType.forValue(sigType)); if (TestingUtils.notEmpty(txid)) q.txid(txid); if (TestingUtils.notEmpty(round)) q.round(round); if (TestingUtils.notEmpty(minRound)) q.minRound(minRound); @@ -213,8 +212,8 @@ public void searchForTransactions(String address, String notePrefix, String txTy SearchForTransactions q = this.indexerClient.searchForTransactions(); if (TestingUtils.notEmpty(address)) q.address(new Address(address)); if (TestingUtils.notEmpty(notePrefix)) q.notePrefix(Encoder.decodeFromBase64(notePrefix)); - if (TestingUtils.notEmpty(txType)) q.txType(QueryMapper.getTxType(txType)); - if (TestingUtils.notEmpty(sigType)) q.sigType(QueryMapper.getSigType(sigType)); + if (TestingUtils.notEmpty(txType)) q.txType(Enums.TxType.forValue(txType)); + if (TestingUtils.notEmpty(sigType)) q.sigType(Enums.SigType.forValue(sigType)); if (TestingUtils.notEmpty(txid)) q.txid(txid); if (TestingUtils.notEmpty(round)) q.round(round); if (TestingUtils.notEmpty(minRound)) q.minRound(minRound); @@ -225,7 +224,7 @@ public void searchForTransactions(String address, String notePrefix, String txTy if (TestingUtils.notEmpty(currencyGT)) q.currencyGreaterThan(currencyGT); if (TestingUtils.notEmpty(currencyLT)) q.currencyLessThan(currencyLT); if (TestingUtils.notEmpty(assetID)) q.assetId(assetID); - if (TestingUtils.notEmpty(addressRole)) q.addressRole(QueryMapper.getAddressRole(addressRole)); + if (TestingUtils.notEmpty(addressRole)) q.addressRole(Enums.AddressRole.forValue(addressRole)); if (TestingUtils.notEmpty(excludeCloseTo)) q.excludeCloseTo(excludeCloseTo.equals("true")); if (TestingUtils.notEmpty(rekeyTo)) q.rekeyTo(rekeyTo.equals("true")); ps.q = q; @@ -285,7 +284,7 @@ public void lookupAccountByID(String string, String string2) throws NoSuchAlgori if (TestingUtils.notEmpty(string2)) { ArrayList excludes = new ArrayList(); for (String excld : string2.split(",")) { - excludes.add(QueryMapper.getExclude(excld)); + excludes.add(Enums.Exclude.forValue(excld)); } q.exclude(excludes); } @@ -320,7 +319,7 @@ public void searchForAccounts(String string) { if (TestingUtils.notEmpty(string)) { ArrayList excludes = new ArrayList(); for (String excld : string.split(",")) { - excludes.add(QueryMapper.getExclude(excld)); + excludes.add(Enums.Exclude.forValue(excld)); } q.exclude(excludes); } diff --git a/src/test/java/com/algorand/algosdk/unit/ProgramSanityCheck.java b/src/test/java/com/algorand/algosdk/unit/ProgramSanityCheck.java new file mode 100644 index 000000000..6e9ffde03 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/unit/ProgramSanityCheck.java @@ -0,0 +1,37 @@ +package com.algorand.algosdk.unit; + +import com.algorand.algosdk.crypto.LogicsigSignature; +import com.algorand.algosdk.util.Encoder; + +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProgramSanityCheck { + byte[] seeminglyProgram; + String actualErrMsg; + + @Given("a base64 encoded program bytes for heuristic sanity check {string}") + public void takeB64encodedBytes(String b64encodedBytes) { + seeminglyProgram = Encoder.decodeFromBase64(b64encodedBytes); + } + + @When("I start heuristic sanity check over the bytes") + public void heuristicCheckOverBytes() { + try { + new LogicsigSignature(seeminglyProgram); + } catch (Exception e) { + actualErrMsg = e.getMessage(); + } + } + + @Then("if the heuristic sanity check throws an error, the error contains {string}") + public void checkErrorIfMatching(String errMsg) { + if (errMsg != null && !errMsg.isEmpty()) + assertThat(actualErrMsg).contains(errMsg); + else + assertThat(actualErrMsg).isNullOrEmpty(); + } +} diff --git a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java index 65889dfe4..ae4f37c96 100644 --- a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java +++ b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java @@ -10,6 +10,7 @@ import com.algorand.algosdk.v2.client.common.Response; import com.algorand.algosdk.v2.client.model.DryrunResponse; import com.algorand.algosdk.v2.client.model.DryrunTxnResult; +import com.algorand.algosdk.v2.client.model.StateProof; import com.google.common.io.Files; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; @@ -71,6 +72,9 @@ public void we_make_any_call_to(String client, String endpoint) throws Exception switch(client) { case "indexer": { switch(endpoint) { + case "lookupBlock": + response = indexer.lookupBlock(1234L).execute(); + break; case "lookupAccountByID": response = indexer.lookupAccountByID(new Address()).execute(); break; @@ -165,7 +169,8 @@ public void we_make_any_call_to(String client, String endpoint) throws Exception response = algod.TealDryrun().execute(); break; case "Proof": - response = algod.GetProof(0L, "").execute(); + case "GetTransactionProof": + response = algod.GetTransactionProof(0L, "").execute(); break; case "AccountInformation": response = algod.AccountInformation(new Address()).execute(); @@ -176,6 +181,16 @@ public void we_make_any_call_to(String client, String endpoint) throws Exception case "AccountAssetInformation": response = algod.AccountAssetInformation(new Address(), 0L).execute(); break; + case "GetBlock": + response = algod.GetBlock(1234L).execute(); + break; + case "GetLightBlockHeaderProof": + response = algod.GetLightBlockHeaderProof(1234L).execute(); + break; + case "GetStateProof": + Response r = algod.GetStateProof(1234L).execute(); + response = r; + break; default: Assertions.fail("Unsupported algod endpoint: " + endpoint); } diff --git a/src/test/java/com/algorand/algosdk/unit/RunCucumberUnitTest.java b/src/test/java/com/algorand/algosdk/unit/RunCucumberUnitTest.java index cd92dad4d..e287616ec 100644 --- a/src/test/java/com/algorand/algosdk/unit/RunCucumberUnitTest.java +++ b/src/test/java/com/algorand/algosdk/unit/RunCucumberUnitTest.java @@ -5,6 +5,6 @@ import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(publish = false, plugin = {"progress"}, tags = "@disabled.by.default", extraGlue = "com.algorand.algosdk.cucumber.shared") +@CucumberOptions(publish = true, plugin = {"progress"}, tags = "@disabled.by.default", extraGlue = "com.algorand.algosdk.cucumber.shared") public class RunCucumberUnitTest { } diff --git a/src/test/java/com/algorand/algosdk/unit/utils/QueryMapper.java b/src/test/java/com/algorand/algosdk/unit/utils/QueryMapper.java deleted file mode 100644 index 15f18b1a1..000000000 --- a/src/test/java/com/algorand/algosdk/unit/utils/QueryMapper.java +++ /dev/null @@ -1,689 +0,0 @@ -/* - * THIS FILE IS GENERATED BY QueryMapperGenerator!!!!!!!!!!!!!!!! - */ -package com.algorand.algosdk.unit.utils; - -import java.security.NoSuchAlgorithmException; -import com.fasterxml.jackson.core.JsonProcessingException; -import java.text.ParseException; -import java.util.ArrayList; - -import com.algorand.algosdk.v2.client.common.Utils; - -import com.algorand.algosdk.crypto.Address; -import com.algorand.algosdk.util.Encoder; -import com.algorand.algosdk.v2.client.algod.*; -import com.algorand.algosdk.v2.client.indexer.*; -import com.algorand.algosdk.v2.client.model.Enums; -import com.algorand.algosdk.v2.client.common.*; - -public class QueryMapper { - - public static Query getClass(String name, IndexerClient client, String args[]) throws NoSuchAlgorithmException { - switch (name) { - case "makeHealthCheck": - return client.makeHealthCheck(); - case "searchForAccounts": - return client.searchForAccounts(); - case "lookupAccountByID": - return client.lookupAccountByID(new Address(args[0])); - case "lookupAccountAssets": - return client.lookupAccountAssets(new Address(args[0])); - case "lookupAccountCreatedAssets": - return client.lookupAccountCreatedAssets(new Address(args[0])); - case "lookupAccountAppLocalStates": - return client.lookupAccountAppLocalStates(new Address(args[0])); - case "lookupAccountCreatedApplications": - return client.lookupAccountCreatedApplications(new Address(args[0])); - case "lookupAccountTransactions": - return client.lookupAccountTransactions(new Address(args[0])); - case "searchForApplications": - return client.searchForApplications(); - case "lookupApplicationByID": - return client.lookupApplicationByID(Long.valueOf(args[0])); - case "lookupApplicationLogsByID": - return client.lookupApplicationLogsByID(Long.valueOf(args[0])); - case "searchForAssets": - return client.searchForAssets(); - case "lookupAssetByID": - return client.lookupAssetByID(Long.valueOf(args[0])); - case "lookupAssetBalances": - return client.lookupAssetBalances(Long.valueOf(args[0])); - case "lookupAssetTransactions": - return client.lookupAssetTransactions(Long.valueOf(args[0])); - case "lookupBlock": - return client.lookupBlock(Long.valueOf(args[0])); - case "lookupTransaction": - return client.lookupTransaction(args[0]); - case "searchForTransactions": - return client.searchForTransactions(); - } - return null; - } - - public static Query getClass(String name, AlgodClient client, String args[]) throws NoSuchAlgorithmException { - switch (name) { - case "HealthCheck": - return client.HealthCheck(); - case "Metrics": - return client.Metrics(); - case "GetGenesis": - return client.GetGenesis(); - case "SwaggerJSON": - return client.SwaggerJSON(); - case "GetVersion": - return client.GetVersion(); - case "AccountInformation": - return client.AccountInformation(new Address(args[0])); - case "AccountAssetInformation": - return client.AccountAssetInformation(new Address(args[0]), Long.valueOf(args[1])); - case "AccountApplicationInformation": - return client.AccountApplicationInformation(new Address(args[0]), Long.valueOf(args[1])); - case "GetPendingTransactionsByAddress": - return client.GetPendingTransactionsByAddress(new Address(args[0])); - case "GetBlock": - return client.GetBlock(Long.valueOf(args[0])); - case "GetProof": - return client.GetProof(Long.valueOf(args[0]), args[1]); - case "GetSupply": - return client.GetSupply(); - case "GetStatus": - return client.GetStatus(); - case "WaitForBlock": - return client.WaitForBlock(Long.valueOf(args[0])); - case "RawTransaction": - return client.RawTransaction(); - case "TransactionParams": - return client.TransactionParams(); - case "GetPendingTransactions": - return client.GetPendingTransactions(); - case "PendingTransactionInformation": - return client.PendingTransactionInformation(args[0]); - case "GetApplicationByID": - return client.GetApplicationByID(Long.valueOf(args[0])); - case "GetAssetByID": - return client.GetAssetByID(Long.valueOf(args[0])); - case "TealCompile": - return client.TealCompile(); - case "TealDryrun": - return client.TealDryrun(); - } - return null; - } - - public static void setValue(Query q, String className, String property, String value) throws ParseException, NoSuchAlgorithmException, JsonProcessingException { - switch (className) { - case "makeHealthCheck": - switch (property) { - } - break; - case "searchForAccounts": - switch (property) { - case "application-id": - ((SearchForAccounts)q).applicationId(Long.valueOf(value)); - break; - case "asset-id": - ((SearchForAccounts)q).assetId(Long.valueOf(value)); - break; - case "auth-addr": - ((SearchForAccounts)q).authAddr(new Address(value)); - break; - case "currency-greater-than": - ((SearchForAccounts)q).currencyGreaterThan(Long.valueOf(value)); - break; - case "currency-less-than": - ((SearchForAccounts)q).currencyLessThan(Long.valueOf(value)); - break; - case "exclude": - ArrayList vals = new ArrayList(); - for (String t : value.split(",")) { - vals.add(getExclude(t)); - } - ((SearchForAccounts)q).exclude(vals); - break; - case "include-all": - ((SearchForAccounts)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((SearchForAccounts)q).limit(Long.valueOf(value)); - break; - case "next": - ((SearchForAccounts)q).next(value); - break; - case "round": - ((SearchForAccounts)q).round(Long.valueOf(value)); - break; - } - break; - case "lookupAccountByID": - switch (property) { - case "exclude": - ArrayList vals = new ArrayList(); - for (String t : value.split(",")) { - vals.add(getExclude(t)); - } - ((SearchForAccounts)q).exclude(vals); - break; - case "include-all": - ((LookupAccountByID)q).includeAll(Boolean.valueOf(value)); - break; - case "round": - ((LookupAccountByID)q).round(Long.valueOf(value)); - break; - } - break; - case "lookupAccountAssets": - switch (property) { - case "asset-id": - ((LookupAccountAssets)q).assetId(Long.valueOf(value)); - break; - case "include-all": - ((LookupAccountAssets)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAccountAssets)q).limit(Long.valueOf(value)); - break; - case "next": - ((LookupAccountAssets)q).next(value); - break; - } - break; - case "lookupAccountCreatedAssets": - switch (property) { - case "asset-id": - ((LookupAccountCreatedAssets)q).assetId(Long.valueOf(value)); - break; - case "include-all": - ((LookupAccountCreatedAssets)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAccountCreatedAssets)q).limit(Long.valueOf(value)); - break; - case "next": - ((LookupAccountCreatedAssets)q).next(value); - break; - } - break; - case "lookupAccountAppLocalStates": - switch (property) { - case "application-id": - ((LookupAccountAppLocalStates)q).applicationId(Long.valueOf(value)); - break; - case "include-all": - ((LookupAccountAppLocalStates)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAccountAppLocalStates)q).limit(Long.valueOf(value)); - break; - case "next": - ((LookupAccountAppLocalStates)q).next(value); - break; - } - break; - case "lookupAccountCreatedApplications": - switch (property) { - case "application-id": - ((LookupAccountCreatedApplications)q).applicationId(Long.valueOf(value)); - break; - case "include-all": - ((LookupAccountCreatedApplications)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAccountCreatedApplications)q).limit(Long.valueOf(value)); - break; - case "next": - ((LookupAccountCreatedApplications)q).next(value); - break; - } - break; - case "lookupAccountTransactions": - switch (property) { - case "after-time": - ((LookupAccountTransactions)q).afterTime(Utils.parseDate(value)); - break; - case "asset-id": - ((LookupAccountTransactions)q).assetId(Long.valueOf(value)); - break; - case "before-time": - ((LookupAccountTransactions)q).beforeTime(Utils.parseDate(value)); - break; - case "currency-greater-than": - ((LookupAccountTransactions)q).currencyGreaterThan(Long.valueOf(value)); - break; - case "currency-less-than": - ((LookupAccountTransactions)q).currencyLessThan(Long.valueOf(value)); - break; - case "limit": - ((LookupAccountTransactions)q).limit(Long.valueOf(value)); - break; - case "max-round": - ((LookupAccountTransactions)q).maxRound(Long.valueOf(value)); - break; - case "min-round": - ((LookupAccountTransactions)q).minRound(Long.valueOf(value)); - break; - case "next": - ((LookupAccountTransactions)q).next(value); - break; - case "note-prefix": - ((LookupAccountTransactions)q).notePrefix(Encoder.decodeFromBase64(value)); - break; - case "rekey-to": - ((LookupAccountTransactions)q).rekeyTo(Boolean.valueOf(value)); - break; - case "round": - ((LookupAccountTransactions)q).round(Long.valueOf(value)); - break; - case "sig-type": - ((LookupAccountTransactions)q).sigType(getSigType(value)); - break; - case "tx-type": - ((LookupAccountTransactions)q).txType(getTxType(value)); - break; - case "txid": - ((LookupAccountTransactions)q).txid(value); - break; - } - break; - case "searchForApplications": - switch (property) { - case "application-id": - ((SearchForApplications)q).applicationId(Long.valueOf(value)); - break; - case "creator": - ((SearchForApplications)q).creator(value); - break; - case "include-all": - ((SearchForApplications)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((SearchForApplications)q).limit(Long.valueOf(value)); - break; - case "next": - ((SearchForApplications)q).next(value); - break; - } - break; - case "lookupApplicationByID": - switch (property) { - case "include-all": - ((LookupApplicationByID)q).includeAll(Boolean.valueOf(value)); - break; - } - break; - case "lookupApplicationLogsByID": - switch (property) { - case "limit": - ((LookupApplicationLogsByID)q).limit(Long.valueOf(value)); - break; - case "max-round": - ((LookupApplicationLogsByID)q).maxRound(Long.valueOf(value)); - break; - case "min-round": - ((LookupApplicationLogsByID)q).minRound(Long.valueOf(value)); - break; - case "next": - ((LookupApplicationLogsByID)q).next(value); - break; - case "sender-address": - ((LookupApplicationLogsByID)q).senderAddress(new Address(value)); - break; - case "txid": - ((LookupApplicationLogsByID)q).txid(value); - break; - } - break; - case "searchForAssets": - switch (property) { - case "asset-id": - ((SearchForAssets)q).assetId(Long.valueOf(value)); - break; - case "creator": - ((SearchForAssets)q).creator(value); - break; - case "include-all": - ((SearchForAssets)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((SearchForAssets)q).limit(Long.valueOf(value)); - break; - case "name": - ((SearchForAssets)q).name(value); - break; - case "next": - ((SearchForAssets)q).next(value); - break; - case "unit": - ((SearchForAssets)q).unit(value); - break; - } - break; - case "lookupAssetByID": - switch (property) { - case "include-all": - ((LookupAssetByID)q).includeAll(Boolean.valueOf(value)); - break; - } - break; - case "lookupAssetBalances": - switch (property) { - case "currency-greater-than": - ((LookupAssetBalances)q).currencyGreaterThan(Long.valueOf(value)); - break; - case "currency-less-than": - ((LookupAssetBalances)q).currencyLessThan(Long.valueOf(value)); - break; - case "include-all": - ((LookupAssetBalances)q).includeAll(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAssetBalances)q).limit(Long.valueOf(value)); - break; - case "next": - ((LookupAssetBalances)q).next(value); - break; - } - break; - case "lookupAssetTransactions": - switch (property) { - case "address": - ((LookupAssetTransactions)q).address(new Address(value)); - break; - case "address-role": - ((LookupAssetTransactions)q).addressRole(getAddressRole(value)); - break; - case "after-time": - ((LookupAssetTransactions)q).afterTime(Utils.parseDate(value)); - break; - case "before-time": - ((LookupAssetTransactions)q).beforeTime(Utils.parseDate(value)); - break; - case "currency-greater-than": - ((LookupAssetTransactions)q).currencyGreaterThan(Long.valueOf(value)); - break; - case "currency-less-than": - ((LookupAssetTransactions)q).currencyLessThan(Long.valueOf(value)); - break; - case "exclude-close-to": - ((LookupAssetTransactions)q).excludeCloseTo(Boolean.valueOf(value)); - break; - case "limit": - ((LookupAssetTransactions)q).limit(Long.valueOf(value)); - break; - case "max-round": - ((LookupAssetTransactions)q).maxRound(Long.valueOf(value)); - break; - case "min-round": - ((LookupAssetTransactions)q).minRound(Long.valueOf(value)); - break; - case "next": - ((LookupAssetTransactions)q).next(value); - break; - case "note-prefix": - ((LookupAssetTransactions)q).notePrefix(Encoder.decodeFromBase64(value)); - break; - case "rekey-to": - ((LookupAssetTransactions)q).rekeyTo(Boolean.valueOf(value)); - break; - case "round": - ((LookupAssetTransactions)q).round(Long.valueOf(value)); - break; - case "sig-type": - ((LookupAssetTransactions)q).sigType(getSigType(value)); - break; - case "tx-type": - ((LookupAssetTransactions)q).txType(getTxType(value)); - break; - case "txid": - ((LookupAssetTransactions)q).txid(value); - break; - } - break; - case "lookupBlock": - switch (property) { - } - break; - case "lookupTransaction": - switch (property) { - } - break; - case "searchForTransactions": - switch (property) { - case "address": - ((SearchForTransactions)q).address(new Address(value)); - break; - case "address-role": - ((SearchForTransactions)q).addressRole(getAddressRole(value)); - break; - case "after-time": - ((SearchForTransactions)q).afterTime(Utils.parseDate(value)); - break; - case "application-id": - ((SearchForTransactions)q).applicationId(Long.valueOf(value)); - break; - case "asset-id": - ((SearchForTransactions)q).assetId(Long.valueOf(value)); - break; - case "before-time": - ((SearchForTransactions)q).beforeTime(Utils.parseDate(value)); - break; - case "currency-greater-than": - ((SearchForTransactions)q).currencyGreaterThan(Long.valueOf(value)); - break; - case "currency-less-than": - ((SearchForTransactions)q).currencyLessThan(Long.valueOf(value)); - break; - case "exclude-close-to": - ((SearchForTransactions)q).excludeCloseTo(Boolean.valueOf(value)); - break; - case "limit": - ((SearchForTransactions)q).limit(Long.valueOf(value)); - break; - case "max-round": - ((SearchForTransactions)q).maxRound(Long.valueOf(value)); - break; - case "min-round": - ((SearchForTransactions)q).minRound(Long.valueOf(value)); - break; - case "next": - ((SearchForTransactions)q).next(value); - break; - case "note-prefix": - ((SearchForTransactions)q).notePrefix(Encoder.decodeFromBase64(value)); - break; - case "rekey-to": - ((SearchForTransactions)q).rekeyTo(Boolean.valueOf(value)); - break; - case "round": - ((SearchForTransactions)q).round(Long.valueOf(value)); - break; - case "sig-type": - ((SearchForTransactions)q).sigType(getSigType(value)); - break; - case "tx-type": - ((SearchForTransactions)q).txType(getTxType(value)); - break; - case "txid": - ((SearchForTransactions)q).txid(value); - break; - } - break; - case "HealthCheck": - switch (property) { - } - break; - case "Metrics": - switch (property) { - } - break; - case "GetGenesis": - switch (property) { - } - break; - case "SwaggerJSON": - switch (property) { - } - break; - case "GetVersion": - switch (property) { - } - break; - case "AccountInformation": - switch (property) { - case "exclude": - ((AccountInformation)q).exclude(getExclude(value)); - break; - } - break; - case "AccountAssetInformation": - switch (property) { - } - break; - case "AccountApplicationInformation": - switch (property) { - } - break; - case "GetPendingTransactionsByAddress": - switch (property) { - case "max": - ((GetPendingTransactionsByAddress)q).max(Long.valueOf(value)); - break; - } - break; - case "GetBlock": - switch (property) { - } - break; - case "GetProof": - switch (property) { - } - break; - case "GetSupply": - switch (property) { - } - break; - case "GetStatus": - switch (property) { - } - break; - case "WaitForBlock": - switch (property) { - } - break; - case "RawTransaction": - switch (property) { - case "rawtxn": - ((RawTransaction)q).rawtxn(Encoder.decodeFromBase64(value)); - break; - } - break; - case "TransactionParams": - switch (property) { - } - break; - case "GetPendingTransactions": - switch (property) { - case "max": - ((GetPendingTransactions)q).max(Long.valueOf(value)); - break; - } - break; - case "PendingTransactionInformation": - switch (property) { - } - break; - case "GetApplicationByID": - switch (property) { - } - break; - case "GetAssetByID": - switch (property) { - } - break; - case "TealCompile": - switch (property) { - case "source": - ((TealCompile)q).source(Encoder.decodeFromBase64(value)); - break; - } - break; - case "TealDryrun": - switch (property) { - case "request": - ((TealDryrun)q).request(null); - break; - } - break; - - } - } - - public static String lookup(Query q, String className) throws Exception { - Response resp = q.execute(); - if (resp.body() == null) { - throw new RuntimeException(resp.message()); - } - return resp.body().toString(); - } - - public static Enums.AddressRole getAddressRole(String val) { - switch(val.toUpperCase()) { - case "SENDER": - return Enums.AddressRole.SENDER; - case "RECEIVER": - return Enums.AddressRole.RECEIVER; - case "FREEZETARGET": - return Enums.AddressRole.FREEZETARGET; - default: - throw new RuntimeException("Enum value not recognized: " + val +"!"); - } - } - public static Enums.SigType getSigType(String val) { - switch(val.toUpperCase()) { - case "SIG": - return Enums.SigType.SIG; - case "MSIG": - return Enums.SigType.MSIG; - case "LSIG": - return Enums.SigType.LSIG; - default: - throw new RuntimeException("Enum value not recognized: " + val +"!"); - } - } - public static Enums.TxType getTxType(String val) { - switch(val.toUpperCase()) { - case "PAY": - return Enums.TxType.PAY; - case "KEYREG": - return Enums.TxType.KEYREG; - case "ACFG": - return Enums.TxType.ACFG; - case "AXFER": - return Enums.TxType.AXFER; - case "AFRZ": - return Enums.TxType.AFRZ; - case "APPL": - return Enums.TxType.APPL; - default: - throw new RuntimeException("Enum value not recognized: " + val +"!"); - } - } - public static Enums.Exclude getExclude(String val) { - switch(val.toUpperCase().replaceAll("-", "")) { - case "ALL": - return Enums.Exclude.ALL; - case "ASSETS": - return Enums.Exclude.ASSETS; - case "CREATEDASSETS": - return Enums.Exclude.CREATEDASSETS; - case "APPSLOCALSTATE": - return Enums.Exclude.APPSLOCALSTATE; - case "CREATEDAPPS": - return Enums.Exclude.CREATEDAPPS; - case "NONE": - return Enums.Exclude.NONE; - default: - throw new RuntimeException("Enum value not recognized: " + val +"!"); - } - } -} \ No newline at end of file diff --git a/src/test/unit.tags b/src/test/unit.tags new file mode 100644 index 000000000..a3f91cec3 --- /dev/null +++ b/src/test/unit.tags @@ -0,0 +1,29 @@ +@unit.abijson +@unit.abijson.byname +@unit.algod +@unit.algod.ledger_refactoring +@unit.applications +@unit.atomic_transaction_composer +@unit.dryrun +@unit.dryrun.trace.application +@unit.feetest +@unit.indexer +@unit.indexer.ledger_refactoring +@unit.indexer.logs +@unit.indexer.rekey +@unit.offline +@unit.program_sanity_check +@unit.rekey +@unit.responses +@unit.responses.231 +@unit.responses.messagepack +@unit.responses.messagepack.231 +@unit.responses.unlimited_assets +@unit.sourcemap +@unit.stateproof.paths +@unit.stateproof.responses +@unit.stateproof.responses.msgp +@unit.tealsign +@unit.transactions +@unit.transactions.keyreg +@unit.transactions.payment diff --git a/test-harness.sh b/test-harness.sh new file mode 100755 index 000000000..a5d28a781 --- /dev/null +++ b/test-harness.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -euo pipefail + +START=$(date "+%s") + +THIS=$(basename "$0") +ENV_FILE=".test-env" +TEST_DIR="src/test/resources/com/algorand/algosdk" +TEST_RESOURCES_DIR="src/test/resources/" + +set -a +source "$ENV_FILE" +set +a + +rootdir=$(dirname "$0") +pushd "$rootdir" + +echo "$THIS: VERBOSE_HARNESS=$VERBOSE_HARNESS" + +## Reset test harness +if [ -d "$SDK_TESTING_HARNESS" ]; then + pushd "$SDK_TESTING_HARNESS" + ./scripts/down.sh + popd + rm -rf "$SDK_TESTING_HARNESS" +else + echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" +fi + +git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" + + +echo "$THIS: OVERWRITE_TESTING_ENVIRONMENT=$OVERWRITE_TESTING_ENVIRONMENT" +if [[ $OVERWRITE_TESTING_ENVIRONMENT == 1 ]]; then + echo "$THIS: OVERWRITE replaced $SDK_TESTING_HARNESS/.env with $ENV_FILE:" + cp "$ENV_FILE" "$SDK_TESTING_HARNESS"/.env +fi + +echo "$THIS: REMOVE_LOCAL_FEATURES=$REMOVE_LOCAL_FEATURES"## Copy feature files into the project resources +if [[ $REMOVE_LOCAL_FEATURES == 1 ]]; then + echo "$THIS: OVERWRITE wipes clean $TEST_DIR/features" + if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/integration && tree $TEST_DIR/unit && tree $TEST_RESOURCES_DIR && echo "$THIS: see the previous for files deleted" ) || true + fi + rm -rf $TEST_DIR/integration + rm -rf $TEST_DIR/unit + rm -rf $TEST_RESOURCES_DIR +fi +mkdir -p $TEST_DIR/integration +mkdir -p $TEST_DIR/unit +mkdir -p $TEST_RESOURCES_DIR + +# The Java implementation of these is too tightly coupled with the +# integration tests, so add them to the integration tests instead. +mv "$SDK_TESTING_HARNESS"/features/unit/offline.feature "$SDK_TESTING_HARNESS"/features/integration/ + +cp -r "$SDK_TESTING_HARNESS"/features/integration/* $TEST_DIR/integration +cp -r "$SDK_TESTING_HARNESS"/features/unit/* $TEST_DIR/unit +cp -r "$SDK_TESTING_HARNESS"/features/resources/* $TEST_RESOURCES_DIR + +if [[ $VERBOSE_HARNESS == 1 ]]; then + ( tree $TEST_DIR/integration && tree $TEST_DIR/unit && tree $TEST_RESOURCES_DIR && echo "$THIS: see the previous for files copied over" ) || true +fi +echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" + +## Start test harness environment +pushd "$SDK_TESTING_HARNESS" + +[[ "$VERBOSE_HARNESS" = 1 ]] && V_FLAG="-v" || V_FLAG="" +echo "$THIS: standing up harnness with command [./up.sh $V_FLAG]" +./scripts/up.sh "$V_FLAG" + +popd +echo "$THIS: seconds it took to finish testing sdk's up.sh: $(($(date "+%s") - START))s" +echo "" +echo "--------------------------------------------------------------------------------" +echo "|" +echo "| To run sandbox commands, cd into $SDK_TESTING_HARNESS/.sandbox " +echo "|" +echo "--------------------------------------------------------------------------------" From 8c4c72343ca4fe90ce50c981cf50000bb0be31c4 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:24:30 -0400 Subject: [PATCH 09/20] Box support for Indexer endpoints (#356) * indexer model from indexer branch tzaffi/box-search * step in cucumber test to see url query match with expected * minor, better testing script * @unit.applications.boxes * rm unused steps * Update .test-env --- .../v2/client/common/IndexerClient.java | 23 +++++ .../LookupApplicationBoxByIDandName.java | 82 ++++++++++++++++++ .../indexer/SearchForApplicationBoxes.java | 83 +++++++++++++++++++ .../algosdk/v2/client/model/Account.java | 16 ++++ .../v2/client/model/BoxDescriptor.java | 2 +- .../v2/client/model/BoxesResponse.java | 7 ++ .../algorand/algosdk/unit/IndexerPaths.java | 12 +++ src/test/unit.tags | 1 + 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForApplicationBoxes.java diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java index 6661c80d5..52151fc75 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java @@ -10,6 +10,8 @@ import com.algorand.algosdk.v2.client.indexer.LookupAccountTransactions; import com.algorand.algosdk.v2.client.indexer.SearchForApplications; import com.algorand.algosdk.v2.client.indexer.LookupApplicationByID; +import com.algorand.algosdk.v2.client.indexer.SearchForApplicationBoxes; +import com.algorand.algosdk.v2.client.indexer.LookupApplicationBoxByIDandName; import com.algorand.algosdk.v2.client.indexer.LookupApplicationLogsByID; import com.algorand.algosdk.v2.client.indexer.SearchForAssets; import com.algorand.algosdk.v2.client.indexer.LookupAssetByID; @@ -133,6 +135,27 @@ public LookupApplicationByID lookupApplicationByID(Long applicationId) { return new LookupApplicationByID((Client) this, applicationId); } + /** + * Given an application ID, returns the box names of that application sorted + * lexicographically. + * /v2/applications/{application-id}/boxes + */ + public SearchForApplicationBoxes searchForApplicationBoxes(Long applicationId) { + return new SearchForApplicationBoxes((Client) this, applicationId); + } + + /** + * Given an application ID and box name, returns base64 encoded box name and value. + * Box names must be in the goal-arg form 'encoding:value'. For ints, use the form + * 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in 'b64:A=='. + * For printable strings, use the form 'str:hello'. For addresses, use the form + * 'addr:XYZ...'. + * /v2/applications/{application-id}/box + */ + public LookupApplicationBoxByIDandName lookupApplicationBoxByIDandName(Long applicationId) { + return new LookupApplicationBoxByIDandName((Client) this, applicationId); + } + /** * Lookup application logs. * /v2/applications/{application-id}/logs diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java new file mode 100644 index 000000000..7c14caf5d --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java @@ -0,0 +1,82 @@ +package com.algorand.algosdk.v2.client.indexer; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.Box; + + +/** + * Given an application ID and box name, returns base64 encoded box name and value. + * Box names must be in the goal-arg form 'encoding:value'. For ints, use the form + * 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in 'b64:A=='. + * For printable strings, use the form 'str:hello'. For addresses, use the form + * 'addr:XYZ...'. + * /v2/applications/{application-id}/box + */ +public class LookupApplicationBoxByIDandName extends Query { + + private Long applicationId; + + /** + * @param applicationId + */ + public LookupApplicationBoxByIDandName(Client client, Long applicationId) { + super(client, new HttpMethod("get")); + this.applicationId = applicationId; + } + + /** + * A box name in goal-arg form 'encoding:value'. For ints, use the form 'int:1234'. + * For raw bytes, use the form 'b64:A=='. For printable strings, use the form + * 'str:hello'. For addresses, use the form 'addr:XYZ...'. + */ + public LookupApplicationBoxByIDandName name(String name) { + addQuery("name", String.valueOf(name)); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(Box.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(Box.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.applicationId == null) { + throw new RuntimeException("application-id is not set. It is a required parameter."); + } + if (!qd.queries.containsKey("name")) { + throw new RuntimeException("name is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("applications")); + addPathSegment(String.valueOf(applicationId)); + addPathSegment(String.valueOf("box")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForApplicationBoxes.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForApplicationBoxes.java new file mode 100644 index 000000000..5cabe8a26 --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForApplicationBoxes.java @@ -0,0 +1,83 @@ +package com.algorand.algosdk.v2.client.indexer; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.BoxesResponse; + + +/** + * Given an application ID, returns the box names of that application sorted + * lexicographically. + * /v2/applications/{application-id}/boxes + */ +public class SearchForApplicationBoxes extends Query { + + private Long applicationId; + + /** + * @param applicationId + */ + public SearchForApplicationBoxes(Client client, Long applicationId) { + super(client, new HttpMethod("get")); + this.applicationId = applicationId; + } + + /** + * Maximum number of results to return. There could be additional pages even if the + * limit is not reached. + */ + public SearchForApplicationBoxes limit(Long limit) { + addQuery("limit", String.valueOf(limit)); + return this; + } + + /** + * The next page of results. Use the next token provided by the previous results. + */ + public SearchForApplicationBoxes next(String next) { + addQuery("next", String.valueOf(next)); + return this; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(BoxesResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(BoxesResponse.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.applicationId == null) { + throw new RuntimeException("application-id is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("applications")); + addPathSegment(String.valueOf(applicationId)); + addPathSegment(String.valueOf("boxes")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Account.java b/src/main/java/com/algorand/algosdk/v2/client/model/Account.java index 8c11e9905..473d551e8 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Account.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Account.java @@ -194,6 +194,20 @@ public String authAddr() throws NoSuchAlgorithmException { @JsonProperty("total-assets-opted-in") public Long totalAssetsOptedIn; + /** + * For app-accounts only. The total number of bytes allocated for the keys and + * values of boxes which belong to the associated application. + */ + @JsonProperty("total-box-bytes") + public Long totalBoxBytes; + + /** + * For app-accounts only. The total number of boxes which belong to the associated + * application. + */ + @JsonProperty("total-boxes") + public Long totalBoxes; + /** * The count of all apps (AppParams objects) created by this account. */ @@ -235,6 +249,8 @@ public boolean equals(Object o) { if (!Objects.deepEquals(this.status, other.status)) return false; if (!Objects.deepEquals(this.totalAppsOptedIn, other.totalAppsOptedIn)) return false; if (!Objects.deepEquals(this.totalAssetsOptedIn, other.totalAssetsOptedIn)) return false; + if (!Objects.deepEquals(this.totalBoxBytes, other.totalBoxBytes)) return false; + if (!Objects.deepEquals(this.totalBoxes, other.totalBoxes)) return false; if (!Objects.deepEquals(this.totalCreatedApps, other.totalCreatedApps)) return false; if (!Objects.deepEquals(this.totalCreatedAssets, other.totalCreatedAssets)) return false; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java b/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java index 9a4a4335b..7e12b7475 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BoxDescriptor.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Box descriptor describes a Box. + * Box descriptor describes an app box without a value. */ public class BoxDescriptor extends PathResponse { diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java index 3b8740b4b..e1ca45e2f 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java @@ -12,6 +12,12 @@ */ public class BoxesResponse extends PathResponse { + /** + * (appidx) application index. + */ + @JsonProperty("application-id") + public Long applicationId; + @JsonProperty("boxes") public List boxes = new ArrayList(); @@ -22,6 +28,7 @@ public boolean equals(Object o) { if (o == null) return false; BoxesResponse other = (BoxesResponse) o; + if (!Objects.deepEquals(this.applicationId, other.applicationId)) return false; if (!Objects.deepEquals(this.boxes, other.boxes)) return false; return true; diff --git a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java index 8c9cb43e8..36307524a 100644 --- a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java @@ -333,4 +333,16 @@ public void searchForApplications(String string) { ps.q = q; } + @When("we make a LookupApplicationBoxByIDandName call with applicationID {long} with encoded box name {string}") + public void lookUpApplicationBox(Long appID, String boxName) { + ps.q = indexerClient.lookupApplicationBoxByIDandName(appID).name(boxName); + } + + @When("we make a SearchForApplicationBoxes call with applicationID {long} with max {long} nextToken {string}") + public void searchApplicationBoxes(Long appID, Long maxRes, String nextToken) { + SearchForApplicationBoxes q = indexerClient.searchForApplicationBoxes(appID); + if (TestingUtils.notEmpty(maxRes)) q.limit(maxRes); + if (TestingUtils.notEmpty(nextToken)) q.next(nextToken); + ps.q = q; + } } diff --git a/src/test/unit.tags b/src/test/unit.tags index a3f91cec3..fc6e96482 100644 --- a/src/test/unit.tags +++ b/src/test/unit.tags @@ -3,6 +3,7 @@ @unit.algod @unit.algod.ledger_refactoring @unit.applications +@unit.applications.boxes @unit.atomic_transaction_composer @unit.dryrun @unit.dryrun.trace.application From 7f6cef97f497cb143d7d863805e9b271dfcdcd4e Mon Sep 17 00:00:00 2001 From: Hang Su Date: Wed, 7 Sep 2022 11:46:19 -0400 Subject: [PATCH 10/20] boxes integration algod test --- src/test/integration.tags | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/integration.tags b/src/test/integration.tags index c9421bf47..52ba486e9 100644 --- a/src/test/integration.tags +++ b/src/test/integration.tags @@ -1,6 +1,7 @@ @abi @algod @applications +@applications.boxes @applications.verified @assets @auction From 62d5ed4d91f407ef8f2275449ddbfeaf31ad0093 Mon Sep 17 00:00:00 2001 From: Hang Su <87964331+ahangsu@users.noreply.github.com> Date: Thu, 15 Sep 2022 16:00:20 -0400 Subject: [PATCH 11/20] Enhancement: Boxes integration test (#385) * add indexer impl in sdk * merge step for indexer * add indexer confirmed steps * add indexer confirmed steps, working! * add indexer confirmed steps * minor * minor * per new step expectation * unify with applications.boxes * phrasing * refactoring * Update .test-env * minor --- .../algosdk/integration/Applications.java | 91 +++++++++++++------ .../algorand/algosdk/integration/Clients.java | 8 +- 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index 1970e3af9..c24edd0e9 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -20,7 +20,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; @@ -256,9 +255,15 @@ public void checkAccountData( assertThat(found).as("Couldn't find key '%s'", hasKey).isTrue(); } - @Then("the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.") - public void contentsOfBoxShouldBe(String encodedBoxName, String boxContents, String errStr) throws Exception { - Response boxResp = clients.v2Client.GetApplicationBoxByName(this.appId).name(encodedBoxName).execute(); + @Then("according to {string}, the contents of the box with name {string} in the current application should be {string}. If there is an error it is {string}.") + public void contentsOfBoxShouldBe(String fromClient, String encodedBoxName, String boxContents, String errStr) throws Exception { + Response boxResp; + if (fromClient.equals("algod")) + boxResp = clients.v2Client.GetApplicationBoxByName(this.appId).name(encodedBoxName).execute(); + else if (fromClient.equals("indexer")) + boxResp = clients.v2IndexerClient.lookupApplicationBoxByIDandName(this.appId).name(encodedBoxName).execute(); + else + throw new IllegalArgumentException("expecting algod or indexer, got " + fromClient); // If an error was expected, make sure it is set correctly. if (StringUtils.isNotEmpty(errStr)) { @@ -267,24 +272,7 @@ public void contentsOfBoxShouldBe(String encodedBoxName, String boxContents, Str return; } - assertThat(boxResp.body().value().equals(boxContents)); - } - - private static byte[] decodeBoxName(String encodedBoxName) { - String[] split = Strings.split(encodedBoxName, ':'); - if (split.length != 2) - throw new RuntimeException("encodedBoxName (" + encodedBoxName + ") does not match expected format"); - - String encoding = split[0]; - String encoded = split[1]; - switch (encoding) { - case "str": - return encoded.getBytes(StandardCharsets.US_ASCII); - case "b64": - return Encoder.decodeFromBase64(encoded); - default: - throw new RuntimeException("Unsupported encoding = " + encoding); - } + assertThat(boxResp.body().value()).isEqualTo(boxContents); } private static boolean contains(byte[] elem, List xs) { @@ -295,17 +283,61 @@ private static boolean contains(byte[] elem, List xs) { return false; } - @Then("the current application should have the following boxes {string}.") - public void checkAppBoxes(String encodedBoxesRaw) throws Exception { + @Then("according to {string}, the current application should have the following boxes {string}.") + public void checkAppBoxes(String fromClient, String encodedBoxesRaw) throws Exception { + Response r; + if (fromClient.equals("algod")) + r = clients.v2Client.GetApplicationBoxes(this.appId).execute(); + else if (fromClient.equals("indexer")) + r = clients.v2IndexerClient.searchForApplicationBoxes(this.appId).execute(); + else + throw new IllegalArgumentException("expecting algod or indexer, got " + fromClient); + + Assert.assertTrue(r.isSuccessful()); + final List expectedNames = Lists.newArrayList(); if (!encodedBoxesRaw.isEmpty()) { - for (String s : Strings.split(encodedBoxesRaw, ',')) { - expectedNames.add(decodeBoxName(s)); + for (String s : Strings.split(encodedBoxesRaw, ':')) { + expectedNames.add(Encoder.decodeFromBase64(s)); } } - final Response r = clients.v2Client.GetApplicationBoxes(this.appId).execute(); + final List actualNames = Lists.newArrayList(); + for (BoxDescriptor b : r.body().boxes) { + actualNames.add(b.name); + } + + Assert.assertEquals("expected and actual box names length do not match", expectedNames.size(), actualNames.size()); + for (byte[] e : expectedNames) { + if (!contains(e, actualNames)) + throw new RuntimeException("expected and actual box names do not match: " + expectedNames + " != " + actualNames); + } + } + + @Then("according to {string}, with {long} being the parameter that limits results, the current application should have {int} boxes.") + public void checkAppBoxesNum(String fromClient, Long limit, int expected_num) throws Exception { + Response r; + if (fromClient.equals("algod")) + r = clients.v2Client.GetApplicationBoxes(this.appId).max(limit).execute(); + else if (fromClient.equals("indexer")) + r = clients.v2IndexerClient.searchForApplicationBoxes(this.appId).limit(limit).execute(); + else + throw new IllegalArgumentException("expecting algod or indexer, got " + fromClient); + Assert.assertTrue(r.isSuccessful()); + Assert.assertEquals("expected " + expected_num + " boxes, actual " + r.body().boxes.size(), + r.body().boxes.size(), expected_num); + } + + @Then("according to indexer, with {long} being the parameter that limits results, and {string} being the parameter that sets the next result, the current application should have the following boxes {string}.") + public void indexerCheckAppBoxesWithParams(Long limit, String next, String encodedBoxesRaw) throws Exception { + Response r = clients.v2IndexerClient.searchForApplicationBoxes(this.appId).limit(limit).next(next).execute(); + final List expectedNames = Lists.newArrayList(); + if (!encodedBoxesRaw.isEmpty()) { + for (String s : Strings.split(encodedBoxesRaw, ':')) { + expectedNames.add(Encoder.decodeFromBase64(s)); + } + } final List actualNames = Lists.newArrayList(); for (BoxDescriptor b : r.body().boxes) { @@ -318,4 +350,9 @@ public void checkAppBoxes(String encodedBoxesRaw) throws Exception { throw new RuntimeException("expected and actual box names do not match: " + expectedNames + " != " + actualNames); } } + + @Then("I sleep for {int} milliseconds for indexer to digest things down.") + public void sleepForNSecondsForIndexer(int milliseconds) throws Exception { + Thread.sleep(milliseconds); + } } diff --git a/src/test/java/com/algorand/algosdk/integration/Clients.java b/src/test/java/com/algorand/algosdk/integration/Clients.java index 4f4b61b22..562f9316f 100644 --- a/src/test/java/com/algorand/algosdk/integration/Clients.java +++ b/src/test/java/com/algorand/algosdk/integration/Clients.java @@ -9,10 +9,16 @@ public class Clients { AlgodClient v2Client = null; - Map indexerClients = new HashMap<>(); + + IndexerClient v2IndexerClient = null; @Given("an algod v2 client connected to {string} port {int} with token {string}") public void an_algod_v2_client_connected_to_port_with_token(String host, Integer port, String token) { v2Client = new AlgodClient(host, port, token); } + + @Given("an indexer v2 client") + public void indexer_v2_client() { + v2IndexerClient = new IndexerClient("localhost", 59999); + } } From 5d8130c22024059cdda164bcfee8487825f7173d Mon Sep 17 00:00:00 2001 From: Hang Su Date: Tue, 20 Sep 2022 13:42:33 -0400 Subject: [PATCH 12/20] remove unnecessary @applications tag --- src/test/integration.tags | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/integration.tags b/src/test/integration.tags index 52ba486e9..91dace71b 100644 --- a/src/test/integration.tags +++ b/src/test/integration.tags @@ -1,6 +1,5 @@ @abi @algod -@applications @applications.boxes @applications.verified @assets From 15c2dfb95d7cd9cd60668ecddc55c636770b1c80 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 23 Sep 2022 14:35:10 -0400 Subject: [PATCH 13/20] Remove boxReferences from public Transaction constructor (#399) --- .../algorand/algosdk/transaction/Transaction.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/algorand/algosdk/transaction/Transaction.java b/src/main/java/com/algorand/algosdk/transaction/Transaction.java index c352b0f39..6078c9e5d 100644 --- a/src/main/java/com/algorand/algosdk/transaction/Transaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/Transaction.java @@ -302,7 +302,6 @@ public static Transaction createPaymentTransaction(Address sender, BigInteger fe null, null, null, - null, null); } @@ -407,7 +406,6 @@ public static Transaction createKeyRegistrationTransaction(Address sender, BigIn null, null, null, - null, null); } @@ -535,7 +533,6 @@ public static Transaction createAssetCreateTransaction(Address sender, BigIntege null, null, null, - null, null); } @@ -737,13 +734,16 @@ private Transaction(@JsonProperty("type") Type type, convertToAddressList(accounts), foreignApps, foreignAssets, - boxReferences, globalStateSchema, applicationId, localStateSchema, clearStateProgram == null ? null : new TEALProgram(clearStateProgram), extraPages ); + // Set fields _not_ exposed by public constructor. Needed because: + // * Adding parameters to a public constructor is a breaking API change. + // * To ensure JSON/msgpack serialization (via Jackson's ObjectMapper) works, must add `@JsonProperty` to _a_ constructor. Using a private constructor here to maintain API backwards compatibility. + if (boxReferences != null) this.boxReferences = boxReferences; } /** @@ -795,7 +795,6 @@ public Transaction( List

accounts, List foreignApps, List foreignAssets, - List boxReferences, StateSchema globalStateSchema, Long applicationId, StateSchema localStateSchema, @@ -846,7 +845,6 @@ public Transaction( accounts, foreignApps, foreignAssets, - boxReferences, globalStateSchema, applicationId, localStateSchema, @@ -905,7 +903,6 @@ public Transaction( List
accounts, List foreignApps, List foreignAssets, - List boxReferences, StateSchema globalStateSchema, Long applicationId, StateSchema localStateSchema, @@ -949,7 +946,6 @@ public Transaction( if (accounts != null) this.accounts = accounts; if (foreignApps != null) this.foreignApps = foreignApps; if (foreignAssets != null) this.foreignAssets = foreignAssets; - if (boxReferences != null) this.boxReferences = boxReferences; if (globalStateSchema != null) this.globalStateSchema = globalStateSchema; if (applicationId != null) this.applicationId = applicationId; if (localStateSchema != null) this.localStateSchema = globalStateSchema; From b5425b78a1dd835a8accc1cd27db4f8d1f935f9e Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 23 Sep 2022 14:35:30 -0400 Subject: [PATCH 14/20] Add boxReference to Transaction.equals (#400) --- .../java/com/algorand/algosdk/transaction/Transaction.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/algorand/algosdk/transaction/Transaction.java b/src/main/java/com/algorand/algosdk/transaction/Transaction.java index 6078c9e5d..8769632c2 100644 --- a/src/main/java/com/algorand/algosdk/transaction/Transaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/Transaction.java @@ -1455,7 +1455,8 @@ public boolean equals(Object o) { freezeState == that.freezeState && rekeyTo.equals(that.rekeyTo) && Arrays.equals(lease, ((Transaction) o).lease) && - extraPages.equals(that.extraPages); + extraPages.equals(that.extraPages) && + boxReferences.equals(that.boxReferences); } /** From d0f69df24615a31bf4ccf74f3c3c271360f38272 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Fri, 23 Sep 2022 15:18:52 -0400 Subject: [PATCH 15/20] Re-run code generator (#401) --- .../algosdk/v2/client/common/AlgodClient.java | 18 +++++----- .../v2/client/common/IndexerClient.java | 14 ++++---- ...a => LookupApplicationBoxByIDAndName.java} | 14 ++++---- .../v2/client/indexer/SearchForAccounts.java | 5 ++- .../algosdk/v2/client/model/Block.java | 7 ++++ .../v2/client/model/BoxesResponse.java | 8 +++++ .../v2/client/model/ParticipationUpdates.java | 33 +++++++++++++++++++ .../algosdk/integration/Applications.java | 2 +- .../algorand/algosdk/unit/IndexerPaths.java | 2 +- 9 files changed, 76 insertions(+), 27 deletions(-) rename src/main/java/com/algorand/algosdk/v2/client/indexer/{LookupApplicationBoxByIDandName.java => LookupApplicationBoxByIDAndName.java} (83%) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/ParticipationUpdates.java diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index dcbad0340..8340e7d16 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -33,9 +33,8 @@ public class AlgodClient extends Client { /** * Construct an AlgodClient for communicating with the REST API. - * - * @param host using a URI format. If the scheme is not supplied the client will use HTTP. - * @param port REST server port. + * @param host using a URI format. If the scheme is not supplied the client will use HTTP. + * @param port REST server port. * @param token authentication token. */ public AlgodClient(String host, int port, String token) { @@ -44,10 +43,9 @@ public AlgodClient(String host, int port, String token) { /** * Construct an AlgodClient with custom token key for communicating with the REST API. - * - * @param host using a URI format. If the scheme is not supplied the client will use HTTP. - * @param port REST server port. - * @param token authentication token. + * @param host using a URI format. If the scheme is not supplied the client will use HTTP. + * @param port REST server port. + * @param token authentication token. * @param tokenKey authentication token key. */ public AlgodClient(String host, int port, String token, String tokenKey) { @@ -111,7 +109,7 @@ public AccountInformation AccountInformation(Address address) { * /v2/accounts/{address}/assets/{asset-id} */ public AccountAssetInformation AccountAssetInformation(Address address, - Long assetId) { + Long assetId) { return new AccountAssetInformation((Client) this, address, assetId); } @@ -123,7 +121,7 @@ public AccountAssetInformation AccountAssetInformation(Address address, * /v2/accounts/{address}/applications/{application-id} */ public AccountApplicationInformation AccountApplicationInformation(Address address, - Long applicationId) { + Long applicationId) { return new AccountApplicationInformation((Client) this, address, applicationId); } @@ -150,7 +148,7 @@ public GetBlock GetBlock(Long round) { * /v2/blocks/{round}/transactions/{txid}/proof */ public GetTransactionProof GetTransactionProof(Long round, - String txid) { + String txid) { return new GetTransactionProof((Client) this, round, txid); } diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java index 52151fc75..3ca218bbf 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/IndexerClient.java @@ -11,7 +11,7 @@ import com.algorand.algosdk.v2.client.indexer.SearchForApplications; import com.algorand.algosdk.v2.client.indexer.LookupApplicationByID; import com.algorand.algosdk.v2.client.indexer.SearchForApplicationBoxes; -import com.algorand.algosdk.v2.client.indexer.LookupApplicationBoxByIDandName; +import com.algorand.algosdk.v2.client.indexer.LookupApplicationBoxByIDAndName; import com.algorand.algosdk.v2.client.indexer.LookupApplicationLogsByID; import com.algorand.algosdk.v2.client.indexer.SearchForAssets; import com.algorand.algosdk.v2.client.indexer.LookupAssetByID; @@ -146,14 +146,14 @@ public SearchForApplicationBoxes searchForApplicationBoxes(Long applicationId) { /** * Given an application ID and box name, returns base64 encoded box name and value. - * Box names must be in the goal-arg form 'encoding:value'. For ints, use the form - * 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in 'b64:A=='. - * For printable strings, use the form 'str:hello'. For addresses, use the form - * 'addr:XYZ...'. + * Box names must be in the goal app call arg form 'encoding:value'. For ints, use + * the form 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in + * 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use + * the form 'addr:XYZ...'. * /v2/applications/{application-id}/box */ - public LookupApplicationBoxByIDandName lookupApplicationBoxByIDandName(Long applicationId) { - return new LookupApplicationBoxByIDandName((Client) this, applicationId); + public LookupApplicationBoxByIDAndName lookupApplicationBoxByIDAndName(Long applicationId) { + return new LookupApplicationBoxByIDAndName((Client) this, applicationId); } /** diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDAndName.java similarity index 83% rename from src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java rename to src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDAndName.java index 7c14caf5d..e4de527c7 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDandName.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupApplicationBoxByIDAndName.java @@ -10,20 +10,20 @@ /** * Given an application ID and box name, returns base64 encoded box name and value. - * Box names must be in the goal-arg form 'encoding:value'. For ints, use the form - * 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in 'b64:A=='. - * For printable strings, use the form 'str:hello'. For addresses, use the form - * 'addr:XYZ...'. + * Box names must be in the goal app call arg form 'encoding:value'. For ints, use + * the form 'int:1234'. For raw bytes, encode base 64 and use 'b64' prefix as in + * 'b64:A=='. For printable strings, use the form 'str:hello'. For addresses, use + * the form 'addr:XYZ...'. * /v2/applications/{application-id}/box */ -public class LookupApplicationBoxByIDandName extends Query { +public class LookupApplicationBoxByIDAndName extends Query { private Long applicationId; /** * @param applicationId */ - public LookupApplicationBoxByIDandName(Client client, Long applicationId) { + public LookupApplicationBoxByIDAndName(Client client, Long applicationId) { super(client, new HttpMethod("get")); this.applicationId = applicationId; } @@ -33,7 +33,7 @@ public LookupApplicationBoxByIDandName(Client client, Long applicationId) { * For raw bytes, use the form 'b64:A=='. For printable strings, use the form * 'str:hello'. For addresses, use the form 'addr:XYZ...'. */ - public LookupApplicationBoxByIDandName name(String name) { + public LookupApplicationBoxByIDAndName name(String name) { addQuery("name", String.valueOf(name)); return this; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForAccounts.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForAccounts.java index 13b069ece..d67db48a1 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForAccounts.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/SearchForAccounts.java @@ -105,7 +105,10 @@ public SearchForAccounts next(String next) { /** * Include results for the specified round. For performance reasons, this parameter - * may be disabled on some configurations. + * may be disabled on some configurations. Using application-id or asset-id filters + * will return both creator and opt-in accounts. Filtering by include-all will + * return creator and opt-in accounts for deleted assets and accounts. Non-opt-in + * managers are not included in the results when asset-id is used. */ public SearchForAccounts round(Long round) { addQuery("round", String.valueOf(round)); diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/Block.java b/src/main/java/com/algorand/algosdk/v2/client/model/Block.java index 83078c5ee..dc4feb686 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/Block.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/Block.java @@ -33,6 +33,12 @@ public String genesisHash() { @JsonProperty("genesis-id") public String genesisId; + /** + * Participation account data that needs to be checked/acted on by the network. + */ + @JsonProperty("participation-updates") + public ParticipationUpdates participationUpdates; + /** * (prev) Previous block hash. */ @@ -150,6 +156,7 @@ public boolean equals(Object o) { Block other = (Block) o; if (!Objects.deepEquals(this.genesisHash, other.genesisHash)) return false; if (!Objects.deepEquals(this.genesisId, other.genesisId)) return false; + if (!Objects.deepEquals(this.participationUpdates, other.participationUpdates)) return false; if (!Objects.deepEquals(this.previousBlockHash, other.previousBlockHash)) return false; if (!Objects.deepEquals(this.rewards, other.rewards)) return false; if (!Objects.deepEquals(this.round, other.round)) return false; diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java index e1ca45e2f..7a9ea9af5 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BoxesResponse.java @@ -21,6 +21,13 @@ public class BoxesResponse extends PathResponse { @JsonProperty("boxes") public List boxes = new ArrayList(); + /** + * Used for pagination, when making another request provide this token with the + * next parameter. + */ + @JsonProperty("next-token") + public String nextToken; + @Override public boolean equals(Object o) { @@ -30,6 +37,7 @@ public boolean equals(Object o) { BoxesResponse other = (BoxesResponse) o; if (!Objects.deepEquals(this.applicationId, other.applicationId)) return false; if (!Objects.deepEquals(this.boxes, other.boxes)) return false; + if (!Objects.deepEquals(this.nextToken, other.nextToken)) return false; return true; } diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/ParticipationUpdates.java b/src/main/java/com/algorand/algosdk/v2/client/model/ParticipationUpdates.java new file mode 100644 index 000000000..554274a9a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/ParticipationUpdates.java @@ -0,0 +1,33 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Participation account data that needs to be checked/acted on by the network. + */ +public class ParticipationUpdates extends PathResponse { + + /** + * (partupdrmv) a list of online accounts that needs to be converted to offline + * since their participation key expired. + */ + @JsonProperty("expired-participation-accounts") + public List expiredParticipationAccounts = new ArrayList(); + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + ParticipationUpdates other = (ParticipationUpdates) o; + if (!Objects.deepEquals(this.expiredParticipationAccounts, other.expiredParticipationAccounts)) return false; + + return true; + } +} diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index c24edd0e9..df1ea2002 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -261,7 +261,7 @@ public void contentsOfBoxShouldBe(String fromClient, String encodedBoxName, Stri if (fromClient.equals("algod")) boxResp = clients.v2Client.GetApplicationBoxByName(this.appId).name(encodedBoxName).execute(); else if (fromClient.equals("indexer")) - boxResp = clients.v2IndexerClient.lookupApplicationBoxByIDandName(this.appId).name(encodedBoxName).execute(); + boxResp = clients.v2IndexerClient.lookupApplicationBoxByIDAndName(this.appId).name(encodedBoxName).execute(); else throw new IllegalArgumentException("expecting algod or indexer, got " + fromClient); diff --git a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java index 36307524a..90032256e 100644 --- a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java @@ -335,7 +335,7 @@ public void searchForApplications(String string) { @When("we make a LookupApplicationBoxByIDandName call with applicationID {long} with encoded box name {string}") public void lookUpApplicationBox(Long appID, String boxName) { - ps.q = indexerClient.lookupApplicationBoxByIDandName(appID).name(boxName); + ps.q = indexerClient.lookupApplicationBoxByIDAndName(appID).name(boxName); } @When("we make a SearchForApplicationBoxes call with applicationID {long} with max {long} nextToken {string}") From f4c4a864623f9a2a62cb8847c915271535e5352c Mon Sep 17 00:00:00 2001 From: Jason Paulos Date: Fri, 23 Sep 2022 12:25:22 -0700 Subject: [PATCH 16/20] Box storage changes (#398) --- .../ApplicationBaseTransactionBuilder.java | 4 -- .../ApplicationCallReferencesSetter.java | 2 +- .../MethodCallTransactionBuilder.java | 3 +- .../algosdk/transaction/AppBoxReference.java | 2 +- .../algosdk/transaction/Transaction.java | 3 +- .../algosdk/integration/Applications.java | 40 ++++++++++--------- .../algosdk/util/ComparableBytes.java | 31 ++++++++++++++ 7 files changed, 57 insertions(+), 28 deletions(-) create mode 100644 src/test/java/com/algorand/algosdk/util/ComparableBytes.java diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java index ed03eb75d..a0d4fb496 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationBaseTransactionBuilder.java @@ -103,10 +103,6 @@ private List convertBoxes(List abrs, return xs; } - /** - * BoxReferences lists the boxes whose state may be accessed during the execution - * of this application call. The access is read-only. - */ public T boxReferences(List boxReferences) { this.appBoxReferences = boxReferences; return (T) this; diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java index 8e5490386..f91a467a0 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/ApplicationCallReferencesSetter.java @@ -31,7 +31,7 @@ public interface ApplicationCallReferencesSetter boxReferences); } diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java index 1c818e3c3..3a28ee086 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java @@ -130,7 +130,8 @@ public T foreignAssets(List foreignAssets) { @Override public T boxReferences(List boxReferences) { if (boxReferences != null) - this.boxReferences = new ArrayList<>(new HashSet<>(boxReferences)); + // duplicate box references can be meaningful, don't get rid of them + this.boxReferences = new ArrayList<>(boxReferences); else this.boxReferences.clear(); return (T) this; diff --git a/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java index 57c13f6f0..c3b7ccd43 100644 --- a/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java +++ b/src/main/java/com/algorand/algosdk/transaction/AppBoxReference.java @@ -50,6 +50,6 @@ public String toString() { } public String nameQueryEncoded() { - return BoxQueryEncoding.encodeBytes(this.getName()); + return BoxQueryEncoding.encodeBytes(name); } } diff --git a/src/main/java/com/algorand/algosdk/transaction/Transaction.java b/src/main/java/com/algorand/algosdk/transaction/Transaction.java index 8769632c2..d08f2b0af 100644 --- a/src/main/java/com/algorand/algosdk/transaction/Transaction.java +++ b/src/main/java/com/algorand/algosdk/transaction/Transaction.java @@ -250,7 +250,7 @@ public static Transaction createPaymentTransaction(Address sender, BigInteger fe Objects.requireNonNull(lastValid, "lastValid is required."); Objects.requireNonNull(genesisHash, "genesisHash is required."); - if (sender == null && closeRemainderTo == null) { + if (receiver == null && closeRemainderTo == null) { throw new IllegalArgumentException("Must set at least one of 'receiver' or 'closeRemainderTo'"); } @@ -1454,7 +1454,6 @@ public boolean equals(Object o) { assetFreezeID.equals(that.assetFreezeID) && freezeState == that.freezeState && rekeyTo.equals(that.rekeyTo) && - Arrays.equals(lease, ((Transaction) o).lease) && extraPages.equals(that.extraPages) && boxReferences.equals(that.boxReferences); } diff --git a/src/test/java/com/algorand/algosdk/integration/Applications.java b/src/test/java/com/algorand/algosdk/integration/Applications.java index df1ea2002..c862fcfe6 100644 --- a/src/test/java/com/algorand/algosdk/integration/Applications.java +++ b/src/test/java/com/algorand/algosdk/integration/Applications.java @@ -5,6 +5,7 @@ import com.algorand.algosdk.logic.StateSchema; import com.algorand.algosdk.transaction.SignedTransaction; import com.algorand.algosdk.transaction.Transaction; +import com.algorand.algosdk.util.ComparableBytes; import com.algorand.algosdk.util.Digester; import com.algorand.algosdk.util.Encoder; import com.algorand.algosdk.v2.client.Utils; @@ -23,7 +24,10 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static com.algorand.algosdk.util.ResourceUtils.loadTEALProgramFromFile; @@ -275,12 +279,18 @@ else if (fromClient.equals("indexer")) assertThat(boxResp.body().value()).isEqualTo(boxContents); } - private static boolean contains(byte[] elem, List xs) { - for (byte[] e : xs) { - if (Arrays.equals(e, elem)) - return true; + private static void assertSetOfByteArraysEqual(Set expected, Set actual) { + Set expectedComparable = new HashSet<>(); + for (byte[] element : expected) { + expectedComparable.add(new ComparableBytes(element)); } - return false; + + Set actualComparable = new HashSet<>(); + for (byte[] element : actual) { + actualComparable.add(new ComparableBytes(element)); + } + + Assert.assertEquals(expectedComparable, actualComparable); } @Then("according to {string}, the current application should have the following boxes {string}.") @@ -295,23 +305,19 @@ else if (fromClient.equals("indexer")) Assert.assertTrue(r.isSuccessful()); - final List expectedNames = Lists.newArrayList(); + final Set expectedNames = new HashSet<>(); if (!encodedBoxesRaw.isEmpty()) { for (String s : Strings.split(encodedBoxesRaw, ':')) { expectedNames.add(Encoder.decodeFromBase64(s)); } } - final List actualNames = Lists.newArrayList(); + final Set actualNames = new HashSet<>(); for (BoxDescriptor b : r.body().boxes) { actualNames.add(b.name); } - Assert.assertEquals("expected and actual box names length do not match", expectedNames.size(), actualNames.size()); - for (byte[] e : expectedNames) { - if (!contains(e, actualNames)) - throw new RuntimeException("expected and actual box names do not match: " + expectedNames + " != " + actualNames); - } + assertSetOfByteArraysEqual(expectedNames, actualNames); } @Then("according to {string}, with {long} being the parameter that limits results, the current application should have {int} boxes.") @@ -332,23 +338,19 @@ else if (fromClient.equals("indexer")) @Then("according to indexer, with {long} being the parameter that limits results, and {string} being the parameter that sets the next result, the current application should have the following boxes {string}.") public void indexerCheckAppBoxesWithParams(Long limit, String next, String encodedBoxesRaw) throws Exception { Response r = clients.v2IndexerClient.searchForApplicationBoxes(this.appId).limit(limit).next(next).execute(); - final List expectedNames = Lists.newArrayList(); + final Set expectedNames = new HashSet<>(); if (!encodedBoxesRaw.isEmpty()) { for (String s : Strings.split(encodedBoxesRaw, ':')) { expectedNames.add(Encoder.decodeFromBase64(s)); } } - final List actualNames = Lists.newArrayList(); + final Set actualNames = new HashSet<>(); for (BoxDescriptor b : r.body().boxes) { actualNames.add(b.name); } - Assert.assertEquals("expected and actual box names length do not match", expectedNames.size(), actualNames.size()); - for (byte[] e : expectedNames) { - if (!contains(e, actualNames)) - throw new RuntimeException("expected and actual box names do not match: " + expectedNames + " != " + actualNames); - } + assertSetOfByteArraysEqual(expectedNames, actualNames); } @Then("I sleep for {int} milliseconds for indexer to digest things down.") diff --git a/src/test/java/com/algorand/algosdk/util/ComparableBytes.java b/src/test/java/com/algorand/algosdk/util/ComparableBytes.java new file mode 100644 index 000000000..da5bd8773 --- /dev/null +++ b/src/test/java/com/algorand/algosdk/util/ComparableBytes.java @@ -0,0 +1,31 @@ +package com.algorand.algosdk.util; + +import java.util.Arrays; + +import org.apache.commons.codec.binary.Hex; + +public class ComparableBytes { + private final byte[] data; + + public ComparableBytes(byte[] data) { + this.data = data; + } + + @Override + public String toString() { + return Hex.encodeHexString(data); + } + + @Override + public int hashCode() { + return Arrays.hashCode(data); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComparableBytes that = (ComparableBytes) o; + return Arrays.equals(data, that.data); + } +} From 89e162296a994d8e7a0b7728569ede016b5ee904 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Wed, 28 Sep 2022 12:57:14 -0400 Subject: [PATCH 17/20] Rework MethodCallParams to be backwards compatible with Boxes (#408) --- .../MethodCallTransactionBuilder.java | 34 ++++++++++++++-- .../algosdk/transaction/MethodCallParams.java | 40 ++++++++++++++----- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java index 3a28ee086..d42b2dd7f 100644 --- a/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java +++ b/src/main/java/com/algorand/algosdk/builder/transaction/MethodCallTransactionBuilder.java @@ -2,6 +2,7 @@ import com.algorand.algosdk.abi.Method; import com.algorand.algosdk.crypto.Address; +import com.algorand.algosdk.crypto.Digest; import com.algorand.algosdk.crypto.TEALProgram; import com.algorand.algosdk.logic.StateSchema; import com.algorand.algosdk.transaction.AppBoxReference; @@ -9,6 +10,7 @@ import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.transaction.TxnSigner; +import java.math.BigInteger; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -174,10 +176,34 @@ public T extraPages(Long extraPages) { * Build a MethodCallParams object. */ public MethodCallParams build() { - return new MethodCallParams( - appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash, + return new MethodCallParamsFactory(appID, method, methodArgs, sender, onCompletion, note, lease, genesisID, genesisHash, firstValid, lastValid, fee, flatFee, rekeyTo, signer, foreignAccounts, foreignAssets, foreignApps, - boxReferences, approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages - ); + boxReferences, approvalProgram, clearStateProgram, globalStateSchema, localStateSchema, extraPages); + } + + /** + * MethodCallParamsFactory exists only as a way to facilitate construction of + * `MethodCallParams` instances via a protected constructor. + *

+ * No extension or other modification is intended. + */ + private static class MethodCallParamsFactory extends MethodCallParams { + + MethodCallParamsFactory(Long appID, Method method, List methodArgs, Address sender, + Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, + BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, + Address rekeyTo, TxnSigner signer, + List
fAccounts, List fAssets, List fApps, List boxes, + TEALProgram approvalProgram, TEALProgram clearProgram, + StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { + super(appID, method, methodArgs, sender, + onCompletion, note, lease, genesisID, genesisHash, + firstValid, lastValid, fee, flatFee, + rekeyTo, signer, + fAccounts, fAssets, fApps, boxes, + approvalProgram, clearProgram, + globalStateSchema, localStateSchema, extraPages); + } + } } diff --git a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java index edc4b0a8c..21d5fd542 100644 --- a/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java +++ b/src/main/java/com/algorand/algosdk/transaction/MethodCallParams.java @@ -22,6 +22,7 @@ * MethodCallParams is an object that holds all parameters necessary to invoke {@link AtomicTransactionComposer#addMethodCall(MethodCallParams)} */ public class MethodCallParams { + // if the abi type argument number > 15, then the abi types after 14th should be wrapped in a tuple private static final int MAX_ABI_ARG_TYPE_LEN = 15; @@ -55,17 +56,13 @@ public class MethodCallParams { public final String genesisID; public final Digest genesisHash; - /** - * NOTE: it's strongly suggested to use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder} - * instead of this constructor to create a new MethodCallParams object. - */ - public MethodCallParams(Long appID, Method method, List methodArgs, Address sender, - Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, - BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, - Address rekeyTo, TxnSigner signer, - List
fAccounts, List fAssets, List fApps, List boxes, - TEALProgram approvalProgram, TEALProgram clearProgram, - StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { + protected MethodCallParams(Long appID, Method method, List methodArgs, Address sender, + Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, + BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, + Address rekeyTo, TxnSigner signer, + List
fAccounts, List fAssets, List fApps, List boxes, + TEALProgram approvalProgram, TEALProgram clearProgram, + StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { if (appID == null || method == null || sender == null || onCompletion == null || signer == null || genesisID == null || genesisHash == null || firstValid == null || lastValid == null || (fee == null && flatFee == null)) throw new IllegalArgumentException("Method call builder error: some required field not added"); if (fee != null && flatFee != null) @@ -122,6 +119,27 @@ public MethodCallParams(Long appID, Method method, List methodArgs, Addr this.extraPages = extraPages; } + /** + * Deprecated - Use {@link com.algorand.algosdk.builder.transaction.MethodCallTransactionBuilder} + * to create a new MethodCallParams object instead. + */ + @Deprecated + public MethodCallParams(Long appID, Method method, List methodArgs, Address sender, + Transaction.OnCompletion onCompletion, byte[] note, byte[] lease, String genesisID, Digest genesisHash, + BigInteger firstValid, BigInteger lastValid, BigInteger fee, BigInteger flatFee, + Address rekeyTo, TxnSigner signer, + List
fAccounts, List fAssets, List fApps, + TEALProgram approvalProgram, TEALProgram clearProgram, + StateSchema globalStateSchema, StateSchema localStateSchema, Long extraPages) { + this(appID, method, methodArgs, sender, + onCompletion, note, lease, genesisID, genesisHash, + firstValid, lastValid, fee, flatFee, + rekeyTo, signer, + fAccounts, fAssets, fApps, new ArrayList(), + approvalProgram, clearProgram, + globalStateSchema, localStateSchema, extraPages); + } + /** * Create the transactions which will carry out the specified method call. *

From 24bd5b72c0fddbbc60f1d6ccfa1cf054eec49357 Mon Sep 17 00:00:00 2001 From: michaeldiamant Date: Mon, 31 Oct 2022 12:40:46 -0400 Subject: [PATCH 18/20] Merge in develop --- .test-env | 2 + CHANGELOG.md | 17 +++++ Makefile | 5 +- README.md | 2 +- pom.xml | 2 +- .../com/algorand/algosdk/abi/Contract.java | 1 - .../com/algorand/algosdk/abi/Interface.java | 1 - .../java/com/algorand/algosdk/abi/Method.java | 7 +- .../com/algorand/algosdk/abi/TypeTuple.java | 5 +- .../algosdk/algod/client/AlgodClient.java | 1 + .../algosdk/algod/client/ApiCallback.java | 2 + .../algosdk/algod/client/ApiClient.java | 2 +- .../algosdk/algod/client/ApiException.java | 2 +- .../algosdk/algod/client/ApiResponse.java | 2 + .../algosdk/algod/client/Configuration.java | 2 +- .../algod/client/GzipRequestInterceptor.java | 1 + .../algorand/algosdk/algod/client/JSON.java | 1 + .../algod/client/ProgressRequestBody.java | 1 + .../algod/client/ProgressResponseBody.java | 1 + .../algosdk/algod/client/StringUtil.java | 2 +- .../algosdk/algod/client/api/AlgodApi.java | 4 ++ .../algosdk/algod/client/api/DefaultApi.java | 4 ++ .../algosdk/algod/client/auth/ApiKeyAuth.java | 2 +- .../algod/client/auth/Authentication.java | 1 + .../algod/client/auth/HttpBasicAuth.java | 1 + .../algosdk/algod/client/auth/OAuth.java | 2 +- .../algosdk/algod/client/auth/OAuthFlow.java | 1 + .../algosdk/algod/client/lib/Pair.java | 2 +- .../algosdk/algod/client/model/Account.java | 1 + .../algod/client/model/AssetHolding.java | 1 + .../algod/client/model/AssetParams.java | 1 + .../algosdk/algod/client/model/Block.java | 2 +- .../algod/client/model/NodeStatus.java | 2 +- .../algod/client/model/Participation.java | 2 +- .../client/model/PaymentTransactionType.java | 2 +- .../client/model/PendingTransactions.java | 2 +- .../algosdk/algod/client/model/Supply.java | 2 +- .../algod/client/model/Transaction.java | 2 +- .../algod/client/model/TransactionFee.java | 2 +- .../algod/client/model/TransactionID.java | 2 +- .../algod/client/model/TransactionList.java | 2 +- .../algod/client/model/TransactionParams.java | 2 +- .../client/model/TransactionResults.java | 2 +- .../algosdk/algod/client/model/Version.java | 2 +- .../algosdk/v2/client/algod/GetBlockHash.java | 65 +++++++++++++++++++ .../algosdk/v2/client/common/AlgodClient.java | 9 +++ .../v2/client/indexer/LookupBlock.java | 9 +++ .../v2/client/model/BlockHashResponse.java | 30 +++++++++ .../com/algorand/algosdk/unit/AlgodPaths.java | 5 ++ .../algorand/algosdk/unit/IndexerPaths.java | 7 ++ .../algosdk/unit/ResponsesShared.java | 3 + src/test/unit.tags | 2 + test-harness.sh | 55 ++++++++++++++++ 53 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockHash.java create mode 100644 src/main/java/com/algorand/algosdk/v2/client/model/BlockHashResponse.java diff --git a/.test-env b/.test-env index eff6690c0..9135343b7 100644 --- a/.test-env +++ b/.test-env @@ -3,6 +3,8 @@ SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" SDK_TESTING_BRANCH="feature/box-storage" SDK_TESTING_HARNESS="test-harness" +INSTALL_ONLY=0 + VERBOSE_HARNESS=0 # WARNING: If set to 1, new features will be LOST when downloading the test harness. diff --git a/CHANGELOG.md b/CHANGELOG.md index c2784a93e..19c94e171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# 1.20.0 + +### Enhancements +* REST API: Add algod block hash endpoint, add indexer block header-only param. by @winder in https://github.com/algorand/java-algorand-sdk/pull/413 + +# 1.19.0 + +### Enhancements +* Deprecation: Add deprecation tags to v1 `algod` API by @algochoi in https://github.com/algorand/java-algorand-sdk/pull/388 +### Other +* Regenerate code with the latest specification file (b243e19e) by @github-actions in https://github.com/algorand/java-algorand-sdk/pull/387 + +## New Contributors +* @github-actions made their first contribution in https://github.com/algorand/java-algorand-sdk/pull/387 + +**Full Changelog**: https://github.com/algorand/java-algorand-sdk/compare/1.18.0...1.19.0 + # 1.18.0 ## What's Changed diff --git a/Makefile b/Makefile index be60d45d2..609884968 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,10 @@ display-all-java-steps: find src/test/java/com/algorand/algosdk -name "*.java" | xargs grep "io.cucumber.java.en" 2>/dev/null | grep -v Binary | cut -d: -f1 | sort | uniq | xargs grep -E "@(Given|Then|When)" harness: - ./test-harness.sh + ./test-harness.sh up + +harness-down: + ./test-harness.sh down docker-javasdk-build: # Build SDK testing environment diff --git a/README.md b/README.md index 5e5d913e1..3201a8f04 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Maven: com.algorand algosdk - 1.18.0 + 1.20.0 ``` diff --git a/pom.xml b/pom.xml index 4ef3c145b..1544dfa91 100755 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.algorand algosdk - 1.18.0 + 1.20.0 jar ${project.groupId}:${project.artifactId} diff --git a/src/main/java/com/algorand/algosdk/abi/Contract.java b/src/main/java/com/algorand/algosdk/abi/Contract.java index 2690f1eb5..b44d52830 100644 --- a/src/main/java/com/algorand/algosdk/abi/Contract.java +++ b/src/main/java/com/algorand/algosdk/abi/Contract.java @@ -1,6 +1,5 @@ package com.algorand.algosdk.abi; -import com.algorand.algosdk.algod.client.StringUtil; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/algorand/algosdk/abi/Interface.java b/src/main/java/com/algorand/algosdk/abi/Interface.java index a00ea4387..324afd63a 100644 --- a/src/main/java/com/algorand/algosdk/abi/Interface.java +++ b/src/main/java/com/algorand/algosdk/abi/Interface.java @@ -1,6 +1,5 @@ package com.algorand.algosdk.abi; -import com.algorand.algosdk.algod.client.StringUtil; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/src/main/java/com/algorand/algosdk/abi/Method.java b/src/main/java/com/algorand/algosdk/abi/Method.java index 0d1f78c24..0ef62c323 100644 --- a/src/main/java/com/algorand/algosdk/abi/Method.java +++ b/src/main/java/com/algorand/algosdk/abi/Method.java @@ -1,6 +1,5 @@ package com.algorand.algosdk.abi; -import com.algorand.algosdk.algod.client.StringUtil; import com.algorand.algosdk.transaction.Transaction; import com.algorand.algosdk.util.CryptoProvider; import com.fasterxml.jackson.annotation.JsonCreator; @@ -20,6 +19,8 @@ import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.StringUtils; + @JsonInclude(JsonInclude.Include.NON_NULL) public class Method { @JsonIgnore @@ -150,7 +151,7 @@ else if (method.charAt(i) == ')') { public String getSignature() { List argStringList = new ArrayList<>(); for (Arg value : this.args) argStringList.add(value.type); - return this.name + "(" + StringUtil.join(argStringList.toArray(new String[0]), ",") + ")" + this.returns.type; + return this.name + "(" + StringUtils.join(argStringList.toArray(new String[0]), ",") + ")" + this.returns.type; } @JsonIgnore @@ -195,7 +196,7 @@ public static Method getMethodByName(List methods, String name) { for(int idx=0;idx childTypes; @@ -23,7 +24,7 @@ public String toString() { List childStrs = new ArrayList<>(); for (ABIType t : this.childTypes) childStrs.add(t.toString()); - return "(" + StringUtil.join(childStrs.toArray(new String[0]), ",") + ")"; + return "(" + StringUtils.join(childStrs.toArray(new String[0]), ",") + ")"; } @Override diff --git a/src/main/java/com/algorand/algosdk/algod/client/AlgodClient.java b/src/main/java/com/algorand/algosdk/algod/client/AlgodClient.java index f9dbf434c..7a9864ff1 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/algod/client/AlgodClient.java @@ -1,4 +1,5 @@ package com.algorand.algosdk.algod.client; +@Deprecated public class AlgodClient extends ApiClient { } diff --git a/src/main/java/com/algorand/algosdk/algod/client/ApiCallback.java b/src/main/java/com/algorand/algosdk/algod/client/ApiCallback.java index 3d0610afb..2c9ca7ec8 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ApiCallback.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ApiCallback.java @@ -22,7 +22,9 @@ * Callback for asynchronous API call. * * @param The return type + * @deprecated Use the equivalent in v2 algod client */ +@Deprecated public interface ApiCallback { /** * This is called when the API call fails. diff --git a/src/main/java/com/algorand/algosdk/algod/client/ApiClient.java b/src/main/java/com/algorand/algosdk/algod/client/ApiClient.java index d4645cb3f..da2cf99b9 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ApiClient.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ApiClient.java @@ -50,7 +50,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; - +@Deprecated public class ApiClient { private String basePath = "http://localhost"; diff --git a/src/main/java/com/algorand/algosdk/algod/client/ApiException.java b/src/main/java/com/algorand/algosdk/algod/client/ApiException.java index 4c66399ba..887ce32e7 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ApiException.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ApiException.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.List; - +@Deprecated public class ApiException extends Exception { private int code = 0; private Map> responseHeaders = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/ApiResponse.java b/src/main/java/com/algorand/algosdk/algod/client/ApiResponse.java index 13e8330ab..a6d0c839f 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ApiResponse.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ApiResponse.java @@ -20,7 +20,9 @@ * API response returned by API call. * * @param The type of data that is deserialized from response body + * @deprecated Use the equivalent in v2 algod client */ +@Deprecated public class ApiResponse { final private int statusCode; final private Map> headers; diff --git a/src/main/java/com/algorand/algosdk/algod/client/Configuration.java b/src/main/java/com/algorand/algosdk/algod/client/Configuration.java index f5be88fd3..d6f969014 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/Configuration.java +++ b/src/main/java/com/algorand/algosdk/algod/client/Configuration.java @@ -13,7 +13,7 @@ package com.algorand.algosdk.algod.client; - +@Deprecated public class Configuration { private static ApiClient defaultApiClient = new ApiClient(); diff --git a/src/main/java/com/algorand/algosdk/algod/client/GzipRequestInterceptor.java b/src/main/java/com/algorand/algosdk/algod/client/GzipRequestInterceptor.java index 926402cac..b048ed735 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/GzipRequestInterceptor.java +++ b/src/main/java/com/algorand/algosdk/algod/client/GzipRequestInterceptor.java @@ -26,6 +26,7 @@ * * Taken from https://github.com/square/okhttp/issues/350 */ +@Deprecated class GzipRequestInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); diff --git a/src/main/java/com/algorand/algosdk/algod/client/JSON.java b/src/main/java/com/algorand/algosdk/algod/client/JSON.java index df46c09d8..1591623cc 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/JSON.java +++ b/src/main/java/com/algorand/algosdk/algod/client/JSON.java @@ -32,6 +32,7 @@ import java.util.Date; import java.util.Map; +@Deprecated public class JSON { private Gson gson; private boolean isLenientOnJson = false; diff --git a/src/main/java/com/algorand/algosdk/algod/client/ProgressRequestBody.java b/src/main/java/com/algorand/algosdk/algod/client/ProgressRequestBody.java index 26ffb4c63..ee41e3e8c 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ProgressRequestBody.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ProgressRequestBody.java @@ -24,6 +24,7 @@ import okio.Okio; import okio.Sink; +@Deprecated public class ProgressRequestBody extends RequestBody { public interface ProgressRequestListener { diff --git a/src/main/java/com/algorand/algosdk/algod/client/ProgressResponseBody.java b/src/main/java/com/algorand/algosdk/algod/client/ProgressResponseBody.java index 1404cbecb..354c94ca5 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/ProgressResponseBody.java +++ b/src/main/java/com/algorand/algosdk/algod/client/ProgressResponseBody.java @@ -24,6 +24,7 @@ import okio.Okio; import okio.Source; +@Deprecated public class ProgressResponseBody extends ResponseBody { public interface ProgressListener { diff --git a/src/main/java/com/algorand/algosdk/algod/client/StringUtil.java b/src/main/java/com/algorand/algosdk/algod/client/StringUtil.java index c76af87ad..f296916b6 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/StringUtil.java +++ b/src/main/java/com/algorand/algosdk/algod/client/StringUtil.java @@ -13,7 +13,7 @@ package com.algorand.algosdk.algod.client; - +@Deprecated public class StringUtil { /** * Check if the given array contains the given value (with case-insensitive comparison). diff --git a/src/main/java/com/algorand/algosdk/algod/client/api/AlgodApi.java b/src/main/java/com/algorand/algosdk/algod/client/api/AlgodApi.java index f1d079f67..02342465b 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/api/AlgodApi.java +++ b/src/main/java/com/algorand/algosdk/algod/client/api/AlgodApi.java @@ -4,6 +4,10 @@ import com.algorand.algosdk.algod.client.ApiClient; import com.algorand.algosdk.algod.client.Configuration; +/** + * @deprecated Use the equivalent in v2 algod client + */ +@Deprecated public class AlgodApi extends DefaultApi { public AlgodApi() { diff --git a/src/main/java/com/algorand/algosdk/algod/client/api/DefaultApi.java b/src/main/java/com/algorand/algosdk/algod/client/api/DefaultApi.java index 33f2b61a6..40c8471ae 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/api/DefaultApi.java +++ b/src/main/java/com/algorand/algosdk/algod/client/api/DefaultApi.java @@ -26,6 +26,10 @@ import java.util.List; import java.util.Map; +/** + * @deprecated Use the equivalent in v2 algod client + */ +@Deprecated public class DefaultApi { private ApiClient apiClient; diff --git a/src/main/java/com/algorand/algosdk/algod/client/auth/ApiKeyAuth.java b/src/main/java/com/algorand/algosdk/algod/client/auth/ApiKeyAuth.java index 23dba9f65..d11d0dfaa 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/auth/ApiKeyAuth.java +++ b/src/main/java/com/algorand/algosdk/algod/client/auth/ApiKeyAuth.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.Map; - +@Deprecated public class ApiKeyAuth implements Authentication { private final String location; private final String paramName; diff --git a/src/main/java/com/algorand/algosdk/algod/client/auth/Authentication.java b/src/main/java/com/algorand/algosdk/algod/client/auth/Authentication.java index a7b2c1fdc..a9b916404 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/auth/Authentication.java +++ b/src/main/java/com/algorand/algosdk/algod/client/auth/Authentication.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; +@Deprecated public interface Authentication { /** * Apply authentication settings to header and query params. diff --git a/src/main/java/com/algorand/algosdk/algod/client/auth/HttpBasicAuth.java b/src/main/java/com/algorand/algosdk/algod/client/auth/HttpBasicAuth.java index 46d7da0b6..6e72de595 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/auth/HttpBasicAuth.java +++ b/src/main/java/com/algorand/algosdk/algod/client/auth/HttpBasicAuth.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Map; +@Deprecated public class HttpBasicAuth implements Authentication { private String username; private String password; diff --git a/src/main/java/com/algorand/algosdk/algod/client/auth/OAuth.java b/src/main/java/com/algorand/algosdk/algod/client/auth/OAuth.java index 51be628fc..4e7678c2b 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/auth/OAuth.java +++ b/src/main/java/com/algorand/algosdk/algod/client/auth/OAuth.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.Map; - +@Deprecated public class OAuth implements Authentication { private String accessToken; diff --git a/src/main/java/com/algorand/algosdk/algod/client/auth/OAuthFlow.java b/src/main/java/com/algorand/algosdk/algod/client/auth/OAuthFlow.java index 34fdccb0f..f7a56a0e1 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/auth/OAuthFlow.java +++ b/src/main/java/com/algorand/algosdk/algod/client/auth/OAuthFlow.java @@ -13,6 +13,7 @@ package com.algorand.algosdk.algod.client.auth; +@Deprecated public enum OAuthFlow { accessCode, implicit, password, application } diff --git a/src/main/java/com/algorand/algosdk/algod/client/lib/Pair.java b/src/main/java/com/algorand/algosdk/algod/client/lib/Pair.java index dce90feee..31189fb43 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/lib/Pair.java +++ b/src/main/java/com/algorand/algosdk/algod/client/lib/Pair.java @@ -13,7 +13,7 @@ package com.algorand.algosdk.algod.client.lib; - +@Deprecated public class Pair { private String name = ""; private String value = ""; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Account.java b/src/main/java/com/algorand/algosdk/algod/client/model/Account.java index 65a9d1aa4..3e8b60460 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Account.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Account.java @@ -26,6 +26,7 @@ */ @ApiModel(description = "Account Description") +@Deprecated public class Account { @SerializedName("address") private String address = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/AssetHolding.java b/src/main/java/com/algorand/algosdk/algod/client/model/AssetHolding.java index 8dc1fa6be..2417055fc 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/AssetHolding.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/AssetHolding.java @@ -15,6 +15,7 @@ import io.swagger.annotations.ApiModelProperty; @ApiModel(description = "AssetParams specifies the holdings of a particular asset.") +@Deprecated public class AssetHolding { @SerializedName("creator") private String creator = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/AssetParams.java b/src/main/java/com/algorand/algosdk/algod/client/model/AssetParams.java index 10d91b95d..27fd76eee 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/AssetParams.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/AssetParams.java @@ -21,6 +21,7 @@ */ @ApiModel(description = "AssetParams specifies the parameters for an asset") +@Deprecated public class AssetParams { @SerializedName("assetname") private String assetname = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Block.java b/src/main/java/com/algorand/algosdk/algod/client/model/Block.java index 0a0939684..16aeeddea 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Block.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Block.java @@ -22,7 +22,7 @@ * Block contains a block information */ @ApiModel(description = "Block contains a block information") - +@Deprecated public class Block { @SerializedName("currentProtocol") private String currentProtocol = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/NodeStatus.java b/src/main/java/com/algorand/algosdk/algod/client/model/NodeStatus.java index 45809b763..32f76c808 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/NodeStatus.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/NodeStatus.java @@ -22,7 +22,7 @@ * NodeStatus contains the information about a node status */ @ApiModel(description = "NodeStatus contains the information about a node status") - +@Deprecated public class NodeStatus { @SerializedName("catchupTime") private java.math.BigInteger catchupTime = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Participation.java b/src/main/java/com/algorand/algosdk/algod/client/model/Participation.java index 58e25bce6..867597e8c 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Participation.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Participation.java @@ -27,7 +27,7 @@ * Participation Description */ @ApiModel(description = "Participation Description") - +@Deprecated public class Participation { @SerializedName("partpkb64") private byte[] partpkb64 = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/PaymentTransactionType.java b/src/main/java/com/algorand/algosdk/algod/client/model/PaymentTransactionType.java index c9e736330..34f59bb4a 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/PaymentTransactionType.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/PaymentTransactionType.java @@ -22,7 +22,7 @@ * PaymentTransactionType contains the additional fields for a payment Transaction */ @ApiModel(description = "PaymentTransactionType contains the additional fields for a payment Transaction") - +@Deprecated public class PaymentTransactionType { @SerializedName("amount") private java.math.BigInteger amount = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/PendingTransactions.java b/src/main/java/com/algorand/algosdk/algod/client/model/PendingTransactions.java index 58fa9406d..29c35bffd 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/PendingTransactions.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/PendingTransactions.java @@ -22,7 +22,7 @@ * PendingTransactions represents a potentially truncated list of transactions currently in the node's transaction pool. */ @ApiModel(description = "PendingTransactions represents a potentially truncated list of transactions currently in the node's transaction pool.") - +@Deprecated public class PendingTransactions { @SerializedName("totalTxns") private java.math.BigInteger totalTxns = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Supply.java b/src/main/java/com/algorand/algosdk/algod/client/model/Supply.java index c9b0bfa0b..c740c5313 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Supply.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Supply.java @@ -22,7 +22,7 @@ * Supply represents the current supply of MicroAlgos in the system */ @ApiModel(description = "Supply represents the current supply of MicroAlgos in the system") - +@Deprecated public class Supply { @SerializedName("onlineMoney") private java.math.BigInteger onlineMoney = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Transaction.java b/src/main/java/com/algorand/algosdk/algod/client/model/Transaction.java index 5591c0bf8..499e4053a 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Transaction.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Transaction.java @@ -24,7 +24,7 @@ * Transaction contains all fields common to all transactions and serves as an envelope to all transactions type */ @ApiModel(description = "Transaction contains all fields common to all transactions and serves as an envelope to all transactions type") - +@Deprecated public class Transaction { @SerializedName("fee") private java.math.BigInteger fee = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionFee.java b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionFee.java index f04588d91..788dd13ad 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionFee.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionFee.java @@ -22,7 +22,7 @@ * TransactionFee contains the suggested fee */ @ApiModel(description = "TransactionFee contains the suggested fee") - +@Deprecated public class TransactionFee { @SerializedName("fee") private java.math.BigInteger fee = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionID.java b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionID.java index 9e097d404..b93448950 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionID.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionID.java @@ -22,7 +22,7 @@ * TransactionID Description */ @ApiModel(description = "TransactionID Description") - +@Deprecated public class TransactionID { @SerializedName("txId") private String txId = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionList.java b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionList.java index 035c83d30..d9c5fe413 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionList.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionList.java @@ -25,7 +25,7 @@ * TransactionList contains a list of transactions */ @ApiModel(description = "TransactionList contains a list of transactions") - +@Deprecated public class TransactionList { @SerializedName("transactions") private List transactions = new ArrayList(); diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionParams.java b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionParams.java index 58c4b5fce..e83f3f562 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionParams.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionParams.java @@ -22,7 +22,7 @@ * TransactionParams contains the parameters that help a client construct a new transaction. */ @ApiModel(description = "TransactionParams contains the parameters that help a client construct a new transaction.") - +@Deprecated public class TransactionParams { @SerializedName("consensusVersion") private String consensusVersion = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionResults.java b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionResults.java index 244eacfea..f206f6a2b 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/TransactionResults.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/TransactionResults.java @@ -27,7 +27,7 @@ * TransactionResults contains information about the side effects of a transaction */ @ApiModel(description = "TransactionResults contains information about the side effects of a transaction") - +@Deprecated public class TransactionResults { @SerializedName("createdasset") private java.math.BigInteger createdasset = null; diff --git a/src/main/java/com/algorand/algosdk/algod/client/model/Version.java b/src/main/java/com/algorand/algosdk/algod/client/model/Version.java index 0218c1ae3..ab1334182 100644 --- a/src/main/java/com/algorand/algosdk/algod/client/model/Version.java +++ b/src/main/java/com/algorand/algosdk/algod/client/model/Version.java @@ -25,7 +25,7 @@ * Note that we annotate this as a model so that legacy clients can directly import a swagger generated Version model. */ @ApiModel(description = "Note that we annotate this as a model so that legacy clients can directly import a swagger generated Version model.") - +@Deprecated public class Version { @SerializedName("genesis_hash_b64") private byte[] genesisHashB64 = null; diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockHash.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockHash.java new file mode 100644 index 000000000..e07dd89fc --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetBlockHash.java @@ -0,0 +1,65 @@ +package com.algorand.algosdk.v2.client.algod; + +import com.algorand.algosdk.v2.client.common.Client; +import com.algorand.algosdk.v2.client.common.HttpMethod; +import com.algorand.algosdk.v2.client.common.Query; +import com.algorand.algosdk.v2.client.common.QueryData; +import com.algorand.algosdk.v2.client.common.Response; +import com.algorand.algosdk.v2.client.model.BlockHashResponse; + + +/** + * Get the block hash for the block on the given round. + * /v2/blocks/{round}/hash + */ +public class GetBlockHash extends Query { + + private Long round; + + /** + * @param round The round from which to fetch block hash information. + */ + public GetBlockHash(Client client, Long round) { + super(client, new HttpMethod("get")); + this.round = round; + } + + /** + * Execute the query. + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute() throws Exception { + Response resp = baseExecute(); + resp.setValueType(BlockHashResponse.class); + return resp; + } + + /** + * Execute the query with custom headers, there must be an equal number of keys and values + * or else an error will be generated. + * @param headers an array of header keys + * @param values an array of header values + * @return the query response object. + * @throws Exception + */ + @Override + public Response execute(String[] headers, String[] values) throws Exception { + Response resp = baseExecute(headers, values); + resp.setValueType(BlockHashResponse.class); + return resp; + } + + protected QueryData getRequestString() { + if (this.round == null) { + throw new RuntimeException("round is not set. It is a required parameter."); + } + addPathSegment(String.valueOf("v2")); + addPathSegment(String.valueOf("blocks")); + addPathSegment(String.valueOf(round)); + addPathSegment(String.valueOf("hash")); + + return qd; + } +} diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index 8340e7d16..cab8449e8 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -10,6 +10,7 @@ import com.algorand.algosdk.v2.client.algod.AccountApplicationInformation; import com.algorand.algosdk.v2.client.algod.GetPendingTransactionsByAddress; import com.algorand.algosdk.v2.client.algod.GetBlock; +import com.algorand.algosdk.v2.client.algod.GetBlockHash; import com.algorand.algosdk.v2.client.algod.GetTransactionProof; import com.algorand.algosdk.v2.client.algod.GetSupply; import com.algorand.algosdk.v2.client.algod.GetStatus; @@ -143,6 +144,14 @@ public GetBlock GetBlock(Long round) { return new GetBlock((Client) this, round); } + /** + * Get the block hash for the block on the given round. + * /v2/blocks/{round}/hash + */ + public GetBlockHash GetBlockHash(Long round) { + return new GetBlockHash((Client) this, round); + } + /** * Get a proof for a transaction in a block. * /v2/blocks/{round}/transactions/{txid}/proof diff --git a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupBlock.java b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupBlock.java index 0e6d117cc..1794d77e3 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupBlock.java +++ b/src/main/java/com/algorand/algosdk/v2/client/indexer/LookupBlock.java @@ -24,6 +24,15 @@ public LookupBlock(Client client, Long roundNumber) { this.roundNumber = roundNumber; } + /** + * Header only flag. When this is set to true, returned block does not contain the + * transactions + */ + public LookupBlock headerOnly(Boolean headerOnly) { + addQuery("header-only", String.valueOf(headerOnly)); + return this; + } + /** * Execute the query. * @return the query response object. diff --git a/src/main/java/com/algorand/algosdk/v2/client/model/BlockHashResponse.java b/src/main/java/com/algorand/algosdk/v2/client/model/BlockHashResponse.java new file mode 100644 index 000000000..8ccb0e91a --- /dev/null +++ b/src/main/java/com/algorand/algosdk/v2/client/model/BlockHashResponse.java @@ -0,0 +1,30 @@ +package com.algorand.algosdk.v2.client.model; + +import java.util.Objects; + +import com.algorand.algosdk.v2.client.common.PathResponse; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Hash of a block header. + */ +public class BlockHashResponse extends PathResponse { + + /** + * Block header hash. + */ + @JsonProperty("blockHash") + public String blockHash; + + @Override + public boolean equals(Object o) { + + if (this == o) return true; + if (o == null) return false; + + BlockHashResponse other = (BlockHashResponse) o; + if (!Objects.deepEquals(this.blockHash, other.blockHash)) return false; + + return true; + } +} diff --git a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java index f3f92e423..fb9c6ba07 100644 --- a/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/AlgodPaths.java @@ -116,4 +116,9 @@ public void getLightBlockHeaderProof(Long round) { public void getStateProof(Long round) { ps.q = algodClient.GetStateProof(round); } + + @When("we make a Lookup Block Hash call against round {long}") + public void getBlockHash(Long round) { + ps.q = algodClient.GetBlockHash(round); + } } diff --git a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java index 90032256e..eb49c99a7 100644 --- a/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java +++ b/src/test/java/com/algorand/algosdk/unit/IndexerPaths.java @@ -345,4 +345,11 @@ public void searchApplicationBoxes(Long appID, Long maxRes, String nextToken) { if (TestingUtils.notEmpty(nextToken)) q.next(nextToken); ps.q = q; } + + @When("we make a Lookup Block call against round {long} and header {string}") + public void anyBlockLookupCall(Long round, String headerOnly) { + LookupBlock q = this.indexerClient.lookupBlock(round); + if (headerOnly.contentEquals("true")) q.headerOnly(true); + ps.q = q; + } } diff --git a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java index ae4f37c96..2565d51cd 100644 --- a/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java +++ b/src/test/java/com/algorand/algosdk/unit/ResponsesShared.java @@ -191,6 +191,9 @@ public void we_make_any_call_to(String client, String endpoint) throws Exception Response r = algod.GetStateProof(1234L).execute(); response = r; break; + case "GetBlockHash": + response = algod.GetBlockHash(1234L).execute(); + break; default: Assertions.fail("Unsupported algod endpoint: " + endpoint); } diff --git a/src/test/unit.tags b/src/test/unit.tags index fc6e96482..b38b77d38 100644 --- a/src/test/unit.tags +++ b/src/test/unit.tags @@ -5,6 +5,7 @@ @unit.applications @unit.applications.boxes @unit.atomic_transaction_composer +@unit.blocksummary @unit.dryrun @unit.dryrun.trace.application @unit.feetest @@ -20,6 +21,7 @@ @unit.responses.messagepack @unit.responses.messagepack.231 @unit.responses.unlimited_assets +@unit.responses.blocksummary @unit.sourcemap @unit.stateproof.paths @unit.stateproof.responses diff --git a/test-harness.sh b/test-harness.sh index a5d28a781..c348966d4 100755 --- a/test-harness.sh +++ b/test-harness.sh @@ -1,6 +1,47 @@ #!/usr/bin/env bash set -euo pipefail +# test-harness.sh setup/start cucumber test environment. +# +# Configuration is managed with environment variables, the ones you +# are most likely to reconfigured are stored in '.test-env'. +# +# Variables: +# SDK_TESTING_URL - URL to algorand-sdk-testing, useful for forks. +# SDK_TESTING_BRANCH - branch to checkout, useful for new tests. +# SDK_TESTING_HARNESS - local directory that the algorand-sdk-testing repo is cloned into. +# VERBOSE_HARNESS - more output while the script runs. +# INSTALL_ONLY - installs feature files only, useful for unit tests. +# +# WARNING: If set to 1, new features will be LOST when downloading the test harness. +# REGARDLESS: modified features are ALWAYS overwritten. +# REMOVE_LOCAL_FEATURES - delete all local cucumber feature files before downloading these from github. +# +# WARNING: Be careful when turning on the next variable. +# In that case you'll need to provide all variables expected by `algorand-sdk-testing`'s `.env` +# OVERWRITE_TESTING_ENVIRONMENT=0 + +SHUTDOWN=0 +if [ $# -ne 0 ]; then + if [ $# -ne 1 ]; then + echo "this script accepts a single argument, which must be 'up' or 'down'." + exit 1 + fi + + case $1 in + 'up') + ;; # default. + 'down') + SHUTDOWN=1 + ;; + *) + echo "unknown parameter '$1'." + echo "this script accepts a single argument, which must be 'up' or 'down'." + exit 1 + ;; + esac +fi + START=$(date "+%s") THIS=$(basename "$0") @@ -23,10 +64,19 @@ if [ -d "$SDK_TESTING_HARNESS" ]; then ./scripts/down.sh popd rm -rf "$SDK_TESTING_HARNESS" + if [[ $SHUTDOWN == 1 ]]; then + echo "$THIS: network shutdown complete." + exit 0 + fi else echo "$THIS: directory $SDK_TESTING_HARNESS does not exist - NOOP" fi +if [[ $SHUTDOWN == 1 ]]; then + echo "$THIS: unable to shutdown network." + exit 1 +fi + git clone --depth 1 --single-branch --branch "$SDK_TESTING_BRANCH" "$SDK_TESTING_URL" "$SDK_TESTING_HARNESS" @@ -63,6 +113,11 @@ if [[ $VERBOSE_HARNESS == 1 ]]; then fi echo "$THIS: seconds it took to get to end of cloning and copying: $(($(date "+%s") - START))s" +if [[ $INSTALL_ONLY == 1 ]]; then + echo "$THIS: configured to install feature files only. Not starting test harness environment." + exit 0 +fi + ## Start test harness environment pushd "$SDK_TESTING_HARNESS" From fde8156955169b79a00bac92c7fad998884b7666 Mon Sep 17 00:00:00 2001 From: michaeldiamant Date: Mon, 31 Oct 2022 12:41:30 -0400 Subject: [PATCH 19/20] Regenerate sources from upstream AVM branches --- .../algosdk/v2/client/algod/GetApplicationBoxes.java | 5 +++-- .../com/algorand/algosdk/v2/client/common/AlgodClient.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java index 5dd27a9a5..f8a734254 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java +++ b/src/main/java/com/algorand/algosdk/v2/client/algod/GetApplicationBoxes.java @@ -9,8 +9,9 @@ /** - * Given an application ID, it returns the box names of that application. No - * particular ordering is guaranteed. + * Given an application ID, return all Box names. No particular ordering is + * guaranteed. Request fails when client or server-side configured limits prevent + * returning all Box names. * /v2/applications/{application-id}/boxes */ public class GetApplicationBoxes extends Query { diff --git a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java index cab8449e8..6211aefa2 100644 --- a/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java +++ b/src/main/java/com/algorand/algosdk/v2/client/common/AlgodClient.java @@ -252,8 +252,9 @@ public GetApplicationByID GetApplicationByID(Long applicationId) { } /** - * Given an application ID, it returns the box names of that application. No - * particular ordering is guaranteed. + * Given an application ID, return all Box names. No particular ordering is + * guaranteed. Request fails when client or server-side configured limits prevent + * returning all Box names. * /v2/applications/{application-id}/boxes */ public GetApplicationBoxes GetApplicationBoxes(Long applicationId) { From e1156c0c336eb978a36e05c3ee755ec71845a612 Mon Sep 17 00:00:00 2001 From: Michael Diamant Date: Tue, 1 Nov 2022 15:42:28 -0400 Subject: [PATCH 20/20] Revert SDK_TESTING_BRANCH to master --- .test-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.test-env b/.test-env index 9135343b7..df783a4fd 100644 --- a/.test-env +++ b/.test-env @@ -1,6 +1,6 @@ # Configs for testing repo download: SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" -SDK_TESTING_BRANCH="feature/box-storage" +SDK_TESTING_BRANCH="master" SDK_TESTING_HARNESS="test-harness" INSTALL_ONLY=0