### Bitcoin Core Fee Estimation Design Doc #### Pure Abstrac Class Approach We introduce an abstract class `Forecaster` from which all fee rate forecasters will be derived. Each derived class must override the virtual functions defined in `Forecaster`. ```mermaid classDiagram class Forecaster { +ForecastType m_forecastType* +virtual ForecastResult EstimateFee(ConfirmationTarget target)* +virtual unsigned int MaxTarget()* } ``` - `ForecastType`: An enum representing the type of forecaster for identification. - `virtual ForecastResult EstimateFee(ConfirmationTarget)*`: Estimates the fee rate required for a transaction to likely confirm in a given target. Confirmation target is also a struct containing an enum that represents the type of confirmation target, whether it's in blocks or in time (e.g., hours). This allows for forward compatibility when a new forecaster that takes time as an input is introduced. Currently, we have implemented one forecaster: **Mempool-based forecaster (`MemPoolForecaster`)**: This forecaster is derived from the `Forecaster` abstract class. The `MemPoolForecaster` estimates fees by generating a block template from the node's mempool. For this, it owns pointers to the mempool and chainstate. It returns the 50th and 75th percentile mining scores as high-priority and low-priority fee estimates, respectively. ```mermaid classDiagram class Forecaster { +ForecastType m_forecastType +virtual ForecastResult EstimateFee(ConfirmationTarget target)* } class MemPoolForecaster { +CTxMemPool* m_mempool +Chainstate* m_chainstate +ForecastResult EstimateFee(ConfirmationTarget target) override } Forecaster <|-- MemPoolForecaster ``` The rationale behind this is to allow easy extension by adding new forecasters. Each new forecaster will simply derive from the `Forecaster` abstract class, implement the required methods, and own whatever data source is needed from the various components of bitcoind. #### `FeeEstimator` Class The `FeeEstimator` class is a new addition available in the node interfaces for use by RPCs and wallets. It owns unique pointers to all available forecasters and is responsible for obtaining estimates from them and making informed decisions. The `FeeEstimator` also holds a pointer to the mempool to perform sanity checks and determine which forecaster to use when estimates are requested. Additionally, it holds a `CBlockPolicyEstimator` pointer. RPCs and wallets will now access the `CBlockPolicyEstimator` through the `FeeEstimator` unique pointer in the node interface. This PR deliberately does **not** modify the existing fee estimation code but instead introduces new modules with cleaner separation. This approach facilitates the introduction of new code and interfaces and makes it easier to phase out the `CBlockPolicyEstimator` code in the future if desired. ```mermaid classDiagram class FeeEstimator { +CTxMemPool* m_mempool +map<ForecastType, std::shared_ptr<Forecaster>> forecasters +CBlockPolicyEstimator* block_policy_estimator +void RegisterForecaster(std::shared_ptr<Forecaster>) +ForecastResult GetFeeEstimateFromForecasters(ConfirmationTarget target) -ForecastResult GetPolicyEstimatorEstimate(ConfirmationTarget target) } class Forecaster { +ForecastType m_forecastType +virtual ForecastResult EstimateFee(ConfirmationTarget target)* } class MemPoolForecaster { +CTxMemPool* m_mempool +Chainstate* m_chainstate +ForecastResult EstimateFee(ConfirmationTarget target) override } class CBlockPolicyEstimator { +CFeeRate estimateSmartFee(unsigned int, FeeCalculation*, bool) } FeeEstimator --> Forecaster FeeEstimator --> CBlockPolicyEstimator Forecaster <|-- MemPoolForecaster ``` Whenever a fee estimate is required, the `FeeEstimator` determines the appropriate `Forecaster` to use. ##### Flowchart ```mermaid graph TD; A[User requests fee estimate] --> B[FeeEstimator receives request] B --> C{Sanity checks} C -->|Fail| D[Return error message] C -->|Pass| E[Call a forecaster and PolicyEstimator for estimates] E --> F[Compare forecaster estimate with PolicyEstimator estimate] F --> G[Return lowest estimate] style A fill:#f9f,stroke:#333,stroke-width:2px; style B fill:#ff9,stroke:#333,stroke-width:2px; style C fill:#9f9,stroke:#333,stroke-width:2px; style D fill:#f96,stroke:#333,stroke-width:2px; style E fill:#9ff,stroke:#333,stroke-width:2px; style F fill:#9f9,stroke:#333,stroke-width:2px; style G fill:#ff9,stroke:#333,stroke-width:2px; ``` `MempoolForecaster` estimates fees for (1, 2) block confirmation targets, referred to as "as soon as possible (ASAP)" fee rate estimates. Using RBF (Replace-By-Fee) is strongly recommended so that transactions can be fee-bumped if desired. #### Next Steps - Extend the feature to the wallet; currently, it's only exposed in an RPC `estimatefee`. - Update forecasters to perform sanity checks (post-cluster mempool). ### FAQ 1. **Will miners broadcast high-fee-rate transactions and then evict them with a conflicting transaction in the next block, tricking users of this fee estimator into making transactions with high fees?** This PR addresses this concern by ensuring that the fee estimate does not surge immediately whenever this happens. The mempool-based forecaster estimate will be higher than `CBlockPolicyEstimator`. As such, the estimate from `CBlockPolicyEstimator` will be returned, preventing users from being victims of this attack. 2. **In times of high confirmation times or high block space demand, fee estimates tend to rise. If the mempool-based forecaster estimate rises, the fee estimator will fall back to `CBlockPolicyEstimator`. If it's low, will that make transactions constructed with the estimate get stuck in the mempool?** The ASAP fee estimate provides an estimate for transactions to confirm in the next one or two blocks. During periods of high confirmation times, fee rates rise due to congestion. Once a block is mined, the mempool clears high mining score transactions, making room for lower-fee-rate transactions. If congestion recurs and the transaction is time-sensitive, users can safely fee bump, assuming it's an RBF-enabled transaction. In times of immediate high block demand, if the mempool-based fee estimate surges while `CBlockPolicyEstimator` estimates are low, users can fee bump their transactions if they take longer to confirm. As more blocks are mined and demand decreases, the mempool forecaster will immediately respond to the new conditions by providing a lower fee estimate, and the fee estimator will return it instead. 3. **Currently, getting a block template is expensive. How do you tackle that in the mempool-based forecaster?** The forecaster caches the recently generated fee rate estimates and only generates a new block template when the time delta between the recent estimate and the current time exceeds 30 seconds.