com.hedera.node.app.hapi.utils.throttles.GasLimitBucketThrottle Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of app-hapi-utils Show documentation
Show all versions of app-hapi-utils Show documentation
Hedera Services API Utilities
/*
* Copyright (C) 2021-2024 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hedera.node.app.hapi.utils.throttles;
import static com.hedera.node.app.hapi.utils.CommonUtils.productWouldOverflow;
import static com.swirlds.base.units.UnitConstants.SECONDS_TO_NANOSECONDS;
/**
* Responsible for throttling transaction by gas limit. Uses a {@link DiscreteLeakyBucket} under the
* hood. Calculates the amount of gas that should be leaked from the bucket based on the amount of
* elapsed nanoseconds since the last time {@link GasLimitBucketThrottle#allow(long, long)} was
* called.
*/
public class GasLimitBucketThrottle {
private static final long TIME_TO_EMPTY = SECONDS_TO_NANOSECONDS;
private final DiscreteLeakyBucket bucket;
private long lastAllowedUnits = 0L;
/**
* Creates an instance of the throttle with the specified capacity.
*
* @param capacity - the capacity for the throttle
*/
public GasLimitBucketThrottle(final long capacity) {
this.bucket = new DiscreteLeakyBucket(capacity);
}
/**
* Calculates and leaks the amount of gas that should be leaked from the bucket based on the
* amount of nanoseconds passed as input argument. Verifies whether there is enough capacity to
* handle a transaction with the specified gas limit. Reserves the capacity needed for the
* transaction if there is enough free space.
*
* @param txGasLimit - the gas limit of the transaction
* @param elapsedNanos - the amount of time passed since the last call
* @return true if there is enough capacity, false if the transaction should be throttled
*/
public boolean allow(final long txGasLimit, final long elapsedNanos) {
leakFor(elapsedNanos);
if (bucket.capacityFree() >= txGasLimit) {
bucket.useCapacity(txGasLimit);
lastAllowedUnits += txGasLimit;
return true;
} else {
return false;
}
}
void leakFor(final long elapsedNanos) {
bucket.leak(effectiveLeak(elapsedNanos));
}
/**
* Returns the percent of the throttle bucket's capacity that is used, given some number of
* nanoseconds have elapsed since the last capacity test.
*
* @param givenElapsedNanos time since last test
* @return the percent of the bucket that is used
*/
double percentUsed(final long givenElapsedNanos) {
final var used = bucket.capacityUsed();
return 100.0 * (used - Math.min(used, effectiveLeak(givenElapsedNanos))) / bucket.totalCapacity();
}
/**
* Returns the percent of the throttle bucket's capacity that is used, as of the last throttling decision.
*
* @return the percent of the bucket that is used
*/
double instantaneousPercentUsed() {
return 100.0 * bucket.capacityUsed() / bucket.totalCapacity();
}
/**
* Returns the approximate ratio of free-to-used capacity in the underlying bucket; if there is
* no capacity used, returns {@code Long.MAX_VALUE}.
*
* @return the free-to-used ratio
*/
public long freeToUsedRatio() {
final var used = bucket.capacityUsed();
return (used == 0) ? Long.MAX_VALUE : bucket.capacityFree() / used;
}
void resetLastAllowedUse() {
lastAllowedUnits = 0;
}
void reclaimLastAllowedUse() {
bucket.leak(lastAllowedUnits);
lastAllowedUnits = 0;
}
/**
* Returns an instance of the {@link DiscreteLeakyBucket} used under the hood.
*
* @return an instance of the {@link DiscreteLeakyBucket} used under the hood
*/
public DiscreteLeakyBucket bucket() {
return bucket;
}
private long effectiveLeak(final long elapsedNanos) {
if (elapsedNanos >= TIME_TO_EMPTY) {
return bucket.totalCapacity();
} else {
return productWouldOverflow(elapsedNanos, bucket.totalCapacity())
? Long.MAX_VALUE / TIME_TO_EMPTY
: elapsedNanos * bucket.totalCapacity() / TIME_TO_EMPTY;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy