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

CryptoDelete handle implementation #6694

Merged
merged 21 commits into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -16,8 +16,11 @@

package com.hedera.node.app.spi.validation;

import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.app.spi.workflows.TransactionHandler;
import edu.umd.cs.findbugs.annotations.NonNull;

/**
* A type that any {@link TransactionHandler} can use to validate the expiry
Expand All @@ -32,7 +35,8 @@ public interface ExpiryValidator {
* @param creationMetadata the expiry metadata for the attempted creation
* @throws HandleException if the metadata is invalid
*/
ExpiryMeta resolveCreationAttempt(boolean entityCanSelfFundRenewal, ExpiryMeta creationMetadata);
@NonNull
ExpiryMeta resolveCreationAttempt(boolean entityCanSelfFundRenewal, @NonNull ExpiryMeta creationMetadata);

/**
* Validates the expiry metadata for an attempt to update an entity, and returns the
Expand All @@ -44,5 +48,31 @@ public interface ExpiryValidator {
* @return the expiry metadata that will result from the update
* @throws HandleException if the metadata is invalid
*/
ExpiryMeta resolveUpdateAttempt(ExpiryMeta currentMetadata, ExpiryMeta updateMetadata);
@NonNull
ExpiryMeta resolveUpdateAttempt(@NonNull ExpiryMeta currentMetadata, @NonNull ExpiryMeta updateMetadata);

/**
* Gets the expiration status of an account
* @param account the account to check
* @param isAutoRenewEnabled whether auto-renew is enabled for the account
* @param expireContracts whether to expire contracts
* @param expireAccounts whether to expire accounts
* @return OK if the account is not expired, otherwise the appropriate error code
*/
@NonNull
ResponseCodeEnum expirationStatus(
Neeharika-Sompalli marked this conversation as resolved.
Show resolved Hide resolved
@NonNull Account account, boolean isAutoRenewEnabled, boolean expireContracts, boolean expireAccounts);

/**
* Gets the expiration status of an account and returns if the account is detached
* @param account the account to check
* @param isAutoRenewEnabled whether auto-renew is enabled for the account
* @param expireContracts whether to expire contracts
* @param expireAccounts whether to expire accounts
* @return true if the account is detached, otherwise false
*/
default boolean isDetached(
@NonNull Account account, boolean isAutoRenewEnabled, boolean expireContracts, boolean expireAccounts) {
return expirationStatus(account, isAutoRenewEnabled, expireContracts, expireAccounts) != ResponseCodeEnum.OK;
}
}
Expand Up @@ -94,6 +94,11 @@ protected void finishCryptoCreate(
accountStore.commit();
}

@Override
protected void finishCryptoDelete(@NonNull final WritableAccountStore accountStore) {
accountStore.commit();
}

@Override
protected void finishConsensusUpdateTopic(@NonNull WritableTopicStore topicStore) {
topicStore.commit();
Expand Down
Expand Up @@ -110,6 +110,7 @@ public void dispatchHandle(
case TOKEN_FEE_SCHEDULE_UPDATE -> dispatchTokenFeeScheduleUpdate(
txn, writableStoreFactory.createTokenStore());
case CRYPTO_CREATE -> dispatchCryptoCreate(txn, writableStoreFactory.createAccountStore());
case CRYPTO_DELETE -> dispatchCryptoDelete(txn, writableStoreFactory.createAccountStore());
case UTIL_PRNG -> dispatchPrng(txn);
default -> throw new IllegalArgumentException(TYPE_NOT_SUPPORTED);
}
Expand Down Expand Up @@ -431,6 +432,18 @@ private void dispatchCryptoCreate(
finishCryptoCreate(recordBuilder, accountStore);
}

/**
* Dispatches the crypto delete transaction to the appropriate handler.
* @param cryptoDelete the crypto delete transaction body
* @param accountStore the writable account store
*/
private void dispatchCryptoDelete(
@NonNull final TransactionBody cryptoDelete, @NonNull final WritableAccountStore accountStore) {
final var handler = handlers.cryptoDeleteHandler();
handler.handle(handleContext, cryptoDelete, accountStore);
finishCryptoDelete(accountStore);
}

/**
* A temporary hook to isolate logic that we expect to move to a workflow, but
* is currently needed when running with facility implementations that are adapters
Expand All @@ -444,6 +457,17 @@ protected void finishCryptoCreate(
// No-op by default
}

/**
* A temporary hook to isolate logic that we expect to move to a workflow, but
* is currently needed when running with facility implementations that are adapters
* for either {@code mono-service} logic or integration tests.
*
* @param accountStore the account store used for the creation
*/
protected void finishCryptoDelete(@NonNull final WritableAccountStore accountStore) {
// No-op by default
}

/**
* Dispatches the token fee schedule update transaction to the appropriate handler.
*
Expand Down
Expand Up @@ -16,11 +16,16 @@

package com.hedera.node.app.workflows.handle.validation;

import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_EXPIRED_AND_PENDING_REMOVAL;
import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXPIRED_AND_PENDING_REMOVAL;
import static com.hedera.hapi.node.base.ResponseCodeEnum.EXPIRATION_REDUCTION_NOT_ALLOWED;
import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT;
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
import static com.hedera.node.app.spi.workflows.HandleException.validateFalse;
import static com.hedera.node.app.spi.workflows.HandleException.validateTrue;

import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.node.app.service.mono.config.HederaNumbers;
import com.hedera.node.app.service.mono.store.models.Id;
import com.hedera.node.app.spi.validation.AttributeValidator;
Expand Down Expand Up @@ -115,6 +120,26 @@ public ExpiryMeta resolveUpdateAttempt(final ExpiryMeta currentMeta, final Expir
return new ExpiryMeta(resolvedExpiry, resolvedAutoRenewPeriod, resolvedAutoRenewNum);
}

/**
* {@inheritDoc}
*/
@Override
public ResponseCodeEnum expirationStatus(
@NonNull final Account account,
final boolean isAutoRenewEnabled,
final boolean expireAccounts,
final boolean expireContracts) {
final var isSmartContract = account.smartContract();
if (!isAutoRenewEnabled
|| account.tinybarBalance() > 0
|| !account.expiredAndPendingRemoval()
|| isExpiryDisabled(isSmartContract, expireAccounts, expireContracts)) {
return OK;
}

return isSmartContract ? CONTRACT_EXPIRED_AND_PENDING_REMOVAL : ACCOUNT_EXPIRED_AND_PENDING_REMOVAL;
}

/**
* Helper to check if an entity with the given metadata has a completely specified
* auto-renew configuration. This is true if either the {@link ExpiryMeta} includes
Expand Down Expand Up @@ -148,4 +173,8 @@ private void validateAutoRenewAccount(final long shard, final long realm, final
final var autoRenewId = new Id(numbers.shard(), numbers.realm(), num);
idValidator.accept(autoRenewId);
}

private boolean isExpiryDisabled(boolean smartContract, boolean expireAccounts, boolean expireContracts) {
return (smartContract && !expireContracts) || (!smartContract && !expireAccounts);
}
}
Expand Up @@ -619,6 +619,14 @@ void dispatchesCryptoCreateAsExpected() {
verify(writableAccountStore).commit();
}

@Test
void dispatchesCryptoDeleteAsExpected() {
given(writableStoreFactory.createAccountStore()).willReturn(writableAccountStore);

dispatcher.dispatchHandle(HederaFunctionality.CRYPTO_DELETE, transactionBody, writableStoreFactory);
verify(writableAccountStore).commit();
}

@Test
void doesntCommitWhenUsageLimitsExceeded() {
final var createBuilder = mock(CreateAccountRecordBuilder.class);
Expand Down
Expand Up @@ -16,16 +16,22 @@

package com.hedera.node.app.workflows.handle.validation;

import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_EXPIRED_AND_PENDING_REMOVAL;
import static com.hedera.hapi.node.base.ResponseCodeEnum.CONTRACT_EXPIRED_AND_PENDING_REMOVAL;
import static com.hedera.hapi.node.base.ResponseCodeEnum.OK;
import static com.hedera.node.app.spi.validation.ExpiryMeta.NA;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;

import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.node.app.service.evm.exceptions.InvalidTransactionException;
import com.hedera.node.app.service.mono.config.HederaNumbers;
import com.hedera.node.app.service.mono.store.AccountStore;
Expand Down Expand Up @@ -61,6 +67,9 @@ class MonoExpiryValidatorTest {
@Mock
private HederaNumbers numbers;

@Mock
private Account account;

private MonoExpiryValidator subject;

@BeforeEach
Expand Down Expand Up @@ -279,6 +288,40 @@ void canUseWildcardForRemovingAutoRenewAccount() {
assertEquals(update, subject.resolveUpdateAttempt(current, update));
}

@Test
void checksIfAccountIsDetachedIfBalanceZero() {
given(account.tinybarBalance()).willReturn(0L);

assertEquals(OK, subject.expirationStatus(account, true, true, true));
assertFalse(subject.isDetached(account, true, true, true));
}

@Test
void failsIfAccountExpiredAndPendingRemoval() {
given(account.expiredAndPendingRemoval()).willReturn(true);

assertEquals(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL, subject.expirationStatus(account, true, true, true));
assertTrue(subject.isDetached(account, true, true, true));

given(account.smartContract()).willReturn(true);
assertEquals(CONTRACT_EXPIRED_AND_PENDING_REMOVAL, subject.expirationStatus(account, true, true, true));
assertTrue(subject.isDetached(account, true, true, true));
}

@Test
void notDetachedIfAccountNotExpired() {
given(account.expiredAndPendingRemoval()).willReturn(false);

assertEquals(OK, subject.expirationStatus(account, true, true, true));
assertFalse(subject.isDetached(account, true, true, true));
}

@Test
void notDetachedIfAutoRenewDisabled() {
assertEquals(OK, subject.expirationStatus(account, false, false, false));
assertFalse(subject.isDetached(account, false, false, false));
}

private static void assertFailsWith(final ResponseCodeEnum expected, final Runnable runnable) {
final var e = assertThrows(HandleException.class, runnable::run);
assertEquals(expected, e.getStatus());
Expand Down
Expand Up @@ -17,7 +17,22 @@
package com.hedera.node.config.data;

import com.swirlds.config.api.ConfigData;
import com.swirlds.config.api.ConfigProperty;
import java.util.Set;

@ConfigData("autoRenew")
public record AutoRenewConfig( // @ConfigProperty(defaultValue = "") Set<EntityType> targetTypes
) {}
public record AutoRenewConfig(
// @ConfigProperty(defaultValue = "") Set<EntityType> targetTypes
@ConfigProperty(defaultValue = "CONTRACT") Set<String> targetTypes) {
public boolean expireContracts() {
return targetTypes.contains("CONTRACT");
}

public boolean expireAccounts() {
return targetTypes.contains("ACCOUNT");
}

public boolean isAutoRenewEnabled() {
return !targetTypes.isEmpty();
}
}
4 changes: 3 additions & 1 deletion hedera-node/hedera-token-service-impl/build.gradle.kts
Expand Up @@ -30,7 +30,9 @@ configurations.all {
dependencies {
implementation(project(":hedera-node:hapi"))
implementation(project(":hedera-node:hedera-config"))
testImplementation(project(mapOf("path" to ":hedera-node:hedera-app")))
implementation(project(":hedera-node:hedera-config"))
testImplementation(project(":hedera-node:hedera-app"))
testImplementation(testFixtures(project(":hedera-node:hedera-config")))
annotationProcessor(libs.dagger.compiler)
api(project(":hedera-node:hedera-token-service"))
implementation(project(":hedera-node:hapi"))
Expand Down
Expand Up @@ -127,6 +127,15 @@ public Optional<Account> getForModify(final AccountID id) {
return Optional.ofNullable(accountState.getForModify(EntityNumVirtualKey.fromLong(accountNum)));
}

/**
* Removes the {@link Account} with the given {@link AccountID} from the state.
* This will add value of the accountId to num in the modifications in state.
* @param accountID - the account id of the account to be removed.
*/
public void remove(@NonNull final AccountID accountID) {
accountState.remove(EntityNumVirtualKey.fromLong(accountID.accountNum()));
}

/**
* Returns the number of accounts in the state. It also includes modifications in the {@link
* WritableKVState}.
Expand Down

This file was deleted.