1
  "mempoolminfee" : n,       (numeric) Minimum fee rate in BTC/kB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee
  "minrelaytxfee" : n,       (numeric) Current minimum relay fee for transactions

https://bitcoincore.org/en/doc/0.21.0/rpc/blockchain/getmempoolinfo/

I am trying to understand how is mempoolminfee calculated. Based on above documentation it should be max of minrelaytxfee and minimum mempool fee. I understand minrelaytxfee is a config option which can be set to anything by user. What is the other fee: 'minimum mempool fee'?

Looking at the code for getmempoolinfo RPC I found this comment in one header file which explains the function that is used for calculation:

    ret.pushKV("mempoolminfee", ValueFromAmount(std::max(pool.GetMinFee(maxmempool), ::minRelayTxFee).GetFeePerK()));

https://github.com/bitcoin/bitcoin/blob/fdd80b0a53b4af0b29cb6e03118e2456d053a757/src/rpc/blockchain.cpp#L1655

    /** The minimum fee to get into the mempool, which may itself not be enough
      *  for larger-sized transactions.
      *  The incrementalRelayFee policy variable is used to bound the time it
      *  takes the fee rate to go back down all the way to 0. When the feerate
      *  would otherwise be half of this, it is set to 0 instead.
      */

https://github.com/bitcoin/bitcoin/blob/fdd80b0a53b4af0b29cb6e03118e2456d053a757/src/txmempool.h#L731

CFeeRate CTxMemPool::GetMinFee(size_t sizelimit) const {
    LOCK(cs);
    if (!blockSinceLastRollingFeeBump || rollingMinimumFeeRate == 0)
        return CFeeRate(llround(rollingMinimumFeeRate));

    int64_t time = GetTime();
    if (time > lastRollingFeeUpdate + 10) {
        double halflife = ROLLING_FEE_HALFLIFE;
        if (DynamicMemoryUsage() < sizelimit / 4)
            halflife /= 4;
        else if (DynamicMemoryUsage() < sizelimit / 2)
            halflife /= 2;

        rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife);
        lastRollingFeeUpdate = time;

        if (rollingMinimumFeeRate < (double)incrementalRelayFee.GetFeePerK() / 2) {
            rollingMinimumFeeRate = 0;
            return CFeeRate(0);
        }
    }
    return std::max(CFeeRate(llround(rollingMinimumFeeRate)), incrementalRelayFee);
}

https://github.com/bitcoin/bitcoin/blob/fdd80b0a53b4af0b29cb6e03118e2456d053a757/src/txmempool.cpp#L1080

I still don't understand what GetMinFee() does. Can someone explain this is in a different way or use some examples?

If I save minrelaytxfee=0.0002 and incrementalrelayfee=0.0003, mempoolminfee is 0.0002 which isn't max of both.

1 Answer 1

3

From the mempool's perspective, the rollingMinimumFeeRate is the actual minimum fee rate (the minrelayfee pertains to other parts of the codebase). It starts at 0, and is updated whenever a transaction package is evicted from the mempool due to size constraints. When the mempool reaches its configured maximum, it removes the lowest feerate package to free up space, and sets the rollingMinimumFeeRate to the feerate the removed package paid.

But then you might wonder how the rollingMinimumFeeRate goes down? What happens if the maximum mempool size is reached, but then transactions get confirmed and the mempool's size is reduced? The rollingMinimumFeeRate needs to decrease eventually, and that is what GetMinFee is doing.

GetMinFee is the second part of updating rollingMinimumFeeRate. It reduces it exponentially according to a schedule. If it has been more than 10 seconds since rollingMinimumFeeRate has changed (either up or down), then it will be divided by some power of 2.

If the mempool is more than half full, and no packages are removed to increase rollingMinimumFeeRate again, then after 12 hours, it will be half of what its original value was. That is to say, rollingMinimumFeeRate has a half life of 12 hours when the mempool is at least half full.

This half life decreases when the mempool is between a quarter full and half full. In that case, it is 6 hours. And when the mempool is less than a quarter full, the half life is 3 hours. This is what this section does:

    double halflife = ROLLING_FEE_HALFLIFE;
    if (DynamicMemoryUsage() < sizelimit / 4)
        halflife /= 4;
    else if (DynamicMemoryUsage() < sizelimit / 2)
        halflife /= 2;

To compute the reduced rollingMinimumFeeRate, we want to figure out how much time has passed since the last update and figure out how much lower it should be given the half life that we want. That is what this line does:

    rollingMinimumFeeRate = rollingMinimumFeeRate / pow(2.0, (time - lastRollingFeeUpdate) / halflife);

This is really just the half life formula: N(t) = N_0 * (1/2) ^ (t/t_1/2).

N_0 is the initial quantity (rollingMinimumFeeRate), t is the time that has passed (time - lastRollingFeeUpdate), and t_1/2 is the half life we want (halflife). If you move the exponent into the fraction, reduce the formula, and write it out in code, you get the aforementioned computation line.

The last piece to this update is to check whether the rollingMinimumFeeRate is less than half of the incrementalRelayFee. If it is, then rollingMinimumFeeRate is set to 0. This is done because exponential decay means that rollingMinimumFeeRate would technically never reach 0 (that's how the exponential decay function works). So instead we just bound it to be no less than half of the incrementalRelayFee. Once it reaches that, it just resets to 0 because at that point, it will be incredibly low anyways.

We use the incrementalRelayFee as the lower bound because that is the minimum feerate a transaction needs to pay after accounting for conflicts in order to be relayed. So a transaction must be paying at least incrementalRelayFee, so we can drop the rollingMinimumFeeRate after it is less than incrementalRelayFee, but to be safe, we do it at half of that value.

That last piece of GetMinFee is at the top of the function, which is simply that the current rollingMinimumFeeRate does not change until we find a block, and an optimization to return it when it is already 0. This first part is important because we don't want to be reducing the mempool's minimum fee when things aren't being removed from the mempool.

1
  • Awesome! Thanks for all the details. While I was waiting for someone to answer, tried reading the code and make sense of each line: i.imgur.com/t3OqqUF.png. Almost understood everything except few things like initial value of rollingMinimumFeeRate, how it updates, why name half life etc. After reading your answer, now I understand everything. TIL: en.wikipedia.org/wiki/…
    – user103136
    Aug 17, 2021 at 17:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.