Skip to content

Commit

Permalink
Remaining system file updates (#9354)
Browse files Browse the repository at this point in the history
Signed-off-by: Michael Heinrichs <netopyr@users.noreply.github.com>
Signed-off-by: Nick Poorman <nick@swirldslabs.com>
  • Loading branch information
netopyr authored and nickpoorman committed Nov 22, 2023
1 parent 93e1c86 commit b3e6203
Show file tree
Hide file tree
Showing 15 changed files with 378 additions and 212 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.hedera.node.app.config.BootstrapConfigProviderImpl;
import com.hedera.node.app.config.ConfigProviderImpl;
import com.hedera.node.app.fees.ExchangeRateManager;
import com.hedera.node.app.fees.FeeManager;
import com.hedera.node.app.fees.FeeService;
import com.hedera.node.app.fees.congestion.CongestionMultipliers;
import com.hedera.node.app.fees.congestion.EntityUtilizationMultiplier;
Expand Down Expand Up @@ -163,6 +164,10 @@ public final class Hedera implements SwirldMain {
* The exchange rate manager
*/
private ExchangeRateManager exchangeRateManager;
/**
* The fee manager
*/
private FeeManager feeManager;
/** The class responsible for remembering objects created in genesis cases */
private final GenesisRecordsBuilder genesisRecordsBuilder;
/**
Expand Down Expand Up @@ -698,6 +703,9 @@ private void genesis(@NonNull final MerkleHederaState state) {
logger.info("Initializing ExchangeRateManager");
exchangeRateManager = new ExchangeRateManager(configProvider);

logger.info("Initializing FeeManager");
feeManager = new FeeManager(exchangeRateManager);

// Create all the nodes in the merkle tree for all the services
onMigrate(state, null);

Expand All @@ -707,8 +715,8 @@ private void genesis(@NonNull final MerkleHederaState state) {
// And now that the entire dependency graph has been initialized, and we have config, and all migration has
// been completed, we are prepared to initialize in-memory data structures. These specifically are loaded
// from information held in state (especially those in special files).
initializeFeeManager(state);
initializeExchangeRateManager(state);
initializeFeeManager(state);
initializeThrottles(state);
}

Expand Down Expand Up @@ -782,6 +790,9 @@ private void restart(
logger.info("Initializing ExchangeRateManager");
exchangeRateManager = new ExchangeRateManager(configProvider);

logger.info("Initializing FeeManager");
feeManager = new FeeManager(exchangeRateManager);

// Create all the nodes in the merkle tree for all the services
// TODO: Actually, we should reinitialize the config on each step along the migration path, so we should pass
// the config provider to the migration code and let it get the right version of config as it goes.
Expand All @@ -793,8 +804,8 @@ private void restart(
// And now that the entire dependency graph has been initialized, and we have config, and all migration has
// been completed, we are prepared to initialize in-memory data structures. These specifically are loaded
// from information held in state (especially those in special files).
initializeFeeManager(state);
initializeExchangeRateManager(state);
initializeFeeManager(state);
initializeThrottles(state);
// TODO We may need to update the config with the latest version in file 121
}
Expand Down Expand Up @@ -826,10 +837,12 @@ private void initializeDagger(@NonNull final MerkleHederaState state, @NonNull f
.configuration(configProvider)
.throttleManager(throttleManager)
.exchangeRateManager(exchangeRateManager)
.feeManager(feeManager)
.systemFileUpdateFacility(new SystemFileUpdateFacility(
configProvider,
throttleManager,
exchangeRateManager,
feeManager,
congestionMultipliers,
backendThrottle,
frontendThrottle))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ interface Builder {
@BindsInstance
Builder exchangeRateManager(ExchangeRateManager exchangeRateManager);

@BindsInstance
Builder feeManager(FeeManager feeManager);

@BindsInstance
Builder maxSignedTxnSize(@MaxSignedTxnSize final int maxSignedTxnSize);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,19 @@ public VersionedConfiguration getConfiguration() {
}

/**
* This method must be called if a property has changed. It will update the configuration and increase the version.
* This method must be called when the network properties or permissions are overridden.
* It will update the configuration and increase the version.
*
* @param propertyFileContent the new property file content
* @param networkProperties the network properties file content
* @param permissions the permissions file content
*/
public void update(@NonNull final Bytes propertyFileContent) {
logger.info("Updating configuration based on new property file content.");
public void update(@NonNull final Bytes networkProperties, @NonNull final Bytes permissions) {
logger.info("Updating configuration caused by properties or permissions override.");
try (final var ignoredLock = updateLock.lock()) {
final var builder = createConfigurationBuilder();
addFileSources(builder, false);
addByteSource(builder, propertyFileContent);
addByteSource(builder, networkProperties);
addByteSource(builder, permissions);
final Configuration config = builder.build();
configuration.set(
new VersionedConfigImpl(config, this.configuration.get().getVersion() + 1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,25 @@

package com.hedera.node.app.fees;

import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS;
import static java.util.Objects.requireNonNull;

import com.hedera.hapi.node.base.CurrentAndNextFeeSchedule;
import com.hedera.hapi.node.base.FeeComponents;
import com.hedera.hapi.node.base.FeeData;
import com.hedera.hapi.node.base.FeeSchedule;
import com.hedera.hapi.node.base.HederaFunctionality;
import com.hedera.hapi.node.base.Key;
import com.hedera.hapi.node.base.ResponseCodeEnum;
import com.hedera.hapi.node.base.SubType;
import com.hedera.hapi.node.base.TransactionFeeSchedule;
import com.hedera.hapi.node.transaction.TransactionBody;
import com.hedera.node.app.spi.fees.FeeCalculator;
import com.hedera.pbj.runtime.io.buffer.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -52,6 +57,16 @@ public final class FeeManager {

private record Entry(HederaFunctionality function, SubType subType) {}

private static final long DEFAULT_FEE = 100_000L;

private static final FeeComponents DEFAULT_FEE_COMPONENTS =
FeeComponents.newBuilder().min(DEFAULT_FEE).max(DEFAULT_FEE).build();
private static final FeeData DEFAULT_FEE_DATA = FeeData.newBuilder()
.networkdata(DEFAULT_FEE_COMPONENTS)
.nodedata(DEFAULT_FEE_COMPONENTS)
.servicedata(DEFAULT_FEE_COMPONENTS)
.build();

/** The current fee schedule, cached for speed. */
private Map<Entry, FeeData> currentFeeDataMap = Collections.emptyMap();
/** The next fee schedule, cached for speed. */
Expand All @@ -71,64 +86,67 @@ public FeeManager(@NonNull final ExchangeRateManager exchangeRateManager) {
*
* @param bytes The new fee schedule file content.
*/
public void update(@NonNull final Bytes bytes) {
public ResponseCodeEnum update(@NonNull final Bytes bytes) {
// Parse the current and next fee schedules
final CurrentAndNextFeeSchedule schedules;
try {
// Parse the current and next fee schedules
final var schedules = CurrentAndNextFeeSchedule.PROTOBUF.parse(bytes.toReadableSequentialData());

// Get the current schedule
var currentSchedule = schedules.currentFeeSchedule();
if (currentSchedule == null) {
// If there is no current schedule, then we default to the default schedule. Since the default
// schedule is completely empty, this will effectively disable the handling of any transactions,
// since we don't know what to charge for them.
logger.warn("Unable to parse current fee schedule, will default to an empty schedule, effectively"
+ "disabling all transactions.");
currentSchedule = FeeSchedule.DEFAULT;
}
schedules = CurrentAndNextFeeSchedule.PROTOBUF.parse(bytes.toReadableSequentialData());
} catch (final BufferUnderflowException | IOException ex) {
return ResponseCodeEnum.FEE_SCHEDULE_FILE_PART_UPLOADED;
}

// Populate the map of HederaFunctionality -> FeeData for the current schedule
this.currentFeeDataMap = new HashMap<>();
if (currentSchedule.hasTransactionFeeSchedule()) {
populateFeeDataMap(currentFeeDataMap, currentSchedule.transactionFeeScheduleOrThrow());
} else {
logger.warn("The current fee schedule is missing transaction information, effectively disabling all"
+ "transactions.");
}
// Get the current schedule
var currentSchedule = schedules.currentFeeSchedule();
if (currentSchedule == null) {
// If there is no current schedule, then we default to the default schedule. Since the default
// schedule is completely empty, this will effectively disable the handling of any transactions,
// since we don't know what to charge for them.
logger.warn("Unable to parse current fee schedule, will default to an empty schedule, effectively"
+ "disabling all transactions.");
currentSchedule = FeeSchedule.DEFAULT;
}

// Get the expiration time of the current schedule
if (currentSchedule.hasExpiryTime()) {
this.currentScheduleExpirationSeconds =
currentSchedule.expiryTimeOrThrow().seconds();
} else {
// If we don't have an expiration time, then we default to 0, which will effectively expire the
// current schedule immediately. This is the safest option.
logger.warn("The current fee schedule has no expiry time, defaulting to 0, effectively expiring it"
+ "immediately");
this.currentScheduleExpirationSeconds = 0;
}
// Populate the map of HederaFunctionality -> FeeData for the current schedule
this.currentFeeDataMap = new HashMap<>();
if (currentSchedule.hasTransactionFeeSchedule()) {
populateFeeDataMap(currentFeeDataMap, currentSchedule.transactionFeeScheduleOrThrow());
} else {
logger.warn("The current fee schedule is missing transaction information, effectively disabling all"
+ "transactions.");
}

// Get the next schedule
var nextSchedule = schedules.nextFeeSchedule();
if (nextSchedule == null) {
// If there is no next schedule, then we default to the current schedule. If we didn't have a current
// schedule either, then basically we have an empty schedule with an expiration time of 0, which will
// still get used since we continue to use the next schedule even if the expiration time has passed.
logger.warn("Unable to parse next fee schedule, will default to the current fee schedule.");
nextFeeDataMap = new HashMap<>(currentFeeDataMap);
// Get the expiration time of the current schedule
if (currentSchedule.hasExpiryTime()) {
this.currentScheduleExpirationSeconds =
currentSchedule.expiryTimeOrThrow().seconds();
} else {
// If we don't have an expiration time, then we default to 0, which will effectively expire the
// current schedule immediately. This is the safest option.
logger.warn("The current fee schedule has no expiry time, defaulting to 0, effectively expiring it"
+ "immediately");
this.currentScheduleExpirationSeconds = 0;
}

// Get the next schedule
var nextSchedule = schedules.nextFeeSchedule();
if (nextSchedule == null) {
// If there is no next schedule, then we default to the current schedule. If we didn't have a current
// schedule either, then basically we have an empty schedule with an expiration time of 0, which will
// still get used since we continue to use the next schedule even if the expiration time has passed.
logger.warn("Unable to parse next fee schedule, will default to the current fee schedule.");
nextFeeDataMap = new HashMap<>(currentFeeDataMap);
} else {
// Populate the map of HederaFunctionality -> FeeData for the current schedule
this.nextFeeDataMap = new HashMap<>();
if (nextSchedule.hasTransactionFeeSchedule()) {
populateFeeDataMap(nextFeeDataMap, nextSchedule.transactionFeeScheduleOrThrow());
} else {
// Populate the map of HederaFunctionality -> FeeData for the current schedule
this.nextFeeDataMap = new HashMap<>();
if (nextSchedule.hasTransactionFeeSchedule()) {
populateFeeDataMap(nextFeeDataMap, nextSchedule.transactionFeeScheduleOrThrow());
} else {
logger.warn("The next fee schedule is missing transaction information, effectively disabling all"
+ "transactions once it becomes active.");
}
logger.warn("The next fee schedule is missing transaction information, effectively disabling all"
+ "transactions once it becomes active.");
}
} catch (final Exception e) {
logger.warn("Unable to parse fee schedule file", e);
}

return SUCCESS;
}

/**
Expand Down Expand Up @@ -181,14 +199,19 @@ public FeeCalculator createFeeCalculator(
/**
* Looks up the fee data for the given transaction and its details.
*/
@Nullable
@NonNull
public FeeData getFeeData(
@NonNull HederaFunctionality functionality, @NonNull Instant consensusTime, @NonNull SubType subType) {
final var feeDataMap =
consensusTime.getEpochSecond() > currentScheduleExpirationSeconds ? nextFeeDataMap : currentFeeDataMap;

// Now, lookup the fee data for the transaction type.
return feeDataMap.get(new Entry(functionality, subType));
final var result = feeDataMap.get(new Entry(functionality, subType));
if (result == null) {
logger.warn("Using default usage prices to calculate fees for {}!", functionality);
return DEFAULT_FEE_DATA;
}
return result;
}

/**
Expand Down

0 comments on commit b3e6203

Please sign in to comment.