Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Dissociate accounts from tokens post run #2090

Merged
merged 8 commits into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions hedera-mirror-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,28 +114,38 @@ hedera:

#### Feature Tags

Tags: Tags allow you to filter which Cucumber scenarios and files are run. By default, tests marked with the @sanity tag
are run. To run a different set of files different tags can be specified
Tags: Tags allow you to filter which Cucumber scenarios and files are run. By default, tests marked with the `@critical`
tag are run. To run a different set of files different tags can be specified

Acceptance test cases
Test Suite Tags

`./mvnw clean integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="@acceptance"`
- `@critical` - Test cases to ensure the network is up and running and satisfies base scenarios. Total cost to run 31.6
ℏ.
- `@release` - Test cases to verify a new deployed version satisfies core scenarios and is release worthy. Total cost to
run 19.2 ℏ.
- `@acceptance` - Test cases to verify most feature scenarios meet customer acceptance criteria. Total cost to run 6.5
ℏ.
- `@fullsuite` - All cases - this will require some configuration of feature files and may include some disabled tests
that will fail on execution. Total cost to run 33.9 ℏ.

All cases
> **_NOTE:_** Any noted total costs are estimates.
> They will fluctuate with test coverage expansion, improvements and network fee schedule changes.

`./mvnw clean integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="@fullsuite"`
Feature based Tags

Negative cases
- `@accounts` - Crypto account focused tests.
- `@topicmessagesbase` - Simple HCS focused tests.
- `@topicmessagesfilter` - HCS focused tests wth varied subscription filters.
- `@tokenbase` - HTS focused tests.
- `@schedulebase` - Scheduled transactions focused tests.

`./mvnw clean integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="@negative"`
To execute run

Edge cases
./mvnw clean integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="<tag name>"

`./mvnw clean integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="@edge"`

Other (search for @? tags within the .feature files for further tags)

`./mvnw integration-test --projects hedera-mirror-test/ -P=acceptance-tests -Dcucumber.filter.tags="@balancecheck"`
> **_NOTE:_** Feature tags can be combined - See [Tag expressions](https://cucumber.io/docs/cucumber/api/). To run a subset of tags
> - `@acceptance and @topicmessagesbase` - all token acceptance scenarios
> - `@acceptance and not @tokenbase` - all acceptance except token scenarios

### Test Layout

Expand Down
2 changes: 1 addition & 1 deletion hedera-mirror-test/src/main/resources/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM adoptopenjdk:11-jdk-hotspot

ENV cucumberFlags "@balancecheck"
ENV cucumberFlags "@critical"
WORKDIR /usr/etc/hedera-mirror-node

COPY .mvn/ .mvn/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
glue = "com.hedera.mirror.test.e2e.acceptance",
plugin = {"pretty", "de.monochromata.cucumber.report.PrettyReports:target/cucumber",
"timeline:target/cucumber/thread-report"},
tags = "@Sanity"
tags = "@critical"
)
@SpringBootTest
@CucumberContextConfiguration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class AccountClient extends AbstractNetworkClient {

private static final long DEFAULT_INITIAL_BALANCE = 1_000_000_000L;
private static final long DEFAULT_INITIAL_BALANCE = 50_000_000L; // 0.5 ℏ
private static final long SMALL_INITIAL_BALANCE = 1_000L; // 1000 tℏ

private ExpandedAccountId tokenTreasuryAccount = null;

Expand All @@ -74,7 +75,7 @@ public ExpandedAccountId getAccount(AccountNameEnum accountNameEnum) {
ExpandedAccountId accountId = accountMap
.computeIfAbsent(accountNameEnum, x -> {
try {
return createNewAccount(DEFAULT_INITIAL_BALANCE,
return createNewAccount(SMALL_INITIAL_BALANCE,
accountNameEnum);
} catch (Exception e) {
log.trace("Issue creating additional account: {}, ex: {}", accountNameEnum, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;

import com.hedera.hashgraph.sdk.AccountBalanceQuery;
import com.hedera.hashgraph.sdk.AccountId;
import com.hedera.hashgraph.sdk.KeyList;
import com.hedera.hashgraph.sdk.PrecheckStatusException;
Expand Down Expand Up @@ -346,4 +347,15 @@ public NetworkTransactionResponse delete(ExpandedAccountId accountId, TokenId to

return networkTransactionResponse;
}

public long getTokenBalance(AccountId accountId, TokenId tokenId) throws TimeoutException, PrecheckStatusException {
long balance = new AccountBalanceQuery()
.setAccountId(accountId)
.execute(client)
.token.get(tokenId);

log.debug("{}'s token balance is {} {} tokens", accountId, balance, tokenId);

return balance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

import io.cucumber.java.After;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
Expand Down Expand Up @@ -76,31 +77,28 @@
public class ScheduleFeature {
private final static int DEFAULT_TINY_HBAR = 1_000;

@Autowired
private ScheduleClient scheduleClient;
@Autowired
private AccountClient accountClient;
@Autowired
private TopicClient topicClient;
private MirrorNodeClient mirrorClient;
@Autowired
private TokenClient tokenClient;

private ScheduleId scheduleId;

@Autowired
private MirrorNodeClient mirrorClient;
private TopicClient topicClient;
@Autowired
private ScheduleClient scheduleClient;

private int currentSignersCount;
private int expectedSignersCount;
private NetworkTransactionResponse networkTransactionResponse;
private TransactionId scheduledTransactionId;

private ScheduleId scheduleId;
private ScheduleInfo scheduleInfo;

private Transaction scheduledTransaction;

private int expectedSignersCount;
private int currentSignersCount;
private TransactionId scheduledTransactionId;
private final int signatoryCountOffset = 1; // Schedule map includes payer account which may not be a required
// signatory
private TokenId tokenId;
private ExpandedAccountId tokenTreasuryAccount;

@Given("I successfully schedule a treasury HBAR disbursement to {string}")
@Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}")
Expand Down Expand Up @@ -187,7 +185,7 @@ public void createNewTokenTransferSchedule(String senderName, String receiverNam
TimeoutException {
expectedSignersCount = 2;
currentSignersCount = 0 + signatoryCountOffset;
ExpandedAccountId sender = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(senderName));
tokenTreasuryAccount = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(senderName));
ExpandedAccountId receiver = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(receiverName));

// create token
Expand All @@ -200,11 +198,11 @@ public void createNewTokenTransferSchedule(String senderName, String receiverNam
RandomStringUtils.randomAlphabetic(4).toUpperCase(),
TokenFreezeStatus.FreezeNotApplicable_VALUE,
TokenKycStatus.KycNotApplicable_VALUE,
sender,
tokenTreasuryAccount,
DEFAULT_TINY_HBAR);
assertNotNull(networkTransactionResponse.getTransactionId());
assertNotNull(networkTransactionResponse.getReceipt());
TokenId tokenId = networkTransactionResponse.getReceipt().tokenId;
tokenId = networkTransactionResponse.getReceipt().tokenId;
assertNotNull(tokenId);

// associate new account, sender as treasury is auto associated
Expand All @@ -216,12 +214,12 @@ public void createNewTokenTransferSchedule(String senderName, String receiverNam
scheduledTransaction = tokenClient
.getTokenTransferTransaction(
tokenId,
sender.getAccountId(),
tokenTreasuryAccount.getAccountId(),
receiver.getAccountId(),
10)
// add Hbar transfer logic
.addHbarTransfer(receiver.getAccountId(), hbarAmount.negated())
.addHbarTransfer(sender.getAccountId(), hbarAmount);
.addHbarTransfer(receiver.getAccountId(), hbarAmount)
.addHbarTransfer(tokenTreasuryAccount.getAccountId(), hbarAmount.negated());

createNewSchedule(scheduledTransaction, null);
}
Expand All @@ -237,7 +235,7 @@ public void createNewHCSSchedule(String accountName) throws ReceiptStatusExcepti
// create topic w submit key
log.debug("Create new topic with {}'s submit key", accountName);
networkTransactionResponse = topicClient
.createTopic(accountClient.getTokenTreasuryAccount(), submitAdmin.getPublicKey());
.createTopic(scheduleClient.getSdkClient().getExpandedOperatorAccountId(), submitAdmin.getPublicKey());
assertNotNull(networkTransactionResponse.getTransactionId());
assertNotNull(networkTransactionResponse.getReceipt());
TopicId topicId = networkTransactionResponse.getReceipt().topicId;
Expand Down Expand Up @@ -373,6 +371,31 @@ public void verifyDeletedScheduleFromMirror() {
verifyScheduleFromMirror(ScheduleStatus.DELETED);
}

@After
public void cleanup() throws ReceiptStatusException, PrecheckStatusException, TimeoutException {
// dissociate all applicable accounts from token to reduce likelihood of max token association error
if (tokenId != null) {
ijungmann marked this conversation as resolved.
Show resolved Hide resolved
// a nonzero balance will result in a TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES error
// not possible to wipe a treasury account as it results in CANNOT_WIPE_TOKEN_TREASURY_ACCOUNT error
// as a result to dissociate first delete token
tokenClient.delete(tokenClient.getSdkClient().getExpandedOperatorAccountId(), tokenId);
dissociateAccount(tokenTreasuryAccount);
dissociateAccount(tokenClient.getSdkClient().getExpandedOperatorAccountId());
tokenId = null;
}
}

private void dissociateAccount(ExpandedAccountId accountId) {
if (accountId != null) {
try {
tokenClient.disssociate(accountId, tokenId);
log.info("Successfully dissociated account {} from token {}", accountId, tokenId);
} catch (Exception ex) {
log.warn("Error dissociating account {} from token {}, error: {}", accountId, tokenId, ex);
}
}
}

private void validateScheduleInfo(ScheduleInfo scheduleInfo) {
assertNotNull(scheduleInfo);
assertThat(scheduleInfo.scheduleId).isEqualTo(scheduleId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

import io.cucumber.java.After;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
Expand Down Expand Up @@ -94,19 +95,13 @@ public void createNewToken(String symbol) throws ReceiptStatusException, Prechec
TokenKycStatus.KycNotApplicable_VALUE);
}

@Given("I successfully onboard a new token account with freeze status {int} and kyc status {int}")
@Given("I successfully create a new token account with freeze status {int} and kyc status {int}")
ijungmann marked this conversation as resolved.
Show resolved Hide resolved
@Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}")
public void createNewToken(int freezeStatus, int kycStatus) throws ReceiptStatusException,
PrecheckStatusException, TimeoutException {
createNewToken(RandomStringUtils.randomAlphabetic(4).toUpperCase(), freezeStatus, kycStatus);
}

@Given("I successfully onboard a new token account")
@Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}")
public void onboardNewTokenAccount() throws PrecheckStatusException, ReceiptStatusException, TimeoutException {
onboardNewTokenAccount(TokenFreezeStatus.FreezeNotApplicable_VALUE, TokenKycStatus.KycNotApplicable_VALUE);
}

@Given("I associate with token")
@Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}")
public void associateWithToken() throws ReceiptStatusException, PrecheckStatusException, TimeoutException {
Expand Down Expand Up @@ -148,7 +143,7 @@ public void setKycStatus(int kycStatus) throws ReceiptStatusException, PrecheckS
@Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}")
public void fundPayerAccountWithTokens(int amount) throws PrecheckStatusException, ReceiptStatusException,
TimeoutException {
transferTokens(tokenId, amount, accountClient.getTokenTreasuryAccount(), tokenClient.getSdkClient()
transferTokens(tokenId, amount, recipient, tokenClient.getSdkClient()
.getOperatorId());
}

Expand Down Expand Up @@ -214,6 +209,7 @@ public void deleteToken() throws PrecheckStatusException, ReceiptStatusException
.delete(tokenClient.getSdkClient().getExpandedOperatorAccountId(), tokenId);
assertNotNull(networkTransactionResponse.getTransactionId());
assertNotNull(networkTransactionResponse.getReceipt());
tokenId = null;
}

@Then("the mirror node REST API should return status {int}")
Expand Down Expand Up @@ -274,18 +270,48 @@ public void verifyMirrorRestTransactionIsPresent(int status, String transactionI
topicClient.publishMessageToDefaultTopic();
}

@After
public void cleanup() throws ReceiptStatusException, PrecheckStatusException, TimeoutException {
// dissociate all applicable accounts from token to reduce likelihood of max token association error
if (tokenId != null) {
// a nonzero balance will result in a TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES error
// not possible to wipe a treasury account as it results in CANNOT_WIPE_TOKEN_TREASURY_ACCOUNT error
// as a result to dissociate first delete token
try {
tokenClient.delete(tokenClient.getSdkClient().getExpandedOperatorAccountId(), tokenId);
dissociateAccount(sender);
dissociateAccount(recipient);
tokenId = null;
} catch (Exception ex) {
log.warn("Error cleaning up token {} and associations error: {}", tokenId, ex);
}
}
}

private void dissociateAccount(ExpandedAccountId accountId) {
if (accountId != null) {
try {
tokenClient.disssociate(accountId, tokenId);
xin-hedera marked this conversation as resolved.
Show resolved Hide resolved
log.info("Successfully dissociated account {} from token {}", accountId, tokenId);
} catch (Exception ex) {
log.warn("Error dissociating account {} from token {}, error: {}", accountId, tokenId, ex);
}
}
}

private void createNewToken(String symbol, int freezeStatus, int kycStatus) throws PrecheckStatusException,
ReceiptStatusException, TimeoutException {
tokenKey = PrivateKey.generate();
PublicKey tokenPublicKey = tokenKey.getPublicKey();
log.debug("Token creation PrivateKey : {}, PublicKey : {}", tokenKey, tokenPublicKey);

sender = tokenClient.getSdkClient().getExpandedOperatorAccountId();
networkTransactionResponse = tokenClient.createToken(
tokenClient.getSdkClient().getExpandedOperatorAccountId(),
symbol,
freezeStatus,
kycStatus,
accountClient.getTokenTreasuryAccount(),
tokenClient.getSdkClient().getExpandedOperatorAccountId(),
INITIAL_SUPPLY);
assertNotNull(networkTransactionResponse.getTransactionId());
assertNotNull(networkTransactionResponse.getReceipt());
Expand Down Expand Up @@ -329,9 +355,11 @@ private void setKycStatus(int kycStatus, ExpandedAccountId accountId) throws Pre
}

private void transferTokens(TokenId tokenId, int amount, ExpandedAccountId sender, AccountId receiver) throws PrecheckStatusException, ReceiptStatusException, TimeoutException {
long startingBalance = tokenClient.getTokenBalance(receiver, tokenId);
networkTransactionResponse = tokenClient.transferToken(tokenId, sender, receiver, amount);
assertNotNull(networkTransactionResponse.getTransactionId());
assertNotNull(networkTransactionResponse.getReceipt());
assertThat(tokenClient.getTokenBalance(receiver, tokenId)).isEqualTo(startingBalance + amount);
}

private void onboardNewTokenAccount(int freezeStatus, int kycStatus) throws ReceiptStatusException,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@accounts @fullsuite
Feature: Account Coverage Feature

@balancecheck @sanity @acceptance @Acceptance
@critical @release @acceptance @balancecheck
Scenario Outline: Validate account balance check scenario
When I request balance info for this account
Then the crypto balance should be greater than or equal to <threshold>
Expand Down
Loading