diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/throttle/HandleThrottleParser.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/throttle/HandleThrottleParser.java index 98d9ca86a4a3..86bb3855cce3 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/throttle/HandleThrottleParser.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/throttle/HandleThrottleParser.java @@ -24,13 +24,34 @@ import java.util.List; public interface HandleThrottleParser { + /* + * Rebuilds the throttle requirements based on the given throttle definitions. + * + * @param defs the throttle definitions to rebuild the throttle requirements based on + */ void rebuildFor(@NonNull final ThrottleDefinitions defs); + /* + * Rebuilds the gas throttle based on the current configuration. + */ void applyGasConfig(); + /* + * Gets the current list of active throttles. + * + * @return the current list of active throttles + */ @NonNull List allActiveThrottles(); + /* + * Resets the usage for all snapshots. + */ + void resetUsageThrottlesTo(List snapshots); + + /* + * Gets the gas throttle. + */ @Nullable - public GasLimitDeterministicThrottle gasLimitThrottle(); + GasLimitDeterministicThrottle gasLimitThrottle(); } diff --git a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java index f1110117c8bf..6cd3b6e5c201 100644 --- a/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java +++ b/hedera-node/hedera-app-spi/src/main/java/com/hedera/node/app/spi/workflows/HandleContext.java @@ -24,6 +24,7 @@ import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.transaction.TransactionBody; +import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.spi.authorization.SystemPrivilege; import com.hedera.node.app.spi.fees.ExchangeRateInfo; import com.hedera.node.app.spi.fees.FeeAccumulator; @@ -43,6 +44,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; +import java.util.List; import java.util.function.Predicate; /** @@ -668,6 +670,26 @@ default T dispatchRemovableChildTransaction( */ void reclaimPreviouslyReservedThrottle(int n, HederaFunctionality function); + /** + * Verifies if the throttle in this operation context has enough capacity to handle the given number of the + * given function at the given time. (The time matters because we want to consider how much + * will have leaked between now and that time.) + * + * @param n the number of the given function + * @param function the function + * @return true if the system should throttle the given number of the given function + * at the instant for which throttling should be calculated + */ + boolean shouldThrottleNOfUnscaled(int n, HederaFunctionality function); + + /** + * For each following child transaction consumes the capacity + * required for that child transaction in the consensus throttle buckets. + * + * @return true if all the child transactions were allowed through the throttle consideration, false otherwise. + */ + boolean hasThrottleCapacityForChildTransactions(); + /** * Create a checkpoint for the current childRecords. * @@ -677,6 +699,19 @@ default T dispatchRemovableChildTransaction( @NonNull RecordListCheckPoint createRecordListCheckPoint(); + /** + * Returns a list of snapshots of the current usage of all active throttles. + * @return the active snapshots + */ + List getUsageSnapshots(); + + /** + * Resets the current usage of all active throttles to the given snapshots. + * + * @param snapshots the snapshots to reset to + */ + void resetUsageThrottlesTo(List snapshots); + /** * Returns whether the current transaction being processed was submitted by this node. * diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/throttle/FakeHandleThrottleParser.java b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/throttle/FakeHandleThrottleParser.java index 033692916e4b..f04644740301 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/throttle/FakeHandleThrottleParser.java +++ b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/throttle/FakeHandleThrottleParser.java @@ -42,6 +42,9 @@ public List allActiveThrottles() { return Collections.emptyList(); } + @Override + public void resetUsageThrottlesTo(List snapshots) {} + @Nullable @Override public GasLimitDeterministicThrottle gasLimitThrottle() { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java index 3fa444516686..9536645b44a5 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/NetworkUtilizationManager.java @@ -17,10 +17,13 @@ package com.hedera.node.app.throttle; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.state.HederaState; import com.hedera.node.app.workflows.TransactionInfo; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; +import java.util.List; /** * Interface which purpose is to do the work of tracking network utilization (and its impact on @@ -78,4 +81,43 @@ void trackFeePayments( * @param state the state of the node */ void saveTo(@NonNull final HederaState state); + + /* + * Updates the throttle requirements for the given transaction and returns whether the transaction + * should be throttled for the current time(Instant.now). + * + * @param txnInfo the transaction to update the throttle requirements for + * @param state the current state of the node + * @param consensusTime the consensus time + * @return whether the transaction should be throttled + */ + boolean shouldThrottle( + @NonNull final TransactionInfo txnInfo, + @NonNull final HederaState state, + @NonNull final Instant consensusTime); + + /** + * Verifies if the throttle in this operation context has enough capacity to handle the given number of the + * given function at the given time. (The time matters because we want to consider how much + * will have leaked between now and that time.) + * + * @param n the number of the given function + * @param function the function + * @return true if the system should throttle the given number of the given function + * at the instant for which throttling should be calculated + */ + boolean shouldThrottleNOfUnscaled(int n, @NonNull HederaFunctionality function, @NonNull Instant consensusTime); + + /** + * Returns a list of snapshots of the current usage of all active throttles. + * @return the active snapshots + */ + List getUsageSnapshots(); + + /** + * Resets the current usage of all active throttles to the given snapshots. + * + * @param snapshots the snapshots to reset to + */ + void resetUsageThrottlesTo(List snapshots); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java index efe77035164b..4e9815a62f07 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/SynchronizedThrottleAccumulator.java @@ -53,7 +53,7 @@ public SynchronizedThrottleAccumulator(ThrottleAccumulator frontendThrottle) { * @return whether the transaction should be throttled */ public synchronized boolean shouldThrottle(@NonNull TransactionInfo txnInfo, HederaState state) { - setDecisionTime(); + setDecisionTime(Instant.now()); return frontendThrottle.shouldThrottle(txnInfo, lastDecisionTime, state); } @@ -67,16 +67,15 @@ public synchronized boolean shouldThrottle(@NonNull TransactionInfo txnInfo, Hed * @return whether the query should be throttled */ public synchronized boolean shouldThrottle(HederaFunctionality queryFunction, Query query, AccountID queryPayerId) { - setDecisionTime(); + setDecisionTime(Instant.now()); return frontendThrottle.shouldThrottle(queryFunction, lastDecisionTime, query, queryPayerId); } - private void setDecisionTime() { - final var now = Instant.now(); - lastDecisionTime = now.isBefore(lastDecisionTime) ? lastDecisionTime : now; - } - public void leakUnusedThrottlePreviouslyReserved(int n, HederaFunctionality function) { frontendThrottle.leakCapacityForNOfUnscaled(n, function); } + + private void setDecisionTime(@NonNull final Instant time) { + lastDecisionTime = time.isBefore(lastDecisionTime) ? lastDecisionTime : time; + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java index 2f1bb59b4938..1d0cddbfd6ab 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/throttle/ThrottleAccumulator.java @@ -288,6 +288,16 @@ public void resetUsage() { } } + /* + * Resets the usage for all snapshots. + */ + @Override + public void resetUsageThrottlesTo(final List snapshots) { + for (int i = 0, n = activeThrottles.size(); i < n; i++) { + activeThrottles.get(i).resetUsageTo(snapshots.get(i)); + } + } + private boolean shouldThrottleTxn( final boolean isScheduled, @NonNull final TransactionInfo txnInfo, @@ -487,11 +497,14 @@ private boolean shouldThrottleScheduleSign( } public static boolean throttleExempt( - @NonNull final AccountID accountID, @NonNull final Configuration configuration) { + @Nullable final AccountID accountID, @NonNull final Configuration configuration) { final long maxThrottleExemptNum = configuration.getConfigData(AccountsConfig.class).lastThrottleExempt(); - final long accountNum = accountID.accountNum().longValue(); - return 1L <= accountNum && accountNum <= maxThrottleExemptNum; + if (accountID != null) { + final long accountNum = accountID.accountNum().longValue(); + return 1L <= accountNum && accountNum <= maxThrottleExemptNum; + } + return false; } private void reclaimLastAllowedUse() { 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 36610c63031d..d290682858f6 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 @@ -24,6 +24,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.SignatureMap; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.Transaction; @@ -201,4 +202,30 @@ public boolean wasLastTxnGasThrottled() { public void leakUnusedGasPreviouslyReserved(@NonNull final TransactionInfo txnInfo, long value) { backendThrottle.leakUnusedGasPreviouslyReserved(txnInfo, value); } + + @Override + public boolean shouldThrottle( + @NonNull final TransactionInfo txnInfo, + @NonNull final HederaState state, + @NonNull final Instant consensusTime) { + return backendThrottle.shouldThrottle(txnInfo, consensusTime, state); + } + + @Override + public boolean shouldThrottleNOfUnscaled( + final int n, @NonNull final HederaFunctionality function, @NonNull final Instant consensusTime) { + return backendThrottle.shouldThrottleNOfUnscaled(n, function, consensusTime); + } + + @Override + public List getUsageSnapshots() { + return backendThrottle.allActiveThrottles().stream() + .map(DeterministicThrottle::usageSnapshot) + .toList(); + } + + @Override + public void resetUsageThrottlesTo(List snapshots) { + backendThrottle.resetUsageThrottlesTo(snapshots); + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionInfo.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionInfo.java index 6ec2115b39d2..9464b216328a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionInfo.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/TransactionInfo.java @@ -25,6 +25,7 @@ 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; /** * Contains information related to a parsed transaction. @@ -49,8 +50,8 @@ public record TransactionInfo( @NonNull Transaction transaction, @NonNull TransactionBody txBody, - @NonNull TransactionID transactionID, - @NonNull AccountID payerID, + @Nullable TransactionID transactionID, + @Nullable AccountID payerID, @NonNull SignatureMap signatureMap, @NonNull Bytes signedBytes, @NonNull HederaFunctionality functionality) { @@ -70,4 +71,22 @@ public TransactionInfo( signedBytes, functionality); } + + public static TransactionInfo from( + @NonNull Transaction transaction, + @NonNull TransactionBody txBody, + @NonNull SignatureMap signatureMap, + @NonNull Bytes signedBytes, + @NonNull HederaFunctionality functionality) { + TransactionID transactionId = null; + AccountID payerId = null; + if (txBody.transactionID() != null) { + transactionId = txBody.transactionID(); + if (transactionId.accountID() != null) { + payerId = txBody.transactionID().accountID(); + } + } + return new TransactionInfo( + transaction, txBody, transactionId, payerId, signatureMap, signedBytes, functionality); + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java index 9305ec36675c..f6cf70c68001 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleContextImpl.java @@ -16,8 +16,11 @@ package com.hedera.node.app.workflows.handle; +import static com.hedera.hapi.node.base.HederaFunctionality.CONTRACT_CALL; +import static com.hedera.hapi.node.base.HederaFunctionality.CONTRACT_CREATE; import static com.hedera.hapi.node.base.ResponseCodeEnum.DUPLICATE_TRANSACTION; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SIGNATURE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.spi.HapiUtils.functionOf; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.CHILD; import static com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory.PRECEDING; @@ -42,6 +45,7 @@ import com.hedera.node.app.fees.FeeManager; import com.hedera.node.app.fees.NoOpFeeAccumulator; import com.hedera.node.app.fees.NoOpFeeCalculator; +import com.hedera.node.app.hapi.utils.throttles.DeterministicThrottle; import com.hedera.node.app.ids.EntityIdService; import com.hedera.node.app.ids.WritableEntityIdStore; import com.hedera.node.app.service.token.TokenService; @@ -75,9 +79,11 @@ import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import com.hedera.node.app.state.HederaRecordCache; import com.hedera.node.app.state.WrappedHederaState; +import com.hedera.node.app.throttle.NetworkUtilizationManager; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.SolvencyPreCheck; import com.hedera.node.app.workflows.TransactionChecker; +import com.hedera.node.app.workflows.TransactionInfo; import com.hedera.node.app.workflows.dispatcher.ReadableStoreFactory; import com.hedera.node.app.workflows.dispatcher.ServiceApiFactory; import com.hedera.node.app.workflows.dispatcher.TransactionDispatcher; @@ -93,6 +99,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Instant; +import java.util.List; import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; @@ -133,6 +140,7 @@ public class HandleContextImpl implements HandleContext, FeeContext { private final Authorizer authorizer; private final SolvencyPreCheck solvencyPreCheck; private final ChildRecordFinalizer childRecordFinalizer; + private final NetworkUtilizationManager networkUtilizationManager; private final SynchronizedThrottleAccumulator synchronizedThrottleAccumulator; private ReadableStoreFactory readableStoreFactory; @@ -164,7 +172,8 @@ public class HandleContextImpl implements HandleContext, FeeContext { * @param authorizer The {@link Authorizer} used to authorize the transaction * @param solvencyPreCheck The {@link SolvencyPreCheck} used to validate if the account is able to pay the fees * @param childRecordFinalizer The {@link ChildRecordFinalizer} used to finalize child records - * @param synchronizedThrottleAccumulator The {@link SynchronizedThrottleAccumulator} used to manage tracking of network utilization + * @param networkUtilizationManager The {@link NetworkUtilizationManager} used to manage the tracking of backend network throttling + * @param synchronizedThrottleAccumulator The {@link SynchronizedThrottleAccumulator} used to manage the tracking of frontend network throttling */ public HandleContextImpl( @NonNull final TransactionBody txBody, @@ -190,6 +199,7 @@ public HandleContextImpl( @NonNull final Authorizer authorizer, @NonNull final SolvencyPreCheck solvencyPreCheck, @NonNull final ChildRecordFinalizer childRecordFinalizer, + @NonNull final NetworkUtilizationManager networkUtilizationManager, @NonNull final SynchronizedThrottleAccumulator synchronizedThrottleAccumulator) { this.txBody = requireNonNull(txBody, "txBody must not be null"); this.functionality = requireNonNull(functionality, "functionality must not be null"); @@ -212,6 +222,8 @@ public HandleContextImpl( requireNonNull(userTransactionConsensusTime, "userTransactionConsensusTime must not be null"); this.authorizer = requireNonNull(authorizer, "authorizer must not be null"); this.childRecordFinalizer = requireNonNull(childRecordFinalizer, "childRecordFinalizer must not be null"); + this.networkUtilizationManager = + requireNonNull(networkUtilizationManager, "networkUtilization must not be null"); this.synchronizedThrottleAccumulator = requireNonNull(synchronizedThrottleAccumulator, "synchronizedThrottleAccumulator must not be null"); @@ -703,6 +715,7 @@ private void dispatchSyntheticTxn( authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); if (dispatchValidationResult != null) { @@ -901,6 +914,63 @@ public void reclaimPreviouslyReservedThrottle(int n, HederaFunctionality functio synchronizedThrottleAccumulator.leakUnusedThrottlePreviouslyReserved(n, function); } + @Override + public boolean shouldThrottleNOfUnscaled(int n, HederaFunctionality function) { + return networkUtilizationManager.shouldThrottleNOfUnscaled(n, function, userTransactionConsensusTime); + } + + public boolean shouldThrottleTxn(TransactionInfo txInfo) { + return networkUtilizationManager.shouldThrottle(txInfo, current(), userTransactionConsensusTime); + } + + @Override + public List getUsageSnapshots() { + return networkUtilizationManager.getUsageSnapshots(); + } + + @Override + public void resetUsageThrottlesTo(List snapshots) { + networkUtilizationManager.resetUsageThrottlesTo(snapshots); + } + + @Override + public boolean hasThrottleCapacityForChildTransactions() { + var isAllowed = true; + final var childRecords = recordListBuilder.childRecordBuilders(); + @Nullable List snapshotsIfNeeded = null; + + for (int i = 0, n = childRecords.size(); i < n && isAllowed; i++) { + final var childRecord = childRecords.get(i); + if (Objects.equals(childRecord.status(), SUCCESS)) { + final var childTx = childRecord.transaction(); + final var childTxBody = childRecord.transactionBody(); + HederaFunctionality childTxFunctionality; + try { + childTxFunctionality = functionOf(childTxBody); + } catch (UnknownHederaFunctionality e) { + throw new IllegalStateException("Invalid transaction body " + childTxBody, e); + } + + if (childTxFunctionality == CONTRACT_CREATE || childTxFunctionality == CONTRACT_CALL) { + continue; + } + if (snapshotsIfNeeded == null) { + snapshotsIfNeeded = getUsageSnapshots(); + } + + final var childTxInfo = TransactionInfo.from( + childTx, childTxBody, childTx.sigMap(), childTx.signedTransactionBytes(), childTxFunctionality); + if (shouldThrottleTxn(childTxInfo)) { + isAllowed = false; + } + } + } + if (!isAllowed) { + resetUsageThrottlesTo(snapshotsIfNeeded); + } + return isAllowed; + } + @Override public boolean isSelfSubmitted() { return Objects.equals( 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 d032ea7f1018..8ccca7d2d2ea 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 @@ -407,6 +407,7 @@ private void handleUserTransaction( authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); // Calculate the fee diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java index fe0c55ecc059..909a3ba55358 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/record/SingleTransactionRecordBuilderImpl.java @@ -438,6 +438,15 @@ public SingleTransactionRecordBuilderImpl memo(@NonNull final String memo) { // ------------------------------------------------------------------------------------------------------------------------ // fields needed for TransactionRecord + /** + * Gets the transaction object. + * + * @return the transaction object + */ + @NonNull + public Transaction transaction() { + return transaction; + } /** * Gets the consensus instant. * diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java index 4f958f480cda..fcf40caefd99 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/HandleContextImplTest.java @@ -89,6 +89,7 @@ import com.hedera.node.app.spi.workflows.record.SingleTransactionRecordBuilder; import com.hedera.node.app.state.HederaRecordCache; import com.hedera.node.app.state.HederaState; +import com.hedera.node.app.throttle.NetworkUtilizationManager; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.workflows.SolvencyPreCheck; import com.hedera.node.app.workflows.TransactionChecker; @@ -182,6 +183,9 @@ class HandleContextImplTest extends StateTestBase implements Scenarios { @Mock private SynchronizedThrottleAccumulator synchronizedThrottleAccumulator; + @Mock + private NetworkUtilizationManager networkUtilizationManager; + @Mock private SelfNodeInfo selfNodeInfo; @@ -235,6 +239,7 @@ private HandleContextImpl createContext(final TransactionBody txBody) { authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); } @@ -265,6 +270,7 @@ void testConstructorWithInvalidArguments() { authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator }; @@ -394,6 +400,7 @@ void setUp() { authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); } @@ -926,6 +933,7 @@ private HandleContextImpl createContext(final TransactionBody txBody, final Tran authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); } diff --git a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java index 36e2e52fad7f..a0202f29071b 100644 --- a/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java +++ b/hedera-node/hedera-app/src/xtest/java/common/BaseScaffoldingModule.java @@ -69,8 +69,10 @@ import com.hedera.node.app.state.HederaState; import com.hedera.node.app.state.recordcache.DeduplicationCacheImpl; import com.hedera.node.app.state.recordcache.RecordCacheImpl; +import com.hedera.node.app.throttle.NetworkUtilizationManager; import com.hedera.node.app.throttle.SynchronizedThrottleAccumulator; import com.hedera.node.app.throttle.ThrottleAccumulator; +import com.hedera.node.app.throttle.impl.NetworkUtilizationManagerImpl; import com.hedera.node.app.validation.ExpiryValidation; import com.hedera.node.app.workflows.SolvencyPreCheck; import com.hedera.node.app.workflows.TransactionChecker; @@ -217,7 +219,9 @@ static BiFunction provideQueryContextFactory( @NonNull final HederaState state, @NonNull final RecordCache recordCache, @NonNull final Configuration configuration, - @NonNull final ExchangeRateManager exchangeRateManager) { + @NonNull final ExchangeRateManager exchangeRateManager, + @NonNull final SynchronizedThrottleAccumulator synchronizedThrottleAccumulator) { + final var consensusTime = Instant.now(); return (query, payerId) -> new QueryContextImpl( state, new ReadableStoreFactory(state), @@ -245,6 +249,7 @@ static Function provideHandleContextCreator( @NonNull final FeeManager feeManager, @NonNull final Authorizer authorizer, @NonNull final ChildRecordFinalizer childRecordFinalizer, + @NonNull final NetworkUtilizationManager networkUtilizationManager, @NonNull final SynchronizedThrottleAccumulator synchronizedThrottleAccumulator) { final var consensusTime = Instant.now(); final var recordListBuilder = new RecordListBuilder(consensusTime); @@ -284,6 +289,7 @@ static Function provideHandleContextCreator( authorizer, solvencyPreCheck, childRecordFinalizer, + networkUtilizationManager, synchronizedThrottleAccumulator); }; } @@ -346,4 +352,15 @@ private static CongestionMultipliers getCongestionMultipliers( return new CongestionMultipliers(txnRateMultiplier, gasFeeMultiplier); } + + @Provides + @Singleton + static NetworkUtilizationManager createNetworkUtilizationManager(@NonNull ConfigProvider configProvider) { + var backendThrottle = new ThrottleAccumulator(() -> 1, configProvider, BACKEND_THROTTLE); + final var genericFeeMultiplier = getThrottleMultiplier(configProvider, backendThrottle); + + final var congestionMultipliers = + getCongestionMultipliers(configProvider, genericFeeMultiplier, backendThrottle); + return new NetworkUtilizationManagerImpl(backendThrottle, congestionMultipliers); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/RootProxyWorldUpdater.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/RootProxyWorldUpdater.java index aad418beffa9..998f20165a72 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/RootProxyWorldUpdater.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/state/RootProxyWorldUpdater.java @@ -16,6 +16,10 @@ package com.hedera.node.app.service.contract.impl.state; +import static com.hedera.hapi.node.base.HederaFunctionality.CRYPTO_CREATE; +import static com.hedera.hapi.node.base.ResponseCodeEnum.CONSENSUS_GAS_EXHAUSTED; +import static com.hedera.node.app.spi.workflows.ResourceExhaustedException.validateResource; + import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.node.contract.ContractNonceInfo; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; @@ -23,6 +27,7 @@ import com.hedera.node.app.service.contract.impl.infra.IterableStorageManager; import com.hedera.node.app.service.contract.impl.infra.RentCalculator; import com.hedera.node.app.service.contract.impl.infra.StorageSizeValidator; +import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.hedera.node.config.data.ContractsConfig; import edu.umd.cs.findbugs.annotations.NonNull; @@ -44,6 +49,7 @@ public class RootProxyWorldUpdater extends ProxyWorldUpdater { private final ContractsConfig contractsConfig; private final IterableStorageManager storageManager; private final StorageSizeValidator storageSizeValidator; + private final HandleContext context; private boolean committed = false; private List createdContractIds; @@ -56,12 +62,14 @@ public RootProxyWorldUpdater( @NonNull final EvmFrameStateFactory evmFrameStateFactory, @NonNull final RentCalculator rentCalculator, @NonNull final IterableStorageManager storageManager, - @NonNull final StorageSizeValidator storageSizeValidator) { + @NonNull final StorageSizeValidator storageSizeValidator, + @NonNull final HandleContext context) { super(enhancement, evmFrameStateFactory, null); this.contractsConfig = Objects.requireNonNull(contractsConfig); this.storageManager = Objects.requireNonNull(storageManager); this.rentCalculator = Objects.requireNonNull(rentCalculator); this.storageSizeValidator = Objects.requireNonNull(storageSizeValidator); + this.context = context; } /** @@ -96,6 +104,15 @@ public void commit() { final var contractChangeSummary = enhancement.operations().summarizeContractChanges(); createdContractIds = contractChangeSummary.newContractIds(); + if (contractsConfig.enforceCreationThrottle()) { + final var creationCapacityIsAvailable = + !context.shouldThrottleNOfUnscaled(createdContractIds.size(), CRYPTO_CREATE); + validateResource(creationCapacityIsAvailable, CONSENSUS_GAS_EXHAUSTED); + } + + final var childThrottleIsAvailable = context.hasThrottleCapacityForChildTransactions(); + validateResource(childThrottleIsAvailable, CONSENSUS_GAS_EXHAUSTED); + // If nonces externalization is enabled, we need to capture the updated nonces if (contractsConfig.noncesExternalizationEnabled()) { updatedContractNonces = contractChangeSummary.updatedContractNonces(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java index 50cc3603dfc0..25da8a219ce4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/records/ContractOperationRecordBuilderTest.java @@ -16,7 +16,8 @@ package com.hedera.node.app.service.contract.impl.test.records; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/RootProxyWorldUpdaterTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/RootProxyWorldUpdaterTest.java index 95bc03e5ff52..4b06ccbad836 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/RootProxyWorldUpdaterTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/state/RootProxyWorldUpdaterTest.java @@ -39,6 +39,7 @@ import com.hedera.node.app.service.contract.impl.state.StorageAccesses; import com.hedera.node.app.service.contract.impl.state.StorageSizeChange; import com.hedera.node.app.service.token.api.ContractChangeSummary; +import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.config.api.Configuration; @@ -78,6 +79,9 @@ class RootProxyWorldUpdaterTest { @Mock private StorageSizeValidator storageSizeValidator; + @Mock + private HandleContext context; + @Mock private EvmFrameState evmFrameState; @@ -134,6 +138,7 @@ void performsAdditionalCommitActionsInOrder() { final var updatedNonces = new ArrayList<>(List.of(new ContractNonceInfo(CALLED_CONTRACT_ID, 1L))); given(hederaOperations.summarizeContractChanges()) .willReturn(new ContractChangeSummary(createdIds, updatedNonces)); + given(context.hasThrottleCapacityForChildTransactions()).willReturn(true); subject.commit(); @@ -154,7 +159,8 @@ private void givenSubjectWith(@NonNull final Configuration configuration, @NonNu () -> evmFrameState, rentCalculator, storageManager, - storageSizeValidator); + storageSizeValidator, + context); } private List pendingChanges() {