From e008f268a629d3a7ced166fd160a99e1f8675a97 Mon Sep 17 00:00:00 2001 From: Ivan Bankov Date: Tue, 2 Jul 2024 15:45:25 +0300 Subject: [PATCH 01/13] feat: Implement pending state for airdrop purposes (#13566) Signed-off-by: ibankov Co-authored-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- hapi/build.gradle.kts | 3 +- .../java/com/hedera/hapi/util/HapiUtils.java | 3 +- .../app/hapi/utils/CommonPbjConverters.java | 9 +- .../app/spi/metrics/StoreMetricsService.java | 1 + .../app/metrics/StoreMetricsServiceImpl.java | 1 + .../node/app/store/ReadableStoreFactory.java | 3 + .../node/app/store/WritableStoreFactory.java | 2 + .../node/config/data/ApiPermissionConfig.java | 4 + .../hedera/node/config/data/TokensConfig.java | 2 + .../token/impl/ReadableAirdropStoreImpl.java | 70 ++++++ .../token/impl/WritableAirdropStore.java | 114 +++++++++ .../token/impl/schemas/V0500TokenSchema.java | 14 ++ .../test/ReadableAirdropStoreImplTest.java | 163 +++++++++++++ .../impl/test/WritableAirdropStoreTest.java | 225 ++++++++++++++++++ .../test/handlers/util/StateBuilderUtil.java | 16 ++ .../test/schemas/V0500TokenSchemaTest.java | 17 ++ .../service/token/ReadableAirdropStore.java | 59 +++++ 17 files changed, 700 insertions(+), 6 deletions(-) create mode 100644 hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java create mode 100644 hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java create mode 100644 hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java create mode 100644 hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java create mode 100644 hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java diff --git a/hapi/build.gradle.kts b/hapi/build.gradle.kts index 1844d5a7cdf5..64fe0dac2ecf 100644 --- a/hapi/build.gradle.kts +++ b/hapi/build.gradle.kts @@ -32,7 +32,8 @@ tasks.cloneHederaProtobufs { // uncomment below to use a specific tag // tag = "v0.51.0" // uncomment below to use a specific branch - branch = "main" + // branch = "main" + branch = "hip-904-TokenClaimAirdrop" } sourceSets { diff --git a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java index 486d5b67f406..6a34b5595bd6 100644 --- a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java +++ b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java @@ -228,12 +228,13 @@ public static HederaFunctionality functionOf(final TransactionBody txn) throws U case TOKEN_UPDATE -> HederaFunctionality.TOKEN_UPDATE; case TOKEN_UPDATE_NFTS -> HederaFunctionality.TOKEN_UPDATE_NFTS; case TOKEN_WIPE -> HederaFunctionality.TOKEN_ACCOUNT_WIPE; - case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT; case UTIL_PRNG -> HederaFunctionality.UTIL_PRNG; case UNCHECKED_SUBMIT -> HederaFunctionality.UNCHECKED_SUBMIT; case NODE_CREATE -> HederaFunctionality.NODE_CREATE; case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE; case NODE_DELETE -> HederaFunctionality.NODE_DELETE; + case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT; + case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP; case UNSET -> throw new UnknownHederaFunctionality(); }; } diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java index 775e6ece76ff..ea6024536f5f 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java @@ -320,12 +320,13 @@ private static R explicitPbjToP case TokenUnpause -> HederaFunctionality.TOKEN_UNPAUSE; case TokenUpdate -> HederaFunctionality.TOKEN_UPDATE; case TokenUpdateNfts -> HederaFunctionality.TOKEN_UPDATE_NFTS; - case TokenReject -> HederaFunctionality.TOKEN_REJECT; case TransactionGetReceipt -> HederaFunctionality.TRANSACTION_GET_RECEIPT; case TransactionGetRecord -> HederaFunctionality.TRANSACTION_GET_RECORD; case TransactionGetFastRecord -> HederaFunctionality.TRANSACTION_GET_FAST_RECORD; case UncheckedSubmit -> HederaFunctionality.UNCHECKED_SUBMIT; case UtilPrng -> HederaFunctionality.UTIL_PRNG; + case TokenReject -> HederaFunctionality.TOKEN_REJECT; + case TokenAirdrop -> HederaFunctionality.TOKEN_AIRDROP; case UNRECOGNIZED -> throw new RuntimeException("Unknown function UNRECOGNIZED"); }; } @@ -656,9 +657,6 @@ private static R explicitPbjToP case MAX_CHILD_RECORDS_EXCEEDED -> ResponseCodeEnum.MAX_CHILD_RECORDS_EXCEEDED; case INSUFFICIENT_BALANCES_FOR_RENEWAL_FEES -> ResponseCodeEnum.INSUFFICIENT_BALANCES_FOR_RENEWAL_FEES; case TRANSACTION_HAS_UNKNOWN_FIELDS -> ResponseCodeEnum.TRANSACTION_HAS_UNKNOWN_FIELDS; - case TOKEN_REFERENCE_REPEATED -> ResponseCodeEnum.TOKEN_REFERENCE_REPEATED; - case TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED -> ResponseCodeEnum.TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED; - case INVALID_OWNER_ID -> ResponseCodeEnum.INVALID_OWNER_ID; case ACCOUNT_IS_IMMUTABLE -> ResponseCodeEnum.ACCOUNT_IS_IMMUTABLE; case ALIAS_ALREADY_ASSIGNED -> ResponseCodeEnum.ALIAS_ALREADY_ASSIGNED; case INVALID_METADATA_KEY -> ResponseCodeEnum.INVALID_METADATA_KEY; @@ -681,6 +679,9 @@ private static R explicitPbjToP case FQDN_SIZE_TOO_LARGE -> ResponseCodeEnum.FQDN_SIZE_TOO_LARGE; case INVALID_ENDPOINT -> ResponseCodeEnum.INVALID_ENDPOINT; case GOSSIP_ENDPOINTS_EXCEEDED_LIMIT -> ResponseCodeEnum.GOSSIP_ENDPOINTS_EXCEEDED_LIMIT; + case TOKEN_REFERENCE_REPEATED -> ResponseCodeEnum.TOKEN_REFERENCE_REPEATED; + case INVALID_OWNER_ID -> ResponseCodeEnum.INVALID_OWNER_ID; + case TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED -> ResponseCodeEnum.TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED; case SERVICE_ENDPOINTS_EXCEEDED_LIMIT -> ResponseCodeEnum.SERVICE_ENDPOINTS_EXCEEDED_LIMIT; case INVALID_IPV4_ADDRESS -> ResponseCodeEnum.INVALID_IPV4_ADDRESS; case EMPTY_TOKEN_REFERENCE_LIST -> ResponseCodeEnum.EMPTY_TOKEN_REFERENCE_LIST; diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java index f4f84dbe5ba9..5d683d586c5c 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java @@ -24,6 +24,7 @@ public interface StoreMetricsService { enum StoreType { TOPIC, ACCOUNT, + AIRDROPS, NFT, TOKEN, TOKEN_RELATION, diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java index fc53290d0dc0..03dea21d6708 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java @@ -35,6 +35,7 @@ public StoreMetricsServiceImpl(@NonNull final Metrics metrics) { this.storeMetricsMap = new EnumMap<>(StoreType.class); storeMetricsMap.put(StoreType.TOPIC, new StoreMetricsImpl(metrics, "topics")); storeMetricsMap.put(StoreType.ACCOUNT, new StoreMetricsImpl(metrics, "accounts")); + storeMetricsMap.put(StoreType.AIRDROPS, new StoreMetricsImpl(metrics, "airdrops")); storeMetricsMap.put(StoreType.NFT, new StoreMetricsImpl(metrics, "nfts")); storeMetricsMap.put(StoreType.TOKEN, new StoreMetricsImpl(metrics, "tokens")); storeMetricsMap.put( diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java index 833e3174f2a3..8ceaaec6169d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/ReadableStoreFactory.java @@ -41,6 +41,7 @@ import com.hedera.node.app.service.schedule.ScheduleService; import com.hedera.node.app.service.schedule.impl.ReadableScheduleStoreImpl; import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.service.token.ReadableAirdropStore; import com.hedera.node.app.service.token.ReadableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.ReadableNftStore; import com.hedera.node.app.service.token.ReadableStakingInfoStore; @@ -48,6 +49,7 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl; +import com.hedera.node.app.service.token.impl.ReadableAirdropStoreImpl; import com.hedera.node.app.service.token.impl.ReadableNetworkStakingRewardsStoreImpl; import com.hedera.node.app.service.token.impl.ReadableNftStoreImpl; import com.hedera.node.app.service.token.impl.ReadableStakingInfoStoreImpl; @@ -80,6 +82,7 @@ private static Map, StoreEntry> createFactoryMap() { Map, StoreEntry> newMap = new HashMap<>(); // Tokens and accounts newMap.put(ReadableAccountStore.class, new StoreEntry(TokenService.NAME, ReadableAccountStoreImpl::new)); + newMap.put(ReadableAirdropStore.class, new StoreEntry(TokenService.NAME, ReadableAirdropStoreImpl::new)); newMap.put(ReadableNftStore.class, new StoreEntry(TokenService.NAME, ReadableNftStoreImpl::new)); newMap.put( ReadableStakingInfoStore.class, new StoreEntry(TokenService.NAME, ReadableStakingInfoStoreImpl::new)); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java index b711ec8d3d0d..b2be64078a9b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/store/WritableStoreFactory.java @@ -36,6 +36,7 @@ import com.hedera.node.app.service.schedule.impl.WritableScheduleStoreImpl; import com.hedera.node.app.service.token.TokenService; import com.hedera.node.app.service.token.impl.WritableAccountStore; +import com.hedera.node.app.service.token.impl.WritableAirdropStore; import com.hedera.node.app.service.token.impl.WritableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; @@ -70,6 +71,7 @@ private static Map, StoreEntry> createFactoryMap() { newMap.put(WritableTopicStore.class, new StoreEntry(ConsensusService.NAME, WritableTopicStore::new)); // TokenService newMap.put(WritableAccountStore.class, new StoreEntry(TokenService.NAME, WritableAccountStore::new)); + newMap.put(WritableAirdropStore.class, new StoreEntry(TokenService.NAME, WritableAirdropStore::new)); newMap.put(WritableNftStore.class, new StoreEntry(TokenService.NAME, WritableNftStore::new)); newMap.put(WritableTokenStore.class, new StoreEntry(TokenService.NAME, WritableTokenStore::new)); newMap.put( diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java index 88eed431430b..7fe8f0764103 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java @@ -59,6 +59,7 @@ import static com.hedera.hapi.node.base.HederaFunctionality.SYSTEM_DELETE; import static com.hedera.hapi.node.base.HederaFunctionality.SYSTEM_UNDELETE; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_ACCOUNT_WIPE; +import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_AIRDROP; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_ASSOCIATE_TO_ACCOUNT; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_BURN; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_CREATE; @@ -177,6 +178,7 @@ * @param getAccountDetails the permission for {@link HederaFunctionality#GET_ACCOUNT_DETAILS} functionality * @param tokenUpdateNfts the permission for {@link HederaFunctionality#TOKEN_UPDATE_NFTS} functionality * @param tokenReject the permission for {@link HederaFunctionality#TOKEN_REJECT} functionality + * @param tokenAirdrop the permission for {@link HederaFunctionality#TOKEN_AIRDROP} functionality * * @param createNode the permission for {@link HederaFunctionality#NODE_CREATE} functionality * @param updateNode the permission for {@link HederaFunctionality#NODE_UPDATE} functionality @@ -248,6 +250,7 @@ public record ApiPermissionConfig( @ConfigProperty(defaultValue = "2-50") PermissionedAccountsRange getAccountDetails, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenUpdateNfts, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenReject, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenAirdrop, @ConfigProperty(defaultValue = "2-55") PermissionedAccountsRange createNode, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange updateNode, @ConfigProperty(defaultValue = "2-55") PermissionedAccountsRange deleteNode) { @@ -298,6 +301,7 @@ public record ApiPermissionConfig( permissionKeys.put(SCHEDULE_SIGN, c -> c.scheduleSign); permissionKeys.put(TOKEN_UPDATE_NFTS, c -> c.tokenUpdateNfts); permissionKeys.put(TOKEN_REJECT, c -> c.tokenReject); + permissionKeys.put(TOKEN_AIRDROP, c -> c.tokenAirdrop); /* Queries */ permissionKeys.put(CONSENSUS_GET_TOPIC_INFO, c -> c.getTopicInfo); permissionKeys.put(CONTRACT_CALL_LOCAL, c -> c.contractCallLocalMethod); diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java index cf17f27c96a4..3b40a8fe38c4 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java @@ -48,6 +48,8 @@ public record TokensConfig( ScaleFactor nftsMintThrottleScaleFactor, @ConfigProperty(value = "nfts.useVirtualMerkle", defaultValue = "true") @NetworkProperty boolean nftsUseVirtualMerkle, + @ConfigProperty(value = "maxAllowedAirdrops", defaultValue = "1000000") @NetworkProperty + long maxAllowedAirdorps, @ConfigProperty(value = "autoCreations.isEnabled", defaultValue = "true") @NetworkProperty boolean autoCreationsIsEnabled, @ConfigProperty(value = "maxMetadataBytes", defaultValue = "100") @NetworkProperty int tokensMaxMetadataBytes, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java new file mode 100644 index 000000000000..2b954223c08c --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 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.service.token.impl; + +import static com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema.AIRDROPS_KEY; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.node.app.service.token.ReadableAccountStore; +import com.hedera.node.app.service.token.ReadableAirdropStore; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Default implementation of {@link ReadableAccountStore} + */ +public class ReadableAirdropStoreImpl implements ReadableAirdropStore { + /** The underlying data storage class that holds the airdrop data. */ + private final ReadableKVState readableAirdropState; + + /** + * Create a new {@link ReadableAirdropStoreImpl} instance. + * + * @param states The state to use. + */ + public ReadableAirdropStoreImpl(@NonNull final ReadableStates states) { + requireNonNull(states); + this.readableAirdropState = states.get(AIRDROPS_KEY); + } + + /** {@inheritDoc} */ + @Override + public boolean exists(@NonNull final PendingAirdropId airdropId) { + return readableAirdropState.contains(requireNonNull(airdropId)); + } + + /** {@inheritDoc} */ + @Override + @Nullable + public PendingAirdropValue get(@NonNull final PendingAirdropId airdropId) { + requireNonNull(airdropId); + if (airdropId.hasNonFungibleToken()) { + return null; + } + return readableAirdropState.get(airdropId); + } + + /** {@inheritDoc} */ + @Override + public long sizeOfState() { + return readableAirdropState.size(); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java new file mode 100644 index 000000000000..47d37a8bdd14 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 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.service.token.impl; + +import static com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema.AIRDROPS_KEY; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.node.app.spi.metrics.StoreMetricsService; +import com.hedera.node.config.data.TokensConfig; +import com.swirlds.config.api.Configuration; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * Provides write methods for modifying underlying data storage mechanisms for + * working with Pending Airdrops. + * + *

This class is not exported from the module. It is an internal implementation detail. + * This class is not complete, it will be extended with other methods like remove, update etc., + */ +public class WritableAirdropStore extends ReadableAirdropStoreImpl { + /** + * The underlying data storage class that holds the Pending Airdrops data. + */ + private final WritableKVState airdropState; + + /** + * Create a new {@link WritableAirdropStore} instance. + * + * @param states The state to use. + */ + public WritableAirdropStore( + @NonNull final WritableStates states, + @NonNull final Configuration configuration, + @NonNull final StoreMetricsService storeMetricsService) { + super(states); + airdropState = states.get(AIRDROPS_KEY); + + final long maxCapacity = configuration.getConfigData(TokensConfig.class).maxAllowedAirdorps(); + final var storeMetrics = storeMetricsService.get(StoreMetricsService.StoreType.AIRDROPS, maxCapacity); + airdropState.setMetrics(storeMetrics); + } + + /** + * Persists a new {@link PendingAirdropId} with given {@link PendingAirdropValue} into the state, + * as well as exporting its ID to the transaction receipt. If there is existing + * airdrop with the same id we add the value to the existing drop. + * + * @param airdropId - the airdropId to be persisted. + * @param airdropValue - the value for the given airdropId to be persisted. + */ + public void put(@NonNull final PendingAirdropId airdropId, @NonNull final PendingAirdropValue airdropValue) { + requireNonNull(airdropId); + requireNonNull(airdropValue); + + if (!airdropState.contains(airdropId)) { + airdropState.put(airdropId, airdropValue); + return; + } + + if (airdropId.hasFungibleTokenType()) { + var existingAirdropValue = + requireNonNull(airdropState.get(airdropId)).amount(); + var newValue = airdropValue.amount() + existingAirdropValue; + var newAirdropValue = + PendingAirdropValue.newBuilder().amount(newValue).build(); + airdropState.put(airdropId, newAirdropValue); + } + } + + /** + * Removes a {@link PendingAirdropId} from the state + * + * @param airdropId the {@code PendingAirdropId} to be removed + */ + public void remove(@NonNull final PendingAirdropId airdropId) { + airdropState.remove(requireNonNull(airdropId)); + } + + /** + * Returns the {@link PendingAirdropValue} with the given airdrop id. If the airdrop contains only NFT return {@code null}. + * If no such airdrop exists, returns {@code null} + * + * @param airdropId - the id of the airdrop, which value should be retrieved + * @return the fungible airdrop value, or {@code null} if no such + * airdrop exists + */ + @Nullable + public PendingAirdropValue getForModify(@NonNull final PendingAirdropId airdropId) { + requireNonNull(airdropId); + if (airdropId.hasNonFungibleToken()) { + return null; + } + return airdropState.getForModify(airdropId); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java index e5887d3af4d1..6fbf2b4927b8 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java @@ -21,12 +21,16 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.node.state.token.Account; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; +import com.swirlds.state.spi.StateDefinition; import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; import java.util.SortedMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -38,6 +42,8 @@ public class V0500TokenSchema extends StakingInfoManagementSchema { private static final Logger log = LogManager.getLogger(V0500TokenSchema.class); private static final String SHARED_VALUES_KEY = "V0500_FIRST_STORAGE_KEYS"; + private static final long MAX_PENDING_AIRDROPS = 1_000_000_000L; + public static final String AIRDROPS_KEY = "PENDING_AIRDROPS"; private static final SemanticVersion VERSION = SemanticVersion.newBuilder().major(0).minor(50).patch(0).build(); @@ -46,6 +52,14 @@ public V0500TokenSchema() { super(VERSION); } + @SuppressWarnings("rawtypes") + @NonNull + @Override + public Set statesToCreate() { + return Set.of(StateDefinition.onDisk( + AIRDROPS_KEY, PendingAirdropId.PROTOBUF, PendingAirdropValue.PROTOBUF, MAX_PENDING_AIRDROPS)); + } + @Override public void migrate(@NonNull final MigrationContext ctx) { requireNonNull(ctx); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java new file mode 100644 index 000000000000..2c7c0f300a00 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 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.service.token.impl.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; + +import com.hedera.hapi.node.base.NftID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.node.app.service.token.impl.ReadableAirdropStoreImpl; +import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableStates; +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.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ReadableAirdropStoreImplTest extends StateBuilderUtil { + + @Mock + private ReadableStates readableStates; + + private ReadableKVState airdrops; + + private ReadableAirdropStoreImpl subject; + + @BeforeEach + public void setUp() { + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + } + + @Test + void getsNullIfAirdropContainsOnlyNFT() { + var nftAirdrop = getNonFungibleAirDrop(); + assertThat(subject.get(nftAirdrop)).isNull(); + } + + @Test + void getsValueIfAirdropContainsFungibleToken() { + var fungibleAirdrop = getFungibleAirdrop(); + var airdropValue = PendingAirdropValue.newBuilder().amount(5).build(); + + airdrops = emptyReadableAirdropStateBuilder() + .value(fungibleAirdrop, airdropValue) + .build(); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + + assertThat(subject.get(fungibleAirdrop)).isNotNull(); + assertThat(subject.get(fungibleAirdrop)).isEqualTo(airdropValue); + } + + @Test + void getsFungibleThatDoesNotExist() { + var fungibleAirdrop = getFungibleAirdrop(); + + airdrops = emptyReadableAirdropStateBuilder().build(); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + + assertThat(subject.get(fungibleAirdrop)).isNull(); + assertThat(subject.get(fungibleAirdrop)).isNull(); + } + + @SuppressWarnings("ConstantConditions") + @Test + void getsFungibleWithNullParam() { + assertThatThrownBy(() -> subject.get(null)).isInstanceOf(NullPointerException.class); + } + + @SuppressWarnings("ConstantConditions") + @Test + void testConstructorCallWithNull() { + assertThatThrownBy(() -> subject = new ReadableAirdropStoreImpl(null)).isInstanceOf(NullPointerException.class); + } + + @Test + void testSizeOfState() { + airdrops = emptyReadableAirdropStateBuilder() + .value(getNonFungibleAirDrop(), PendingAirdropValue.newBuilder().build()) + .build(); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + assertThat(readableStates.get(StateBuilderUtil.AIRDROPS).size()).isEqualTo(subject.sizeOfState()); + } + + @Test + void testExists() { + var fungibleAirdrop = getFungibleAirdrop(); + + airdrops = emptyReadableAirdropStateBuilder() + .value( + fungibleAirdrop, + PendingAirdropValue.newBuilder().amount(5).build()) + .build(); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + + final var store = new ReadableAirdropStoreImpl(readableStates); + assertThat(readableStates.get(StateBuilderUtil.AIRDROPS).contains(fungibleAirdrop)) + .isEqualTo(store.exists(fungibleAirdrop)); + } + + @Test + void testDoesNotExists() { + var fungibleAirdrop = getFungibleAirdrop(); + + airdrops = emptyReadableAirdropStateBuilder().build(); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); + subject = new ReadableAirdropStoreImpl(readableStates); + + final var store = new ReadableAirdropStoreImpl(readableStates); + assertThat(readableStates.get(StateBuilderUtil.AIRDROPS).contains(fungibleAirdrop)) + .isEqualTo(store.exists(fungibleAirdrop)); + } + + private PendingAirdropId getNonFungibleAirDrop() { + return PendingAirdropId.newBuilder() + .nonFungibleToken(NftID.newBuilder() + .serialNumber(123_456) + .tokenId(TokenID.newBuilder() + .tokenNum(1) + .shardNum(2) + .realmNum(3) + .build()) + .build()) + .build(); + } + + private PendingAirdropId getFungibleAirdrop() { + return PendingAirdropId.newBuilder() + .fungibleTokenType( + TokenID.newBuilder().realmNum(1).shardNum(2).tokenNum(3).build()) + .build(); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java new file mode 100644 index 000000000000..a4b7243bec13 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2024 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.service.token.impl.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import com.hedera.hapi.node.base.NftID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.base.TokenID; +import com.hedera.node.app.service.token.impl.WritableAirdropStore; +import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; +import com.hedera.node.app.spi.metrics.StoreMetricsService; +import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.config.api.Configuration; +import com.swirlds.platform.test.fixtures.state.MapWritableKVState; +import com.swirlds.state.spi.WritableStates; +import java.util.Objects; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WritableAirdropStoreTest extends StateBuilderUtil { + + private final Configuration configuration = HederaTestConfigBuilder.createConfig(); + + @Mock + private WritableStates writableStates; + + @Mock + private StoreMetricsService storeMetricsService; + + private MapWritableKVState writableAirdropState; + + private WritableAirdropStore subject; + + @BeforeEach + public void setUp() { + writableAirdropState = emptyWritableAirdropStateBuilder().build(); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); + subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); + } + + @Test + void putsAirdropsToState() { + final var airdropId = getFungibleAirdrop(); + final var airdropValue = PendingAirdropValue.newBuilder().amount(20).build(); + + assertThat(writableAirdropState.contains(airdropId)).isFalse(); + + subject.put(airdropId, airdropValue); + + assertThat(writableAirdropState.contains(airdropId)).isTrue(); + final var tokenValue = writableAirdropState.get(airdropId); + assertThat(airdropValue).isEqualTo(tokenValue); + } + + @Test + void putsUpdatesExistingAirdrop() { + final var airdropId = getFungibleAirdrop(); + final var airdropValue = PendingAirdropValue.newBuilder().amount(30).build(); + writableAirdropState = emptyWritableAirdropStateBuilder() + .value(airdropId, airdropValue) + .build(); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); + subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); + + final var newAirdropValue = PendingAirdropValue.newBuilder().amount(20).build(); + + assertThat(writableAirdropState.contains(airdropId)).isTrue(); + + subject.put(airdropId, newAirdropValue); + + assertThat(writableAirdropState.contains(airdropId)).isTrue(); + final var tokenValue = + Objects.requireNonNull(writableAirdropState.get(airdropId)).amount(); + assertThat(tokenValue).isEqualTo(airdropValue.amount() + newAirdropValue.amount()); + } + + @Test + void putsDoesNotUpdateNftIfExists() { + final var nftId = getNonFungibleAirDrop(); + final var nftValue = PendingAirdropValue.newBuilder().build(); + var stateSpy = Mockito.spy( + writableAirdropState = emptyWritableAirdropStateBuilder() + .value(nftId, nftValue) + .build()); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); + subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); + + assertThat(writableAirdropState.contains(nftId)).isTrue(); + + subject.put(nftId, nftValue); + + assertThat(writableAirdropState.contains(nftId)).isTrue(); + final var tokenValue = + Objects.requireNonNull(writableAirdropState.get(nftId)).amount(); + assertThat(tokenValue).isEqualTo(0L); + verify(stateSpy, times(0)).put(any(), any()); + } + + @Test + void removesAirdropById() { + final var fungibleAirdropToRemove = getFungibleAirdrop(); + final var fungibleAirdropValue = airdropWithValue(15); + final var nftToRemove = getNonFungibleAirDrop(); + writableAirdropState = emptyWritableAirdropStateBuilder() + .value(fungibleAirdropToRemove, fungibleAirdropValue) + .value(nftToRemove, emptyAirdropValue()) + .build(); + + assertThat(writableAirdropState.contains(fungibleAirdropToRemove)).isTrue(); + assertThat(writableAirdropState.contains(nftToRemove)).isTrue(); + + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); + subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); + + assertThat(subject.exists(fungibleAirdropToRemove)).isTrue(); + assertThat(subject.exists(nftToRemove)).isTrue(); + subject.remove(fungibleAirdropToRemove); + subject.remove(nftToRemove); + assertThat(subject.exists(fungibleAirdropToRemove)).isFalse(); + assertThat(subject.exists(nftToRemove)).isFalse(); + } + + @Test + void getForModifyReturnsImmutableAirDrop() { + final var airdropId = getFungibleAirdrop(); + final var airdropValue = airdropWithValue(255); + final var nftAirdropId = getNonFungibleAirDrop(); + final var nftValue = emptyAirdropValue(); + + subject.put(airdropId, airdropValue); + subject.put(nftAirdropId, nftValue); + + final var readAirdrop = subject.getForModify(airdropId); + assertThat(readAirdrop).isNotNull(); + assertThat(airdropValue).isEqualTo(readAirdrop); + + final var readNft = subject.getForModify(nftAirdropId); + assertThat(nftAirdropId).isNotNull(); + assertThat(readNft).isNull(); + } + + @Test + void getForModifyNonExisting() { + final var nonExistingAirdropId = getFungibleAirdrop(); + final var nonExistingNftAirdropId = getNonFungibleAirDrop(); + + assertThat(subject.exists(nonExistingAirdropId)).isFalse(); + assertThat(subject.exists(nonExistingNftAirdropId)).isFalse(); + + final var readAirdrop = subject.getForModify(nonExistingAirdropId); + final var readNft = subject.getForModify(nonExistingNftAirdropId); + + assertThat(readAirdrop).isNull(); + assertThat(readNft).isNull(); + } + + @Test + void testConstructorCallWithNull() { + assertThatThrownBy(() -> subject = new WritableAirdropStore(null, configuration, storeMetricsService)) + .isInstanceOf(NullPointerException.class); + } + + @Test + void testGetWithNullParam() { + assertThatThrownBy(() -> subject.getForModify(null)).isInstanceOf(NullPointerException.class); + } + + private PendingAirdropId getNonFungibleAirDrop() { + return PendingAirdropId.newBuilder() + .nonFungibleToken(NftID.newBuilder() + .serialNumber(123456) + .tokenId(TokenID.newBuilder() + .tokenNum(1) + .shardNum(2) + .realmNum(3) + .build()) + .build()) + .build(); + } + + private PendingAirdropId getFungibleAirdrop() { + return PendingAirdropId.newBuilder() + .fungibleTokenType( + TokenID.newBuilder().realmNum(1).shardNum(2).tokenNum(3).build()) + .build(); + } + + private PendingAirdropValue airdropWithValue(long value) { + return PendingAirdropValue.newBuilder().amount(value).build(); + } + + private PendingAirdropValue emptyAirdropValue() { + return PendingAirdropValue.newBuilder().build(); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java index c5af250aa24f..8bd4f4ae1ee8 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java @@ -18,6 +18,8 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.NftID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.primitives.ProtoBytes; @@ -38,6 +40,10 @@ public class StateBuilderUtil { * The state key for accounts. */ public static final String ACCOUNTS = "ACCOUNTS"; + /** + * The state key for pending airdrops. + */ + public static final String AIRDROPS = "PENDING_AIRDROPS"; /** * The state key for aliases. */ @@ -73,6 +79,16 @@ protected MapWritableKVState.Builder emptyWritableAccountSta return MapWritableKVState.builder(ACCOUNTS); } + @NonNull + protected MapReadableKVState.Builder emptyReadableAirdropStateBuilder() { + return MapReadableKVState.builder(AIRDROPS); + } + + @NonNull + protected MapWritableKVState.Builder emptyWritableAirdropStateBuilder() { + return MapWritableKVState.builder(AIRDROPS); + } + @NonNull protected MapReadableKVState.Builder emptyReadableTokenRelsStateBuilder() { return MapReadableKVState.builder(TOKEN_RELS); diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java index 7c218cdd6aa4..31c10f6941b5 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java @@ -24,12 +24,16 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; import com.swirlds.state.test.fixtures.MapWritableKVState; +import com.swirlds.state.spi.StateDefinition; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; @@ -64,6 +68,19 @@ class V0500TokenSchemaTest { private final V0500TokenSchema subject = new V0500TokenSchema(); + @Test + @DisplayName("verify states to create") + void verifyStatesToCreate() { + var sortedResult = subject.statesToCreate().stream() + .sorted(Comparator.comparing(StateDefinition::stateKey)) + .toList(); + + final var firstStateDef = sortedResult.getFirst(); + assertThat(firstStateDef.stateKey()).isEqualTo("PENDING_AIRDROPS"); + assertThat(firstStateDef.keyCodec()).isEqualTo(PendingAirdropId.PROTOBUF); + assertThat(firstStateDef.valueCodec()).isEqualTo(PendingAirdropValue.PROTOBUF); + } + @Test @DisplayName("skips migration without shared values") void throwsWithoutSharedValues() { diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java new file mode 100644 index 000000000000..9606f032ae23 --- /dev/null +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.service.token; + +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Provides read-only methods for interacting with the underlying data storage mechanisms for + * pending Airdrop states. + */ +public interface ReadableAirdropStore { + /** + * Fetches an {@link PendingAirdropValue} object from state for given {@link PendingAirdropId}. If the airdrop contains only NFT return {@code null}. + * If the airdrop could not be fetched because the given airdrop doesn't exist, returns {@code null}. + * + * @param airdropId given airdrop id + * @return {@link PendingAirdropValue} object if successfully fetched or {@code null} if the airdrop doesn't exist + */ + PendingAirdropValue get(@NonNull final PendingAirdropId airdropId); + + /** + * Returns whether a given PendingAirdropId exists in state. + * + * @param airdropId - the id of the airdrop + * @return true if the airdrop exists, false otherwise + */ + boolean exists(@NonNull final PendingAirdropId airdropId); + + /** + * Returns the number of entities in the pending airdrops state. + * @return the size of the pending airdrops state + */ + long sizeOfState(); + + /** + * Warms the system by preloading a token into memory + * + *

The default implementation is empty because preloading data into memory is only used for some implementations. + * + * @param airdropId the token id + */ + default void warm(@NonNull final PendingAirdropId airdropId) {} +} From f330cd670b270b6e702779b8cc3d34c1c325d274 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:14:02 +0300 Subject: [PATCH 02/13] fix: compile errors Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java | 2 ++ .../hedera/node/app/hapi/utils/CommonPbjConverters.java | 7 +++++++ .../impl/test/handlers/NetworkAdminHandlerTestBase.java | 1 + .../app/service/schedule/impl/handlers/HandlerUtility.java | 1 + .../service/token/impl/test/WritableAirdropStoreTest.java | 2 +- .../impl/test/handlers/util/CryptoHandlerTestBase.java | 2 ++ .../impl/test/handlers/util/TokenHandlerTestBase.java | 1 + .../token/impl/test/schemas/V0500TokenSchemaTest.java | 2 +- .../service/token/impl/test/util/SigReqAdapterUtils.java | 1 + 9 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java index 6a34b5595bd6..023fe25f6583 100644 --- a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java +++ b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java @@ -235,6 +235,8 @@ public static HederaFunctionality functionOf(final TransactionBody txn) throws U case NODE_DELETE -> HederaFunctionality.NODE_DELETE; case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT; case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP; + case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP; + case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP; case UNSET -> throw new UnknownHederaFunctionality(); }; } diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java index ea6024536f5f..549ccac99abc 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/CommonPbjConverters.java @@ -327,6 +327,8 @@ private static R explicitPbjToP case UtilPrng -> HederaFunctionality.UTIL_PRNG; case TokenReject -> HederaFunctionality.TOKEN_REJECT; case TokenAirdrop -> HederaFunctionality.TOKEN_AIRDROP; + case TokenCancelAirdrop -> HederaFunctionality.TOKEN_CANCEL_AIRDROP; + case TokenClaimAirdrop -> HederaFunctionality.TOKEN_CLAIM_AIRDROP; case UNRECOGNIZED -> throw new RuntimeException("Unknown function UNRECOGNIZED"); }; } @@ -686,6 +688,11 @@ private static R explicitPbjToP case INVALID_IPV4_ADDRESS -> ResponseCodeEnum.INVALID_IPV4_ADDRESS; case EMPTY_TOKEN_REFERENCE_LIST -> ResponseCodeEnum.EMPTY_TOKEN_REFERENCE_LIST; case UPDATE_NODE_ACCOUNT_NOT_ALLOWED -> ResponseCodeEnum.UPDATE_NODE_ACCOUNT_NOT_ALLOWED; + case PENDING_NFT_AIRDROP_ALREADY_EXISTS -> ResponseCodeEnum.PENDING_NFT_AIRDROP_ALREADY_EXISTS; + case ACCOUNT_HAS_PENDING_AIRDROPS -> ResponseCodeEnum.ACCOUNT_HAS_PENDING_AIRDROPS; + case EMPTY_PENDING_AIRDROP_ID_LIST -> ResponseCodeEnum.EMPTY_PENDING_AIRDROP_ID_LIST; + case PENDING_AIRDROP_ID_REPEATED -> ResponseCodeEnum.PENDING_AIRDROP_ID_REPEATED; + case MAX_PENDING_AIRDROP_ID_EXCEEDED -> ResponseCodeEnum.MAX_PENDING_AIRDROP_ID_EXCEEDED; case UNRECOGNIZED -> throw new RuntimeException("UNRECOGNIZED Response code!"); }; } diff --git a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java index 70d88bc69cb7..e749d1a38909 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java +++ b/hedera-node/hedera-network-admin-service-impl/src/test/java/com/hedera/node/app/service/networkadmin/impl/test/handlers/NetworkAdminHandlerTestBase.java @@ -433,6 +433,7 @@ protected void givenValidAccount( tokenAllowances, 2, false, + null, null); } diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java index 0b2b8bc38af6..0d4910a68fd9 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java @@ -169,6 +169,7 @@ static HederaFunctionality functionalityForType(final DataOneOfType transactionT case NODE_CREATE -> HederaFunctionality.NODE_CREATE; case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE; case NODE_DELETE -> HederaFunctionality.NODE_DELETE; + case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP; case UNSET -> HederaFunctionality.NONE; }; } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java index a4b7243bec13..dcd2cc161b28 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java @@ -32,8 +32,8 @@ import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; -import com.swirlds.platform.test.fixtures.state.MapWritableKVState; import com.swirlds.state.spi.WritableStates; +import com.swirlds.state.test.fixtures.MapWritableKVState; import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java index 966539f04003..53c643c62c12 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoHandlerTestBase.java @@ -341,6 +341,7 @@ protected Account givenValidAccount(final long accountNum) { Collections.emptyList(), 2, false, + null, null); } @@ -377,6 +378,7 @@ protected void givenValidContract() { Collections.emptyList(), 2, false, + null, null); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java index 557677e47739..4a587c4a3ff7 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/TokenHandlerTestBase.java @@ -279,6 +279,7 @@ protected Account newPayerAccount() { Collections.emptyList(), 2, false, + null, null); } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java index 31c10f6941b5..0fdcdd7e06bb 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java @@ -31,8 +31,8 @@ import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; -import com.swirlds.state.test.fixtures.MapWritableKVState; import com.swirlds.state.spi.StateDefinition; +import com.swirlds.state.test.fixtures.MapWritableKVState; import java.util.Comparator; import java.util.HashMap; import java.util.Map; diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java index f7357724549b..9dd970f15f0a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/util/SigReqAdapterUtils.java @@ -531,6 +531,7 @@ private static Account toPbjAccount( fungibleTokenAllowances, 2, false, + null, null); } } From 290620ea5d121f9122b9824c11f29b31fd9f740b Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:28:24 +0300 Subject: [PATCH 03/13] fix: change store to map of AccountAirdrop instead of PendingAirdropValue Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- .../app/spi/metrics/StoreMetricsService.java | 2 +- .../app/metrics/StoreMetricsServiceImpl.java | 2 +- .../hedera/node/config/data/TokensConfig.java | 2 +- .../token/impl/ReadableAirdropStoreImpl.java | 9 +-- .../token/impl/WritableAirdropStore.java | 59 +++++++++++++------ .../test/ReadableAirdropStoreImplTest.java | 56 ++++++++++-------- .../test/handlers/util/StateBuilderUtil.java | 6 +- .../service/token/ReadableAirdropStore.java | 8 +-- 8 files changed, 83 insertions(+), 61 deletions(-) diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java index 5d683d586c5c..be5e0897cea5 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/metrics/StoreMetricsService.java @@ -24,7 +24,7 @@ public interface StoreMetricsService { enum StoreType { TOPIC, ACCOUNT, - AIRDROPS, + AIRDROP, NFT, TOKEN, TOKEN_RELATION, diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java index 03dea21d6708..79a9fbde7923 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java @@ -35,7 +35,7 @@ public StoreMetricsServiceImpl(@NonNull final Metrics metrics) { this.storeMetricsMap = new EnumMap<>(StoreType.class); storeMetricsMap.put(StoreType.TOPIC, new StoreMetricsImpl(metrics, "topics")); storeMetricsMap.put(StoreType.ACCOUNT, new StoreMetricsImpl(metrics, "accounts")); - storeMetricsMap.put(StoreType.AIRDROPS, new StoreMetricsImpl(metrics, "airdrops")); + storeMetricsMap.put(StoreType.AIRDROP, new StoreMetricsImpl(metrics, "airdrops")); storeMetricsMap.put(StoreType.NFT, new StoreMetricsImpl(metrics, "nfts")); storeMetricsMap.put(StoreType.TOKEN, new StoreMetricsImpl(metrics, "tokens")); storeMetricsMap.put( diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java index 3b40a8fe38c4..f684de4c1c05 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TokensConfig.java @@ -49,7 +49,7 @@ public record TokensConfig( @ConfigProperty(value = "nfts.useVirtualMerkle", defaultValue = "true") @NetworkProperty boolean nftsUseVirtualMerkle, @ConfigProperty(value = "maxAllowedAirdrops", defaultValue = "1000000") @NetworkProperty - long maxAllowedAirdorps, + long maxAllowedAirdrops, @ConfigProperty(value = "autoCreations.isEnabled", defaultValue = "true") @NetworkProperty boolean autoCreationsIsEnabled, @ConfigProperty(value = "maxMetadataBytes", defaultValue = "100") @NetworkProperty int tokensMaxMetadataBytes, diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java index 2b954223c08c..3e299ab2da45 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -20,7 +20,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.state.token.AccountAirdrop; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableAirdropStore; import com.swirlds.state.spi.ReadableKVState; @@ -33,7 +33,7 @@ */ public class ReadableAirdropStoreImpl implements ReadableAirdropStore { /** The underlying data storage class that holds the airdrop data. */ - private final ReadableKVState readableAirdropState; + private final ReadableKVState readableAirdropState; /** * Create a new {@link ReadableAirdropStoreImpl} instance. @@ -54,11 +54,8 @@ public boolean exists(@NonNull final PendingAirdropId airdropId) { /** {@inheritDoc} */ @Override @Nullable - public PendingAirdropValue get(@NonNull final PendingAirdropId airdropId) { + public AccountAirdrop get(@NonNull final PendingAirdropId airdropId) { requireNonNull(airdropId); - if (airdropId.hasNonFungibleToken()) { - return null; - } return readableAirdropState.get(airdropId); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java index 47d37a8bdd14..3beea78546a1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java @@ -21,6 +21,7 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.state.token.AccountAirdrop; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; @@ -40,7 +41,7 @@ public class WritableAirdropStore extends ReadableAirdropStoreImpl { /** * The underlying data storage class that holds the Pending Airdrops data. */ - private final WritableKVState airdropState; + private final WritableKVState airdropState; /** * Create a new {@link WritableAirdropStore} instance. @@ -54,35 +55,58 @@ public WritableAirdropStore( super(states); airdropState = states.get(AIRDROPS_KEY); - final long maxCapacity = configuration.getConfigData(TokensConfig.class).maxAllowedAirdorps(); - final var storeMetrics = storeMetricsService.get(StoreMetricsService.StoreType.AIRDROPS, maxCapacity); + final long maxCapacity = configuration.getConfigData(TokensConfig.class).maxAllowedAirdrops(); + final var storeMetrics = storeMetricsService.get(StoreMetricsService.StoreType.AIRDROP, maxCapacity); airdropState.setMetrics(storeMetrics); } /** - * Persists a new {@link PendingAirdropId} with given {@link PendingAirdropValue} into the state, + * Persists a new {@link PendingAirdropId} with given {@link AccountAirdrop} into the state, * as well as exporting its ID to the transaction receipt. If there is existing * airdrop with the same id we add the value to the existing drop. * * @param airdropId - the airdropId to be persisted. - * @param airdropValue - the value for the given airdropId to be persisted. + * @param accountAirdrop - the account airdrop mapping for the given airdropId to be persisted. */ - public void put(@NonNull final PendingAirdropId airdropId, @NonNull final PendingAirdropValue airdropValue) { + public void put(@NonNull final PendingAirdropId airdropId, @NonNull final AccountAirdrop accountAirdrop) { requireNonNull(airdropId); - requireNonNull(airdropValue); + requireNonNull(accountAirdrop); if (!airdropState.contains(airdropId)) { - airdropState.put(airdropId, airdropValue); + airdropState.put(airdropId, accountAirdrop); return; } if (airdropId.hasFungibleTokenType()) { - var existingAirdropValue = - requireNonNull(airdropState.get(airdropId)).amount(); - var newValue = airdropValue.amount() + existingAirdropValue; - var newAirdropValue = - PendingAirdropValue.newBuilder().amount(newValue).build(); - airdropState.put(airdropId, newAirdropValue); + final var existingAccountAirdrop = requireNonNull(airdropState.get(airdropId)); + final var existingAirdropValue = + requireNonNull(airdropState.get(airdropId)).pendingAirdropValue(); + requireNonNull(existingAirdropValue); + requireNonNull(accountAirdrop.pendingAirdropValue()); + final var newValue = accountAirdrop.pendingAirdropValue().amount() + existingAirdropValue.amount(); + final var newAccountAirdrop = existingAccountAirdrop + .copyBuilder() + .pendingAirdropValue( + PendingAirdropValue.newBuilder().amount(newValue).build()) + .build(); + airdropState.put(airdropId, newAccountAirdrop); + } + } + + /** + * Updates given {@link PendingAirdropId} with new {@link AccountAirdrop} value the state, + * as well as exporting its ID to the transaction receipt. If there is no existing + * airdrop with the same id do nothing. + * + * @param airdropId - the airdropId to be updated. + * @param accountAirdrop - the account airdrop mapping for the given airdropId to be updated. + */ + public void patch(@NonNull final PendingAirdropId airdropId, @NonNull final AccountAirdrop accountAirdrop) { + requireNonNull(airdropId); + requireNonNull(accountAirdrop); + + if (airdropState.contains(airdropId)) { + airdropState.put(airdropId, accountAirdrop); } } @@ -96,7 +120,7 @@ public void remove(@NonNull final PendingAirdropId airdropId) { } /** - * Returns the {@link PendingAirdropValue} with the given airdrop id. If the airdrop contains only NFT return {@code null}. + * Returns the {@link AccountAirdrop} with the given airdrop id. If the airdrop contains only NFT return {@code null}. * If no such airdrop exists, returns {@code null} * * @param airdropId - the id of the airdrop, which value should be retrieved @@ -104,11 +128,8 @@ public void remove(@NonNull final PendingAirdropId airdropId) { * airdrop exists */ @Nullable - public PendingAirdropValue getForModify(@NonNull final PendingAirdropId airdropId) { + public AccountAirdrop getForModify(@NonNull final PendingAirdropId airdropId) { requireNonNull(airdropId); - if (airdropId.hasNonFungibleToken()) { - return null; - } return airdropState.getForModify(airdropId); } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java index 2c7c0f300a00..311540fa5911 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java @@ -24,10 +24,12 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.AccountAirdrop; import com.hedera.node.app.service.token.impl.ReadableAirdropStoreImpl; import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableStates; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -40,37 +42,31 @@ class ReadableAirdropStoreImplTest extends StateBuilderUtil { @Mock private ReadableStates readableStates; - private ReadableKVState airdrops; + private ReadableKVState airdrops; private ReadableAirdropStoreImpl subject; @BeforeEach public void setUp() { - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); } - @Test - void getsNullIfAirdropContainsOnlyNFT() { - var nftAirdrop = getNonFungibleAirDrop(); - assertThat(subject.get(nftAirdrop)).isNull(); - } - @Test void getsValueIfAirdropContainsFungibleToken() { var fungibleAirdrop = getFungibleAirdrop(); - var airdropValue = PendingAirdropValue.newBuilder().amount(5).build(); + var airdropValue = airdropWithValue(5); + var accountAirdrop = accountAirdropWith(fungibleAirdrop, airdropValue); airdrops = emptyReadableAirdropStateBuilder() - .value(fungibleAirdrop, airdropValue) + .value(fungibleAirdrop, accountAirdrop) .build(); - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(subject.get(fungibleAirdrop)).isNotNull(); - assertThat(subject.get(fungibleAirdrop)).isEqualTo(airdropValue); + assertThat(Objects.requireNonNull(subject.get(fungibleAirdrop)).pendingAirdropValue()) + .isEqualTo(airdropValue); } @Test @@ -78,8 +74,7 @@ void getsFungibleThatDoesNotExist() { var fungibleAirdrop = getFungibleAirdrop(); airdrops = emptyReadableAirdropStateBuilder().build(); - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(subject.get(fungibleAirdrop)).isNull(); @@ -101,10 +96,9 @@ void testConstructorCallWithNull() { @Test void testSizeOfState() { airdrops = emptyReadableAirdropStateBuilder() - .value(getNonFungibleAirDrop(), PendingAirdropValue.newBuilder().build()) + .value(getNonFungibleAirDrop(), accountAirdropWith(getFungibleAirdrop(), null)) .build(); - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(readableStates.get(StateBuilderUtil.AIRDROPS).size()).isEqualTo(subject.sizeOfState()); } @@ -112,14 +106,13 @@ void testSizeOfState() { @Test void testExists() { var fungibleAirdrop = getFungibleAirdrop(); + var fungibleValue = airdropWithValue(10); + var accountAirdrop = accountAirdropWith(fungibleAirdrop, fungibleValue); airdrops = emptyReadableAirdropStateBuilder() - .value( - fungibleAirdrop, - PendingAirdropValue.newBuilder().amount(5).build()) + .value(fungibleAirdrop, accountAirdrop) .build(); - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); final var store = new ReadableAirdropStoreImpl(readableStates); @@ -132,8 +125,7 @@ void testDoesNotExists() { var fungibleAirdrop = getFungibleAirdrop(); airdrops = emptyReadableAirdropStateBuilder().build(); - given(readableStates.get(AIRDROPS)) - .willReturn(airdrops); + given(readableStates.get(AIRDROPS)).willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); final var store = new ReadableAirdropStoreImpl(readableStates); @@ -160,4 +152,16 @@ private PendingAirdropId getFungibleAirdrop() { TokenID.newBuilder().realmNum(1).shardNum(2).tokenNum(3).build()) .build(); } + + private PendingAirdropValue airdropWithValue(long value) { + return PendingAirdropValue.newBuilder().amount(value).build(); + } + + private AccountAirdrop accountAirdropWith( + PendingAirdropId pendingAirdropId, PendingAirdropValue pendingAirdropValue) { + return AccountAirdrop.newBuilder() + .pendingAirdropId(pendingAirdropId) + .pendingAirdropValue(pendingAirdropValue) + .build(); + } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java index 8bd4f4ae1ee8..55b28e9fcdc5 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java @@ -19,11 +19,11 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; +import com.hedera.hapi.node.state.token.AccountAirdrop; import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; @@ -80,12 +80,12 @@ protected MapWritableKVState.Builder emptyWritableAccountSta } @NonNull - protected MapReadableKVState.Builder emptyReadableAirdropStateBuilder() { + protected MapReadableKVState.Builder emptyReadableAirdropStateBuilder() { return MapReadableKVState.builder(AIRDROPS); } @NonNull - protected MapWritableKVState.Builder emptyWritableAirdropStateBuilder() { + protected MapWritableKVState.Builder emptyWritableAirdropStateBuilder() { return MapWritableKVState.builder(AIRDROPS); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java index 9606f032ae23..e589f147eae7 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.token; import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.state.token.AccountAirdrop; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -26,13 +26,13 @@ */ public interface ReadableAirdropStore { /** - * Fetches an {@link PendingAirdropValue} object from state for given {@link PendingAirdropId}. If the airdrop contains only NFT return {@code null}. + * Fetches an {@link AccountAirdrop} object from state for given {@link PendingAirdropId}. * If the airdrop could not be fetched because the given airdrop doesn't exist, returns {@code null}. * * @param airdropId given airdrop id - * @return {@link PendingAirdropValue} object if successfully fetched or {@code null} if the airdrop doesn't exist + * @return {@link AccountAirdrop} object if successfully fetched or {@code null} if the airdrop doesn't exist */ - PendingAirdropValue get(@NonNull final PendingAirdropId airdropId); + AccountAirdrop get(@NonNull final PendingAirdropId airdropId); /** * Returns whether a given PendingAirdropId exists in state. From 954687a9a26c4f7d1377e0a2a2ec1403610d2818 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:29:38 +0300 Subject: [PATCH 04/13] fix: compile error Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- .../impl/test/WritableAirdropStoreTest.java | 80 +++++++++++-------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java index dcd2cc161b28..d795d22f2fe0 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java @@ -27,6 +27,7 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; +import com.hedera.hapi.node.state.token.AccountAirdrop; import com.hedera.node.app.service.token.impl.WritableAirdropStore; import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; import com.hedera.node.app.spi.metrics.StoreMetricsService; @@ -53,75 +54,81 @@ class WritableAirdropStoreTest extends StateBuilderUtil { @Mock private StoreMetricsService storeMetricsService; - private MapWritableKVState writableAirdropState; + private MapWritableKVState writableAirdropState; private WritableAirdropStore subject; @BeforeEach public void setUp() { writableAirdropState = emptyWritableAirdropStateBuilder().build(); - given(writableStates.get(AIRDROPS)) - .willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); } @Test void putsAirdropsToState() { final var airdropId = getFungibleAirdrop(); - final var airdropValue = PendingAirdropValue.newBuilder().amount(20).build(); + final var airdropValue = airdropWithValue(20); + final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); assertThat(writableAirdropState.contains(airdropId)).isFalse(); - subject.put(airdropId, airdropValue); + subject.put(airdropId, accountAirdrop); assertThat(writableAirdropState.contains(airdropId)).isTrue(); - final var tokenValue = writableAirdropState.get(airdropId); + assertThat(writableAirdropState.get(airdropId)).isNotNull(); + final var tokenValue = + Objects.requireNonNull(writableAirdropState.get(airdropId)).pendingAirdropValue(); assertThat(airdropValue).isEqualTo(tokenValue); } @Test void putsUpdatesExistingAirdrop() { final var airdropId = getFungibleAirdrop(); - final var airdropValue = PendingAirdropValue.newBuilder().amount(30).build(); + final var airdropValue = airdropWithValue(30); + final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); writableAirdropState = emptyWritableAirdropStateBuilder() - .value(airdropId, airdropValue) + .value(airdropId, accountAirdrop) .build(); - given(writableStates.get(AIRDROPS)) - .willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); - final var newAirdropValue = PendingAirdropValue.newBuilder().amount(20).build(); + final var newAirdropValue = airdropWithValue(20); + final var newAccountAirdrop = accountAirdrop + .copyBuilder() + .pendingAirdropValue(newAirdropValue) + .build(); assertThat(writableAirdropState.contains(airdropId)).isTrue(); - subject.put(airdropId, newAirdropValue); + subject.put(airdropId, newAccountAirdrop); assertThat(writableAirdropState.contains(airdropId)).isTrue(); - final var tokenValue = - Objects.requireNonNull(writableAirdropState.get(airdropId)).amount(); + final var tokenValue = Objects.requireNonNull(Objects.requireNonNull(writableAirdropState.get(airdropId)) + .pendingAirdropValue()) + .amount(); assertThat(tokenValue).isEqualTo(airdropValue.amount() + newAirdropValue.amount()); } @Test void putsDoesNotUpdateNftIfExists() { final var nftId = getNonFungibleAirDrop(); - final var nftValue = PendingAirdropValue.newBuilder().build(); + final var accountAirdrop = accountAirdropWith(nftId, null); var stateSpy = Mockito.spy( writableAirdropState = emptyWritableAirdropStateBuilder() - .value(nftId, nftValue) + .value(nftId, accountAirdrop) .build()); - given(writableStates.get(AIRDROPS)) - .willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); assertThat(writableAirdropState.contains(nftId)).isTrue(); - subject.put(nftId, nftValue); + subject.put(nftId, accountAirdrop); assertThat(writableAirdropState.contains(nftId)).isTrue(); final var tokenValue = - Objects.requireNonNull(writableAirdropState.get(nftId)).amount(); - assertThat(tokenValue).isEqualTo(0L); + Objects.requireNonNull(writableAirdropState.get(nftId)).pendingAirdropValue(); + assertThat(tokenValue).isNull(); verify(stateSpy, times(0)).put(any(), any()); } @@ -129,17 +136,18 @@ void putsDoesNotUpdateNftIfExists() { void removesAirdropById() { final var fungibleAirdropToRemove = getFungibleAirdrop(); final var fungibleAirdropValue = airdropWithValue(15); + final var fungibleAccountAirdrop = accountAirdropWith(fungibleAirdropToRemove, fungibleAirdropValue); final var nftToRemove = getNonFungibleAirDrop(); + final var nftAccountAirdrop = accountAirdropWith(nftToRemove, null); writableAirdropState = emptyWritableAirdropStateBuilder() - .value(fungibleAirdropToRemove, fungibleAirdropValue) - .value(nftToRemove, emptyAirdropValue()) + .value(fungibleAirdropToRemove, fungibleAccountAirdrop) + .value(nftToRemove, nftAccountAirdrop) .build(); assertThat(writableAirdropState.contains(fungibleAirdropToRemove)).isTrue(); assertThat(writableAirdropState.contains(nftToRemove)).isTrue(); - given(writableStates.get(AIRDROPS)) - .willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); assertThat(subject.exists(fungibleAirdropToRemove)).isTrue(); @@ -154,19 +162,21 @@ void removesAirdropById() { void getForModifyReturnsImmutableAirDrop() { final var airdropId = getFungibleAirdrop(); final var airdropValue = airdropWithValue(255); + final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); final var nftAirdropId = getNonFungibleAirDrop(); - final var nftValue = emptyAirdropValue(); + final var nftAccountAirdrop = accountAirdropWith(nftAirdropId, null); - subject.put(airdropId, airdropValue); - subject.put(nftAirdropId, nftValue); + subject.put(airdropId, accountAirdrop); + subject.put(nftAirdropId, nftAccountAirdrop); final var readAirdrop = subject.getForModify(airdropId); assertThat(readAirdrop).isNotNull(); - assertThat(airdropValue).isEqualTo(readAirdrop); + assertThat(airdropValue).isEqualTo(readAirdrop.pendingAirdropValue()); final var readNft = subject.getForModify(nftAirdropId); assertThat(nftAirdropId).isNotNull(); - assertThat(readNft).isNull(); + assertThat(readNft).isNotNull(); + assertThat(readNft.pendingAirdropValue()).isNull(); } @Test @@ -184,12 +194,14 @@ void getForModifyNonExisting() { assertThat(readNft).isNull(); } + @SuppressWarnings("ConstantConditions") @Test void testConstructorCallWithNull() { assertThatThrownBy(() -> subject = new WritableAirdropStore(null, configuration, storeMetricsService)) .isInstanceOf(NullPointerException.class); } + @SuppressWarnings("ConstantConditions") @Test void testGetWithNullParam() { assertThatThrownBy(() -> subject.getForModify(null)).isInstanceOf(NullPointerException.class); @@ -219,7 +231,11 @@ private PendingAirdropValue airdropWithValue(long value) { return PendingAirdropValue.newBuilder().amount(value).build(); } - private PendingAirdropValue emptyAirdropValue() { - return PendingAirdropValue.newBuilder().build(); + private AccountAirdrop accountAirdropWith( + PendingAirdropId pendingAirdropId, PendingAirdropValue pendingAirdropValue) { + return AccountAirdrop.newBuilder() + .pendingAirdropId(pendingAirdropId) + .pendingAirdropValue(pendingAirdropValue) + .build(); } } From 2121300e25562d6b09ced2627459cc0d9c86bf7d Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:53:07 +0300 Subject: [PATCH 05/13] feat: move the schema definition from 50 to 53 Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- .../token/impl/ReadableAirdropStoreImpl.java | 2 +- .../token/impl/WritableAirdropStore.java | 2 +- .../token/impl/schemas/V0500TokenSchema.java | 14 ------ .../token/impl/schemas/V0530TokenSchema.java | 45 +++++++++++++++++ .../test/schemas/V0500TokenSchemaTest.java | 17 ------- .../test/schemas/V0530TokenSchemaTest.java | 48 +++++++++++++++++++ 6 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java create mode 100644 hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java index 3e299ab2da45..ee9a5edc83b4 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -16,7 +16,7 @@ package com.hedera.node.app.service.token.impl; -import static com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema.AIRDROPS_KEY; +import static com.hedera.node.app.service.token.impl.schemas.V0530TokenSchema.AIRDROPS_KEY; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.PendingAirdropId; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java index 3beea78546a1..4dd439597cbf 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java @@ -16,7 +16,7 @@ package com.hedera.node.app.service.token.impl; -import static com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema.AIRDROPS_KEY; +import static com.hedera.node.app.service.token.impl.schemas.V0530TokenSchema.AIRDROPS_KEY; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.PendingAirdropId; diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java index 6fbf2b4927b8..e5887d3af4d1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0500TokenSchema.java @@ -21,16 +21,12 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; -import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.node.state.token.Account; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; -import com.swirlds.state.spi.StateDefinition; import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Set; import java.util.SortedMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -42,8 +38,6 @@ public class V0500TokenSchema extends StakingInfoManagementSchema { private static final Logger log = LogManager.getLogger(V0500TokenSchema.class); private static final String SHARED_VALUES_KEY = "V0500_FIRST_STORAGE_KEYS"; - private static final long MAX_PENDING_AIRDROPS = 1_000_000_000L; - public static final String AIRDROPS_KEY = "PENDING_AIRDROPS"; private static final SemanticVersion VERSION = SemanticVersion.newBuilder().major(0).minor(50).patch(0).build(); @@ -52,14 +46,6 @@ public V0500TokenSchema() { super(VERSION); } - @SuppressWarnings("rawtypes") - @NonNull - @Override - public Set statesToCreate() { - return Set.of(StateDefinition.onDisk( - AIRDROPS_KEY, PendingAirdropId.PROTOBUF, PendingAirdropValue.PROTOBUF, MAX_PENDING_AIRDROPS)); - } - @Override public void migrate(@NonNull final MigrationContext ctx) { requireNonNull(ctx); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java new file mode 100644 index 000000000000..15d76717c5f5 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 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.service.token.impl.schemas; + +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.base.SemanticVersion; +import com.swirlds.state.spi.StateDefinition; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; + +public class V0530TokenSchema extends StakingInfoManagementSchema { + + private static final long MAX_PENDING_AIRDROPS = 1_000_000_000L; + public static final String AIRDROPS_KEY = "PENDING_AIRDROPS"; + + private static final SemanticVersion VERSION = + SemanticVersion.newBuilder().major(0).minor(53).patch(0).build(); + + public V0530TokenSchema() { + super(VERSION); + } + + @SuppressWarnings("rawtypes") + @NonNull + @Override + public Set statesToCreate() { + return Set.of(StateDefinition.onDisk( + AIRDROPS_KEY, PendingAirdropId.PROTOBUF, PendingAirdropValue.PROTOBUF, MAX_PENDING_AIRDROPS)); + } +} diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java index 0fdcdd7e06bb..7c218cdd6aa4 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0500TokenSchemaTest.java @@ -24,16 +24,12 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; -import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.state.token.Account; import com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema; import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; -import com.swirlds.state.spi.StateDefinition; import com.swirlds.state.test.fixtures.MapWritableKVState; -import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedMap; @@ -68,19 +64,6 @@ class V0500TokenSchemaTest { private final V0500TokenSchema subject = new V0500TokenSchema(); - @Test - @DisplayName("verify states to create") - void verifyStatesToCreate() { - var sortedResult = subject.statesToCreate().stream() - .sorted(Comparator.comparing(StateDefinition::stateKey)) - .toList(); - - final var firstStateDef = sortedResult.getFirst(); - assertThat(firstStateDef.stateKey()).isEqualTo("PENDING_AIRDROPS"); - assertThat(firstStateDef.keyCodec()).isEqualTo(PendingAirdropId.PROTOBUF); - assertThat(firstStateDef.valueCodec()).isEqualTo(PendingAirdropValue.PROTOBUF); - } - @Test @DisplayName("skips migration without shared values") void throwsWithoutSharedValues() { diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java new file mode 100644 index 000000000000..7a89ac9f2db0 --- /dev/null +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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.service.token.impl.test.schemas; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.hedera.hapi.node.base.PendingAirdropId; +import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.node.app.service.token.impl.schemas.V0530TokenSchema; +import com.swirlds.state.spi.StateDefinition; +import java.util.Comparator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class V0530TokenSchemaTest { + + private final V0530TokenSchema subject = new V0530TokenSchema(); + + @Test + @DisplayName("verify states to create") + void verifyStatesToCreate() { + var sortedResult = subject.statesToCreate().stream() + .sorted(Comparator.comparing(StateDefinition::stateKey)) + .toList(); + + final var firstStateDef = sortedResult.getFirst(); + assertThat(firstStateDef.stateKey()).isEqualTo("PENDING_AIRDROPS"); + assertThat(firstStateDef.keyCodec()).isEqualTo(PendingAirdropId.PROTOBUF); + assertThat(firstStateDef.valueCodec()).isEqualTo(PendingAirdropValue.PROTOBUF); + } +} From 0ced88c047c3fac7f5f9cb60c8dcb176f6dcb439 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:59:50 +0300 Subject: [PATCH 06/13] fix: register the new schema Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Signed-off-by: ibankov --- .../hedera/node/app/service/token/impl/TokenServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java index 376483ab5b8a..e68ce57f56b9 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/TokenServiceImpl.java @@ -22,6 +22,7 @@ import com.hedera.node.app.service.token.impl.schemas.SyntheticAccountCreator; import com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema; import com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema; +import com.hedera.node.app.service.token.impl.schemas.V0530TokenSchema; import com.swirlds.state.spi.SchemaRegistry; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.ZoneId; @@ -44,5 +45,6 @@ public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); registry.register(new V0490TokenSchema(new SyntheticAccountCreator())); registry.register(new V0500TokenSchema()); + registry.register(new V0530TokenSchema()); } } From afa3951f69c2d97d3db9a15e4717224690f04f57 Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Thu, 18 Jul 2024 01:16:27 +0300 Subject: [PATCH 07/13] feat: add claim and airdrop to functionalityForType Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../node/app/service/schedule/impl/handlers/HandlerUtility.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java index 0d4910a68fd9..4ee88e8aa96c 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java @@ -170,6 +170,8 @@ static HederaFunctionality functionalityForType(final DataOneOfType transactionT case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE; case NODE_DELETE -> HederaFunctionality.NODE_DELETE; case TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP; + case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP; + case TOKEN_AIRDROP -> HederaFunctionality.TOKEN_AIRDROP; case UNSET -> HederaFunctionality.NONE; }; } From 7ca9a7168a2ee2dd549135681e1686b45c85531a Mon Sep 17 00:00:00 2001 From: ibankov Date: Thu, 18 Jul 2024 12:31:18 +0300 Subject: [PATCH 08/13] Add ApiPermissionConfig for new hedera functionalities Signed-off-by: ibankov --- .../com/hedera/node/config/data/ApiPermissionConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java index 7fe8f0764103..dfde84696748 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ApiPermissionConfig.java @@ -62,6 +62,8 @@ import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_AIRDROP; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_ASSOCIATE_TO_ACCOUNT; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_BURN; +import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_CANCEL_AIRDROP; +import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_CLAIM_AIRDROP; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_CREATE; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_DELETE; import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_DISSOCIATE_FROM_ACCOUNT; @@ -179,6 +181,8 @@ * @param tokenUpdateNfts the permission for {@link HederaFunctionality#TOKEN_UPDATE_NFTS} functionality * @param tokenReject the permission for {@link HederaFunctionality#TOKEN_REJECT} functionality * @param tokenAirdrop the permission for {@link HederaFunctionality#TOKEN_AIRDROP} functionality + * @param tokenCancelAirdrop the permission for {@link HederaFunctionality#TOKEN_CANCEL_AIRDROP} functionality + * @param tokenClaimAirdrop the permission for {@link HederaFunctionality#TOKEN_CLAIM_AIRDROP} functionality * * @param createNode the permission for {@link HederaFunctionality#NODE_CREATE} functionality * @param updateNode the permission for {@link HederaFunctionality#NODE_UPDATE} functionality @@ -251,6 +255,8 @@ public record ApiPermissionConfig( @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenUpdateNfts, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenReject, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenAirdrop, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenCancelAirdrop, + @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenClaimAirdrop, @ConfigProperty(defaultValue = "2-55") PermissionedAccountsRange createNode, @ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange updateNode, @ConfigProperty(defaultValue = "2-55") PermissionedAccountsRange deleteNode) { @@ -302,6 +308,8 @@ public record ApiPermissionConfig( permissionKeys.put(TOKEN_UPDATE_NFTS, c -> c.tokenUpdateNfts); permissionKeys.put(TOKEN_REJECT, c -> c.tokenReject); permissionKeys.put(TOKEN_AIRDROP, c -> c.tokenAirdrop); + permissionKeys.put(TOKEN_CANCEL_AIRDROP, c -> c.tokenCancelAirdrop); + permissionKeys.put(TOKEN_CLAIM_AIRDROP, c -> c.tokenClaimAirdrop); /* Queries */ permissionKeys.put(CONSENSUS_GET_TOPIC_INFO, c -> c.getTopicInfo); permissionKeys.put(CONTRACT_CALL_LOCAL, c -> c.contractCallLocalMethod); From 195542d0757092c232d5319bdd74d6bf463cf926 Mon Sep 17 00:00:00 2001 From: ibankov Date: Thu, 18 Jul 2024 15:22:48 +0300 Subject: [PATCH 09/13] Fix Token Service schema registry unit test Signed-off-by: ibankov --- .../node/app/service/token/impl/TokenServiceImplTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java index 3a15ba4c6b79..ef5b1c246364 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/TokenServiceImplTest.java @@ -25,6 +25,7 @@ import com.hedera.node.app.service.token.TokenServiceDefinition; import com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema; import com.hedera.node.app.service.token.impl.schemas.V0500TokenSchema; +import com.hedera.node.app.service.token.impl.schemas.V0530TokenSchema; import com.swirlds.state.spi.Schema; import com.swirlds.state.spi.SchemaRegistry; import org.assertj.core.api.Assertions; @@ -58,11 +59,12 @@ void registerSchemasRegistersTokenSchema() { subject.registerSchemas(schemaRegistry); final var captor = ArgumentCaptor.forClass(Schema.class); - verify(schemaRegistry, times(2)).register(captor.capture()); + verify(schemaRegistry, times(3)).register(captor.capture()); final var schemas = captor.getAllValues(); - assertThat(schemas).hasSize(2); + assertThat(schemas).hasSize(3); assertThat(schemas.getFirst()).isInstanceOf(V0490TokenSchema.class); - assertThat(schemas.getLast()).isInstanceOf(V0500TokenSchema.class); + assertThat(schemas.get(1)).isInstanceOf(V0500TokenSchema.class); + assertThat(schemas.getLast()).isInstanceOf(V0530TokenSchema.class); } @Test From 559905a889dc687dcf46e385edb0104c0cebfb75 Mon Sep 17 00:00:00 2001 From: ibankov Date: Thu, 18 Jul 2024 16:22:08 +0300 Subject: [PATCH 10/13] Proto changes for AccountPendingAirdrop Signed-off-by: ibankov --- .../token/impl/ReadableAirdropStoreImpl.java | 6 ++-- .../token/impl/WritableAirdropStore.java | 16 ++++----- .../test/ReadableAirdropStoreImplTest.java | 34 ++++++++++-------- .../impl/test/WritableAirdropStoreTest.java | 36 ++++++++++--------- .../test/handlers/util/StateBuilderUtil.java | 6 ++-- .../service/token/ReadableAirdropStore.java | 8 ++--- 6 files changed, 56 insertions(+), 50 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java index ee9a5edc83b4..334a60a69cff 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -20,7 +20,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableAirdropStore; import com.swirlds.state.spi.ReadableKVState; @@ -33,7 +33,7 @@ */ public class ReadableAirdropStoreImpl implements ReadableAirdropStore { /** The underlying data storage class that holds the airdrop data. */ - private final ReadableKVState readableAirdropState; + private final ReadableKVState readableAirdropState; /** * Create a new {@link ReadableAirdropStoreImpl} instance. @@ -54,7 +54,7 @@ public boolean exists(@NonNull final PendingAirdropId airdropId) { /** {@inheritDoc} */ @Override @Nullable - public AccountAirdrop get(@NonNull final PendingAirdropId airdropId) { + public AccountPendingAirdrop get(@NonNull final PendingAirdropId airdropId) { requireNonNull(airdropId); return readableAirdropState.get(airdropId); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java index 4dd439597cbf..1d92f40eb307 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/WritableAirdropStore.java @@ -21,7 +21,7 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; @@ -41,7 +41,7 @@ public class WritableAirdropStore extends ReadableAirdropStoreImpl { /** * The underlying data storage class that holds the Pending Airdrops data. */ - private final WritableKVState airdropState; + private final WritableKVState airdropState; /** * Create a new {@link WritableAirdropStore} instance. @@ -61,14 +61,14 @@ public WritableAirdropStore( } /** - * Persists a new {@link PendingAirdropId} with given {@link AccountAirdrop} into the state, + * Persists a new {@link PendingAirdropId} with given {@link AccountPendingAirdrop} into the state, * as well as exporting its ID to the transaction receipt. If there is existing * airdrop with the same id we add the value to the existing drop. * * @param airdropId - the airdropId to be persisted. * @param accountAirdrop - the account airdrop mapping for the given airdropId to be persisted. */ - public void put(@NonNull final PendingAirdropId airdropId, @NonNull final AccountAirdrop accountAirdrop) { + public void put(@NonNull final PendingAirdropId airdropId, @NonNull final AccountPendingAirdrop accountAirdrop) { requireNonNull(airdropId); requireNonNull(accountAirdrop); @@ -94,14 +94,14 @@ public void put(@NonNull final PendingAirdropId airdropId, @NonNull final Accoun } /** - * Updates given {@link PendingAirdropId} with new {@link AccountAirdrop} value the state, + * Updates given {@link PendingAirdropId} with new {@link AccountPendingAirdrop} value the state, * as well as exporting its ID to the transaction receipt. If there is no existing * airdrop with the same id do nothing. * * @param airdropId - the airdropId to be updated. * @param accountAirdrop - the account airdrop mapping for the given airdropId to be updated. */ - public void patch(@NonNull final PendingAirdropId airdropId, @NonNull final AccountAirdrop accountAirdrop) { + public void patch(@NonNull final PendingAirdropId airdropId, @NonNull final AccountPendingAirdrop accountAirdrop) { requireNonNull(airdropId); requireNonNull(accountAirdrop); @@ -120,7 +120,7 @@ public void remove(@NonNull final PendingAirdropId airdropId) { } /** - * Returns the {@link AccountAirdrop} with the given airdrop id. If the airdrop contains only NFT return {@code null}. + * Returns the {@link AccountPendingAirdrop} with the given airdrop id. If the airdrop contains only NFT return {@code null}. * If no such airdrop exists, returns {@code null} * * @param airdropId - the id of the airdrop, which value should be retrieved @@ -128,7 +128,7 @@ public void remove(@NonNull final PendingAirdropId airdropId) { * airdrop exists */ @Nullable - public AccountAirdrop getForModify(@NonNull final PendingAirdropId airdropId) { + public AccountPendingAirdrop getForModify(@NonNull final PendingAirdropId airdropId) { requireNonNull(airdropId); return airdropState.getForModify(airdropId); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java index 311540fa5911..4f8b3135e445 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/ReadableAirdropStoreImplTest.java @@ -24,7 +24,7 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.node.app.service.token.impl.ReadableAirdropStoreImpl; import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; import com.swirlds.state.spi.ReadableKVState; @@ -42,13 +42,14 @@ class ReadableAirdropStoreImplTest extends StateBuilderUtil { @Mock private ReadableStates readableStates; - private ReadableKVState airdrops; + private ReadableKVState airdrops; private ReadableAirdropStoreImpl subject; @BeforeEach public void setUp() { - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); } @@ -56,12 +57,13 @@ public void setUp() { void getsValueIfAirdropContainsFungibleToken() { var fungibleAirdrop = getFungibleAirdrop(); var airdropValue = airdropWithValue(5); - var accountAirdrop = accountAirdropWith(fungibleAirdrop, airdropValue); + var accountAirdrop = accountAirdropWith(airdropValue); airdrops = emptyReadableAirdropStateBuilder() .value(fungibleAirdrop, accountAirdrop) .build(); - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(subject.get(fungibleAirdrop)).isNotNull(); @@ -74,7 +76,8 @@ void getsFungibleThatDoesNotExist() { var fungibleAirdrop = getFungibleAirdrop(); airdrops = emptyReadableAirdropStateBuilder().build(); - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(subject.get(fungibleAirdrop)).isNull(); @@ -96,9 +99,10 @@ void testConstructorCallWithNull() { @Test void testSizeOfState() { airdrops = emptyReadableAirdropStateBuilder() - .value(getNonFungibleAirDrop(), accountAirdropWith(getFungibleAirdrop(), null)) + .value(getNonFungibleAirDrop(), accountAirdropWith(null)) .build(); - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); assertThat(readableStates.get(StateBuilderUtil.AIRDROPS).size()).isEqualTo(subject.sizeOfState()); } @@ -107,12 +111,13 @@ void testSizeOfState() { void testExists() { var fungibleAirdrop = getFungibleAirdrop(); var fungibleValue = airdropWithValue(10); - var accountAirdrop = accountAirdropWith(fungibleAirdrop, fungibleValue); + var accountAirdrop = accountAirdropWith(fungibleValue); airdrops = emptyReadableAirdropStateBuilder() .value(fungibleAirdrop, accountAirdrop) .build(); - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); final var store = new ReadableAirdropStoreImpl(readableStates); @@ -125,7 +130,8 @@ void testDoesNotExists() { var fungibleAirdrop = getFungibleAirdrop(); airdrops = emptyReadableAirdropStateBuilder().build(); - given(readableStates.get(AIRDROPS)).willReturn(airdrops); + given(readableStates.get(AIRDROPS)) + .willReturn(airdrops); subject = new ReadableAirdropStoreImpl(readableStates); final var store = new ReadableAirdropStoreImpl(readableStates); @@ -157,10 +163,8 @@ private PendingAirdropValue airdropWithValue(long value) { return PendingAirdropValue.newBuilder().amount(value).build(); } - private AccountAirdrop accountAirdropWith( - PendingAirdropId pendingAirdropId, PendingAirdropValue pendingAirdropValue) { - return AccountAirdrop.newBuilder() - .pendingAirdropId(pendingAirdropId) + private AccountPendingAirdrop accountAirdropWith(PendingAirdropValue pendingAirdropValue) { + return AccountPendingAirdrop.newBuilder() .pendingAirdropValue(pendingAirdropValue) .build(); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java index d795d22f2fe0..0e8bac9ca478 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/WritableAirdropStoreTest.java @@ -27,7 +27,7 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; import com.hedera.hapi.node.base.TokenID; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.node.app.service.token.impl.WritableAirdropStore; import com.hedera.node.app.service.token.impl.test.handlers.util.StateBuilderUtil; import com.hedera.node.app.spi.metrics.StoreMetricsService; @@ -54,14 +54,15 @@ class WritableAirdropStoreTest extends StateBuilderUtil { @Mock private StoreMetricsService storeMetricsService; - private MapWritableKVState writableAirdropState; + private MapWritableKVState writableAirdropState; private WritableAirdropStore subject; @BeforeEach public void setUp() { writableAirdropState = emptyWritableAirdropStateBuilder().build(); - given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); } @@ -69,7 +70,7 @@ public void setUp() { void putsAirdropsToState() { final var airdropId = getFungibleAirdrop(); final var airdropValue = airdropWithValue(20); - final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); + final var accountAirdrop = accountAirdropWith(airdropValue); assertThat(writableAirdropState.contains(airdropId)).isFalse(); @@ -86,11 +87,12 @@ void putsAirdropsToState() { void putsUpdatesExistingAirdrop() { final var airdropId = getFungibleAirdrop(); final var airdropValue = airdropWithValue(30); - final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); + final var accountAirdrop = accountAirdropWith(airdropValue); writableAirdropState = emptyWritableAirdropStateBuilder() .value(airdropId, accountAirdrop) .build(); - given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); final var newAirdropValue = airdropWithValue(20); @@ -113,12 +115,13 @@ void putsUpdatesExistingAirdrop() { @Test void putsDoesNotUpdateNftIfExists() { final var nftId = getNonFungibleAirDrop(); - final var accountAirdrop = accountAirdropWith(nftId, null); + final var accountAirdrop = accountAirdropWith(null); var stateSpy = Mockito.spy( writableAirdropState = emptyWritableAirdropStateBuilder() .value(nftId, accountAirdrop) .build()); - given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); assertThat(writableAirdropState.contains(nftId)).isTrue(); @@ -136,9 +139,9 @@ void putsDoesNotUpdateNftIfExists() { void removesAirdropById() { final var fungibleAirdropToRemove = getFungibleAirdrop(); final var fungibleAirdropValue = airdropWithValue(15); - final var fungibleAccountAirdrop = accountAirdropWith(fungibleAirdropToRemove, fungibleAirdropValue); + final var fungibleAccountAirdrop = accountAirdropWith(fungibleAirdropValue); final var nftToRemove = getNonFungibleAirDrop(); - final var nftAccountAirdrop = accountAirdropWith(nftToRemove, null); + final var nftAccountAirdrop = accountAirdropWith(null); writableAirdropState = emptyWritableAirdropStateBuilder() .value(fungibleAirdropToRemove, fungibleAccountAirdrop) .value(nftToRemove, nftAccountAirdrop) @@ -147,7 +150,8 @@ void removesAirdropById() { assertThat(writableAirdropState.contains(fungibleAirdropToRemove)).isTrue(); assertThat(writableAirdropState.contains(nftToRemove)).isTrue(); - given(writableStates.get(AIRDROPS)).willReturn(writableAirdropState); + given(writableStates.get(AIRDROPS)) + .willReturn(writableAirdropState); subject = new WritableAirdropStore(writableStates, configuration, storeMetricsService); assertThat(subject.exists(fungibleAirdropToRemove)).isTrue(); @@ -162,9 +166,9 @@ void removesAirdropById() { void getForModifyReturnsImmutableAirDrop() { final var airdropId = getFungibleAirdrop(); final var airdropValue = airdropWithValue(255); - final var accountAirdrop = accountAirdropWith(airdropId, airdropValue); + final var accountAirdrop = accountAirdropWith(airdropValue); final var nftAirdropId = getNonFungibleAirDrop(); - final var nftAccountAirdrop = accountAirdropWith(nftAirdropId, null); + final var nftAccountAirdrop = accountAirdropWith(null); subject.put(airdropId, accountAirdrop); subject.put(nftAirdropId, nftAccountAirdrop); @@ -231,10 +235,8 @@ private PendingAirdropValue airdropWithValue(long value) { return PendingAirdropValue.newBuilder().amount(value).build(); } - private AccountAirdrop accountAirdropWith( - PendingAirdropId pendingAirdropId, PendingAirdropValue pendingAirdropValue) { - return AccountAirdrop.newBuilder() - .pendingAirdropId(pendingAirdropId) + private AccountPendingAirdrop accountAirdropWith(PendingAirdropValue pendingAirdropValue) { + return AccountPendingAirdrop.newBuilder() .pendingAirdropValue(pendingAirdropValue) .build(); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java index 55b28e9fcdc5..f70a224fef5a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/StateBuilderUtil.java @@ -23,7 +23,7 @@ import com.hedera.hapi.node.state.common.EntityIDPair; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.token.Account; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import com.hedera.hapi.node.state.token.Nft; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; @@ -80,12 +80,12 @@ protected MapWritableKVState.Builder emptyWritableAccountSta } @NonNull - protected MapReadableKVState.Builder emptyReadableAirdropStateBuilder() { + protected MapReadableKVState.Builder emptyReadableAirdropStateBuilder() { return MapReadableKVState.builder(AIRDROPS); } @NonNull - protected MapWritableKVState.Builder emptyWritableAirdropStateBuilder() { + protected MapWritableKVState.Builder emptyWritableAirdropStateBuilder() { return MapWritableKVState.builder(AIRDROPS); } diff --git a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java index e589f147eae7..b904f0de7940 100644 --- a/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java +++ b/hedera-node/hedera-token-service/src/main/java/com/hedera/node/app/service/token/ReadableAirdropStore.java @@ -17,7 +17,7 @@ package com.hedera.node.app.service.token; import com.hedera.hapi.node.base.PendingAirdropId; -import com.hedera.hapi.node.state.token.AccountAirdrop; +import com.hedera.hapi.node.state.token.AccountPendingAirdrop; import edu.umd.cs.findbugs.annotations.NonNull; /** @@ -26,13 +26,13 @@ */ public interface ReadableAirdropStore { /** - * Fetches an {@link AccountAirdrop} object from state for given {@link PendingAirdropId}. + * Fetches an {@link AccountPendingAirdrop} object from state for given {@link PendingAirdropId}. * If the airdrop could not be fetched because the given airdrop doesn't exist, returns {@code null}. * * @param airdropId given airdrop id - * @return {@link AccountAirdrop} object if successfully fetched or {@code null} if the airdrop doesn't exist + * @return {@link AccountPendingAirdrop} object if successfully fetched or {@code null} if the airdrop doesn't exist */ - AccountAirdrop get(@NonNull final PendingAirdropId airdropId); + AccountPendingAirdrop get(@NonNull final PendingAirdropId airdropId); /** * Returns whether a given PendingAirdropId exists in state. From 8185b9655ad7c4976a031e75c25d134ebd42863f Mon Sep 17 00:00:00 2001 From: Valentin Tronkov Date: Fri, 19 Jul 2024 06:27:13 +0300 Subject: [PATCH 11/13] Update hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java Co-authored-by: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Signed-off-by: Valentin Tronkov --- .../node/app/service/token/impl/ReadableAirdropStoreImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java index 334a60a69cff..83e8134cc698 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -29,7 +29,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; /** - * Default implementation of {@link ReadableAccountStore} + * Default implementation of {@link ReadableAirdropStore} */ public class ReadableAirdropStoreImpl implements ReadableAirdropStore { /** The underlying data storage class that holds the airdrop data. */ From 2b1e601eb25631e26a065cec4615cf6074ffd15b Mon Sep 17 00:00:00 2001 From: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> Date: Fri, 19 Jul 2024 06:49:28 +0300 Subject: [PATCH 12/13] nit: remove unused import Signed-off-by: Valentin Tronkov <99957253+vtronkov@users.noreply.github.com> --- .../node/app/service/token/impl/ReadableAirdropStoreImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java index 83e8134cc698..611641fd0708 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/ReadableAirdropStoreImpl.java @@ -21,7 +21,6 @@ import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.state.token.AccountPendingAirdrop; -import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.ReadableAirdropStore; import com.swirlds.state.spi.ReadableKVState; import com.swirlds.state.spi.ReadableStates; From b03967b55566c7defe3a125d94686c6b4e16edd0 Mon Sep 17 00:00:00 2001 From: ibankov Date: Mon, 22 Jul 2024 18:52:01 +0300 Subject: [PATCH 13/13] change proto branch Signed-off-by: ibankov --- hapi/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi/build.gradle.kts b/hapi/build.gradle.kts index 64fe0dac2ecf..7acaf031564e 100644 --- a/hapi/build.gradle.kts +++ b/hapi/build.gradle.kts @@ -33,7 +33,7 @@ tasks.cloneHederaProtobufs { // tag = "v0.51.0" // uncomment below to use a specific branch // branch = "main" - branch = "hip-904-TokenClaimAirdrop" + branch = "hip-904-proto-updates" } sourceSets {