diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java index 555ebdf17d6a..61c2d4aa0458 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java @@ -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; @@ -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; /** @@ -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); @@ -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); } @@ -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. @@ -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 } @@ -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)) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java index f1d867432a9b..14847f5ce085 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java @@ -152,6 +152,9 @@ interface Builder { @BindsInstance Builder exchangeRateManager(ExchangeRateManager exchangeRateManager); + @BindsInstance + Builder feeManager(FeeManager feeManager); + @BindsInstance Builder maxSignedTxnSize(@MaxSignedTxnSize final int maxSignedTxnSize); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java index 44c2de8b2764..7cb0264bc396 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/config/ConfigProviderImpl.java @@ -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)); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java index 2104cce0f609..ce2e6df82ca2 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/FeeManager.java @@ -16,13 +16,16 @@ 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; @@ -30,6 +33,8 @@ 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; @@ -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 currentFeeDataMap = Collections.emptyMap(); /** The next fee schedule, cached for speed. */ @@ -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; } /** @@ -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; } /** diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleManager.java index e5f69fa9b792..0dd9385ec564 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleManager.java @@ -16,6 +16,10 @@ package com.hedera.node.app.throttle; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION; +import static com.hedera.hapi.node.base.ResponseCodeEnum.UNPARSEABLE_THROTTLE_DEFINITIONS; + import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.transaction.ThrottleBucket; @@ -24,6 +28,7 @@ import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; @@ -45,10 +50,9 @@ public class ThrottleManager { private static final ThrottleDefinitions DEFAULT_THROTTLE_DEFINITIONS = ThrottleDefinitions.DEFAULT; public static final Set expectedOps = ExpectedCustomThrottles.ACTIVE_OPS.stream() .map(protoOp -> HederaFunctionality.fromProtobufOrdinal(protoOp.getNumber())) - .collect(Collectors.toSet()); + .collect(Collectors.toCollection(() -> EnumSet.noneOf(HederaFunctionality.class))); private ThrottleDefinitions throttleDefinitions; - private com.hederahashgraph.api.proto.java.ThrottleDefinitions throttleDefinitionsProto; private List throttleBuckets; public ThrottleManager() { @@ -62,37 +66,25 @@ public ThrottleManager() { * * @param bytes The protobuf encoded {@link ThrottleDefinitions}. */ - public void update(@NonNull final Bytes bytes) { + public ResponseCodeEnum update(@NonNull final Bytes bytes) { // Parse the throttle file. If we cannot parse it, we just continue with whatever our previous rate was. + final ThrottleDefinitions tempThrottleDefinitions; try { - final var tempThrottleDefinitions = ThrottleDefinitions.PROTOBUF.parse(bytes.toReadableSequentialData()); - validate(tempThrottleDefinitions); - - throttleDefinitions = tempThrottleDefinitions; - throttleDefinitionsProto = - com.hederahashgraph.api.proto.java.ThrottleDefinitions.parseFrom(bytes.toByteArray()); - } catch (HandleException e) { - throw e; - } catch (final Exception e) { - // Not being able to parse the throttle file is not fatal, and may happen if the throttle file - // was too big for a single file update for example. - logger.warn("Unable to parse the throttle file", e); + tempThrottleDefinitions = ThrottleDefinitions.PROTOBUF.parse(bytes.toReadableSequentialData()); + } catch (IOException e) { + throw new HandleException(UNPARSEABLE_THROTTLE_DEFINITIONS); } + validate(tempThrottleDefinitions); + throttleDefinitions = tempThrottleDefinitions; + throttleBuckets = throttleDefinitions.throttleBuckets(); - final var rawThrottleBuckets = throttleDefinitions.throttleBuckets(); - if (rawThrottleBuckets != null) { - throttleBuckets = rawThrottleBuckets; - } else { - logger.warn("Throttle definition file did not contain throttle buckets!"); - throttleBuckets = DEFAULT_THROTTLE_DEFINITIONS.throttleBuckets(); - } + return allExpectedOperations(throttleDefinitions) ? SUCCESS : SUCCESS_BUT_MISSING_EXPECTED_OPERATION; } /** * Checks if the throttle definitions are valid. */ private void validate(ThrottleDefinitions throttleDefinitions) { - checkForMissingExpectedOperations(throttleDefinitions); checkForZeroOpsPerSec(throttleDefinitions); checkForRepeatedOperations(throttleDefinitions); } @@ -100,16 +92,14 @@ private void validate(ThrottleDefinitions throttleDefinitions) { /** * Checks if there are missing {@link HederaFunctionality} operations from the expected ones that should be throttled. */ - private void checkForMissingExpectedOperations(ThrottleDefinitions throttleDefinitions) { - Set customizedOps = new HashSet<>(); - for (var bucket : throttleDefinitions.throttleBuckets()) { - for (var group : bucket.throttleGroups()) { + private boolean allExpectedOperations(ThrottleDefinitions throttleDefinitions) { + final Set customizedOps = EnumSet.noneOf(HederaFunctionality.class); + for (final var bucket : throttleDefinitions.throttleBuckets()) { + for (final var group : bucket.throttleGroups()) { customizedOps.addAll(group.operations()); } } - if (customizedOps.isEmpty() || !expectedOps.equals(EnumSet.copyOf(customizedOps))) { - throw new HandleException(ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION); - } + return customizedOps.containsAll(expectedOps); } /** @@ -164,8 +154,4 @@ public List throttleBuckets() { public ThrottleDefinitions throttleDefinitions() { return throttleDefinitions; } - - public com.hederahashgraph.api.proto.java.ThrottleDefinitions throttleDefinitionsProto() { - return throttleDefinitionsProto; - } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImpl.java index 5bc1d78325d0..65ce6ecf276d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/impl/NetworkUtilizationManagerImpl.java @@ -120,7 +120,7 @@ public void resetFrom(@NonNull final HederaState state) { try { final var gasThrottleUsageSnapshot = fromPbj(throttleSnapshots.gasThrottle()); activeGasThrottle.resetUsageTo(gasThrottleUsageSnapshot); - log.info("Reset {} with saved gas throttle usage snapshot", gasThrottleUsageSnapshot); + log.debug("Reset {} with saved gas throttle usage snapshot", gasThrottleUsageSnapshot); } catch (final IllegalArgumentException e) { log.warn(String.format( "Saved gas throttle usage snapshot was not compatible with the" diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 4d27a4968ad4..71d2fb51f9ae 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -468,8 +468,10 @@ private void handleUserTransaction( } } - // Notify responsible facility if system-file was uploaded - systemFileUpdateFacility.handleTxBody(stack, txBody); + // Notify responsible facility if system-file was uploaded. + // Returns SUCCESS if no system-file was uploaded + final var fileUpdateResult = systemFileUpdateFacility.handleTxBody(stack, txBody); + recordBuilder.status(fileUpdateResult); // Notify if dual state was updated dualStateUpdateFacility.handleTxBody(stack, dualState, txBody); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java index c55908bf1daf..9a2f509e273a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacility.java @@ -16,20 +16,24 @@ package com.hedera.node.app.workflows.handle; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.FileID; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.transaction.TransactionBody; 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.congestion.CongestionMultipliers; -import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.state.HederaState; import com.hedera.node.app.throttle.ThrottleAccumulator; import com.hedera.node.app.throttle.ThrottleManager; import com.hedera.node.app.util.FileUtilities; import com.hedera.node.config.data.FilesConfig; +import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; +import com.swirlds.config.api.Configuration; import edu.umd.cs.findbugs.annotations.NonNull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -47,6 +51,7 @@ public class SystemFileUpdateFacility { private final ConfigProviderImpl configProvider; private final ThrottleManager throttleManager; private final ExchangeRateManager exchangeRateManager; + private final FeeManager feeManager; private final CongestionMultipliers congestionMultipliers; private final ThrottleAccumulator backendThrottle; private final ThrottleAccumulator frontendThrottle; @@ -60,12 +65,14 @@ public SystemFileUpdateFacility( @NonNull final ConfigProviderImpl configProvider, @NonNull final ThrottleManager throttleManager, @NonNull final ExchangeRateManager exchangeRateManager, + @NonNull final FeeManager feeManager, @NonNull final CongestionMultipliers congestionMultipliers, @NonNull final ThrottleAccumulator backendThrottle, @NonNull final ThrottleAccumulator frontendThrottle) { this.configProvider = requireNonNull(configProvider, "configProvider must not be null"); this.throttleManager = requireNonNull(throttleManager, " throttleManager must not be null"); this.exchangeRateManager = requireNonNull(exchangeRateManager, "exchangeRateManager must not be null"); + this.feeManager = requireNonNull(feeManager, "feeManager must not be null"); this.congestionMultipliers = requireNonNull(congestionMultipliers, "congestionMultipliers must not be null"); this.backendThrottle = requireNonNull(backendThrottle, "backendThrottle must not be null"); this.frontendThrottle = requireNonNull(frontendThrottle, "frontendThrottle must not be null"); @@ -78,7 +85,7 @@ public SystemFileUpdateFacility( * @param state the current state (the updated file content needs to be committed to the state) * @param txBody the transaction body */ - public void handleTxBody(@NonNull final HederaState state, @NonNull final TransactionBody txBody) { + public ResponseCodeEnum handleTxBody(@NonNull final HederaState state, @NonNull final TransactionBody txBody) { requireNonNull(state, "state must not be null"); requireNonNull(txBody, "txBody must not be null"); @@ -89,7 +96,7 @@ public void handleTxBody(@NonNull final HederaState state, @NonNull final Transa } else if (txBody.hasFileAppend()) { fileID = txBody.fileAppendOrThrow().fileIDOrThrow(); } else { - return; + return SUCCESS; } // Check if the file is a special file @@ -98,49 +105,51 @@ public void handleTxBody(@NonNull final HederaState state, @NonNull final Transa final var fileNum = fileID.fileNum(); final var payer = txBody.transactionIDOrThrow().accountIDOrThrow(); if (fileNum > ledgerConfig.numReservedSystemEntities()) { - return; + return SUCCESS; } // If it is a special file, call the updater. // We load the file only, if there is an updater for it. final var config = configuration.getConfigData(FilesConfig.class); - try { - if (fileNum == config.addressBook()) { - logger.error("Update of address book not implemented"); - } else if (fileNum == config.nodeDetails()) { - logger.error("Update of node details not implemented"); - } else if (fileNum == config.feeSchedules()) { - logger.error("Update of fee schedules not implemented"); - } else if (fileNum == config.exchangeRates()) { - exchangeRateManager.update(FileUtilities.getFileContent(state, fileID), payer); - } else if (fileNum == config.networkProperties()) { - configProvider.update(FileUtilities.getFileContent(state, fileID)); - backendThrottle.applyGasConfig(); - frontendThrottle.applyGasConfig(); - // Updating the multiplier source to use the new gas throttle - // values that are coming from the network properties - congestionMultipliers.resetExpectations(); - } else if (fileNum == config.hapiPermissions()) { - logger.error("Update of HAPI permissions not implemented"); - } else if (fileNum == config.throttleDefinitions()) { - throttleManager.update(FileUtilities.getFileContent(state, fileID)); - backendThrottle.rebuildFor(throttleManager.throttleDefinitions()); - frontendThrottle.rebuildFor(throttleManager.throttleDefinitions()); + if (fileNum == config.feeSchedules()) { + return feeManager.update(FileUtilities.getFileContent(state, fileID)); + } else if (fileNum == config.exchangeRates()) { + exchangeRateManager.update(FileUtilities.getFileContent(state, fileID), payer); + } else if (fileNum == config.networkProperties()) { + final var networkProperties = FileUtilities.getFileContent(state, fileID); + final var permissions = + FileUtilities.getFileContent(state, createFileID(config.hapiPermissions(), configuration)); + configProvider.update(networkProperties, permissions); + backendThrottle.applyGasConfig(); + frontendThrottle.applyGasConfig(); - // Updating the multiplier source to use the new throttle definitions - congestionMultipliers.resetExpectations(); - } else if (fileNum == config.upgradeFileNumber()) { - logger.error("Update of file number not implemented"); - } - } catch (HandleException e) { - // handle exception suppose to propagate the exception to the caller - throw e; - } catch (final RuntimeException e) { - logger.warn( - "Exception while calling updater for file {}. " + "If the file is incomplete, this is expected.", - fileID, - e); + // Updating the multiplier source to use the new gas throttle + // values that are coming from the network properties + congestionMultipliers.resetExpectations(); + } else if (fileNum == config.hapiPermissions()) { + final var networkProperties = + FileUtilities.getFileContent(state, createFileID(config.networkProperties(), configuration)); + final var permissions = FileUtilities.getFileContent(state, fileID); + configProvider.update(networkProperties, permissions); + } else if (fileNum == config.throttleDefinitions()) { + final var result = throttleManager.update(FileUtilities.getFileContent(state, fileID)); + backendThrottle.rebuildFor(throttleManager.throttleDefinitions()); + frontendThrottle.rebuildFor(throttleManager.throttleDefinitions()); + + // Updating the multiplier source to use the new throttle definitions + congestionMultipliers.resetExpectations(); + return result; } + return SUCCESS; + } + + private FileID createFileID(final long fileNum, @NonNull final Configuration configuration) { + final var hederaConfig = configuration.getConfigData(HederaConfig.class); + return FileID.newBuilder() + .realmNum(hederaConfig.realm()) + .shardNum(hederaConfig.shard()) + .fileNum(fileNum) + .build(); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java index 3b9a15813a52..74131dead88d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java @@ -30,6 +30,7 @@ import com.hedera.node.app.HederaInjectionComponent; 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.congestion.CongestionMultipliers; import com.hedera.node.app.fees.congestion.EntityUtilizationMultiplier; import com.hedera.node.app.fees.congestion.ThrottleMultiplier; @@ -110,6 +111,7 @@ void setUp() { final var congestionMultipliers = new CongestionMultipliers(genericFeeMultiplier, gasFeeMultiplier); final var exchangeRateManager = new ExchangeRateManager(configProvider); + final var feeManager = new FeeManager(exchangeRateManager); final var throttleManager = new ThrottleManager(); app = DaggerHederaInjectionComponent.builder() @@ -122,11 +124,13 @@ void setUp() { configProvider, throttleManager, exchangeRateManager, + feeManager, congestionMultipliers, backendThrottle, frontendThrottle)) .networkUtilizationManager(new NetworkUtilizationManagerImpl(backendThrottle, congestionMultipliers)) .throttleManager(throttleManager) + .feeManager(feeManager) .self(selfNodeInfo) .maxSignedTxnSize(1024) .currentPlatformStatus(() -> PlatformStatus.ACTIVE) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/config/ConfigProviderImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/config/ConfigProviderImplTest.java index 07e5008599a7..1a94b2693e4c 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/config/ConfigProviderImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/config/ConfigProviderImplTest.java @@ -149,10 +149,9 @@ void testGenesisPropertiesFileIsOptional(final EnvironmentVariables environment) void testUpdateDoesUseApplicationProperties() { // given final var configProvider = new ConfigProviderImpl(false); - final Bytes bytes = Bytes.wrap(new byte[] {}); // when - configProvider.update(bytes); + configProvider.update(Bytes.EMPTY, Bytes.EMPTY); final VersionedConfiguration configuration = configProvider.getConfiguration(); final String value1 = configuration.getValue("foo.test"); final String value2 = configuration.getValue("bar.test"); @@ -167,10 +166,9 @@ void testUpdateDoesUseApplicationProperties() { void testUpdateDoesNotUseGenesisProperties() { // given final var configProvider = new ConfigProviderImpl(true); - final Bytes bytes = Bytes.wrap(new byte[] {}); // when - configProvider.update(bytes); + configProvider.update(Bytes.EMPTY, Bytes.EMPTY); final VersionedConfiguration configuration = configProvider.getConfiguration(); // then @@ -188,7 +186,26 @@ void testUpdateProvidesConfigProperty() { final Bytes bytes = ServicesConfigurationList.PROTOBUF.toBytes(servicesConfigurationList); // when - configProvider.update(bytes); + configProvider.update(bytes, Bytes.EMPTY); + final VersionedConfiguration configuration = configProvider.getConfiguration(); + final String value = configuration.getValue("update.test"); + + // then + assertThat(configuration.getVersion()).isEqualTo(1); + assertThat(value).isEqualTo("789"); + } + + @Test + void testUpdateProvidesConfigProperty2() { + // given + final var configProvider = new ConfigProviderImpl(true); + final ServicesConfigurationList servicesConfigurationList = ServicesConfigurationList.newBuilder() + .nameValue(Setting.newBuilder().name("update.test").value("789").build()) + .build(); + final Bytes bytes = ServicesConfigurationList.PROTOBUF.toBytes(servicesConfigurationList); + + // when + configProvider.update(Bytes.EMPTY, bytes); final VersionedConfiguration configuration = configProvider.getConfiguration(); final String value = configuration.getValue("update.test"); @@ -209,7 +226,30 @@ void testUpdateProvidesConfigProperties() { final Bytes bytes = ServicesConfigurationList.PROTOBUF.toBytes(servicesConfigurationList); // when - configProvider.update(bytes); + configProvider.update(bytes, Bytes.EMPTY); + VersionedConfiguration configuration = configProvider.getConfiguration(); + final String value1 = configuration.getValue("update.test1"); + final String value2 = configuration.getValue("update.test2"); + + // then + assertThat(configuration.getVersion()).isEqualTo(1); + assertThat(value1).isEqualTo("789"); + assertThat(value2).isEqualTo("abc"); + } + + @Test + void testUpdateProvidesConfigProperties2() { + // given + final var configProvider = new ConfigProviderImpl(true); + final ServicesConfigurationList servicesConfigurationList = ServicesConfigurationList.newBuilder() + .nameValue( + Setting.newBuilder().name("update.test1").value("789").build(), + Setting.newBuilder().name("update.test2").value("abc").build()) + .build(); + final Bytes bytes = ServicesConfigurationList.PROTOBUF.toBytes(servicesConfigurationList); + + // when + configProvider.update(Bytes.EMPTY, bytes); VersionedConfiguration configuration = configProvider.getConfiguration(); final String value1 = configuration.getValue("update.test1"); final String value2 = configuration.getValue("update.test2"); @@ -227,7 +267,8 @@ void testUpdateWithNullBytes() { final var configProvider = new ConfigProviderImpl(true); // then - assertThatThrownBy(() -> configProvider.update(null)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> configProvider.update(null, Bytes.EMPTY)).isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> configProvider.update(Bytes.EMPTY, null)).isInstanceOf(NullPointerException.class); } @Test @@ -237,6 +278,7 @@ void testUpdateWithInvalidBytes() { final Bytes bytes = Bytes.wrap("\\uxxxx".getBytes(StandardCharsets.UTF_8)); // then - assertThatCode(() -> configProvider.update(bytes)).doesNotThrowAnyException(); + assertThatCode(() -> configProvider.update(bytes, Bytes.EMPTY)).doesNotThrowAnyException(); + assertThatCode(() -> configProvider.update(Bytes.EMPTY, bytes)).doesNotThrowAnyException(); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleManagerTest.java index 0f80a50320de..1aa70f3c3101 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/throttle/ThrottleManagerTest.java @@ -18,10 +18,10 @@ import static com.hedera.hapi.node.base.HederaFunctionality.CRYPTO_CREATE; import static com.hedera.hapi.node.base.HederaFunctionality.CRYPTO_TRANSFER; +import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchException; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.hapi.node.base.HederaFunctionality; @@ -29,23 +29,13 @@ import com.hedera.hapi.node.transaction.ThrottleBucket; import com.hedera.hapi.node.transaction.ThrottleDefinitions; import com.hedera.hapi.node.transaction.ThrottleGroup; -import com.hedera.node.app.spi.fixtures.util.LogCaptor; -import com.hedera.node.app.spi.fixtures.util.LogCaptureExtension; import com.hedera.node.app.spi.fixtures.util.LoggingSubject; -import com.hedera.node.app.spi.fixtures.util.LoggingTarget; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.pbj.runtime.io.buffer.Bytes; import java.util.List; -import java.util.stream.Stream; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -@ExtendWith(LogCaptureExtension.class) class ThrottleManagerTest { ThrottleGroup throttleGroup = ThrottleGroup.newBuilder() @@ -78,9 +68,6 @@ class ThrottleManagerTest { @LoggingSubject ThrottleManager subject; - @LoggingTarget - private LogCaptor logCaptor; - @BeforeEach void setUp() { subject = new ThrottleManager(); @@ -102,24 +89,15 @@ void defaultExpectedFields() { assertEquals(ThrottleDefinitions.DEFAULT.throttleBuckets(), subject.throttleBuckets()); } - @ParameterizedTest - @MethodSource("invalidArgumentsOnUpdateSource") - void invalidArgumentsOnUpdate(Bytes bytes) { - // when - subject.update(bytes); - - // expect - assertThat(logCaptor.warnLogs(), hasItems(startsWith("Unable to parse the throttle file"))); - - // default values are applied - assertEquals(ThrottleDefinitions.DEFAULT, subject.throttleDefinitions()); - assertEquals(ThrottleDefinitions.DEFAULT.throttleBuckets(), subject.throttleBuckets()); - } + @SuppressWarnings("DataFlowIssue") + @Test + void invalidArgumentsOnUpdate() { + assertThatThrownBy(() -> subject.update(null)).isInstanceOf(NullPointerException.class); - private static Stream invalidArgumentsOnUpdateSource() { - return Stream.of( - null, Arguments.of(Bytes.wrap(new byte[] {0x01})) // invalid bytes - ); + final var notParsableBytes = Bytes.wrap(new byte[] {0x01}); + assertThatThrownBy(() -> subject.update(notParsableBytes)) + .isInstanceOf(HandleException.class) + .has(responseCode(ResponseCodeEnum.UNPARSEABLE_THROTTLE_DEFINITIONS)); } @Test @@ -134,13 +112,10 @@ void handleThrottleFileTxBodyWithEmptyListOfGroups() { var throttleDefinitions = new ThrottleDefinitions(List.of(throttleBucket)); // when - Exception exception = - catchException(() -> subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions))); + final var result = subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions)); // then - Assertions.assertThat(exception).isInstanceOf(HandleException.class); - Assertions.assertThat(((HandleException) exception).getStatus()) - .isEqualTo(ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION); + assertThat(result).isEqualTo(ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION); } @Test @@ -161,13 +136,10 @@ void handleThrottleFileTxBodyWithNotAllRequiredOperations() { var throttleDefinitions = new ThrottleDefinitions(List.of(throttleBucket)); // when - Exception exception = - catchException(() -> subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions))); + final var result = subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions)); // then - Assertions.assertThat(exception).isInstanceOf(HandleException.class); - Assertions.assertThat(((HandleException) exception).getStatus()) - .isEqualTo(ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION); + assertThat(result).isEqualTo(ResponseCodeEnum.SUCCESS_BUT_MISSING_EXPECTED_OPERATION); } @Test @@ -191,8 +163,8 @@ void handleThrottleFileTxBodyWithZeroOpsPerSec() { catchException(() -> subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions))); // then - Assertions.assertThat(exception).isInstanceOf(HandleException.class); - Assertions.assertThat(((HandleException) exception).getStatus()) + assertThat(exception).isInstanceOf(HandleException.class); + assertThat(((HandleException) exception).getStatus()) .isEqualTo(ResponseCodeEnum.THROTTLE_GROUP_HAS_ZERO_OPS_PER_SEC); } @@ -222,8 +194,8 @@ void handleThrottleFileTxBodyWithRepeatedOperation() { catchException(() -> subject.update(ThrottleDefinitions.PROTOBUF.toBytes(throttleDefinitions))); // then - Assertions.assertThat(exception).isInstanceOf(HandleException.class); - Assertions.assertThat(((HandleException) exception).getStatus()) + assertThat(exception).isInstanceOf(HandleException.class); + assertThat(((HandleException) exception).getStatus()) .isEqualTo(ResponseCodeEnum.OPERATION_REPEATED_IN_BUCKET_GROUPS); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java index a2940cff0945..082461c027ba 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleWorkflowTest.java @@ -189,7 +189,7 @@ private static PreHandleResult createPreHandleResult(@NonNull Status status, @No @Mock private ParentRecordFinalizer finalizer; - @Mock + @Mock(strictness = LENIENT) private SystemFileUpdateFacility systemFileUpdateFacility; @Mock @@ -258,6 +258,7 @@ void setup() throws PreCheckException { when(authorizer.isAuthorized(eq(ALICE.accountID()), any())).thenReturn(true); when(authorizer.hasPrivilegedAuthorization(eq(ALICE.accountID()), any(), any())) .thenReturn(SystemPrivilege.UNNECESSARY); + when(systemFileUpdateFacility.handleTxBody(any(), any())).thenReturn(SUCCESS); workflow = new HandleWorkflow( networkInfo, diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java index 00fc0effd5d5..6273d87e3a13 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/SystemFileUpdateFacilityTest.java @@ -19,6 +19,7 @@ import static com.hedera.node.app.service.file.impl.FileServiceImpl.BLOBS_KEY; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -33,6 +34,7 @@ import com.hedera.hapi.node.transaction.TransactionBody; 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.congestion.CongestionMultipliers; import com.hedera.node.app.fixtures.state.FakeHederaState; import com.hedera.node.app.service.file.FileService; @@ -45,6 +47,7 @@ import com.hedera.node.config.converter.BytesConverter; import com.hedera.node.config.converter.LongPairConverter; import com.hedera.node.config.data.FilesConfig; +import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.LedgerConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.test.framework.config.TestConfigBuilder; @@ -56,7 +59,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mock.Strictness; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -74,11 +76,15 @@ class SystemFileUpdateFacilityTest implements TransactionFactory { private SystemFileUpdateFacility subject; + @Mock private ThrottleManager throttleManager; @Mock private ExchangeRateManager exchangeRateManager; + @Mock + private FeeManager feeManager; + @Mock private CongestionMultipliers congestionMultipliers; @@ -97,15 +103,16 @@ void setUp() { .withConverter(new BytesConverter()) .withConverter(new LongPairConverter()) .withConfigDataType(FilesConfig.class) + .withConfigDataType(HederaConfig.class) .withConfigDataType(LedgerConfig.class) .getOrCreateConfig(); when(configProvider.getConfiguration()).thenReturn(new VersionedConfigImpl(config, 1L)); - throttleManager = new ThrottleManager(); subject = new SystemFileUpdateFacility( configProvider, throttleManager, exchangeRateManager, + feeManager, congestionMultipliers, throttleAccumulator, synchronizedThrottleAccumulator); @@ -123,6 +130,7 @@ void testMethodsWithInvalidArguments() { null, throttleManager, exchangeRateManager, + feeManager, congestionMultipliers, throttleAccumulator, synchronizedThrottleAccumulator)) @@ -131,6 +139,7 @@ void testMethodsWithInvalidArguments() { configProvider, null, exchangeRateManager, + feeManager, congestionMultipliers, throttleAccumulator, synchronizedThrottleAccumulator)) @@ -139,6 +148,7 @@ void testMethodsWithInvalidArguments() { configProvider, throttleManager, null, + feeManager, congestionMultipliers, throttleAccumulator, synchronizedThrottleAccumulator)) @@ -148,6 +158,7 @@ void testMethodsWithInvalidArguments() { throttleManager, exchangeRateManager, null, + congestionMultipliers, throttleAccumulator, synchronizedThrottleAccumulator)) .isInstanceOf(NullPointerException.class); @@ -155,6 +166,16 @@ void testMethodsWithInvalidArguments() { configProvider, throttleManager, exchangeRateManager, + feeManager, + null, + throttleAccumulator, + synchronizedThrottleAccumulator)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> new SystemFileUpdateFacility( + configProvider, + throttleManager, + exchangeRateManager, + feeManager, congestionMultipliers, null, synchronizedThrottleAccumulator)) @@ -163,6 +184,7 @@ void testMethodsWithInvalidArguments() { configProvider, throttleManager, exchangeRateManager, + feeManager, congestionMultipliers, throttleAccumulator, null)) @@ -186,39 +208,107 @@ void testCrytpoTransferShouldBeNoOp() { } @Test - void testUpdateConfigFile() { + void testUpdateNetworkPropertiesFile() { // given - final var fileID = FileID.newBuilder().fileNum(121L).build(); + final var configuration = configProvider.getConfiguration(); + final var config = configuration.getConfigData(FilesConfig.class); + final var fileID = + FileID.newBuilder().fileNum(config.networkProperties()).build(); final var txBody = TransactionBody.newBuilder() .transactionID(TransactionID.newBuilder() .accountID(AccountID.newBuilder().accountNum(50L).build()) .build()) .fileUpdate(FileUpdateTransactionBody.newBuilder().fileID(fileID)); files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); + final var permissionFileID = + FileID.newBuilder().fileNum(config.hapiPermissions()).build(); + final var permissionContent = Bytes.wrap("Good-bye World"); + files.put( + permissionFileID, File.newBuilder().contents(permissionContent).build()); // when subject.handleTxBody(state, txBody.build()); // then - verify(configProvider).update(FILE_BYTES); + verify(configProvider).update(eq(FILE_BYTES), eq(permissionContent)); } @Test - void testAppendConfigFile() { + void testAppendNetworkPropertiesFile() { // given - final var fileID = FileID.newBuilder().fileNum(121L).build(); + final var configuration = configProvider.getConfiguration(); + final var config = configuration.getConfigData(FilesConfig.class); + final var fileID = + FileID.newBuilder().fileNum(config.networkProperties()).build(); final var txBody = TransactionBody.newBuilder() .transactionID(TransactionID.newBuilder() .accountID(AccountID.newBuilder().accountNum(50L).build()) .build()) .fileAppend(FileAppendTransactionBody.newBuilder().fileID(fileID)); files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); + final var permissionFileID = + FileID.newBuilder().fileNum(config.hapiPermissions()).build(); + final var permissionContent = Bytes.wrap("Good-bye World"); + files.put( + permissionFileID, File.newBuilder().contents(permissionContent).build()); // when subject.handleTxBody(state, txBody.build()); // then - verify(configProvider).update(FILE_BYTES); + verify(configProvider).update(eq(FILE_BYTES), eq(permissionContent)); + } + + @Test + void testUpdatePermissionsFile() { + // given + final var configuration = configProvider.getConfiguration(); + final var config = configuration.getConfigData(FilesConfig.class); + final var fileID = FileID.newBuilder().fileNum(config.hapiPermissions()).build(); + final var txBody = TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder() + .accountID(AccountID.newBuilder().accountNum(50L).build()) + .build()) + .fileUpdate(FileUpdateTransactionBody.newBuilder().fileID(fileID)); + files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); + final var networkPropertiesFileID = + FileID.newBuilder().fileNum(config.networkProperties()).build(); + final var networkPropertiesContent = Bytes.wrap("Good-bye World"); + files.put( + networkPropertiesFileID, + File.newBuilder().contents(networkPropertiesContent).build()); + + // when + subject.handleTxBody(state, txBody.build()); + + // then + verify(configProvider).update(eq(networkPropertiesContent), eq(FILE_BYTES)); + } + + @Test + void testAppendPermissionsFile() { + // given + final var configuration = configProvider.getConfiguration(); + final var config = configuration.getConfigData(FilesConfig.class); + final var fileID = FileID.newBuilder().fileNum(config.hapiPermissions()).build(); + final var txBody = TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder() + .accountID(AccountID.newBuilder().accountNum(50L).build()) + .build()) + .fileAppend(FileAppendTransactionBody.newBuilder().fileID(fileID)); + files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); + final var networkPropertiesFileID = + FileID.newBuilder().fileNum(config.networkProperties()).build(); + final var networkPropertiesContent = Bytes.wrap("Good-bye World"); + files.put( + networkPropertiesFileID, + File.newBuilder().contents(networkPropertiesContent).build()); + + // when + subject.handleTxBody(state, txBody.build()); + + // then + verify(configProvider).update(eq(networkPropertiesContent), eq(FILE_BYTES)); } @Test @@ -233,18 +323,9 @@ void throttleMangerUpdatedOnFileUpdate() { .transactionID(TransactionID.newBuilder() .accountID(AccountID.newBuilder().accountNum(50L).build()) .build()) - .fileAppend(FileAppendTransactionBody.newBuilder().fileID(fileID)); + .fileUpdate(FileUpdateTransactionBody.newBuilder().fileID(fileID)); files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); - throttleManager = Mockito.mock(ThrottleManager.class); - subject = new SystemFileUpdateFacility( - configProvider, - throttleManager, - exchangeRateManager, - congestionMultipliers, - throttleAccumulator, - synchronizedThrottleAccumulator); - // when subject.handleTxBody(state, txBody.build()); @@ -264,7 +345,7 @@ void exchangeRateManagerUpdatedOnFileUpdate() { .transactionID(TransactionID.newBuilder() .accountID(AccountID.newBuilder().accountNum(50L).build()) .build()) - .fileAppend(FileAppendTransactionBody.newBuilder().fileID(fileID)); + .fileUpdate(FileUpdateTransactionBody.newBuilder().fileID(fileID)); files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); // when @@ -276,4 +357,26 @@ void exchangeRateManagerUpdatedOnFileUpdate() { FileUtilities.getFileContent(state, fileID), AccountID.newBuilder().accountNum(50L).build()); } + + @Test + void feeManagerUpdatedOnFileUpdate() { + // given + final var configuration = configProvider.getConfiguration(); + final var config = configuration.getConfigData(FilesConfig.class); + + final var fileNum = config.feeSchedules(); + final var fileID = FileID.newBuilder().fileNum(fileNum).build(); + final var txBody = TransactionBody.newBuilder() + .transactionID(TransactionID.newBuilder() + .accountID(AccountID.newBuilder().accountNum(50L).build()) + .build()) + .fileUpdate(FileUpdateTransactionBody.newBuilder().fileID(fileID)); + files.put(fileID, File.newBuilder().contents(FILE_BYTES).build()); + + // when + subject.handleTxBody(state, txBody.build()); + + // then + verify(feeManager, times(1)).update(FileUtilities.getFileContent(state, fileID)); + } } diff --git a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java index 816f1cd23ce7..240be9f01e93 100644 --- a/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java +++ b/hedera-node/hedera-mono-service/src/main/java/com/hedera/node/app/service/mono/utils/MiscUtils.java @@ -829,7 +829,7 @@ public static void safeResetThrottles( final var throttle = throttles.get(i); try { throttle.resetUsageTo(savedUsageSnapshot); - log.info("Reset {} with saved usage snapshot", throttle); + log.debug("Reset {} with saved usage snapshot", throttle); } catch (final Exception e) { log.warn( "Saved {} usage snapshot #{} was not compatible with the corresponding" diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java index 0bb2dd5d0770..dc7fcce25dca 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/throttling/ThrottleDefValidationSuite.java @@ -37,8 +37,12 @@ import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestMethodOrder; @HapiTestSuite +@TestMethodOrder(OrderAnnotation.class) public class ThrottleDefValidationSuite extends HapiSuite { private static final Logger log = LogManager.getLogger(ThrottleDefValidationSuite.class); @@ -75,6 +79,7 @@ private HapiSpec updateWithMissingTokenMintFails() { } @HapiTest + @Order(100) // this needs to be executed after all other tests private HapiSpec ensureDefaultsRestored() { var defaultThrottles = protoDefsFromResource("testSystemFiles/throttles-dev.json");