diff --git a/hedera-mirror-datagenerator/pom.xml b/hedera-mirror-datagenerator/pom.xml index 2ed44399213..76f2c9cd1ef 100644 --- a/hedera-mirror-datagenerator/pom.xml +++ b/hedera-mirror-datagenerator/pom.xml @@ -22,7 +22,7 @@ com.hedera.hashgraph sdk - 2.0.5-beta.4 + 2.0.5-beta.7 com.hedera diff --git a/hedera-mirror-datagenerator/src/main/java/com/hedera/datagenerator/sdk/supplier/schedule/ScheduleCreateTransactionSupplier.java b/hedera-mirror-datagenerator/src/main/java/com/hedera/datagenerator/sdk/supplier/schedule/ScheduleCreateTransactionSupplier.java index 44736106771..ebb9a52c0a4 100644 --- a/hedera-mirror-datagenerator/src/main/java/com/hedera/datagenerator/sdk/supplier/schedule/ScheduleCreateTransactionSupplier.java +++ b/hedera-mirror-datagenerator/src/main/java/com/hedera/datagenerator/sdk/supplier/schedule/ScheduleCreateTransactionSupplier.java @@ -76,7 +76,7 @@ public class ScheduleCreateTransactionSupplier implements TransactionSupplier signingKeys = createSigningKeys(); @@ -103,9 +103,7 @@ public ScheduleCreateTransaction get() { // set nodeAccountId and freeze inner transaction TransactionId transactionId = TransactionId.generate(getOperatorId()); - innerTransaction.setNodeAccountIds(Collections.singletonList(getNodeId())); innerTransaction.setTransactionId(transactionId.setScheduled(true)); - innerTransaction.freeze(); String scheduleMemo = Utility.getMemo("Mirror node created test schedule"); ScheduleCreateTransaction scheduleCreateTransaction = innerTransaction @@ -125,9 +123,14 @@ public ScheduleCreateTransaction get() { // add initial set of required signatures to ScheduleCreate transaction if (totalSignatoryCount > 0) { - getSigningKeys().forEach(k -> scheduleCreateTransaction.addScheduleSignature( - k.getPublicKey(), - k.signTransaction(innerTransaction))); + scheduleCreateTransaction.setNodeAccountIds(Collections.singletonList(getNodeId())); + getSigningKeys().forEach(pk -> { + byte[] signature = pk.signTransaction(scheduleCreateTransaction); + scheduleCreateTransaction.addSignature( + pk.getPublicKey(), + signature); + }); + log.debug("Added {} signatures to ScheduleCreate", totalSignatoryCount); } return scheduleCreateTransaction; @@ -161,4 +164,8 @@ private List createSignatoryKeys() { return keys; } + + private AccountId createPayerAccountId() { + return payerAccount == null ? null : AccountId.fromString(payerAccount); + } } diff --git a/hedera-mirror-monitor/pom.xml b/hedera-mirror-monitor/pom.xml index 14d26d06180..b4a60c6726d 100644 --- a/hedera-mirror-monitor/pom.xml +++ b/hedera-mirror-monitor/pom.xml @@ -41,7 +41,7 @@ com.hedera.hashgraph sdk - 2.0.5-beta.4 + 2.0.5-beta.7 com.lmax diff --git a/hedera-mirror-monitor/src/main/java/com/hedera/mirror/monitor/publish/TransactionPublisher.java b/hedera-mirror-monitor/src/main/java/com/hedera/mirror/monitor/publish/TransactionPublisher.java index 8d8f01c4e4e..a444f70d773 100644 --- a/hedera-mirror-monitor/src/main/java/com/hedera/mirror/monitor/publish/TransactionPublisher.java +++ b/hedera-mirror-monitor/src/main/java/com/hedera/mirror/monitor/publish/TransactionPublisher.java @@ -42,6 +42,7 @@ import com.hedera.hashgraph.sdk.AccountId; import com.hedera.hashgraph.sdk.Client; import com.hedera.hashgraph.sdk.PrivateKey; +import com.hedera.hashgraph.sdk.Transaction; import com.hedera.hashgraph.sdk.TransactionId; import com.hedera.hashgraph.sdk.TransactionResponse; import com.hedera.mirror.monitor.MonitorProperties; @@ -84,11 +85,8 @@ private Mono doPublish(PublishRequest request) { log.trace("Publishing: {}", request); int clientIndex = counter.getAndUpdate(n -> (n + 1 < clients.get().size()) ? n + 1 : 0); Client client = clients.get().get(clientIndex); - int nodeIndex = secureRandom.nextInt(nodeAccountIds.get().size()); - List nodeAccountId = List.of(nodeAccountIds.get().get(nodeIndex)); - return Mono - .fromFuture(() -> request.getTransaction().setNodeAccountIds(nodeAccountId).executeAsync(client)) + return getTransactionResponse(request, client) .flatMap(transactionResponse -> processTransactionResponse(client, request, transactionResponse)) .map(PublishResponse.PublishResponseBuilder::build) .doOnNext(response -> { @@ -99,8 +97,22 @@ private Mono doPublish(PublishRequest request) { .timeout(request.getTimeout()); } + private Mono getTransactionResponse(PublishRequest request, Client client) { + Transaction transaction = request.getTransaction(); + + // set transaction node where applicable + if (transaction.getNodeAccountIds() == null) { + int nodeIndex = secureRandom.nextInt(nodeAccountIds.get().size()); + List nodeAccountId = List.of(nodeAccountIds.get().get(nodeIndex)); + transaction.setNodeAccountIds(nodeAccountId); + } + + return Mono.fromFuture(transaction.executeAsync(client)); + } + private Mono processTransactionResponse(Client client, - PublishRequest request, TransactionResponse transactionResponse) { + PublishRequest request, + TransactionResponse transactionResponse) { TransactionId transactionId = transactionResponse.transactionId; PublishResponse.PublishResponseBuilder builder = PublishResponse.builder() .request(request) diff --git a/hedera-mirror-test/pom.xml b/hedera-mirror-test/pom.xml index 8a7b50499ca..27d3c9be6d2 100644 --- a/hedera-mirror-test/pom.xml +++ b/hedera-mirror-test/pom.xml @@ -29,7 +29,7 @@ com.hedera.hashgraph sdk - 2.0.5-beta.4 + 2.0.5-beta.7 io.grpc diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/AbstractNetworkClient.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/AbstractNetworkClient.java index 23a6539b598..50e9b8586a9 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/AbstractNetworkClient.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/AbstractNetworkClient.java @@ -56,11 +56,12 @@ public TransactionId executeTransaction(Transaction transaction, KeyList keyList for (Key k : keyList) { transaction = transaction.sign((PrivateKey) k); } + log.debug("{} additional signatures added to transaction", keyList.size()); } TransactionResponse transactionResponse = (TransactionResponse) transaction.execute(client); TransactionId transactionId = transactionResponse.transactionId; - log.debug("Executed transaction {}.", transactionId); + log.debug("Executed transaction {} with {} signatures.", transactionId, keyList == null ? 0 : keyList.size()); return transactionId; } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/ScheduleClient.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/ScheduleClient.java index 245c8abc7d4..8008d92218f 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/ScheduleClient.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/ScheduleClient.java @@ -21,11 +21,11 @@ */ import java.util.Collections; -import java.util.List; import java.util.concurrent.TimeoutException; import lombok.Value; import lombok.extern.log4j.Log4j2; +import com.hedera.hashgraph.sdk.KeyList; import com.hedera.hashgraph.sdk.PrecheckStatusException; import com.hedera.hashgraph.sdk.PrivateKey; import com.hedera.hashgraph.sdk.ReceiptStatusException; @@ -48,16 +48,12 @@ public ScheduleClient(SDKClient sdkClient) { } public NetworkTransactionResponse createSchedule(ExpandedAccountId payerAccountId, Transaction transaction, - String memo, List innerSignatureKeyList) throws ReceiptStatusException, + String memo, KeyList signatureKeyList) throws ReceiptStatusException, PrecheckStatusException, TimeoutException { log.debug("Create new schedule"); TransactionId transactionId = TransactionId.generate(sdkClient.getOperatorId()).setScheduled(true); - - // set nodeAccountId and freeze inner transaction - transaction.setNodeAccountIds(Collections.singletonList(sdkClient.getNodeId())); transaction.setTransactionId(transactionId); - transaction.freeze(); ScheduleCreateTransaction scheduleCreateTransaction = transaction.schedule() .setAdminKey(payerAccountId.getPublicKey()) @@ -67,11 +63,17 @@ public NetworkTransactionResponse createSchedule(ExpandedAccountId payerAccountI .setTransactionId(transactionId.setScheduled(false)) .setTransactionMemo(memo); - if (innerSignatureKeyList != null) { + if (signatureKeyList != null) { + scheduleCreateTransaction.setNodeAccountIds(Collections.singletonList(sdkClient.getNodeId())); + // add initial set of required signatures to ScheduleCreate transaction - innerSignatureKeyList.forEach(k -> scheduleCreateTransaction.addScheduleSignature( - k.getPublicKey(), - k.signTransaction(transaction))); + signatureKeyList.forEach(k -> { + PrivateKey pk = (PrivateKey) k; + byte[] signature = pk.signTransaction(scheduleCreateTransaction); + scheduleCreateTransaction.addSignature( + pk.getPublicKey(), + signature); + }); } NetworkTransactionResponse networkTransactionResponse = @@ -83,20 +85,16 @@ public NetworkTransactionResponse createSchedule(ExpandedAccountId payerAccountI } public NetworkTransactionResponse signSchedule(ExpandedAccountId expandedAccountId, - Transaction scheduledTransaction, ScheduleId scheduleId) throws ReceiptStatusException, PrecheckStatusException, TimeoutException { - log.debug("Sign schedule {}", scheduleId); - byte[] signature = expandedAccountId.getPrivateKey().signTransaction(scheduledTransaction); - ScheduleSignTransaction scheduleSignTransaction = new ScheduleSignTransaction() .setMaxTransactionFee(sdkClient.getMaxTransactionFee()) - .setScheduleId(scheduleId) - .addScheduleSignature(expandedAccountId.getPublicKey(), signature); + .setScheduleId(scheduleId); - NetworkTransactionResponse networkTransactionResponse = - executeTransactionAndRetrieveReceipt(scheduleSignTransaction, null); + NetworkTransactionResponse networkTransactionResponse = executeTransactionAndRetrieveReceipt( + scheduleSignTransaction, + KeyList.of(expandedAccountId.getPrivateKey())); log.debug("Signed schedule {}", scheduleId); return networkTransactionResponse; diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/TopicClient.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/TopicClient.java index 9ea7996d43a..cd786759fe7 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/TopicClient.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/client/TopicClient.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeoutException; -import lombok.Value; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.ArrayUtils; @@ -49,10 +48,10 @@ import com.hedera.mirror.test.e2e.acceptance.response.NetworkTransactionResponse; @Log4j2 -@Value public class TopicClient extends AbstractNetworkClient { private static final Duration autoRenewPeriod = Duration.ofSeconds(8000000); private final Map recordPublishInstants; + private TopicId defaultTopicId = null; public TopicClient(SDKClient sdkClient) { super(sdkClient); @@ -151,6 +150,22 @@ public TopicMessageSubmitTransaction getTopicMessageSubmitTransaction(TopicId to .setMessage(message); } + public TopicId getDefaultTopicId() throws PrecheckStatusException, ReceiptStatusException, TimeoutException { + if (defaultTopicId == null) { + NetworkTransactionResponse networkTransactionResponse = createTopic(sdkClient + .getExpandedOperatorAccountId(), null); + defaultTopicId = networkTransactionResponse.getReceipt().topicId; + log.debug("Created TopicId: '{}' for use in current test session", defaultTopicId); + } + + return defaultTopicId; + } + + public void publishMessageToDefaultTopic() throws ReceiptStatusException, PrecheckStatusException, + TimeoutException { + publishMessagesToTopic(getDefaultTopicId(), "Background message", null, 1, false); + } + public TransactionId publishMessageToTopic(TopicId topicId, byte[] message, KeyList submitKeys) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { TopicMessageSubmitTransaction consensusMessageSubmitTransaction = new TopicMessageSubmitTransaction() .setTopicId(topicId) diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AccountFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AccountFeature.java index af1b39df0d0..11a6fcfba9c 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AccountFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/AccountFeature.java @@ -66,6 +66,14 @@ public void createNewAccount(long initialBalance) throws ReceiptStatusException, assertNotNull(accountId); } + @When("I send {long} tℏ to newly created account") + public void sendTinyHbars(long amount) throws TimeoutException, PrecheckStatusException, + ReceiptStatusException { + startingBalance = accountClient.getBalance(accountId); + TransactionReceipt receipt = accountClient.sendCryptoTransfer(accountId, Hbar.fromTinybars(amount)); + assertNotNull(receipt); + } + @When("I send {long} tℏ to account {int}") public void sendTinyHbars(long amount, int accountNum) throws TimeoutException, PrecheckStatusException, ReceiptStatusException { diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/ScheduleFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/ScheduleFeature.java index 71609aa9c83..a8933598554 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/ScheduleFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/ScheduleFeature.java @@ -30,10 +30,12 @@ import io.cucumber.junit.platform.engine.Cucumber; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.TimeoutException; +import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.RandomStringUtils; import org.opentest4j.AssertionFailedError; @@ -95,7 +97,7 @@ public class ScheduleFeature { private MirrorNodeClient mirrorClient; private NetworkTransactionResponse networkTransactionResponse; - private TransactionId scheduleCreateTransactionId; + private TransactionId scheduledTransactionId; private ScheduleInfo scheduleInfo; @@ -103,6 +105,8 @@ public class ScheduleFeature { private int expectedSignersCount; private int currentSignersCount; + private final int signatoryCountOffset = 1; // Schedule map includes payer account which may not be a required + // signatory @Given("I successfully schedule a treasury HBAR disbursement to {string}") @Retryable(value = {PrecheckStatusException.class}, exceptionExpression = "#{message.contains('BUSY')}") @@ -110,7 +114,7 @@ public void createNewHBarTransferSchedule(String accountName) throws ReceiptStat PrecheckStatusException, TimeoutException { expectedSignersCount = 2; - currentSignersCount = 0; + currentSignersCount = 0 + signatoryCountOffset; ExpandedAccountId recipient = accountClient .getAccount(AccountClient.AccountNameEnum.valueOf(accountName)); // receiverSigRequired scheduledTransaction = accountClient @@ -127,14 +131,14 @@ public void createNewHBarTransferSchedule(String accountName) throws ReceiptStat public void createNewCryptoAccountSchedule() throws ReceiptStatusException, PrecheckStatusException, TimeoutException { expectedSignersCount = 1; - currentSignersCount = 0; + currentSignersCount = 0 + signatoryCountOffset; ExpandedAccountId alice = accountClient.getAccount(AccountClient.AccountNameEnum.ALICE); scheduledTransaction = accountClient .getAccountCreateTransaction( Hbar.fromTinybars(DEFAULT_TINY_HBAR), KeyList.of(alice.getPublicKey()), - false, + true, "scheduled account create"); createNewSchedule(scheduledTransaction, null); @@ -147,10 +151,10 @@ public void createNewCryptoAccountSchedule(int initSignatureCount, String accoun PrecheckStatusException, TimeoutException { expectedSignersCount = 2 + initSignatureCount; // new account, accountName and initSignatureCount - currentSignersCount = initSignatureCount + 1; + currentSignersCount = initSignatureCount + signatoryCountOffset; ExpandedAccountId finalSignatory = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(accountName)); - List privateKeyList = new ArrayList<>(); + KeyList privateKeyList = new KeyList(); KeyList publicKeyList = new KeyList(); for (int i = 0; i < initSignatureCount; i++) { PrivateKey accountKey = PrivateKey.generate(); @@ -175,10 +179,11 @@ public void createNewCryptoAccountSchedule(int initSignatureCount, String accoun accountClient.getSdkClient().getOperatorId(), Hbar.fromTinybars(DEFAULT_TINY_HBAR)); - // add sender private key to ensure only Alice's signature is teh only signature left that is required + // add sender private key to ensure only Alice's signature is the only signature left that is required privateKeyList.add(newAccountId.getPrivateKey()); createNewSchedule(scheduledTransaction, privateKeyList); + currentSignersCount++; } @Given("I successfully schedule a token transfer from {string} to {string}") @@ -187,7 +192,7 @@ public void createNewTokenTransferSchedule(String senderName, String receiverNam PrecheckStatusException, TimeoutException { expectedSignersCount = 2; - currentSignersCount = 0; + currentSignersCount = 0 + signatoryCountOffset; ExpandedAccountId sender = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(senderName)); ExpandedAccountId receiver = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(receiverName)); @@ -232,7 +237,7 @@ public void createNewTokenTransferSchedule(String senderName, String receiverNam public void createNewHCSSchedule(String accountName) throws ReceiptStatusException, PrecheckStatusException, TimeoutException { expectedSignersCount = 1; - currentSignersCount = 0; + currentSignersCount = 0 + signatoryCountOffset; ExpandedAccountId submitAdmin = accountClient.getAccount(AccountClient.AccountNameEnum.valueOf(accountName)); // create topic w submit key @@ -252,7 +257,7 @@ public void createNewHCSSchedule(String accountName) throws ReceiptStatusExcepti createNewSchedule(scheduledTransaction, null); } - private void createNewSchedule(Transaction transaction, List innerSignatureKeyList) throws PrecheckStatusException, + private void createNewSchedule(Transaction transaction, KeyList innerSignatureKeyList) throws PrecheckStatusException, ReceiptStatusException, TimeoutException { log.debug("Schedule creation "); @@ -264,23 +269,26 @@ private void createNewSchedule(Transaction transaction, List innerSi innerSignatureKeyList); assertNotNull(networkTransactionResponse.getTransactionId()); - // cache schedule create transaction id for confirmation of scheduled transaction later - scheduleCreateTransactionId = networkTransactionResponse.getTransactionId(); - assertNotNull(networkTransactionResponse.getReceipt()); scheduleId = networkTransactionResponse.getReceipt().scheduleId; assertNotNull(scheduleId); + + // cache schedule create transaction id for confirmation of scheduled transaction later + scheduledTransactionId = networkTransactionResponse.getReceipt().scheduledTransactionId; + assertNotNull(scheduledTransactionId); } public void signSignature(ExpandedAccountId signatoryAccount) throws PrecheckStatusException, ReceiptStatusException, TimeoutException { - currentSignersCount++; + currentSignersCount++; // add signatoryAccount and payer networkTransactionResponse = scheduleClient.signSchedule( signatoryAccount, - scheduledTransaction, scheduleId); assertNotNull(networkTransactionResponse.getTransactionId()); assertNotNull(networkTransactionResponse.getReceipt()); + + scheduledTransactionId = networkTransactionResponse.getReceipt().scheduledTransactionId; + assertNotNull(scheduledTransactionId); } @Then("the scheduled transaction is signed by {string}") @@ -306,58 +314,23 @@ public void deleteSchedule() throws PrecheckStatusException, ReceiptStatusExcept } @Then("the network confirms schedule presence") - public void verifyNetworkScheduleResponse() throws TimeoutException, PrecheckStatusException { - scheduleInfo = new ScheduleInfoQuery() - .setScheduleId(scheduleId) - .setNodeAccountIds(Collections.singletonList(AccountId.fromString(acceptanceProps.getNodeId()))) - .execute(scheduleClient.getClient()); - - assertNotNull(scheduleInfo); - assertThat(scheduleInfo.scheduleId).isEqualTo(scheduleId); - assertThat(scheduleInfo.adminKey) - .isEqualTo(accountClient.getSdkClient().getExpandedOperatorAccountId().getPublicKey()); - assertThat(scheduleInfo.payerAccountId) - .isEqualTo(accountClient.getSdkClient().getExpandedOperatorAccountId().getAccountId()); + public void verifyNetworkScheduleResponse() throws TimeoutException, PrecheckStatusException, + ReceiptStatusException { + verifyNetworkScheduleStatus(ScheduleStatus.NON_EXECUTED); } - @Then("the network confirms the executed schedule is removed from state") - public void verifyNetworkScheduleNotPresentResponse() throws TimeoutException, PrecheckStatusException, + @Then("the network confirms the schedule is executed") + public void verifyNetworkScheduleExecutedResponse() throws TimeoutException, + PrecheckStatusException, ReceiptStatusException { - boolean invalidSchedule = false; - try { - scheduleInfo = new ScheduleInfoQuery() - .setScheduleId(scheduleId) - .setNodeAccountIds(Collections.singletonList(AccountId.fromString(acceptanceProps.getNodeId()))) - .execute(scheduleClient.getClient()); - - // verify executed from 3 min record, set scheduled=true on scheduleCreateTransactionId and get receipt - } catch (PrecheckStatusException ex) { - assertThat(ex).hasMessageContaining("INVALID_SCHEDULE_ID"); - invalidSchedule = true; - } - - assertThat(invalidSchedule).isTrue(); - log.info("Schedule {} no longer returned from network state", scheduleId); - - TransactionId scheduledTransactionId = scheduleCreateTransactionId.setScheduled(true); - assertNotNull(scheduledTransactionId); - log.debug("Executed transaction {}.", scheduledTransactionId); - - TransactionReceipt transactionReceipt = scheduledTransactionId.getReceipt(scheduleClient.getClient()); - assertNotNull(transactionReceipt); + verifyNetworkScheduleStatus(ScheduleStatus.EXECUTED); } - private void verifyScheduleInfoFromNetwork(int expectedSignatoriesCount) throws TimeoutException, - PrecheckStatusException { - scheduleInfo = new ScheduleInfoQuery() - .setScheduleId(scheduleId) - .setNodeAccountIds(Collections.singletonList(AccountId.fromString(acceptanceProps.getNodeId()))) - .execute(scheduleClient.getClient()); - - assertNotNull(scheduleInfo); - assertThat(scheduleInfo.scheduleId).isEqualTo(scheduleId); - assertThat(scheduleInfo.signatories).isNotEmpty(); - assertThat(scheduleInfo.signatories.size()).isEqualTo(expectedSignatoriesCount); + @Then("the network confirms the schedule is deleted") + public void verifyNetworkScheduleDeletedResponse() throws TimeoutException, + PrecheckStatusException, + ReceiptStatusException { + verifyNetworkScheduleStatus(ScheduleStatus.DELETED); } @Then("the network confirms some signers have provided their signatures") @@ -386,8 +359,7 @@ public void verifyMirrorAPIResponses(int status) { backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") public void verifyExecutedScheduleFromMirror() { - MirrorScheduleResponse mirrorSchedule = verifyScheduleFromMirror(); - assertThat(mirrorSchedule.getExecutedTimestamp()).isNotNull(); + MirrorScheduleResponse mirrorSchedule = verifyScheduleFromMirror(ScheduleStatus.EXECUTED); verifyScheduledTransaction(mirrorSchedule.getExecutedTimestamp()); } @@ -396,15 +368,97 @@ public void verifyExecutedScheduleFromMirror() { backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") public void verifyNonExecutedScheduleFromMirror() { - MirrorScheduleResponse mirrorSchedule = verifyScheduleFromMirror(); - assertThat(mirrorSchedule.getExecutedTimestamp()).isNull(); + verifyScheduleFromMirror(ScheduleStatus.NON_EXECUTED); } - public MirrorScheduleResponse verifyScheduleFromMirror() { + @Then("the mirror node REST API should verify the deleted schedule entity") + @Retryable(value = {AssertionError.class, AssertionFailedError.class, WebClientResponseException.class}, + backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), + maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") + public void verifyDeletedScheduleFromMirror() { + verifyScheduleFromMirror(ScheduleStatus.DELETED); + } + + private void validateScheduleInfo(ScheduleInfo scheduleInfo) { + assertNotNull(scheduleInfo); + assertThat(scheduleInfo.scheduleId).isEqualTo(scheduleId); + assertThat(scheduleInfo.adminKey) + .isEqualTo(accountClient.getSdkClient().getExpandedOperatorAccountId().getPublicKey()); + assertThat(scheduleInfo.payerAccountId) + .isEqualTo(accountClient.getSdkClient().getExpandedOperatorAccountId().getAccountId()); + } + + private void verifyNetworkScheduleStatus(ScheduleStatus scheduleStatus) throws TimeoutException, + PrecheckStatusException, + ReceiptStatusException { + scheduleInfo = new ScheduleInfoQuery() + .setScheduleId(scheduleId) + .setNodeAccountIds(Collections.singletonList(AccountId.fromString(acceptanceProps.getNodeId()))) + .execute(scheduleClient.getClient()); + + // verify executed from 3 min record, set scheduled=true on scheduleCreateTransactionId and get receipt + validateScheduleInfo(scheduleInfo); + + switch (scheduleStatus) { + case NON_EXECUTED: + assertThat(scheduleInfo.executedAt).isNull(); + assertThat(scheduleInfo.deletedAt).isNull(); + break; + case EXECUTED: + assertThat(scheduleInfo.deletedAt).isNull(); + assertThat(scheduleInfo.executedAt).isNotNull(); + TransactionReceipt transactionReceipt = scheduledTransactionId.getReceipt(scheduleClient.getClient()); + assertNotNull(transactionReceipt); + log.debug("Executed transaction {} was confirmed", scheduledTransactionId); + break; + case DELETED: + assertThat(scheduleInfo.deletedAt).isNotNull(); + assertThat(scheduleInfo.executedAt).isNull(); + break; + default: + break; + } + + log.info("Schedule {} status was confirmed by network state", scheduleId); + } + + private void verifyScheduleInfoFromNetwork(int expectedSignatoriesCount) throws TimeoutException, + PrecheckStatusException { + scheduleInfo = new ScheduleInfoQuery() + .setScheduleId(scheduleId) + .setNodeAccountIds(Collections.singletonList(AccountId.fromString(acceptanceProps.getNodeId()))) + .execute(scheduleClient.getClient()); + + assertNotNull(scheduleInfo); + assertThat(scheduleInfo.scheduleId).isEqualTo(scheduleId); + assertThat(scheduleInfo.signatories.size()).isEqualTo(expectedSignatoriesCount); + } + + private MirrorScheduleResponse verifyScheduleFromMirror(ScheduleStatus scheduleStatus) { MirrorScheduleResponse mirrorSchedule = mirrorClient.getScheduleInfo(scheduleId.toString()); + assertNotNull(mirrorSchedule); assertThat(mirrorSchedule.getScheduleId()).isEqualTo(scheduleId.toString()); - assertThat(mirrorSchedule.getSignatures().size()).isEqualTo(currentSignersCount); + log.info("{} has {} signatories", scheduleId, mirrorSchedule.getSignatures().size()); + + Set signatureSet = new HashSet(); + mirrorSchedule.getSignatures().forEach((k) -> { + // get unique set of signatures + signatureSet.add(k.getPublicKeyPrefix()); + }); + assertThat(signatureSet.size()).isEqualTo(currentSignersCount); + + switch (scheduleStatus) { + case DELETED: + case NON_EXECUTED: + assertThat(mirrorSchedule.getExecutedTimestamp()).isNull(); + break; + case EXECUTED: + assertThat(mirrorSchedule.getExecutedTimestamp()).isNotNull(); + break; + default: + break; + } return mirrorSchedule; } @@ -459,4 +513,11 @@ public void closeClients() { tokenClient.close(); topicClient.close(); } + + @RequiredArgsConstructor + public enum ScheduleStatus { + NON_EXECUTED, + EXECUTED, + DELETED + } } diff --git a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/TokenFeature.java b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/TokenFeature.java index a25e64307cf..038dcf30648 100644 --- a/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/TokenFeature.java +++ b/hedera-mirror-test/src/test/java/com/hedera/mirror/test/e2e/acceptance/steps/TokenFeature.java @@ -51,6 +51,7 @@ import com.hedera.mirror.test.e2e.acceptance.client.AccountClient; import com.hedera.mirror.test.e2e.acceptance.client.MirrorNodeClient; import com.hedera.mirror.test.e2e.acceptance.client.TokenClient; +import com.hedera.mirror.test.e2e.acceptance.client.TopicClient; import com.hedera.mirror.test.e2e.acceptance.config.AcceptanceTestProperties; import com.hedera.mirror.test.e2e.acceptance.props.ExpandedAccountId; import com.hedera.mirror.test.e2e.acceptance.props.MirrorCryptoBalance; @@ -75,6 +76,8 @@ public class TokenFeature { private AccountClient accountClient; @Autowired private MirrorNodeClient mirrorClient; + @Autowired + private TopicClient topicClient; private PrivateKey tokenKey; private TokenId tokenId; private ExpandedAccountId sender; @@ -221,34 +224,45 @@ public void deleteToken() throws PrecheckStatusException, ReceiptStatusException @Retryable(value = {AssertionError.class, AssertionFailedError.class, WebClientResponseException.class}, backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") - public void verifyMirrorAPIResponses(int status) { + public void verifyMirrorAPIResponses(int status) throws PrecheckStatusException, ReceiptStatusException, + TimeoutException { verifyTransactions(status); + + // publish background message to network to reduce possibility of stale info in low TPS environment + topicClient.publishMessageToDefaultTopic(); } @Then("the mirror node REST API should return status {int} for token fund flow") @Retryable(value = {AssertionError.class, AssertionFailedError.class, WebClientResponseException.class}, backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") - public void verifyMirrorTokenFundFlow(int status) { + public void verifyMirrorTokenFundFlow(int status) throws PrecheckStatusException, ReceiptStatusException, + TimeoutException { verifyBalances(); verifyTransactions(status); verifyToken(); verifyTokenTransfers(); + + // publish background message to network to reduce possibility of stale info in low TPS environment + topicClient.publishMessageToDefaultTopic(); } @Then("the mirror node REST API should confirm token update") @Retryable(value = {AssertionError.class, AssertionFailedError.class, WebClientResponseException.class}, backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") - public void verifyMirrorTokenUpdateFlow() { + public void verifyMirrorTokenUpdateFlow() throws PrecheckStatusException, ReceiptStatusException, TimeoutException { verifyTokenUpdate(); + + // publish background message to network to reduce possibility of stale info in low TPS environment + topicClient.publishMessageToDefaultTopic(); } @Then("the mirror node REST API should return status {int} for transaction {string}") @Retryable(value = {AssertionError.class, AssertionFailedError.class, WebClientResponseException.class}, backoff = @Backoff(delayExpression = "#{@restPollingProperties.delay.toMillis()}"), maxAttemptsExpression = "#{@restPollingProperties.maxAttempts}") - public void verifyMirrorRestTransactionIsPresent(int status, String transactionIdString) { + public void verifyMirrorRestTransactionIsPresent(int status, String transactionIdString) throws PrecheckStatusException, ReceiptStatusException, TimeoutException { MirrorTransactionsResponse mirrorTransactionsResponse = mirrorClient.getTransactions(transactionIdString); List transactions = mirrorTransactionsResponse.getTransactions(); @@ -260,6 +274,9 @@ public void verifyMirrorRestTransactionIsPresent(int status, String transactionI if (status == HttpStatus.OK.value()) { assertThat(mirrorTransaction.getResult()).isEqualTo("SUCCESS"); } + + // publish background message to network to reduce possibility of stale info in low TPS environment + topicClient.publishMessageToDefaultTopic(); } private void createNewToken(String symbol, int freezeStatus, int kycStatus) throws PrecheckStatusException, diff --git a/hedera-mirror-test/src/test/resources/features/schedule/schedule.feature b/hedera-mirror-test/src/test/resources/features/schedule/schedule.feature index f31d253fc18..c823a848b3f 100644 --- a/hedera-mirror-test/src/test/resources/features/schedule/schedule.feature +++ b/hedera-mirror-test/src/test/resources/features/schedule/schedule.feature @@ -14,7 +14,7 @@ Feature: Schedule Base Coverage Feature When the scheduled transaction is signed by treasuryAccount And the mirror node REST API should return status for the schedule transaction And the mirror node REST API should verify the executed schedule entity - And the network confirms the executed schedule is removed from state + And the network confirms the schedule is executed Examples: | accountName | httpStatusCode | | "CAROL" | 200 | @@ -26,9 +26,9 @@ Feature: Schedule Base Coverage Feature Then the mirror node REST API should return status for the schedule transaction And the mirror node REST API should verify the non executed schedule entity When I successfully delete the schedule - And the network confirms the executed schedule is removed from state + And the network confirms the schedule is deleted Then the mirror node REST API should return status for the schedule transaction - And the mirror node REST API should verify the non executed schedule entity + And the mirror node REST API should verify the deleted schedule entity Examples: | httpStatusCode | | 200 | @@ -42,11 +42,11 @@ Feature: Schedule Base Coverage Feature When the scheduled transaction is signed by Then the mirror node REST API should return status for the schedule transaction And the mirror node REST API should verify the executed schedule entity - And the network confirms the executed schedule is removed from state + And the network confirms the schedule is executed Examples: | initialSignatureCount | accountName | httpStatusCode | | 3 | "ALICE" | 200 | - | 10 | "DAVE" | 200 | +# | 10 | "DAVE" | 200 | @acceptance @Acceptance Scenario Outline: Validate scheduled Hbar and Token transfer - ScheduleCreate of TokenTransfer and multi ScheduleSign @@ -61,7 +61,7 @@ Feature: Schedule Base Coverage Feature When the scheduled transaction is signed by And the mirror node REST API should return status for the schedule transaction And the mirror node REST API should verify the executed schedule entity - When the network confirms the executed schedule is removed from state + When the network confirms the schedule is executed Examples: | sender | receiver | httpStatusCode | | "ALICE" | "DAVE" | 200 | @@ -75,7 +75,7 @@ Feature: Schedule Base Coverage Feature When the scheduled transaction is signed by Then the mirror node REST API should return status for the schedule transaction And the mirror node REST API should verify the executed schedule entity - And the network confirms the executed schedule is removed from state + And the network confirms the schedule is executed Examples: | accountName | httpStatusCode | | "ALICE" | 200 | diff --git a/hedera-mirror-test/src/test/resources/features/setup/setup.feature b/hedera-mirror-test/src/test/resources/features/setup/setup.feature index 99ad1c4c0a6..d38e891761e 100644 --- a/hedera-mirror-test/src/test/resources/features/setup/setup.feature +++ b/hedera-mirror-test/src/test/resources/features/setup/setup.feature @@ -16,4 +16,13 @@ Feature: Setup entities for various features Then the new balance should reflect cryptotransfer of Examples: | amount | account | - | 1000 | 1043 | + | 1000 | | + + @onboardaccount + Scenario Outline: Setup new accounts for hbar and token transfers + Given I create a new account with balance tℏ + When I send tℏ to newly created account + Then the new balance should reflect cryptotransfer of + Examples: + | amount | topUp | + | 1000 | 1000 |