All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hedera.node.app.hapi.utils.throttles.GasLimitBucketThrottle Maven / Gradle / Ivy

There is a newer version: 0.56.6
Show newest version
/*
 * 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