com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of app-service-token-impl Show documentation
Show all versions of app-service-token-impl Show documentation
Default Hedera Token Service Implementation
/*
* 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 static com.hedera.node.app.service.token.impl.schemas.SyntheticAccountCreator.asAccountId;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.VisibleForTesting;
import com.hedera.hapi.node.base.AccountID;
import com.hedera.hapi.node.base.NftID;
import com.hedera.hapi.node.base.SemanticVersion;
import com.hedera.hapi.node.base.TokenID;
import com.hedera.hapi.node.state.common.EntityIDPair;
import com.hedera.hapi.node.state.common.EntityNumber;
import com.hedera.hapi.node.state.primitives.ProtoBytes;
import com.hedera.hapi.node.state.token.Account;
import com.hedera.hapi.node.state.token.NetworkStakingRewards;
import com.hedera.hapi.node.state.token.Nft;
import com.hedera.hapi.node.state.token.StakingNodeInfo;
import com.hedera.hapi.node.state.token.Token;
import com.hedera.hapi.node.state.token.TokenRelation;
import com.hedera.node.config.data.AccountsConfig;
import com.hedera.node.config.data.HederaConfig;
import com.hedera.node.config.data.LedgerConfig;
import com.hedera.node.config.data.StakingConfig;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import com.swirlds.state.lifecycle.MigrationContext;
import com.swirlds.state.lifecycle.Schema;
import com.swirlds.state.lifecycle.StateDefinition;
import com.swirlds.state.spi.WritableKVState;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Consumer;
import java.util.stream.LongStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* Initial mod-service schema for the token service.
*/
public class V0490TokenSchema extends Schema {
private static final Logger log = LogManager.getLogger(V0490TokenSchema.class);
// These need to be big so databases are created at right scale. If they are too small then the on disk hash map
// buckets will be too full which results in very poor performance. Have chosen 10 billion as should give us
// plenty of runway.
private static final long MAX_STAKING_INFOS = 1_000_000L;
private static final long MAX_TOKENS = 1_000_000_000L;
private static final long MAX_ACCOUNTS = 1_000_000_000L;
private static final long MAX_TOKEN_RELS = 1_000_000_000L;
private static final long MAX_MINTABLE_NFTS = 1_000_000_000L;
private static final long FIRST_RESERVED_SYSTEM_CONTRACT = 350L;
private static final long LAST_RESERVED_SYSTEM_CONTRACT = 399L;
private static final long FIRST_POST_SYSTEM_FILE_ENTITY = 200L;
private static final SemanticVersion VERSION =
SemanticVersion.newBuilder().major(0).minor(49).patch(0).build();
public static final String NFTS_KEY = "NFTS";
public static final String TOKENS_KEY = "TOKENS";
public static final String ALIASES_KEY = "ALIASES";
public static final String ACCOUNTS_KEY = "ACCOUNTS";
public static final String TOKEN_RELS_KEY = "TOKEN_RELS";
public static final String STAKING_INFO_KEY = "STAKING_INFOS";
public static final String STAKING_NETWORK_REWARDS_KEY = "STAKING_NETWORK_REWARDS";
private final SyntheticAccountCreator syntheticAccountCreator;
/**
* Constructor for this schema. Each of the supplier params should produce a {@link SortedSet} of
* {@link Account} objects, where each account object represents a _synthetic record_ (see {@link
* SyntheticAccountCreator} for more details). Even though these sorted sets contain account
* objects, these account objects may or may not yet exist in state. They're usually not needed,
* but are required for an event recovery situation.
*
*/
public V0490TokenSchema(@NonNull final SyntheticAccountCreator syntheticAccountCreator) {
super(VERSION);
this.syntheticAccountCreator = requireNonNull(syntheticAccountCreator);
}
@NonNull
@Override
public Set statesToCreate() {
return Set.of(
StateDefinition.onDisk(TOKENS_KEY, TokenID.PROTOBUF, Token.PROTOBUF, MAX_TOKENS),
StateDefinition.onDisk(ACCOUNTS_KEY, AccountID.PROTOBUF, Account.PROTOBUF, MAX_ACCOUNTS),
StateDefinition.onDisk(ALIASES_KEY, ProtoBytes.PROTOBUF, AccountID.PROTOBUF, MAX_ACCOUNTS),
StateDefinition.onDisk(NFTS_KEY, NftID.PROTOBUF, Nft.PROTOBUF, MAX_MINTABLE_NFTS),
StateDefinition.onDisk(TOKEN_RELS_KEY, EntityIDPair.PROTOBUF, TokenRelation.PROTOBUF, MAX_TOKEN_RELS),
StateDefinition.onDisk(
STAKING_INFO_KEY, EntityNumber.PROTOBUF, StakingNodeInfo.PROTOBUF, MAX_STAKING_INFOS),
StateDefinition.singleton(STAKING_NETWORK_REWARDS_KEY, NetworkStakingRewards.PROTOBUF));
}
@Override
public void migrate(@NonNull final MigrationContext ctx) {
final var isGenesis = ctx.previousVersion() == null;
if (isGenesis) {
createGenesisSchema(ctx);
}
}
private void createGenesisSchema(@NonNull final MigrationContext ctx) {
// Create the network rewards state
initializeNetworkRewards(ctx);
initializeStakingNodeInfo(ctx);
// Get the map for storing all the created accounts
final var accounts = ctx.newStates().get(ACCOUNTS_KEY);
if (accounts.size() != 0) {
throw new IllegalStateException("Accounts map should be empty at genesis");
}
// We will use these various configs for creating accounts. It would be nice to consolidate them somehow
final var ledgerConfig = ctx.configuration().getConfigData(LedgerConfig.class);
final var hederaConfig = ctx.configuration().getConfigData(HederaConfig.class);
final var accountsConfig = ctx.configuration().getConfigData(AccountsConfig.class);
// Generate synthetic accounts based on the genesis configuration
final Consumer> noOpCb = ignore -> {};
syntheticAccountCreator.generateSyntheticAccounts(ctx.configuration(), noOpCb, noOpCb, noOpCb, noOpCb, noOpCb);
// ---------- Create system accounts -------------------------
for (final Account acct : syntheticAccountCreator.systemAccounts()) {
accounts.put(acct.accountIdOrThrow(), acct);
}
log.info(
"Created {} system accounts",
syntheticAccountCreator.systemAccounts().size());
// ---------- Create staking fund accounts -------------------------
for (final Account acct : syntheticAccountCreator.stakingAccounts()) {
accounts.put(acct.accountIdOrThrow(), acct);
}
log.info(
"Created {} staking accounts",
syntheticAccountCreator.stakingAccounts().size());
// ---------- Create treasury clones -------------------------
for (final Account acct : syntheticAccountCreator.treasuryClones()) {
accounts.put(acct.accountIdOrThrow(), acct);
}
log.info(
"Created {} treasury clones",
syntheticAccountCreator.treasuryClones().size());
// ---------- Create miscellaneous accounts -------------------------
for (final Account acct : syntheticAccountCreator.multiUseAccounts()) {
accounts.put(acct.accountIdOrThrow(), acct);
}
log.info(
"Created {} miscellaneous accounts",
syntheticAccountCreator.multiUseAccounts().size());
// ---------- Create blocklist accounts -------------------------
if (accountsConfig.blocklistEnabled()) {
final var existingAliases = ctx.newStates().get(ALIASES_KEY);
if (existingAliases.size() != 0) {
throw new IllegalStateException("Aliases map should be empty at genesis");
}
for (final Account acct : syntheticAccountCreator.blocklistAccounts()) {
final var id = asAccountId(ctx.newEntityNum(), hederaConfig);
if (!Objects.equals(
id.accountNumOrThrow(), acct.accountIdOrThrow().accountNumOrThrow())) {
throw new IllegalStateException(
"Next entity num " + id + " did not match synthetic block list account " + acct);
}
// Put the account and its alias in state
accounts.put(id, acct);
existingAliases.put(acct.alias(), id);
}
}
log.info(
"Created {} blocklist accounts",
syntheticAccountCreator.blocklistAccounts().size());
// ---------- Balances Safety Check -------------------------
// Add up the balances of all accounts, they must match 50,000,000,000 HBARs (config)
final var totalBalance = getTotalBalanceOfAllAccounts(accounts, hederaConfig);
if (totalBalance != ledgerConfig.totalTinyBarFloat()) {
throw new IllegalStateException("Total balance of all accounts does not match the total float: actual: "
+ totalBalance + " vs expected: " + ledgerConfig.totalTinyBarFloat());
}
log.info(
"Ledger float is {} tinyBars; {} modified accounts.",
totalBalance,
accounts.modifiedKeys().size());
}
/**
* Get the total balance of all accounts. Since we cannot iterate over the accounts in VirtualMap,
* we have to do this manually.
*
* @param accounts The accounts map
* @param hederaConfig The Hedera configuration
* @return The total balance of all accounts
*/
public long getTotalBalanceOfAllAccounts(
@NonNull final WritableKVState accounts, @NonNull final HederaConfig hederaConfig) {
long totalBalance = 0;
long curAccountId = 1; // Start with the first account ID
long totalAccounts = accounts.size();
do {
final Account account = accounts.get(asAccountId(curAccountId, hederaConfig));
if (account != null) {
totalBalance += account.tinybarBalance();
totalAccounts--;
}
curAccountId++;
} while (totalAccounts > 0);
return totalBalance;
}
/**
* Get the entity numbers of all system entities that are not contracts.
*
* @param numReservedSystemEntities The number of reserved system entities
* @return The entity numbers of all system entities that are not contracts
*/
@VisibleForTesting
public static long[] nonContractSystemNums(final long numReservedSystemEntities) {
return LongStream.rangeClosed(FIRST_POST_SYSTEM_FILE_ENTITY, numReservedSystemEntities)
.filter(i -> i < FIRST_RESERVED_SYSTEM_CONTRACT || i > LAST_RESERVED_SYSTEM_CONTRACT)
.toArray();
}
private void initializeStakingNodeInfo(@NonNull final MigrationContext ctx) {
final var config = ctx.configuration();
final var ledgerConfig = config.getConfigData(LedgerConfig.class);
final var stakingConfig = config.getConfigData(StakingConfig.class);
final var addressBook = ctx.genesisNetworkInfo().addressBook();
final var numberOfNodes = addressBook.size();
final long maxStakePerNode = ledgerConfig.totalTinyBarFloat() / numberOfNodes;
final long minStakePerNode = 0;
final var numRewardHistoryStoredPeriods = stakingConfig.rewardHistoryNumStoredPeriods();
final var stakingInfoState = ctx.newStates().get(STAKING_INFO_KEY);
final var rewardSumHistory = new Long[numRewardHistoryStoredPeriods + 1];
Arrays.fill(rewardSumHistory, 0L);
for (final var node : addressBook) {
final var nodeNumber = node.nodeId();
final var stakingInfo = StakingNodeInfo.newBuilder()
.nodeNumber(nodeNumber)
.maxStake(maxStakePerNode)
.minStake(minStakePerNode)
.rewardSumHistory(Arrays.asList(rewardSumHistory))
.weight(500)
.build();
stakingInfoState.put(EntityNumber.newBuilder().number(nodeNumber).build(), stakingInfo);
}
}
private void initializeNetworkRewards(@NonNull final MigrationContext ctx) {
// Set genesis network rewards state
final var networkRewardsState = ctx.newStates().getSingleton(STAKING_NETWORK_REWARDS_KEY);
final var networkRewards = NetworkStakingRewards.newBuilder()
.pendingRewards(0)
.totalStakedRewardStart(0)
.totalStakedStart(0)
.stakingRewardsActivated(true)
.build();
networkRewardsState.put(networkRewards);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy