Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement pending state for airdrop purposes #13566

Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ abstract class GitClone : DefaultTask() {
commandLine(
"git",
"clone",
"https://github.com/hashgraph/hedera-protobufs.git",
"https://github.com/LimeChain/hedera-protobufs.git",
"-q"
ibankov marked this conversation as resolved.
Show resolved Hide resolved
)
} else {
Expand Down
2 changes: 1 addition & 1 deletion hapi/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ tasks.cloneHederaProtobufs {
// uncomment below to use a specific tag
// tag = "v0.51.0"
// uncomment below to use a specific branch
branch = "main"
branch = "hip-904-register-airdrop-proto"
}

sourceSets {
Expand Down
2 changes: 2 additions & 0 deletions hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@
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;

Check warning on line 236 in hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java

View check run for this annotation

Codecov / codecov/patch

hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java#L235-L236

Added lines #L235 - L236 were not covered by tests
case UNSET -> throw new UnknownHederaFunctionality();
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public interface StoreMetricsService {
enum StoreType {
TOPIC,
ACCOUNT,
AIRDROPS,
NFT,
TOKEN,
TOKEN_RELATION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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"));

Check warning on line 38 in hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-app/src/main/java/com/hedera/node/app/metrics/StoreMetricsServiceImpl.java#L38

Added line #L38 was not covered by tests
storeMetricsMap.put(StoreType.NFT, new StoreMetricsImpl(metrics, "nfts"));
storeMetricsMap.put(StoreType.TOKEN, new StoreMetricsImpl(metrics, "tokens"));
storeMetricsMap.put(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
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;
import com.hedera.node.app.service.token.ReadableTokenRelationStore;
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;
Expand Down Expand Up @@ -77,6 +79,7 @@ private static Map<Class<?>, StoreEntry> createFactoryMap() {
Map<Class<?>, 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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,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;
Expand Down Expand Up @@ -65,6 +66,7 @@ private static Map<Class<?>, 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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,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;
Expand All @@ -74,6 +75,7 @@
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_GRANT_KYC_TO_ACCOUNT;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_MINT;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_PAUSE;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_REJECT;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_REVOKE_KYC_FROM_ACCOUNT;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_UNFREEZE_ACCOUNT;
import static com.hedera.hapi.node.base.HederaFunctionality.TOKEN_UNPAUSE;
Expand Down Expand Up @@ -176,11 +178,13 @@
* @param freeze the permission for {@link HederaFunctionality#FREEZE} functionality
* @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
* @param deleteNode the permission for {@link HederaFunctionality#NODE_DELETE} functionality
* @param getNodeInfo the permission for {@link HederaFunctionality#NODE_GET_INFO} functionality
* @param getNodeInfo the permission for {@link HederaFunctionality#NODE_GET_INFO} functionality
*/
@ConfigData
public record ApiPermissionConfig(
Expand Down Expand Up @@ -247,6 +251,8 @@ public record ApiPermissionConfig(
@ConfigProperty(defaultValue = "2-58") PermissionedAccountsRange freeze,
@ConfigProperty(defaultValue = "2-50") PermissionedAccountsRange getAccountDetails,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenUpdateNfts,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenReject,
ibankov marked this conversation as resolved.
Show resolved Hide resolved
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange tokenAirdrop,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange createNode,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange updateNode,
@ConfigProperty(defaultValue = "0-*") PermissionedAccountsRange deleteNode,
Expand Down Expand Up @@ -297,6 +303,8 @@ public record ApiPermissionConfig(
permissionKeys.put(SCHEDULE_DELETE, c -> c.scheduleDelete);
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ public record TokensConfig(
ScaleFactor nftsMintThrottleScaleFactor,
@ConfigProperty(value = "nfts.useVirtualMerkle", defaultValue = "true") @NetworkProperty
boolean nftsUseVirtualMerkle,
@ConfigProperty(value = "maxAllowedAirdrops", defaultValue = "1000000") @NetworkProperty
long maxAllowedAirdorps,
Comment on lines +51 to +52
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for @netopyr to give us the exact value here.

@ConfigProperty(value = "autoCreations.isEnabled", defaultValue = "true") @NetworkProperty
boolean autoCreationsIsEnabled,
@ConfigProperty(value = "maxMetadataBytes", defaultValue = "100") @NetworkProperty int tokensMaxMetadataBytes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@
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;

Check warning on line 358 in hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java#L357-L358

Added lines #L357 - L358 were not covered by tests
case UNRECOGNIZED -> throw new RuntimeException("Unknown function UNRECOGNIZED");
};
}
Expand Down Expand Up @@ -458,6 +460,8 @@
.TransactionGetFastRecord;
case UNCHECKED_SUBMIT -> com.hederahashgraph.api.proto.java.HederaFunctionality.UncheckedSubmit;
case UTIL_PRNG -> com.hederahashgraph.api.proto.java.HederaFunctionality.UtilPrng;
case TOKEN_REJECT -> com.hederahashgraph.api.proto.java.HederaFunctionality.TokenReject;
case TOKEN_AIRDROP -> com.hederahashgraph.api.proto.java.HederaFunctionality.TokenAirdrop;

Check warning on line 464 in hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java#L463-L464

Added lines #L463 - L464 were not covered by tests
};
}

Expand Down Expand Up @@ -798,6 +802,9 @@
case GOSSIP_ENDPOINTS_EXCEEDED_LIMIT -> ResponseCodeEnum.GOSSIP_ENDPOINTS_EXCEEDED_LIMIT;
case SERVICE_ENDPOINTS_EXCEEDED_LIMIT -> ResponseCodeEnum.SERVICE_ENDPOINTS_EXCEEDED_LIMIT;
case INVALID_IPV4_ADDRESS -> ResponseCodeEnum.INVALID_IPV4_ADDRESS;
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;

Check warning on line 807 in hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java#L805-L807

Added lines #L805 - L807 were not covered by tests
case UNRECOGNIZED -> throw new RuntimeException("UNRECOGNIZED Response code!");
};
}
Expand Down Expand Up @@ -1362,6 +1369,11 @@
case INVALID_IPV4_ADDRESS -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_IPV4_ADDRESS;

// case UNRECOGNIZED -> throw new RuntimeException("UNRECOGNIZED Response code!");
case TOKEN_REFERENCE_REPEATED -> com.hederahashgraph.api.proto.java.ResponseCodeEnum

Check warning on line 1372 in hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java#L1372

Added line #L1372 was not covered by tests
.TOKEN_REFERENCE_REPEATED;
case INVALID_OWNER_ID -> com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_OWNER_ID;
case TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED -> com.hederahashgraph.api.proto.java.ResponseCodeEnum

Check warning on line 1375 in hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java

View check run for this annotation

Codecov / codecov/patch

hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/pbj/PbjConverter.java#L1374-L1375

Added lines #L1374 - L1375 were not covered by tests
.TOKEN_REFERENCE_LIST_SIZE_LIMIT_EXCEEDED;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ static HederaFunctionality functionalityForType(final DataOneOfType transactionT
case NODE_UPDATE -> HederaFunctionality.NODE_UPDATE;
case NODE_DELETE -> HederaFunctionality.NODE_DELETE;
case UNSET -> HederaFunctionality.NONE;
case TOKEN_REJECT -> HederaFunctionality.TOKEN_REJECT;
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<PendingAirdropId, PendingAirdropValue> 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();
}
Neeharika-Sompalli marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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<PendingAirdropId, PendingAirdropValue> 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));
vtronkov marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* 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);
ibankov marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading