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 20 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 @@ -47,10 +47,10 @@ jobs:

while IFS= read -r line; do
[[ "${line}" =~ ^release/([0-9]+).([0-9]+) ]] || continue

major="${BASH_REMATCH[1]}"
minor="${BASH_REMATCH[2]}"

if [[ "${major}" -eq 0 && "${minor}" -lt 38 ]]; then
continue
fi
Expand Down
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

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

/**
* Various entity types in all services
*/
public enum EntityType {
ACCOUNT,
CONTRACT,
FILE,
NFT,
SCHEDULE,
TOKEN,
TOKEN_ASSOCIATION,
TOPIC

Check warning on line 30 in hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/EntityType.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/validation/EntityType.java#L22-L30

Added lines #L22 - L30 were not covered by tests
}
Expand Up @@ -16,8 +16,10 @@

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

import com.hedera.hapi.node.base.ResponseCodeEnum;
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 +34,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 +47,37 @@ 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);

/**
*
* @return OK if the account is not expired, otherwise the appropriate error code
*/
/**
* Gets the expiration status of an entity based on the {@link EntityType}.
* @param entityType entity type
* @param isMarkedExpired if the entity is marked as expired and pending removal
* @param balanceAvailableForSelfRenewal if balance is available for self renewal
* @return OK if the entity is not expired, otherwise the appropriate error code
*/
@NonNull
ResponseCodeEnum expirationStatus(
Neeharika-Sompalli marked this conversation as resolved.
Show resolved Hide resolved
@NonNull final EntityType entityType,
final boolean isMarkedExpired,
final long balanceAvailableForSelfRenewal);

/**
* Gets the expiration status of an account and returns if the account is detached
* @param entityType entity type
* @param isMarkedExpired if the entity is marked as expired and pending removal
* @param balanceAvailableForSelfRenewal if balance is available for self renewal
* @return true if the account is detached, otherwise false
*/
default boolean isDetached(
@NonNull final EntityType entityType,
final boolean isMarkedExpired,
final long balanceAvailableForSelfRenewal) {
return expirationStatus(entityType, isMarkedExpired, balanceAvailableForSelfRenewal) != ResponseCodeEnum.OK;
}
}
Expand Up @@ -85,6 +85,7 @@ public void dispatchHandle(@NonNull final HandleContext context) {
case CONSENSUS_DELETE_TOPIC -> dispatchConsensusDeleteTopic(context);
case CONSENSUS_SUBMIT_MESSAGE -> dispatchConsensusSubmitMessage(context);
case CRYPTO_CREATE_ACCOUNT -> dispatchCryptoCreate(context);
case CRYPTO_DELETE -> dispatchCryptoDelete(context);
case TOKEN_ASSOCIATE -> dispatchTokenAssociate(context);
case TOKEN_FREEZE -> dispatchTokenFreeze(context);
case TOKEN_UNFREEZE -> dispatchTokenUnfreeze(context);
Expand Down Expand Up @@ -175,6 +176,17 @@ private void dispatchTokenGrantKycToAccount(@NonNull final HandleContext handleC
finishTokenGrantKycToAccount(handleContext);
}

private void dispatchCryptoDelete(@NonNull final HandleContext handleContext) {
final var handler = handlers.cryptoDeleteHandler();
handler.handle(handleContext);
finishCryptoDelete(handleContext);
}

protected void finishCryptoDelete(@NonNull final HandleContext handleContext) {
final var accountStore = handleContext.writableStore(WritableAccountStore.class);
accountStore.commit();
}

private void finishTokenGrantKycToAccount(@NonNull final HandleContext handleContext) {
final var tokenRelStore = handleContext.writableStore(WritableTokenRelationStore.class);
tokenRelStore.commit();
Expand Down
Expand Up @@ -26,6 +26,7 @@
import com.hedera.node.app.spi.validation.AttributeValidator;
import com.hedera.node.app.spi.validation.ExpiryValidator;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.config.ConfigProvider;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.function.LongSupplier;
import javax.inject.Inject;
Expand All @@ -43,7 +44,8 @@ public MonoExpiryValidator(
@NonNull final AccountStore accountStore,
@NonNull final AttributeValidator attributeValidator,
@NonNull final LongSupplier consensusSecondNow,
@NonNull final HederaNumbers numbers) {
@NonNull final HederaNumbers numbers,
@NonNull final ConfigProvider configProvider) {
super(
id -> {
try {
Expand All @@ -54,6 +56,7 @@ public MonoExpiryValidator(
},
attributeValidator,
consensusSecondNow,
numbers);
numbers,
configProvider);
}
}
Expand Up @@ -16,21 +16,29 @@

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.node.app.service.mono.config.HederaNumbers;
import com.hedera.node.app.service.mono.store.models.Id;
import com.hedera.node.app.spi.validation.AttributeValidator;
import com.hedera.node.app.spi.validation.EntityType;
import com.hedera.node.app.spi.validation.ExpiryMeta;
import com.hedera.node.app.spi.validation.ExpiryValidator;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.config.ConfigProvider;
import com.hedera.node.config.data.AutoRenewConfig;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.LongSupplier;
import javax.inject.Inject;

/**
* An implementation of {@link ExpiryValidator} that encapsulates the current policies
Expand All @@ -42,16 +50,20 @@ public class StandardizedExpiryValidator implements ExpiryValidator {
private final LongSupplier consensusSecondNow;
private final AttributeValidator attributeValidator;
private final HederaNumbers numbers;
private final ConfigProvider configProvider;

@Inject
public StandardizedExpiryValidator(
@NonNull final Consumer<Id> idValidator,
@NonNull final AttributeValidator attributeValidator,
@NonNull final LongSupplier consensusSecondNow,
@NonNull final HederaNumbers numbers) {
@NonNull final HederaNumbers numbers,
@NonNull final ConfigProvider configProvider) {
this.attributeValidator = Objects.requireNonNull(attributeValidator);
this.consensusSecondNow = Objects.requireNonNull(consensusSecondNow);
this.numbers = Objects.requireNonNull(numbers);
this.idValidator = Objects.requireNonNull(idValidator);
this.configProvider = Objects.requireNonNull(configProvider);
}

/**
Expand Down Expand Up @@ -115,6 +127,27 @@ public ExpiryMeta resolveUpdateAttempt(final ExpiryMeta currentMeta, final Expir
return new ExpiryMeta(resolvedExpiry, resolvedAutoRenewPeriod, resolvedAutoRenewNum);
}

/**
* {@inheritDoc}
*/
@Override
public ResponseCodeEnum expirationStatus(
@NonNull final EntityType entityType,
final boolean isMarkedExpired,
final long balanceAvailableForSelfRenewal) {
final var isSmartContract = entityType.equals(EntityType.CONTRACT);
final var autoRenewConfig = configProvider.getConfiguration().getConfigData(AutoRenewConfig.class);
if (!autoRenewConfig.isAutoRenewEnabled()
|| balanceAvailableForSelfRenewal > 0
|| !isMarkedExpired
|| isExpiryDisabled(
isSmartContract, autoRenewConfig.expireAccounts(), autoRenewConfig.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 +181,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 @@ -665,6 +665,19 @@ void dispatchesCryptoCreateAsExpected() {
verify(writableAccountStore).commit();
}

@Test
void dispatchesCryptoDeleteAsExpected() {
final var txnBody = TransactionBody.newBuilder()
.cryptoDelete(CryptoDeleteTransactionBody.DEFAULT)
.build();

given(handleContext.body()).willReturn(txnBody);
given(handleContext.writableStore(WritableAccountStore.class)).willReturn(writableAccountStore);

dispatcher.dispatchHandle(handleContext);
verify(writableAccountStore).commit();
}

@Test
void doesntCommitWhenUsageLimitsExceeded() {
final var txnBody = TransactionBody.newBuilder()
Expand Down
Expand Up @@ -16,28 +16,40 @@

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.config.VersionedConfigImpl;
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;
import com.hedera.node.app.service.mono.store.models.Id;
import com.hedera.node.app.spi.validation.AttributeValidator;
import com.hedera.node.app.spi.validation.EntityType;
import com.hedera.node.app.spi.validation.ExpiryMeta;
import com.hedera.node.app.spi.workflows.HandleException;
import com.hedera.node.config.ConfigProvider;
import com.hedera.node.config.VersionedConfiguration;
import com.hedera.node.config.testfixtures.HederaTestConfigBuilder;
import java.util.function.LongSupplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mock.Strictness;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
Expand All @@ -48,6 +60,7 @@ class MonoExpiryValidatorTest {
private static final long aPeriod = 666_666L;
private static final long bPeriod = 777_777L;
private static final long anAutoRenewNum = 888;
private static final long DEFAULT_CONFIG_VERSION = 1;

@Mock
private AttributeValidator attributeValidator;
Expand All @@ -61,11 +74,23 @@ class MonoExpiryValidatorTest {
@Mock
private HederaNumbers numbers;

@Mock
private Account account;

@Mock(strictness = Strictness.LENIENT)
private ConfigProvider configProvider;

private VersionedConfiguration configuration;

private MonoExpiryValidator subject;

@BeforeEach
void setUp() {
subject = new MonoExpiryValidator(accountStore, attributeValidator, consensusSecondNow, numbers);
subject =
new MonoExpiryValidator(accountStore, attributeValidator, consensusSecondNow, numbers, configProvider);
configuration =
new VersionedConfigImpl(new HederaTestConfigBuilder().getOrCreateConfig(), DEFAULT_CONFIG_VERSION);
given(configProvider.getConfiguration()).willReturn(configuration);
}

@Test
Expand Down Expand Up @@ -279,6 +304,33 @@ void canUseWildcardForRemovingAutoRenewAccount() {
assertEquals(update, subject.resolveUpdateAttempt(current, update));
}

@Test
void checksIfAccountIsDetachedIfBalanceZero() {
assertEquals(OK, subject.expirationStatus(EntityType.ACCOUNT, false, 0));
assertFalse(subject.isDetached(EntityType.ACCOUNT, false, 0));
}

@Test
void failsIfAccountExpiredAndPendingRemoval() {
assertEquals(ACCOUNT_EXPIRED_AND_PENDING_REMOVAL, subject.expirationStatus(EntityType.ACCOUNT, true, 0L));
assertTrue(subject.isDetached(EntityType.ACCOUNT, true, 0));

assertEquals(CONTRACT_EXPIRED_AND_PENDING_REMOVAL, subject.expirationStatus(EntityType.CONTRACT, true, 0L));
assertTrue(subject.isDetached(EntityType.CONTRACT, true, 0));
}

@Test
void notDetachedIfAccountNotExpired() {
assertEquals(OK, subject.expirationStatus(EntityType.ACCOUNT, false, 0L));
assertFalse(subject.isDetached(EntityType.ACCOUNT, false, 10));
}

@Test
void notDetachedIfAutoRenewDisabled() {
assertEquals(OK, subject.expirationStatus(EntityType.ACCOUNT, false, 0L));
assertFalse(subject.isDetached(EntityType.ACCOUNT, false, 0));
}

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(

Check warning on line 24 in hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java#L24

Added line #L24 was not covered by tests
// @ConfigProperty(defaultValue = "") Set<EntityType> targetTypes
@ConfigProperty(defaultValue = "CONTRACT,ACCOUNT") Set<String> targetTypes) {
public boolean expireContracts() {
return targetTypes.contains("CONTRACT");

Check warning on line 28 in hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java#L28

Added line #L28 was not covered by tests
}

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

Check warning on line 32 in hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/AutoRenewConfig.java#L32

Added line #L32 was not covered by tests
}

public boolean isAutoRenewEnabled() {
return !targetTypes.isEmpty();
}
}