A bunch of things were broken in our mempool fee estimation logic. All fixed now. Summary:
Fixed a bug whereby we would never increase the fee above the minimum fee because of the placement of a if bucketMinFee <= globalMinFeeRate check.
Fixed a bug where we were overwriting the mempool fee register in Start(), which was causing the mempool fee estimator to have no txns in it, and thus always return the minimum value. This meant we were not considering mempool congestion at all.
In many places, we were confusing "fee bucket growth rate basis points" with "fee bucket multiplier". The former value would be something like 1000 (= 10%) while the latter would map to 1.1 (= 10000 + 1000 / 10000). This caused fee-time ordering to be basically completely broken. All fixed now, and fixed tests.
Just to add a little more detail: The tests were very well-written and I think they exercise this logic very well. The reason why they were passing before, though, is because we were setting the value incorrectly in the Init() and passed it wrong as an argument, and the two sortof compensated for each other in the tests. But in production, we would go down a different path that wouldn't compensate properly, which is how I found the bug. Anyway it's all fixed now.
Set optimized defaults for the mempool dynamic fee params and added a deep comment explaining why we chose these values where they are define. Also made sure we're using them consistently in all the relevant places. These params optimize heavily toward getting your txn into the next block, which is what we want. They cause reordering issues if you're sending txns at a rate much higher than 1 block per second, but this is correct behavior, and the comments include suggestions on how to mitigate these issues (eg by manually setting the fee or using an atomic txn):
MempoolCongestionFactorBasisPoints
MempoolPriorityPercentileBasisPoints
PastBlocksCongestionFactorBasisPoints
PastBlocksPriorityPercentileBasisPoints
In computeFeeTimeBucketRangeFromExponent, there was a weird edge-case where we could have a fee bucket with start less than end. This can't happen in a real scenario, though, only when the bucket growth rate is like 1bp, which is ridiculously small. And I only found it because of the growth rate <> multiplier issue mentioned previously, which was causing a 10% growth rate to be threaded through as 1bp.
For reference, in case it's useful, the way I found all this stuff was I slowed the block time down to 1 block every 10s and made the NumPastBlocks for the block estimator 5 blocks using the params in constants.go (so that txns would accumulate in the mempool) and added logging of the fees. Then I wrote a script that blasted the mempool with txns and noticed that the fees weren't adjusting properly, which led me down the rabbit-hole to find all of these issues. After fixing all the issues I took some time to optimize all the params, and then used my script to exercise everything and make sure it's fully 100% adapting correctly. Specifically, I saw that the fee goes up correctly once the mempool has a full block's worth of txns accumulated in it, stays high for a few blocks because of the block estimator, and then starts to go down as more blocks come through. It all works really well.
A bunch of things were broken in our mempool fee estimation logic. All fixed now. Summary:
if bucketMinFee <= globalMinFeeRate
check.1000 (= 10%)
while the latter would map to1.1 (= 10000 + 1000 / 10000)
. This caused fee-time ordering to be basically completely broken. All fixed now, and fixed tests.computeFeeTimeBucketRangeFromExponent
, there was a weird edge-case where we could have a fee bucket with start less than end. This can't happen in a real scenario, though, only when the bucket growth rate is like 1bp, which is ridiculously small. And I only found it because of the growth rate <> multiplier issue mentioned previously, which was causing a 10% growth rate to be threaded through as 1bp.For reference, in case it's useful, the way I found all this stuff was I slowed the block time down to 1 block every 10s and made the NumPastBlocks for the block estimator 5 blocks using the params in constants.go (so that txns would accumulate in the mempool) and added logging of the fees. Then I wrote a script that blasted the mempool with txns and noticed that the fees weren't adjusting properly, which led me down the rabbit-hole to find all of these issues. After fixing all the issues I took some time to optimize all the params, and then used my script to exercise everything and make sure it's fully 100% adapting correctly. Specifically, I saw that the fee goes up correctly once the mempool has a full block's worth of txns accumulated in it, stays high for a few blocks because of the block estimator, and then starts to go down as more blocks come through. It all works really well.