Skip to content

Commit 369b9a0

Browse files
Testing: Modify cucumber steps to use dev mode network (#350)
1 parent 5e373bd commit 369b9a0

File tree

4 files changed

+135
-19
lines changed

4 files changed

+135
-19
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ unit:
22
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"
33

44
integration:
5-
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"
5+
mvn test \
6+
-Dtest=com.algorand.algosdk.integration.RunCucumberIntegrationTest \
7+
-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 @compile or @dryrun or @indexer.applications or @indexer.231 or @abi or @c2c"
68

79
docker-test:
810
./run_integration_tests.sh

src/test/java/com/algorand/algosdk/integration/Applications.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public void sendTransactionWithTransientAccountAndCheckForError(String error) th
135135

136136
@Given("I wait for the transaction to be confirmed.")
137137
public void waitForTransactionToBeConfirmed() throws Exception {
138-
Utils.waitForConfirmation(clients.v2Client, txId, 5);
138+
Utils.waitForConfirmation(clients.v2Client, txId, 1);
139139
}
140140

141141
// TODO: Use V2 Pending Transaction endpoint when it is available.
@@ -165,7 +165,7 @@ public void fundAppAccount(Integer amount) throws Exception {
165165
SignedTransaction stx = base.signWithAddress(tx, sender);
166166

167167
Response<PostTransactionsResponse> rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute();
168-
Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5);
168+
Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1);
169169
}
170170

171171
@Then("I get the account address for the current application and see that it matches the app id's hash")

src/test/java/com/algorand/algosdk/integration/Stepdefs.java

Lines changed: 129 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import java.util.Collections;
4343
import java.util.List;
4444
import java.util.Set;
45+
import java.util.concurrent.ThreadLocalRandom;
4546

4647
import static com.algorand.algosdk.util.ResourceUtils.loadResource;
4748
import static org.assertj.core.api.Assertions.assertThat;
@@ -118,6 +119,31 @@ public class Stepdefs {
118119
Response<CompileResponse> compileResponse;
119120
Response<DryrunResponse> dryrunResponse;
120121

122+
private static class DevModeState {
123+
static final long ACCOUNT_FUNDING_MICROALGOS = 100_000_000;
124+
private Account advanceRounds;
125+
126+
/**
127+
* randomAmount minimizes the chance `advanceRounds` issues duplicate transactions by randomizing the payment amount.
128+
*/
129+
private long randomAmount() {
130+
return ThreadLocalRandom.current().nextLong(1, (long) (ACCOUNT_FUNDING_MICROALGOS * .01));
131+
}
132+
133+
public SignedTransaction selfPay(TransactionParams tp) throws Exception {
134+
Transaction tx =
135+
Transaction.PaymentTransactionBuilder()
136+
.sender(advanceRounds.getAddress())
137+
.suggestedParams(tp)
138+
.amount(randomAmount())
139+
.receiver(advanceRounds.getAddress())
140+
.build();
141+
return advanceRounds.signTransaction(tx);
142+
}
143+
}
144+
145+
private final DevModeState dms = new DevModeState();
146+
121147
protected Address getAddress(int i) {
122148
if (addresses == null) {
123149
throw new RuntimeException("Addresses not initialized, must use given 'wallet information'");
@@ -158,6 +184,48 @@ public SignedTransaction signWithAddress(Transaction tx, Address addr) throws co
158184
return acct.signTransaction(tx);
159185
}
160186

187+
/**
188+
* advanceRound is a convenience method intended for testing with DevMode networks.
189+
* <p>
190+
* Since DevMode block generation requires a transaction rather than time passing, test assertions may require advancing rounds. advanceRound issues advanceCount payments to advance rounds.
191+
*/
192+
private void advanceRoundsV1(int advanceCount) {
193+
initializeDevModeAccount();
194+
for (int i = 0; i < advanceCount; i++) {
195+
try {
196+
acl.rawTransaction(Encoder.encodeToMsgPack(dms.selfPay(acl.transactionParams())));
197+
} catch (Exception e) {
198+
throw new RuntimeException(e);
199+
}
200+
}
201+
}
202+
203+
/**
204+
* initializeDevModeAccount performs a one-time account initialization per inclusion in a scenario outline. No attempt is made to delete the account.
205+
*/
206+
public void initializeDevModeAccount() {
207+
if (dms.advanceRounds != null) {
208+
return;
209+
}
210+
211+
try {
212+
getParams();
213+
dms.advanceRounds = new Account();
214+
Address sender = getAddress(0);
215+
Transaction tx =
216+
Transaction.PaymentTransactionBuilder()
217+
.sender(sender)
218+
.suggestedParams(acl.transactionParams())
219+
.amount(DevModeState.ACCOUNT_FUNDING_MICROALGOS)
220+
.receiver(dms.advanceRounds.getAddress())
221+
.build();
222+
SignedTransaction st = signWithAddress(tx, sender);
223+
acl.rawTransaction(Encoder.encodeToMsgPack(st));
224+
} catch (Exception e) {
225+
throw new RuntimeException(e);
226+
}
227+
}
228+
161229
/**
162230
* Convenience method to export a key and initialize an account to use for signing.
163231
*/
@@ -447,8 +515,8 @@ public void status() throws ApiException{
447515
}
448516

449517
@When("I get status after this block")
450-
public void statusBlock() throws ApiException, InterruptedException {
451-
Thread.sleep(4000);
518+
public void statusBlock() throws Exception {
519+
advanceRoundsV1(1);
452520
statusAfter = acl.waitForBlock(status.getLastRound());
453521
}
454522

@@ -526,21 +594,48 @@ public void msigNotInWallet()throws com.algorand.algosdk.kmd.client.ApiException
526594
}
527595

528596
@When("I generate a key using kmd")
529-
public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException{
597+
public void genKeyKmd() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException {
530598
GenerateKeyRequest req = new GenerateKeyRequest();
531599
req.setDisplayMnemonic(false);
532600
req.setWalletHandleToken(handle);
533601
pk = new Address(kcl.generateKey(req).getAddress());
534602
}
535603

604+
@When("I generate a key using kmd for rekeying and fund it")
605+
public void genKeyKmdRekey() throws com.algorand.algosdk.kmd.client.ApiException, NoSuchAlgorithmException {
606+
GenerateKeyRequest req = new GenerateKeyRequest();
607+
req.setDisplayMnemonic(false);
608+
req.setWalletHandleToken(handle);
609+
rekey = new Address(kcl.generateKey(req).getAddress());
610+
611+
// Fund rekey address
612+
try {
613+
getParams();
614+
Address sender = getAddress(0);
615+
Transaction tx =
616+
Transaction.PaymentTransactionBuilder()
617+
.sender(sender)
618+
.suggestedParams(acl.transactionParams())
619+
.amount(100_000_000)
620+
.receiver(rekey)
621+
.build();
622+
SignedTransaction st = signWithAddress(tx, sender);
623+
acl.rawTransaction(Encoder.encodeToMsgPack(st));
624+
} catch (Exception e) {
625+
throw new RuntimeException(e);
626+
}
627+
}
628+
629+
Address rekey;
630+
536631
@Then("the key should be in the wallet")
537632
public void keyInWallet() throws com.algorand.algosdk.kmd.client.ApiException {
538633
ListKeysRequest req = new ListKeysRequest();
539634
req.setWalletHandleToken(handle);
540635
List<String> keys = kcl.listKeysInWallet(req).getAddresses();
541636
boolean exists = false;
542-
for (String k : keys){
543-
if (k.equals(pk.toString())){
637+
for (String k : keys) {
638+
if (k.equals(pk.toString())) {
544639
exists = true;
545640
}
546641
}
@@ -632,6 +727,7 @@ public void aClient() throws FileNotFoundException, IOException{
632727
algodClient.setBasePath("http://localhost:" + algodPort);
633728
acl = new AlgodApi(algodClient);
634729
}
730+
635731
@Given("an algod v2 client")
636732
public void aClientv2() throws FileNotFoundException, IOException{
637733
aclv2 = new com.algorand.algosdk.v2.client.common.AlgodClient(
@@ -660,21 +756,30 @@ public void walletInfo() throws com.algorand.algosdk.kmd.client.ApiException, No
660756
}
661757

662758
@Given("default transaction with parameters {int} {string}")
663-
public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException{
759+
public void defaultTxn(int amt, String note) throws ApiException, NoSuchAlgorithmException {
760+
defaultTxnWithAddress(amt, note, getAddress(0));
761+
}
762+
763+
@Given("default transaction with parameters {int} {string} and rekeying key")
764+
public void defaultTxnForRekeying(int amt, String note) {
765+
defaultTxnWithAddress(amt, note, rekey);
766+
}
767+
768+
private void defaultTxnWithAddress(int amt, String note, Address sender) {
664769
getParams();
665-
if (note.equals("none")){
770+
if (note.equals("none")) {
666771
this.note = null;
667-
} else{
772+
} else {
668773
this.note = Encoder.decodeFromBase64(note);
669774
}
670775
txnBuilder = Transaction.PaymentTransactionBuilder()
671-
.sender(getAddress(0))
776+
.sender(sender)
672777
.suggestedParams(params)
673778
.note(this.note)
674779
.amount(amt)
675780
.receiver(getAddress(1));
676781
txn = txnBuilder.build();
677-
pk = getAddress(0);
782+
pk = sender;
678783
}
679784

680785
@Given("default multisig transaction with parameters {int} {string}")
@@ -715,23 +820,32 @@ public void sendMsigTxn() throws JsonProcessingException, ApiException{
715820
}
716821

717822
@Then("the transaction should go through")
718-
public void checkTxn() throws ApiException, InterruptedException{
823+
public void checkTxn() throws Exception {
719824
String ans = acl.pendingTransactionInformation(txid).getFrom();
720825
assertThat(this.txn.sender.toString()).isEqualTo(ans);
721-
acl.waitForBlock(lastRound.add(BigInteger.valueOf(2)));
826+
waitForAlgodTransactionProcessingToComplete();
722827
String senderFromResponse = acl.transactionInformation(txn.sender.toString(), txid).getFrom();
723828
assertThat(senderFromResponse).isEqualTo(txn.sender.toString());
724829
assertThat(acl.transaction(txid).getFrom()).isEqualTo(senderFromResponse);
725830
}
726831

832+
/**
833+
* waitForAlgodTransactionProcessingToComplete is a Dev mode helper method that's a rough analog to `acl.waitForBlock(lastRound.add(BigInteger.valueOf(2)));`.
834+
* <p>
835+
* 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.
836+
*/
837+
private static void waitForAlgodTransactionProcessingToComplete() throws Exception {
838+
Thread.sleep(500);
839+
}
840+
727841
@Then("I can get the transaction by ID")
728-
public void txnbyID() throws ApiException, InterruptedException{
729-
acl.waitForBlock(lastRound.add(BigInteger.valueOf(2)));
842+
public void txnByID() throws Exception {
843+
waitForAlgodTransactionProcessingToComplete();
730844
assertThat(acl.transaction(txid).getFrom()).isEqualTo(pk.toString());
731845
}
732846

733847
@Then("the transaction should not go through")
734-
public void txnFail(){
848+
public void txnFail() {
735849
assertThat(err).isTrue();
736850
}
737851

src/test/java/com/algorand/algosdk/integration/TransientAccount.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void createAndFundTransientAccount(Long amount) throws Exception {
3838
SignedTransaction stx = base.signWithAddress(tx, sender);
3939

4040
Response<PostTransactionsResponse> rPost = clients.v2Client.RawTransaction().rawtxn(Encoder.encodeToMsgPack(stx)).execute();
41-
Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 5);
41+
Utils.waitForConfirmation(clients.v2Client, rPost.body().txId, 1);
4242
}
4343

4444
}

0 commit comments

Comments
 (0)