All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.hapi.util.HapiUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2023-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.hapi.util;

import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.ContractID;
import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.KeyList;
import com.hedera.hapi.node.base.SemanticVersion;
import com.hedera.hapi.node.base.Timestamp;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.transaction.Query;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.time.Instant;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * Utility class for working with the HAPI. We might move this to the HAPI project.
 */
public class HapiUtils {
    private static final int EVM_ADDRESS_ALIAS_LENGTH = 20;
    public static final Pattern ALPHA_PRE_PATTERN = Pattern.compile("alpha[.](\\d+)");
    public static final Key EMPTY_KEY_LIST =
            Key.newBuilder().keyList(KeyList.DEFAULT).build();
    public static final long FUNDING_ACCOUNT_EXPIRY = 33197904000L;

    /** A {@link Comparator} for {@link AccountID}s. Sorts first by account number, then by alias. */
    public static final Comparator ACCOUNT_ID_COMPARATOR = (o1, o2) -> {
        if (o1 == o2) return 0;
        if (o1.hasAccountNum() && !o2.hasAccountNum()) return -1;
        if (!o1.hasAccountNum() && o2.hasAccountNum()) return 1;
        if (o1.hasAccountNum()) {
            return Long.compare(o1.accountNumOrThrow(), o2.accountNumOrThrow());
        } else {
            final var alias1 = o1.aliasOrElse(Bytes.EMPTY);
            final var alias2 = o2.aliasOrElse(Bytes.EMPTY);
            // FUTURE: Can replace with Bytes.compare or a built-in Bytes comparator when available
            final var diff = alias1.length() - alias2.length();
            if (diff < 0) return -1;
            if (diff > 0) return 1;
            for (long i = 0; i < alias1.length(); i++) {
                final var b1 = alias1.getByte(i);
                final var b2 = alias2.getByte(i);
                if (b1 < b2) return -1;
                if (b1 > b2) return 1;
            }
        }
        return 0;
    };

    /** A {@link Comparator} for {@link ContractID}s. Sorts first by contract number, then by evm address. */
    public static final Comparator CONTRACT_ID_COMPARATOR = (o1, o2) -> {
        if (o1 == o2) return 0;
        if (o1.hasContractNum() && !o2.hasContractNum()) return -1;
        if (!o1.hasContractNum() && o2.hasContractNum()) return 1;
        if (o1.hasContractNum()) {
            return Long.compare(o1.contractNumOrThrow(), o2.contractNumOrThrow());
        } else {
            final var alias1 = o1.evmAddressOrElse(Bytes.EMPTY);
            final var alias2 = o2.evmAddressOrElse(Bytes.EMPTY);
            // FUTURE: Can replace with Bytes.compare or a built-in Bytes comparator when available
            final var diff = alias1.length() - alias2.length();
            if (diff < 0) return -1;
            if (diff > 0) return 1;
            for (long i = 0; i < alias1.length(); i++) {
                final var b1 = alias1.getByte(i);
                final var b2 = alias2.getByte(i);
                if (b1 < b2) return -1;
                if (b1 > b2) return 1;
            }
        }
        return 0;
    };

    /** A simple {@link Comparator} for {@link Timestamp}s. */
    public static final Comparator TIMESTAMP_COMPARATOR =
            Comparator.comparingLong(Timestamp::seconds).thenComparingInt(Timestamp::nanos);

    private HapiUtils() {}

    /**
     * Determines whether the given account is a "hollow" account, i.e., one that has no keys and has an alias
     * that matches the length of an EVM address.
     *
     * @param account The account to check
     * @return {@code true} if the account is a hollow account, {@code false} otherwise.
     */
    public static boolean isHollow(@NonNull final Account account) {
        requireNonNull(account);
        return (account.accountIdOrThrow().accountNum() > 1000
                && account.keyOrElse(EMPTY_KEY_LIST).equals(EMPTY_KEY_LIST)
                && account.alias() != null
                && account.alias().length() == EVM_ADDRESS_ALIAS_LENGTH);
    }

    /** Converts the given {@link Instant} into a {@link Timestamp}. */
    public static Timestamp asTimestamp(@NonNull final Instant instant) {
        return new Timestamp(instant.getEpochSecond(), instant.getNano());
    }

    /** Converts the given {@link Timestamp} into an {@link Instant}. */
    public static Instant asInstant(@NonNull final Timestamp timestamp) {
        return Instant.ofEpochSecond(timestamp.seconds(), timestamp.nanos());
    }

    /** Subtracts the given number of seconds from the given {@link Timestamp}, returning a new {@link Timestamp}. */
    public static Timestamp minus(@NonNull final Timestamp ts, final long seconds) {
        return Timestamp.newBuilder()
                .seconds(ts.seconds() - seconds)
                .nanos(ts.nanos())
                .build();
    }

    /** Determines whether the first timestamp is before the second timestamp. Think of it as, "Is t1 before t2?" */
    public static boolean isBefore(@NonNull final Timestamp t1, @NonNull final Timestamp t2) {
        return TIMESTAMP_COMPARATOR.compare(t1, t2) < 0;
    }

    /** Given a key, determines the number of cryptographic keys contained within it. */
    public static int countOfCryptographicKeys(@NonNull final Key key) {
        return switch (key.key().kind()) {
            case ECDSA_384, ED25519, RSA_3072, ECDSA_SECP256K1 -> 1;
            case KEY_LIST -> key.keyListOrThrow().keys().stream()
                    .mapToInt(HapiUtils::countOfCryptographicKeys)
                    .sum();
            case THRESHOLD_KEY -> key.thresholdKeyOrThrow().keysOrElse(KeyList.DEFAULT).keys().stream()
                    .mapToInt(HapiUtils::countOfCryptographicKeys)
                    .sum();
            case CONTRACT_ID, DELEGATABLE_CONTRACT_ID, UNSET -> 0;
        };
    }

    // Suppressing the warning that this field is not used
    @SuppressWarnings("java:S1068")
    private static final Set QUERY_FUNCTIONS = EnumSet.of(
            HederaFunctionality.CONSENSUS_GET_TOPIC_INFO,
            HederaFunctionality.GET_BY_SOLIDITY_ID,
            HederaFunctionality.CONTRACT_CALL_LOCAL,
            HederaFunctionality.CONTRACT_GET_INFO,
            HederaFunctionality.CONTRACT_GET_BYTECODE,
            HederaFunctionality.CONTRACT_GET_RECORDS,
            HederaFunctionality.CRYPTO_GET_ACCOUNT_BALANCE,
            HederaFunctionality.CRYPTO_GET_ACCOUNT_RECORDS,
            HederaFunctionality.CRYPTO_GET_INFO,
            HederaFunctionality.CRYPTO_GET_LIVE_HASH,
            HederaFunctionality.FILE_GET_CONTENTS,
            HederaFunctionality.FILE_GET_INFO,
            HederaFunctionality.TRANSACTION_GET_RECEIPT,
            HederaFunctionality.TRANSACTION_GET_RECORD,
            HederaFunctionality.GET_VERSION_INFO,
            HederaFunctionality.TOKEN_GET_INFO,
            HederaFunctionality.SCHEDULE_GET_INFO,
            HederaFunctionality.TOKEN_GET_NFT_INFO,
            HederaFunctionality.TOKEN_GET_NFT_INFOS,
            HederaFunctionality.TOKEN_GET_ACCOUNT_NFT_INFOS,
            HederaFunctionality.NETWORK_GET_EXECUTION_TIME,
            HederaFunctionality.GET_ACCOUNT_DETAILS);

    public static HederaFunctionality functionOf(final TransactionBody txn) throws UnknownHederaFunctionality {
        return switch (txn.data().kind()) {
            case CONSENSUS_CREATE_TOPIC -> HederaFunctionality.CONSENSUS_CREATE_TOPIC;
            case CONSENSUS_UPDATE_TOPIC -> HederaFunctionality.CONSENSUS_UPDATE_TOPIC;
            case CONSENSUS_DELETE_TOPIC -> HederaFunctionality.CONSENSUS_DELETE_TOPIC;
            case CONSENSUS_SUBMIT_MESSAGE -> HederaFunctionality.CONSENSUS_SUBMIT_MESSAGE;
            case CONTRACT_CALL -> HederaFunctionality.CONTRACT_CALL;
            case CONTRACT_CREATE_INSTANCE -> HederaFunctionality.CONTRACT_CREATE;
            case CONTRACT_UPDATE_INSTANCE -> HederaFunctionality.CONTRACT_UPDATE;
            case CONTRACT_DELETE_INSTANCE -> HederaFunctionality.CONTRACT_DELETE;
            case CRYPTO_ADD_LIVE_HASH -> HederaFunctionality.CRYPTO_ADD_LIVE_HASH;
            case CRYPTO_APPROVE_ALLOWANCE -> HederaFunctionality.CRYPTO_APPROVE_ALLOWANCE;
            case CRYPTO_CREATE_ACCOUNT -> HederaFunctionality.CRYPTO_CREATE;
            case CRYPTO_UPDATE_ACCOUNT -> HederaFunctionality.CRYPTO_UPDATE;
            case CRYPTO_DELETE -> HederaFunctionality.CRYPTO_DELETE;
            case CRYPTO_DELETE_ALLOWANCE -> HederaFunctionality.CRYPTO_DELETE_ALLOWANCE;
            case CRYPTO_DELETE_LIVE_HASH -> HederaFunctionality.CRYPTO_DELETE_LIVE_HASH;
            case CRYPTO_TRANSFER -> HederaFunctionality.CRYPTO_TRANSFER;
            case ETHEREUM_TRANSACTION -> HederaFunctionality.ETHEREUM_TRANSACTION;
            case FILE_APPEND -> HederaFunctionality.FILE_APPEND;
            case FILE_CREATE -> HederaFunctionality.FILE_CREATE;
            case FILE_UPDATE -> HederaFunctionality.FILE_UPDATE;
            case FILE_DELETE -> HederaFunctionality.FILE_DELETE;
            case FREEZE -> HederaFunctionality.FREEZE;
            case NODE_STAKE_UPDATE -> HederaFunctionality.NODE_STAKE_UPDATE;
            case SCHEDULE_CREATE -> HederaFunctionality.SCHEDULE_CREATE;
            case SCHEDULE_SIGN -> HederaFunctionality.SCHEDULE_SIGN;
            case SCHEDULE_DELETE -> HederaFunctionality.SCHEDULE_DELETE;
            case SYSTEM_DELETE -> HederaFunctionality.SYSTEM_DELETE;
            case SYSTEM_UNDELETE -> HederaFunctionality.SYSTEM_UNDELETE;
            case TOKEN_ASSOCIATE -> HederaFunctionality.TOKEN_ASSOCIATE_TO_ACCOUNT;
            case TOKEN_BURN -> HederaFunctionality.TOKEN_BURN;
            case TOKEN_CREATION -> HederaFunctionality.TOKEN_CREATE;
            case TOKEN_DELETION -> HederaFunctionality.TOKEN_DELETE;
            case TOKEN_DISSOCIATE -> HederaFunctionality.TOKEN_DISSOCIATE_FROM_ACCOUNT;
            case TOKEN_FEE_SCHEDULE_UPDATE -> HederaFunctionality.TOKEN_FEE_SCHEDULE_UPDATE;
            case TOKEN_FREEZE -> HederaFunctionality.TOKEN_FREEZE_ACCOUNT;
            case TOKEN_GRANT_KYC -> HederaFunctionality.TOKEN_GRANT_KYC_TO_ACCOUNT;
            case TOKEN_MINT -> HederaFunctionality.TOKEN_MINT;
            case TOKEN_PAUSE -> HederaFunctionality.TOKEN_PAUSE;
            case TOKEN_REVOKE_KYC -> HederaFunctionality.TOKEN_REVOKE_KYC_FROM_ACCOUNT;
            case TOKEN_UNFREEZE -> HederaFunctionality.TOKEN_UNFREEZE_ACCOUNT;
            case TOKEN_UNPAUSE -> HederaFunctionality.TOKEN_UNPAUSE;
            case TOKEN_UPDATE -> HederaFunctionality.TOKEN_UPDATE;
            case TOKEN_UPDATE_NFTS -> HederaFunctionality.TOKEN_UPDATE_NFTS;
            case TOKEN_WIPE -> HederaFunctionality.TOKEN_ACCOUNT_WIPE;
            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 TOKEN_CANCEL_AIRDROP -> HederaFunctionality.TOKEN_CANCEL_AIRDROP;
            case TOKEN_CLAIM_AIRDROP -> HederaFunctionality.TOKEN_CLAIM_AIRDROP;
            case UNSET -> throw new UnknownHederaFunctionality();
        };
    }

    public static HederaFunctionality functionOf(final Query txn) throws UnknownHederaFunctionality {
        return switch (txn.query().kind()) {
            case TOKEN_GET_ACCOUNT_NFT_INFOS -> HederaFunctionality.TOKEN_GET_ACCOUNT_NFT_INFOS;
            case TOKEN_GET_NFT_INFOS -> HederaFunctionality.TOKEN_GET_NFT_INFOS;
            case ACCOUNT_DETAILS -> HederaFunctionality.GET_ACCOUNT_DETAILS;
            case CONSENSUS_GET_TOPIC_INFO -> HederaFunctionality.CONSENSUS_GET_TOPIC_INFO;
            case CONTRACT_CALL_LOCAL -> HederaFunctionality.CONTRACT_CALL_LOCAL;
            case CONTRACT_GET_BYTECODE -> HederaFunctionality.CONTRACT_GET_BYTECODE;
            case CONTRACT_GET_INFO -> HederaFunctionality.CONTRACT_GET_INFO;
            case CONTRACT_GET_RECORDS -> HederaFunctionality.CONTRACT_GET_RECORDS;
            case CRYPTO_GET_ACCOUNT_RECORDS -> HederaFunctionality.CRYPTO_GET_ACCOUNT_RECORDS;
            case CRYPTO_GET_INFO -> HederaFunctionality.CRYPTO_GET_INFO;
            case CRYPTO_GET_LIVE_HASH -> HederaFunctionality.CRYPTO_GET_LIVE_HASH;
            case FILE_GET_CONTENTS -> HederaFunctionality.FILE_GET_CONTENTS;
            case FILE_GET_INFO -> HederaFunctionality.FILE_GET_INFO;
            case CRYPTO_GET_PROXY_STAKERS -> HederaFunctionality.CRYPTO_GET_STAKERS;
            case GET_BY_SOLIDITY_ID -> HederaFunctionality.GET_BY_SOLIDITY_ID;
            case CRYPTOGET_ACCOUNT_BALANCE -> HederaFunctionality.CRYPTO_GET_ACCOUNT_BALANCE;
            case GET_BY_KEY -> HederaFunctionality.GET_BY_KEY;
            case NETWORK_GET_EXECUTION_TIME -> HederaFunctionality.NETWORK_GET_EXECUTION_TIME;
            case SCHEDULE_GET_INFO -> HederaFunctionality.SCHEDULE_GET_INFO;
            case TOKEN_GET_INFO -> HederaFunctionality.TOKEN_GET_INFO;
            case TOKEN_GET_NFT_INFO -> HederaFunctionality.TOKEN_GET_NFT_INFO;
            case NETWORK_GET_VERSION_INFO -> HederaFunctionality.GET_VERSION_INFO;
            case TRANSACTION_GET_RECEIPT -> HederaFunctionality.TRANSACTION_GET_RECEIPT;
            case TRANSACTION_GET_RECORD -> HederaFunctionality.TRANSACTION_GET_RECORD;
            case TRANSACTION_GET_FAST_RECORD -> HederaFunctionality.TRANSACTION_GET_FAST_RECORD;
            case UNSET -> throw new UnknownHederaFunctionality();
        };
    }

    /**
     * Utility to convert a {@link SemanticVersion} into a nicely formatted String.
     * @param version The version to convert
     * @return The string representation
     */
    public static String toString(@Nullable final SemanticVersion version) {
        if (version == null) {
            return "";
        }
        var baseVersion = new StringBuilder("v");
        baseVersion
                .append(version.major())
                .append(".")
                .append(version.minor())
                .append(".")
                .append(version.patch());
        if (!version.pre().isBlank()) {
            baseVersion.append("-").append(version.pre());
        }
        if (!version.build().isBlank()) {
            baseVersion.append("+").append(version.build());
        }
        return baseVersion.toString();
    }

    /**
     * Parses an account from a string of the form shardNum.realmNum.accountNum
     * @param string The input string
     * @return The corresponding {@link AccountID}
     * @throws IllegalArgumentException if the string is not a dot-separated triplet of numbers
     */
    public static AccountID parseAccount(@NonNull final String string) {
        try {
            final var parts = string.split("\\.");
            return AccountID.newBuilder()
                    .shardNum(Long.parseLong(parts[0]))
                    .realmNum(Long.parseLong(parts[1]))
                    .accountNum(Long.parseLong(parts[2]))
                    .build();
        } catch (final NumberFormatException | ArrayIndexOutOfBoundsException e) {
            throw new IllegalArgumentException(String.format("'%s' is not a dot-separated triplet", string));
        }
    }

    /**
     * Utility to convert an {@link AccountID} into a nicely formatted String.
     * @param id The id to convert
     * @return The string representation
     */
    public static String toString(@NonNull final AccountID id) {
        var builder = new StringBuilder()
                .append(id.shardNum())
                .append(".")
                .append(id.realmNum())
                .append(".");

        if (id.hasAccountNum()) {
            builder.append(id.accountNum());
        } else if (id.hasAlias()) {
            builder.append(id.alias());
        } else {
            builder.append("-");
        }
        return builder.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy