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

com.hedera.node.app.service.token.impl.ReadableAccountStoreImpl Maven / Gradle / Ivy

There is a newer version: 0.56.6
Show newest version
/*
 * Copyright (C) 2022-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.hapi.node.base.AccountID.AccountOneOfType.ACCOUNT_NUM;
import static com.hedera.node.app.service.token.AliasUtils.asKeyFromAliasOrElse;
import static com.hedera.node.app.service.token.AliasUtils.extractEvmAddress;
import static com.hedera.node.app.service.token.AliasUtils.extractIdFromAddressAlias;
import static com.hedera.node.app.service.token.AliasUtils.isEntityNumAlias;
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.state.primitives.ProtoBytes;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.node.app.service.token.ReadableAccountStore;
import com.hedera.pbj.runtime.io.buffer.Bytes;
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;
import java.util.Optional;

/**
 * Default implementation of {@link ReadableAccountStore}
 */
public class ReadableAccountStoreImpl implements ReadableAccountStore {
    /** The underlying data storage class that holds the account data. */
    private final ReadableKVState accountState;
    /**
     * The underlying data storage class that holds the aliases data built from the state. An alias can only be defined
     * at the time an account (or contract account) is created, and cannot be changed (except when the contract or
     * account is deleted, we could remove it then).
     *
     * 

An alias may either be: *

    *
  • A "long-zero" address (sometimes called a "mirror" address). This form takes the [shard].[realm].[num] * and converts it into a single 20-byte long address. It is called "long-zero" because in the default network * where shard and realm are both 0, it looks like a lot of zeros followed by a few bytes of info. Long zero * aliases are not "real", they are not stored in this map, and they are not stored on accounts. They are * computed directly from the account ID.
  • *
  • An EVM address. This is always 20 bytes long. It could be used by accounts or contracts. It can be * specified as part of hollow-account creation or as part of a normal crypto-create transaction.
  • *
  • A protobuf-encoded key. The key can either be an ECDSA SECP256K1 key, or an ED25519 key (or in the future * we may support other types of keys). If, and only if, it is an ECDSA SECP256K1 key, we can extract an EVM * address from the key. Users can refer to their account using either the protobuf-encoded key alias, or * the corresponding EVM address in this one case.
  • *
* *

This alias map contains no mappings from long-zero to account ID. Since we can compute the account ID from a * long-zero address, we don't need to store it in this map. If we did store it in this map, we would have an entry * for every account, NFT, and other entity, which seems utterly wasteful. * *

This alias map will contain a mapping from EVM address to corresponding Account ID, whether the EVM address * was the alias on the account, or was derived from an ECDSA SECP256K1 protobuf encoded key alias on the account. * *

This alias map will also contain the raw protobuf-encoded key alias, regardless of what type of * protobuf-encoded key was used (ED25519 or ECDSA SECP256K1). */ private final ReadableKVState aliases; /** * Create a new {@link ReadableAccountStoreImpl} instance. * * @param states The state to use. */ public ReadableAccountStoreImpl(@NonNull final ReadableStates states) { this.accountState = states.get("ACCOUNTS"); this.aliases = states.get("ALIASES"); } /** Get the account state. Convenience method for auto-casting to the right kind of state (readable vs. writable) */ protected > T accountState() { return (T) accountState; } /** Get the alias state. Convenience method for auto-casting to the right kind of state (readable vs. writable) */ protected > T aliases() { return (T) aliases; } /** * Returns the {@link Account} for a given {@link AccountID}. If the account has an alias * set on it, it doesn't look in the alias map to find the account ID and returns null * * @param accountID the {@code AccountID} which {@code Account is requested} * @return an {@link Optional} with the {@code Account}, if it was found, an empty {@code * Optional} otherwise */ @Override @Nullable public Account getAccountById(@NonNull final AccountID accountID) { return getAccountLeaf(accountID); } @Override @Nullable public AccountID getAccountIDByAlias(@NonNull final Bytes alias) { return aliases.get(new ProtoBytes(alias)); } @Override public boolean containsAlias(@NonNull Bytes alias) { return aliases.contains(new ProtoBytes(alias)); } @Override public boolean contains(@NonNull final AccountID accountID) { return accountState().contains(accountID); } /* Helper methods */ /** * Returns the account leaf for the given account id. If the account doesn't exist, returns * {@link Optional}. * * @param id given account number * @return merkle leaf for the given account number */ @Nullable protected Account getAccountLeaf(@NonNull final AccountID id) { // The Account ID may be aliased, in which case we need to convert it to a number-based account ID first. requireNonNull(id); final var accountOneOf = id.account(); return accountOneOf.kind() == ACCOUNT_NUM ? accountState.get(id) : null; } /** * Returns the {@link Account} for a given {@link AccountID}. If the account has an alias * set on it, looks in the alias map to find the account ID. This method should only be used in * {@code CryptoTransfer} since aliases are allowed only for auto-created accounts. * * @param accountID the {@code AccountID} which {@code Account is requested} * @return an {@link Optional} with the {@code Account}, if it was found, an empty {@code * Optional} otherwise */ @Override @Nullable public Account getAliasedAccountById(@NonNull final AccountID accountID) { return getAliasedAccountLeaf(accountID); } /** * Returns the account leaf for the given account id. If the account doesn't exist, returns * {@link Optional}. * * @param id given account number * @return merkle leaf for the given account number */ @Nullable protected Account getAliasedAccountLeaf(@NonNull final AccountID id) { // The Account ID may be aliased, in which case we need to convert it to a number-based account ID first. requireNonNull(id); final var accountId = lookupAliasedAccountId(id); return accountId == null ? null : accountState.get(accountId); } @Override public long getNumberOfAccounts() { return accountState.size(); } /** * Given some {@link AccountID}, if it is an alias, then convert it to a number-based account ID. If it is not an * alias, then just return it. If the given id is bogus, containing neither an account number nor an alias, or * containing an alias that we simply don't know about, then return null. * * @param id The account ID that possibly has an alias to convert to an Account ID without an alias. * @return The result, or null if the id is invalid or there is no known alias-to-account mapping for it. */ @Nullable protected AccountID lookupAliasedAccountId(@NonNull final AccountID id) { final var accountOneOf = id.account(); return switch (accountOneOf.kind()) { case ACCOUNT_NUM -> id; case ALIAS -> { // An alias may either be long-zero (in which case it isn't in our alias map), or it may be // any other form of valid alias (in which case it will be in the map). So we do a quick check // first to see if it is a valid long zero, and if not, then we look it up in the map. final Bytes alias = accountOneOf.as(); if (isEntityNumAlias(alias)) { yield id.copyBuilder() .accountNum(extractIdFromAddressAlias(alias)) .build(); } // Since it wasn't long-zero, we will just look up in the aliases map. It may be an EVM address alias, // in which case it is in the map, or it may be a protobuf-encoded key alias, in which case it *may* // also be in the map. When someone gives us a protobuf-encoded ECDSA key, we store both the alias to // the ECDSA key *and* the EVM address in the alias map. But if somebody only gives us the EVM address, // we cannot compute the ECDSA key from it, so we only store the EVM address in the alias map. So if we // do this look up and cannot find the answer, then we have to check if the key is an ECDSA key, and // if it is, we have to compute the EVM address from it, and then look up the EVM address in the map. final var found = aliases.get(new ProtoBytes(alias)); if (found != null) yield found; yield aliases.get(new ProtoBytes(extractEvmAddress(asKeyFromAliasOrElse(alias, null)))); } case UNSET -> null; }; } public long sizeOfAccountState() { return accountState().size(); } @Override public void warm(@NonNull final AccountID accountID) { final var unaliasedId = lookupAliasedAccountId(accountID); if (unaliasedId != null) { accountState.warm(unaliasedId); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy