fees: enable CBlockPolicyEstimator return sub 1 sat/vb fee rate estimates #33199

pull ismaelsadeeq wants to merge 5 commits into bitcoin:master from ismaelsadeeq:08-2025-low-fee-rate-estimate changing 4 files +68 −35
  1. ismaelsadeeq commented at 3:15 pm on August 15, 2025: member

    This is a simple PR that updates the block policy estimator’s MIN_BUCKET_FEERATE constant to be 100, which is identical to the policy DEFAULT_MIN_RELAY_TX_FEE.

    This change enables the block policy fee rate estimator to return sub-1 sat/vB fee rate estimates.

    The change is correct because the estimator creates buckets of fee rates from MIN_BUCKET_FEERATE, MIN_BUCKET_FEERATE + FEE_SPACING, MIN_BUCKET_FEERATE + 2 * FEE_SPACING, … up to MAX_BUCKET_FEERATE.

    This means it will record sub-1 sat/vB fee rates in the buckets and may return them as a fee rate estimate when that bucket is the lowest one with sufficient transactions for a given target.


    While touching this part of the fee estimator code, this PR got rid of the dummy value persisted in the file

  2. DrahtBot commented at 3:15 pm on August 15, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33199.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK achow101, musaHaruna, polespinasa
    Concept ACK kevkevinpal

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    No conflicts as of last run.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • , the fee estimator will start tracking fresh stats. -> ; the fee estimator will start tracking fresh stats. [Fixes a comma splice in the release notes sentence “Restarting a node … invalidates previously saved estimates in fee_estimates.dat, the fee estimator will start tracking fresh stats.”]

    2026-02-05 10:54:18

  3. ismaelsadeeq force-pushed on Aug 15, 2025
  4. in test/functional/feature_fee_estimation.py:433 in 413c81b134 outdated
    431+            self.broadcast_and_mine(self.nodes[1], self.nodes[2], low_feerate, TXS_COUNT)
    432         # conservative mode will consider longer time horizons while economical mode does not
    433         # Check the fee estimates for both modes after mining low fee transactions.
    434         check_fee_estimates_btw_modes(self.nodes[0], high_feerate, low_feerate)
    435 
    436+    def test_sub_1s_per_vb_estimates(self):
    


    polespinasa commented at 5:39 pm on August 19, 2025:
    I was wondering if the new test could be avoided by defining low_feerate as MINIMUM_RELAY_FEE_RATE in the previous test test_estimation_modes.

    ismaelsadeeq commented at 6:03 pm on August 19, 2025:

    The test_sub_1s_per_vb_estimates is not even necessary hence can be removed because we have a general test in sanity_check_estimates_range.

    I added test_sub_1s_per_vb_estimates to convince reviewers and myself that it works.

    However that said, I don’t think its okay to bundle test together like this, but curious to know your opinion on why we would want to avoid adding the test.


    polespinasa commented at 6:10 pm on August 19, 2025:

    Less code the better :)

    It’s not a new or modified feature to be tested, it’s just a value that has been lowered.


    kevkevinpal commented at 11:54 am on August 26, 2025:
    Just an fyi, this test passes even without the changes in src/policy/fees.h I also agree with @polespinasa that if another test already covers this, then I’d rather not have redundant tests added

    ismaelsadeeq commented at 12:13 pm on September 1, 2025:

    This is a nice catch. The reason is that all transactions the fee estimator sees with a fee rate below 1 sat/vB are added to the 1–1.1 sat/vB fee rate bucket. When that bucket is the lowest with sufficient data, the average is taken, which allows it to pass.

    I modified the change to be more accurate by mixing in some 1 sat/vB transactions. This way, it will fail on master and also serve as a test case for that edge case.

    Hence I think we require this test I will resolve #33199 (review) cc @polespinasa

  5. polespinasa commented at 5:42 pm on August 19, 2025: member

    concept ACK

    This change makes much sense to me, if the min relay fee is lowered the feerate tracking should also take that into account.

  6. in src/policy/fees/block_policy_estimator.h:186 in 413c81b134 outdated
    180@@ -180,13 +181,10 @@ class CBlockPolicyEstimator : public CValidationInterface
    181     static constexpr double SUFFICIENT_TXS_SHORT = 0.5;
    182 
    183     /** Minimum and Maximum values for tracking feerates
    184-     * The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
    185-     * might ever want to track.  Historically this has been 1000 since it was
    186-     * inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
    187-     * invalidates old estimates files. So leave it at 1000 unless it becomes
    


    kevkevinpal commented at 11:58 am on August 26, 2025:
    I am not sure if we need a release note, since according to this note, it will invalidate old estimates files? Maybe someone else can chime in on this

    ismaelsadeeq commented at 12:05 pm on September 1, 2025:
    Done
  7. kevkevinpal commented at 11:59 am on August 26, 2025: contributor

    Concept ACK 413c81b

    It makes sense to update MIN_BUCKET_FEERATE with the recent changes to be sub 1 sat/vbyte

    I do think the additional test is a bit redundant but I am okay in adding if others think it makes sense

  8. maflcko commented at 10:52 am on August 28, 2025: member
    This picks up #13990? If yes, it may be good to explain the differences.
  9. maflcko added the label TX fees and policy on Aug 28, 2025
  10. ismaelsadeeq force-pushed on Sep 1, 2025
  11. ismaelsadeeq force-pushed on Sep 1, 2025
  12. ismaelsadeeq commented at 1:28 pm on September 1, 2025: member

    This picks up #13990? If yes, it may be good to explain the differences.

    This is different from #13990 in approach:

    It omits the consistency check and avoids extending the fee rate buckets when the min bucket fee rate changes. Instead, it builds on #29702 by simply bumping the fees file version, which ensures that estimates saved in previous files are not read. As such after a restart, the estimator starts afresh.

  13. in doc/release-notes-33199.md:7 in 0eb02ea1a3 outdated
    0@@ -0,0 +1,9 @@
    1+Fee Estimation
    2+========================
    3+
    4+- The Bitcoin Core fee estimator minimum fee rate bucket was updated from **1 sat/vB** to **0.1 sat/vB**,
    5+  which matches the node’s default `minrelayfee`.
    6+  This means that for a given confirmation target, if a sub-1 sat/vB fee rate bucket is the minimum tracked
    7+  with sufficient data, it's average value will be returned as the fee rate estimate.
    


    maflcko commented at 2:17 pm on September 1, 2025:
    with sufficient data, it's average value will be returned as the fee rate estimate. -> its average value will be returned as the fee rate estimate. ["it's" (contraction of "it is") is incorrect here; the possessive "its" is required.]
    

    ismaelsadeeq commented at 5:08 pm on October 29, 2025:
    Fixed
  14. in test/functional/feature_fee_estimation.py:499 in 0eb02ea1a3 outdated
    495         self.clear_estimates()
    496         self.log.info("Test estimatesmartfee modes")
    497         self.test_estimation_modes()
    498 
    499+        self.clear_estimates()
    500+        self.log.info("Test that estimatesmartfee return sub 1s/vb fee rate estimate")
    


    maflcko commented at 2:17 pm on September 1, 2025:
     self.log.info("Test that estimatesmartfee return sub 1s/vb fee rate estimate") -> self.log.info("Test that estimatesmartfee returns a sub 1s/vb fee rate estimate") [subject-verb agreement and article: "estimatesmartfee return" should be "estimatesmartfee returns" and include "a" for clarity.]
    

    ismaelsadeeq commented at 5:08 pm on October 29, 2025:
    Fixed
  15. DrahtBot added the label Needs rebase on Oct 28, 2025
  16. ismaelsadeeq force-pushed on Oct 29, 2025
  17. ismaelsadeeq force-pushed on Oct 29, 2025
  18. ismaelsadeeq commented at 5:09 pm on October 29, 2025: member
    Rebased and fixed nits.
  19. DrahtBot removed the label Needs rebase on Oct 29, 2025
  20. in src/policy/fees/block_policy_estimator.h:185 in 7348685f4f outdated
    181@@ -181,13 +182,10 @@ class CBlockPolicyEstimator : public CValidationInterface
    182     static constexpr double SUFFICIENT_TXS_SHORT = 0.5;
    183 
    184     /** Minimum and Maximum values for tracking feerates
    185-     * The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we
    186-     * might ever want to track.  Historically this has been 1000 since it was
    187-     * inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
    188-     * invalidates old estimates files. So leave it at 1000 unless it becomes
    189-     * necessary to lower it, and then lower it substantially.
    190+     * The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate
    


    polespinasa commented at 5:23 pm on October 29, 2025:
    nit: I would keep the original first sentence: The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate we might want to track + which should be DEFAULT_MIN_RELAY_TX_FEE.

    ismaelsadeeq commented at 2:39 pm on November 7, 2025:
    What you mentioned is exactly what the comment is now.
  21. in src/policy/fees/block_policy_estimator.cpp:36 in a1a1bfa244 outdated
    32@@ -33,8 +33,8 @@
    33 #include <utility>
    34 
    35 // The current format written, and the version required to read. Must be
    36-// increased to at least 289900+1 on the next breaking change.
    37-constexpr int CURRENT_FEES_FILE_VERSION{149900};
    38+// increased to at least 309900+1 on the next breaking change.
    


    polespinasa commented at 5:28 pm on October 29, 2025:

    Is a1a1bfa2445da11369323b649253f81d288f2813 necessary? Won’t old versions be able to read the file even if estimations are sub 1sat/vb?

    just curiosity, how is this number chosen? Related to release number?


    ismaelsadeeq commented at 2:43 pm on November 7, 2025:

    Is a1a1bfa necessary? Won’t old versions be able to read the file even if estimations are sub 1sat/vb?

    Yes it is necessary, old clients won’t be able to read the file saved with new client, and new client won’t also be able to read the file saved by old client see #33199 (comment) comment with a linked PR that allows for that approach.

    just curiosity, how is this number chosen? Related to release number?

    Yes but not necessary can be any number that can be bumped to indicate a breaking change like the one in previous commit.

  22. in src/policy/fees/block_policy_estimator.cpp:983 in a1a1bfa244 outdated
    979@@ -980,7 +980,6 @@ bool CBlockPolicyEstimator::Write(AutoFile& fileout) const
    980     try {
    981         LOCK(m_cs_fee_estimator);
    982         fileout << CURRENT_FEES_FILE_VERSION;
    983-        fileout << int{0}; // Unused dummy field. Written files may contain any value in [0, 289900]
    


    polespinasa commented at 6:23 pm on October 29, 2025:
    this changes feel out of scope for this pr

    ismaelsadeeq commented at 2:50 pm on November 7, 2025:
    I am doing a breaking change here so I deleted it because it is indeed dummy and unused. But you are right it is unrelated. I add it in a separate commit.
  23. polespinasa commented at 6:23 pm on October 29, 2025: member
    Just some nits and questions :=)
  24. ismaelsadeeq force-pushed on Nov 7, 2025
  25. ismaelsadeeq commented at 2:51 pm on November 7, 2025: member

    Just some nits and questions :=)

    I force pushed to address the comment, thanks for review @polespinasa

  26. musaHaruna commented at 3:08 pm on December 13, 2025: contributor

    Concept ACK (code review up to b767897)

    I’m new to fee estimation, but I was able to understand this PR because it is fairly straightforward.

    The idea is to make DEFAULT_MIN_RELAY_TX_FEE i.e the minimum feerate a node is willing to relay and accept into its mempool by default equal to MIN_BUCKET_FEERATE, which is used by the fee estimator (CBlockPolicyEstimator).

    By default, if a transaction’s feerate is below DEFAULT_MIN_RELAY_TX_FEE, the node: will not relay it, and will not keep it in its mempool.

    Nodes are free to choose a different value via the -minrelaytxfee option, but this value (DEFAULT_MIN_RELAY_TX_FEE) defines the default policy.

    MIN_BUCKET_FEERATE lives in the fee estimator and defines the lowest feerate bucket that the estimator tracks. Any transaction with a feerate below this value was previously ignored by the estimator. Importantly, MIN_BUCKET_FEERATE does not reject transactions and does not affect relay or mempool policy it only affects what data the fee estimator records.

    Before this PR mempool policy accepts transactions down to DEFAULT_MIN_RELAY_TX_FEE = 100 (i.e. 1 sat/vB). while fee estimator (CBlockPolicyEstimator) Starts tracking fees at a higher minimum: MIN_BUCKET_FEERATE = 1000 (i.e. 10 sat/vB).

    As a result, transactions that were perfectly valid, accepted, and relayed by the node were not recorded by the fee estimator. Therefore the estimator could not learn from low-fee traffic.

    By making: MIN_BUCKET_FEERATE == DEFAULT_MIN_RELAY_TX_FEE the fee estimator now tracks all transactions the node is willing to relay, including sub-1 sat/vB transactions (i.e. transactions with feerates below 1 sat/vB, if the relay policy allows them. Not sure if am correct on that), and can return lower and more accurate fee estimates.

    The change is correct because the estimator creates buckets of fee rates from MIN_BUCKET_FEERATE, MIN_BUCKET_FEERATE + FEE_SPACING, MIN_BUCKET_FEERATE + 2 * FEE_SPACING, … up to MAX_BUCKET_FEERATE.

    Confirmed this behaviour by digging into CBlockPolicyEstimator bucket construction logic.

  27. in test/functional/feature_fee_estimation.py:183 in b76789787c outdated
    179+            self.memutxo = newmem
    180+
    181     def transact_and_mine(self, numblocks, mining_node):
    182-        min_fee = Decimal("0.00001")
    183+        min_fee = MINIMUM_RELAY_FEE_RATE
    184         # We will now mine numblocks blocks generating on average 100 transactions between each block
    


    musaHaruna commented at 3:23 pm on December 13, 2025:

    nit: feel free to ignore

    0        # We will now mine "numblocks" blocks, generating on average 100 transactions between each block
    

    I think makes it easier to read


    ismaelsadeeq commented at 10:45 am on February 5, 2026:
    I did not touch the line, so leaving it as is.
  28. in src/policy/fees/block_policy_estimator.cpp:1007 in 44d25dcaed outdated
    1002@@ -1004,8 +1003,8 @@ bool CBlockPolicyEstimator::Read(AutoFile& filein)
    1003 {
    1004     try {
    1005         LOCK(m_cs_fee_estimator);
    1006-        int nVersionRequired, dummy;
    1007-        filein >> nVersionRequired >> dummy;
    1008+        int nVersionRequired;
    1009+        filein >> nVersionRequired;
    


    polespinasa commented at 9:56 am on January 7, 2026:
    I would add a small comment on the PR description saying that this change is also happening.

    ismaelsadeeq commented at 2:49 pm on January 12, 2026:
    done, thanks.
  29. polespinasa commented at 10:09 am on January 7, 2026: member

    code looks good to me, currently trying to test it on mainnet will ack if positive results :)

    a small nit for more clarity on the PR

  30. polespinasa approved
  31. polespinasa commented at 11:57 am on January 7, 2026: member
    cr and tACK b76789787c43802aaf1207813792ea49ecf18f55
  32. DrahtBot requested review from musaHaruna on Jan 7, 2026
  33. DrahtBot requested review from kevkevinpal on Jan 7, 2026
  34. achow101 commented at 10:09 pm on February 3, 2026: member
    ACK b76789787c43802aaf1207813792ea49ecf18f55
  35. in src/policy/fees/block_policy_estimator.h:188 in b76789787c
    189-     * necessary to lower it, and then lower it substantially.
    190+     * The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate
    191+     * which should be DEFAULT_MIN_RELAY_TX_FEE.
    192      */
    193-    static constexpr double MIN_BUCKET_FEERATE = 1000;
    194+    static constexpr double MIN_BUCKET_FEERATE = DEFAULT_MIN_RELAY_TX_FEE;
    


    maflcko commented at 2:49 pm on February 4, 2026:
    This will silently break the format, when the default is changed? I think it would be better to keep this hard-coded, but keep the comment to encourage to adjust this to match the default rate, in a new change along with bumping CURRENT_FEES_FILE_VERSION+=1

  36. ismaelsadeeq force-pushed on Feb 5, 2026
  37. ismaelsadeeq commented at 10:28 am on February 5, 2026: member

    Forced pushed from b76789787c43802aaf1207813792ea49ecf18f55 to bb0bf4ac18024521f7de6969a501e03198c44982 compare diff ecf18f55..bb0bf4ac18

    I addressed @maflcko comment in #33199 (review). Apart from not wanting to silently break the old fee estimate file, we might want to wait a bit before updating the block policy estimator until the new min relay fee update in the network gets some adoption.

    Thanks for review @polespinasa @musaHaruna @achow101 @maflcko

  38. in src/policy/fees/block_policy_estimator.h:190 in bb0bf4ac18
    190+     * The MIN_BUCKET_FEERATE has been historically inheriting DEFAULT_MIN_RELAY_TX_FEE.
    191+     * It is hardcoded because changing it is disruptive, as it invalidates existing fee
    192+     * estimate files.
    193+     *
    194+     * Whenever DEFAULT_MIN_RELAY_TX_FEE changes, this value should be updated
    195+     * accordingly.
    


    maflcko commented at 10:31 am on February 5, 2026:
    0     * accordingly. At the same time CURRENT_FEES_FILE_VERSION should be bumped.
    

    nit: Would make sense to mention this?

  39. in src/policy/fees/block_policy_estimator.h:185 in bb0bf4ac18
    185-     * might ever want to track.  Historically this has been 1000 since it was
    186-     * inheriting DEFAULT_MIN_RELAY_TX_FEE and changing it is disruptive as it
    187-     * invalidates old estimates files. So leave it at 1000 unless it becomes
    188-     * necessary to lower it, and then lower it substantially.
    189+     * The MIN_BUCKET_FEERATE should just be set to the lowest reasonable feerate.
    190+     * The MIN_BUCKET_FEERATE has been historically inheriting DEFAULT_MIN_RELAY_TX_FEE.
    


    maflcko commented at 10:33 am on February 5, 2026:

    LLM:

    • “has been historically inheriting” → “has historically inherited”: The present perfect continuous (“has been inheriting”) is not necessary here; the simple present perfect (“has inherited”) is more natural for describing a state or practice that has been true up to now.
  40. maflcko commented at 10:34 am on February 5, 2026: member
    Left some tiny nits. Feel free to ignore/close/dismiss.
  41. fees: reduce `MIN_BUCKET_FEERATE` to 100
    - The DEFAULT_MIN_RELAY_TX_FEE has been reduced to
      100 in v30. This change updates block policy fee estimator
      to reflect that change.
    b54dedcc85
  42. fees: bump fees file version fc4fbda42a
  43. fees: delete unused dummy field
    - This commit deletes a dummy field that is
      "dummy" and unused.
    243e48cf49
  44. test: ensure fee estimator provide fee rate estimate < 1 s/vb
    - This commit also refactors the test the better track confirmed
      utxos.
    704a09fe71
  45. doc: add release notes 8966352df3
  46. ismaelsadeeq force-pushed on Feb 5, 2026
  47. DrahtBot added the label CI failed on Feb 5, 2026
  48. ismaelsadeeq commented at 10:55 am on February 5, 2026: member

    Forced pushed from bb0bf4ac18024521f7de6969a501e03198c44982 to 8966352df3fc56fd2c00a45eecd292a240a34546 compare diff 8c44982..89663

    I Addressed LLM comment and nit from @maflcko

  49. DrahtBot removed the label CI failed on Feb 5, 2026
  50. achow101 commented at 9:39 pm on February 5, 2026: member
    ACK 8966352df3fc56fd2c00a45eecd292a240a34546
  51. DrahtBot requested review from polespinasa on Feb 5, 2026
  52. musaHaruna commented at 11:19 am on February 11, 2026: contributor
    ACK 8966352
  53. polespinasa approved
  54. polespinasa commented at 4:01 pm on February 12, 2026: member
    ACK 8966352df3fc56fd2c00a45eecd292a240a34546
  55. ismaelsadeeq commented at 3:57 pm on February 19, 2026: member
    It would be nice to have this in v31.0
  56. achow101 added this to the milestone 31.0 on Feb 19, 2026
  57. achow101 merged this on Feb 19, 2026
  58. achow101 closed this on Feb 19, 2026

  59. ismaelsadeeq deleted the branch on Feb 19, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-02-26 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me