Skip to content

Commit

Permalink
CryptoDelete handle implementation (#6694)
Browse files Browse the repository at this point in the history
Signed-off-by: Neeharika-Sompalli <neeharika.sompalli@swirldslabs.com>
  • Loading branch information
Neeharika-Sompalli committed May 26, 2023
1 parent 06dd0da commit 8b5431c
Show file tree
Hide file tree
Showing 16 changed files with 631 additions and 62 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/platform-zxcron-release-jrs-regression.yaml
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
}
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(
@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(
// @ConfigProperty(defaultValue = "") Set<EntityType> targetTypes
@ConfigProperty(defaultValue = "CONTRACT,ACCOUNT") Set<String> targetTypes) {
public boolean expireContracts() {
return targetTypes.contains("CONTRACT");
}

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

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

0 comments on commit 8b5431c

Please sign in to comment.