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

Calculate acceptance test operator balance from USD #8957

Merged
merged 1 commit into from
Aug 6, 2024
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
3 changes: 2 additions & 1 deletion hedera-mirror-test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ include:
| `hedera.mirror.test.acceptance.createOperatorAccount` | true | Whether to create a separate operator account to run the acceptance tests. |
| `hedera.mirror.test.acceptance.emitBackgroundMessages` | false | Whether background topic messages should be emitted. |
| `hedera.mirror.test.acceptance.feature.maxContractFunctionGas` | 3000000 | The maximum amount of gas an account is willing to pay for a contract call. |
| `hedera.mirror.test.acceptance.feature.rejectToken` | true | Whether the RejectToken functionality should be verified or not. |
| `hedera.mirror.test.acceptance.feature.sidecars` | false | Whether information in sidecars should be used to verify test scenarios. |
| `hedera.mirror.test.acceptance.maxNodes` | 10 | The maximum number of nodes to validate from the address book. |
| `hedera.mirror.test.acceptance.maxRetries` | 2 | The number of times client should retry mirror node on supported failures. |
Expand All @@ -52,7 +53,7 @@ include:
| `hedera.mirror.test.acceptance.mirrorNodeAddress` | testnet.mirrornode.hedera.com:443 | The mirror node gRPC server endpoint including IP address and port. |
| `hedera.mirror.test.acceptance.network` | TESTNET | Which Hedera network to use. Can be either `MAINNET`, `PREVIEWNET`, `TESTNET` or `OTHER`. |
| `hedera.mirror.test.acceptance.nodes` | [] | A map of custom consensus node with the key being the account ID and the value its endpoint. |
| `hedera.mirror.test.acceptance.operatorBalance` | 80000000000 | The amount of tinybars to fund the operator. Applicable only when `createOperatorAccount` is `true`. |
| `hedera.mirror.test.acceptance.operatorBalance` | 60 | The amount of dollars to fund the operator. Applicable only when `createOperatorAccount` is `true`. |
| `hedera.mirror.test.acceptance.operatorId` | 0.0.2 | Operator account ID used to pay for transactions. |
| `hedera.mirror.test.acceptance.operatorKey` | Genesis key | Operator ED25519 or ECDSA private key used to sign transactions in hex encoded DER format. |
| `hedera.mirror.test.acceptance.rest.baseUrl` | https://testnet.mirrornode.hedera.com/api/v1 | The URL to the mirror node REST API. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
import com.hedera.hashgraph.sdk.AccountDeleteTransaction;
import com.hedera.hashgraph.sdk.AccountId;
import com.hedera.hashgraph.sdk.Client;
import com.hedera.hashgraph.sdk.EvmAddress;
import com.hedera.hashgraph.sdk.Hbar;
import com.hedera.hashgraph.sdk.PrivateKey;
import com.hedera.hashgraph.sdk.PublicKey;
import com.hedera.hashgraph.sdk.TopicDeleteTransaction;
import com.hedera.hashgraph.sdk.TopicId;
import com.hedera.hashgraph.sdk.TransactionReceipt;
import com.hedera.mirror.test.e2e.acceptance.config.AcceptanceTestProperties;
import com.hedera.mirror.test.e2e.acceptance.config.SdkProperties;
import com.hedera.mirror.test.e2e.acceptance.props.ExpandedAccountId;
import com.hedera.mirror.test.e2e.acceptance.props.NodeProperties;
import jakarta.inject.Named;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -61,6 +62,7 @@ public class SDKClient implements Cleanable {
private final AcceptanceTestProperties acceptanceTestProperties;
private final SdkProperties sdkProperties;
private final MirrorNodeClient mirrorNodeClient;
private final TopicId topicId;

@Getter
private final ExpandedAccountId expandedOperatorAccountId;
Expand All @@ -81,9 +83,10 @@ public SDKClient(
.setMaxAttempts(sdkProperties.getMaxAttempts())
.setMaxNodeReadmitTime(Duration.ofSeconds(60L))
.setMaxNodesPerTransaction(sdkProperties.getMaxNodesPerTransaction());
startupProbe.validateEnvironment(client);
var receipt = startupProbe.validateEnvironment(client);
this.topicId = receipt != null ? receipt.topicId : null;
validateClient();
expandedOperatorAccountId = getOperatorAccount();
expandedOperatorAccountId = getOperatorAccount(receipt);
this.client.setOperator(expandedOperatorAccountId.getAccountId(), expandedOperatorAccountId.getPrivateKey());
validateNetworkMap = this.client.getNetwork();
}
Expand All @@ -98,6 +101,19 @@ public void clean() {
var createdAccountId = expandedOperatorAccountId.getAccountId();
var operatorId = defaultOperator.getAccountId();

if (topicId != null) {
try {
var response = new TopicDeleteTransaction()
.setTopicId(topicId)
.freezeWith(client)
.sign(defaultOperator.getPrivateKey())
.execute(client);
log.info("Deleted startup probe topic {} via {}", topicId, response.transactionId);
} catch (Exception e) {
log.warn("Unable to delete startup probe topic {}", topicId, e);
}
}

if (!operatorId.equals(createdAccountId)) {
try {
var response = new AccountDeleteTransaction()
Expand Down Expand Up @@ -152,20 +168,35 @@ private Map<String, AccountId> getNetworkMap(Set<NodeProperties> nodes) {
.collect(Collectors.toMap(NodeProperties::getEndpoint, p -> AccountId.fromString(p.getAccountId())));
}

private ExpandedAccountId getOperatorAccount() {
private double getExchangeRate(TransactionReceipt receipt) {
if (receipt == null || receipt.exchangeRate == null) {
var currentRate = mirrorNodeClient.getExchangeRates().getCurrentRate();
int cents = currentRate.getCentEquivalent();
int hbars = currentRate.getHbarEquivalent();
return (double) cents / (double) hbars;
} else {
return receipt.exchangeRate.exchangeRateInCents;
}
}

private ExpandedAccountId getOperatorAccount(TransactionReceipt receipt) {
try {
if (acceptanceTestProperties.isCreateOperatorAccount()) {
// Use the same operator key in case we need to later manually update/delete any created entities.
PrivateKey privateKey = defaultOperator.getPrivateKey();
PublicKey publicKey = privateKey.getPublicKey();
EvmAddress alias = null;
if (privateKey.isECDSA()) {
alias = publicKey.toEvmAddress();
}
var privateKey = defaultOperator.getPrivateKey();
var publicKey = privateKey.getPublicKey();
var alias = privateKey.isECDSA() ? publicKey.toEvmAddress() : null;

// Convert USD balance property to hbars using exchange rate from probe
double exchangeRate = getExchangeRate(receipt);
var exchangeRateUsd = BigDecimal.valueOf(exchangeRate).divide(BigDecimal.valueOf(100));
var balance =
Hbar.from(acceptanceTestProperties.getOperatorBalance().divide(exchangeRateUsd));

var accountId = new AccountCreateTransaction()
.setInitialBalance(Hbar.fromTinybars(acceptanceTestProperties.getOperatorBalance()))
.setKey(publicKey)
.setAlias(alias)
.setInitialBalance(balance)
.setKey(publicKey)
.execute(client)
.getReceipt(client)
.accountId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.hedera.hashgraph.sdk.TopicMessageSubmitTransaction;
import com.hedera.hashgraph.sdk.Transaction;
import com.hedera.hashgraph.sdk.TransactionId;
import com.hedera.hashgraph.sdk.TransactionReceipt;
import com.hedera.hashgraph.sdk.TransactionReceiptQuery;
import com.hedera.hashgraph.sdk.TransactionResponse;
import com.hedera.mirror.test.e2e.acceptance.config.AcceptanceTestProperties;
Expand Down Expand Up @@ -55,22 +56,30 @@
@RequiredArgsConstructor
public class StartupProbe {

private static final Duration WAIT = Duration.ofSeconds(30L);

private final AcceptanceTestProperties acceptanceTestProperties;
private final RestClient.Builder restClient;

public void validateEnvironment(Client client) {
public TransactionReceipt validateEnvironment(Client client) {
var startupTimeout = acceptanceTestProperties.getStartupTimeout();
var stopwatch = Stopwatch.createStarted();

if (startupTimeout.equals(Duration.ZERO)) {
log.warn("Startup probe disabled");
return;
return null;
}

// Adjust these lower to recover faster since all nodes might be down during a reset. Restore at the end.
var maxNodeBackoff = client.getNodeMaxBackoff();
client.setNodeMaxBackoff(WAIT);
client.setMaxNodeReadmitTime(WAIT);

log.info("Creating a topic to confirm node connectivity");
var transactionId = executeTransaction(client, stopwatch, () -> new TopicCreateTransaction()).transactionId;
var receiptQuery = new TransactionReceiptQuery().setTransactionId(transactionId);
var topicId = executeQuery(client, stopwatch, () -> receiptQuery).topicId;
var receipt = executeQuery(client, stopwatch, () -> receiptQuery);
var topicId = receipt.topicId;
log.info("Created topic {} successfully", topicId);

callRestEndpoint(stopwatch, transactionId);
Expand All @@ -82,7 +91,11 @@ public void validateEnvironment(Client client) {
submitMessage(client, stopwatch, topicId);
} while (System.currentTimeMillis() - startTime > 10_000);

client.setNodeMaxBackoff(maxNodeBackoff);
client.setMaxNodeReadmitTime(maxNodeBackoff);

log.info("Startup probe successful");
return receipt;
}

@SneakyThrows
Expand Down Expand Up @@ -130,14 +143,14 @@ private TransactionResponse executeTransaction(
Client client, Stopwatch stopwatch, Supplier<Transaction<?>> transaction) {
var retry = retryOperations(stopwatch);
return retry.execute(
r -> transaction.get().setMaxAttempts(Integer.MAX_VALUE).execute(client, Duration.ofSeconds(30L)));
r -> transaction.get().setMaxAttempts(Integer.MAX_VALUE).execute(client, WAIT));
}

@SneakyThrows
private <T> T executeQuery(Client client, Stopwatch stopwatch, Supplier<Query<T, ?>> transaction) {
var retry = retryOperations(stopwatch);
return retry.execute(
r -> transaction.get().setMaxAttempts(Integer.MAX_VALUE).execute(client, Duration.ofSeconds(30L)));
r -> transaction.get().setMaxAttempts(Integer.MAX_VALUE).execute(client, WAIT));
}

private void callRestEndpoint(Stopwatch stopwatch, TransactionId transactionId) {
Expand Down Expand Up @@ -172,7 +185,8 @@ private RetryOperations retryOperations(Stopwatch stopwatch) {
.withListener(new RetryListener() {
@Override
public <T, E extends Throwable> void onError(RetryContext r, RetryCallback<T, E> c, Throwable t) {
log.warn("Retry attempt #{} with error: {}", r.getRetryCount(), t.getMessage());
log.warn(
"Retry attempt #{} with error: {} {}", r.getRetryCount(), t.getClass(), t.getMessage());
}
})
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@
import com.hedera.mirror.test.e2e.acceptance.client.ContractClient.NodeNameEnum;
import com.hedera.mirror.test.e2e.acceptance.props.NodeProperties;
import jakarta.inject.Named;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.LinkedHashSet;
import java.util.Set;
Expand Down Expand Up @@ -75,9 +78,10 @@ public class AcceptanceTestProperties {
@NotNull
private Set<NodeProperties> nodes = new LinkedHashSet<>();

@Max(50_000_000_000L * 100_000_000L)
@Min(100_000_000L)
private long operatorBalance = Hbar.from(800).toTinybars();
@NotNull
@DecimalMax("1000000")
@DecimalMin("1.0")
private BigDecimal operatorBalance = BigDecimal.valueOf(60); // Amount in USD

@NotBlank
private String operatorId = "0.0.2";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@ public class FeatureProperties {
@Max(5_000_000)
private long maxContractFunctionGas = 3_000_000;

private boolean rejectToken = true;

private boolean sidecars = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public void verifyMirrorAPIHollowAccountResponse(int amount) {
assertNotNull(mirrorAccountResponse.getAccount());
assertEquals(amount, mirrorAccountResponse.getBalance().getBalance());
// Hollow account indicated by not having a public key defined.
assertEquals(ACCOUNT_EMPTY_KEYLIST, mirrorAccountResponse.getKey().getKey());
assertThat(mirrorAccountResponse.getKey()).isNull();
}

@And("the mirror node REST API should indicate not found when using evm address to retrieve as a contract")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import com.hedera.mirror.test.e2e.acceptance.client.TokenClient;
import com.hedera.mirror.test.e2e.acceptance.client.TokenClient.TokenNameEnum;
import com.hedera.mirror.test.e2e.acceptance.client.TokenClient.TokenResponse;
import com.hedera.mirror.test.e2e.acceptance.config.AcceptanceTestProperties;
import com.hedera.mirror.test.e2e.acceptance.props.ExpandedAccountId;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
Expand All @@ -87,6 +88,7 @@
@RequiredArgsConstructor
public class TokenFeature extends AbstractFeature {

private final AcceptanceTestProperties properties;
private final TokenClient tokenClient;
private final AccountClient accountClient;
private final MirrorNodeClient mirrorClient;
Expand Down Expand Up @@ -457,6 +459,10 @@ public void burnToken(int amount) {

@Given("{account} rejects the fungible token")
public void rejectFungibleToken(AccountNameEnum ownerName) {
if (!properties.getFeatureProperties().isRejectToken()) {
return;
}

var owner = accountClient.getAccount(ownerName);
networkTransactionResponse = tokenClient.rejectFungibleToken(List.of(tokenId), owner);
assertThat(networkTransactionResponse.getTransactionId()).isNotNull();
Expand All @@ -467,6 +473,10 @@ public void rejectFungibleToken(AccountNameEnum ownerName) {
@Then("the mirror node REST API should return the transaction {account} returns {int} fungible token to {account}")
public void verifyTokenTransferForRejectedFungibleToken(
AccountNameEnum senderName, long amount, AccountNameEnum treasuryName) {
if (!properties.getFeatureProperties().isRejectToken()) {
return;
}

var sender = accountClient.getAccount(senderName).getAccountId();
var treasury = accountClient.getAccount(treasuryName).getAccountId();

Expand All @@ -488,6 +498,10 @@ public void verifyTokenTransferForRejectedFungibleToken(

@Given("{account} rejects serial number index {int}")
public void rejectNonFungibleToken(AccountNameEnum ownerName, int index) {
if (!properties.getFeatureProperties().isRejectToken()) {
return;
}

long serialNumber = tokenNftInfoMap.get(tokenId).get(index).serialNumber();
var nftId = new NftId(tokenId, serialNumber);
var owner = accountClient.getAccount(ownerName);
Expand All @@ -501,6 +515,10 @@ public void rejectNonFungibleToken(AccountNameEnum ownerName, int index) {
@Then(
"the mirror node REST API should return the transaction {account} returns serial number index {int} to {account}")
public void verifyTokenTransferForRejectedNft(AccountNameEnum senderName, int index, AccountNameEnum treasuryName) {
if (!properties.getFeatureProperties().isRejectToken()) {
return;
}

var sender = accountClient.getAccount(senderName).getAccountId();
var treasury = accountClient.getAccount(treasuryName).getAccountId();
long serialNumber = tokenNftInfoMap.get(tokenId).get(index).serialNumber();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
public class ContractCallResponseWrapper {
private static final String EMPTY_RESULT = "0x0000000000000000000000000000000000000000000000000000000000000000";

@NonNull private final ContractCallResponse response;
@NonNull
private final ContractCallResponse response;

public String getResult() {
return response.getResult();
Expand Down Expand Up @@ -120,4 +121,9 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(response);
}

@Override
public String toString() {
return response.getResult();
}
}
Loading