Coverage Report

Created: 2026-10-07 13:27

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/rpc/fees.cpp
Line
Count
Source
1
// Copyright (c) 2010 Satoshi Nakamoto
2
// Copyright (c) 2009-present The Bitcoin Core developers
3
// Distributed under the MIT software license, see the accompanying
4
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
6
#include <util/fees.h>
7
8
#include <common/messages.h>
9
#include <core_io.h>
10
#include <node/context.h>
11
#include <policy/feerate.h>
12
#include <policy/fees/block_policy_estimator.h>
13
#include <policy/fees/estimator_man.h>
14
#include <policy/fees/mempool_estimator.h>
15
#include <rpc/protocol.h>
16
#include <rpc/register.h> // IWYU pragma: associated
17
#include <rpc/request.h>
18
#include <rpc/server.h>
19
#include <rpc/server_util.h>
20
#include <rpc/util.h>
21
#include <txmempool.h>
22
#include <univalue.h>
23
#include <util/check.h>
24
#include <util/expected.h>
25
#include <validationinterface.h>
26
27
#include <algorithm>
28
#include <array>
29
#include <cmath>
30
#include <map>
31
#include <optional>
32
#include <string>
33
#include <string_view>
34
#include <utility>
35
#include <vector>
36
37
using common::FeeModeFromString;
38
using common::FeeModesDetail;
39
using common::InvalidEstimateModeErrorMessage;
40
using node::NodeContext;
41
42
static RPCMethod estimatesmartfee()
43
2.75k
{
44
2.75k
    return RPCMethod{
45
2.75k
        "estimatesmartfee",
46
2.75k
        "Estimates the approximate fee per kilobyte needed for a transaction to begin\n"
47
2.75k
        "confirmation within conf_target blocks if possible and return the number of blocks\n"
48
2.75k
        "for which the estimate is valid. Uses virtual transaction size as defined\n"
49
2.75k
        "in BIP 141 (witness data is discounted).\n",
50
2.75k
        {
51
2.75k
            {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
52
2.75k
            {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
53
2.75k
              + FeeModesDetail(std::string("default mode will be used"))},
54
2.75k
            {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
55
2.75k
                {
56
2.75k
                    {"fee_rate_estimator", RPCArg::Type::STR, RPCArg::Default{"auto"},
57
2.75k
                     "Selects which fee rate estimator to use.\n"
58
2.75k
                     "\"auto\" (the default) combines both estimators, returning the lower of the block policy and\n"
59
2.75k
                     "mempool fee rate estimates. If the mempool fee rate estimator cannot produce an estimate, the\n"
60
2.75k
                     "block policy fee rate estimate is returned instead. An error is returned only if the block\n"
61
2.75k
                     "policy fee rate estimate is unavailable.\n"
62
2.75k
                     "\"block_policy\" uses only the block policy fee rate estimator.\n"
63
2.75k
                     "\"mempool_policy\" uses only the mempool fee rate estimator."},
64
2.75k
                    {"verbosity", RPCArg::Type::NUM, RPCArg::Default{1},
65
2.75k
                     "1 returns feerate or errors. 2 also returns \"mempool_health_statistics\"."},
66
2.75k
                },
67
2.75k
            },
68
2.75k
        },
69
2.75k
        RPCResult{
70
2.75k
            RPCResult::Type::OBJ, "", "",
71
2.75k
            {
72
2.75k
                {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
73
2.75k
                {RPCResult::Type::STR, "estimator", /*optional=*/true, "the fee estimator used to produce the result (only present for successful estimates when fee_rate_estimator is \"auto\")"},
74
2.75k
                {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
75
2.75k
                    {
76
2.75k
                        {RPCResult::Type::STR, "", "error"},
77
2.75k
                    }},
78
2.75k
                {RPCResult::Type::NUM, "blocks", "the confirmation target in blocks for the returned fee rate estimate.\n"
79
2.75k
                "For the block policy fee rate estimator, this is the target the estimate was found at, clamped to at\n"
80
2.75k
                "least 2 and at most the estimator's maximum usable target. For the mempool fee rate\n"
81
2.75k
                "estimator, it is always 2."},
82
2.75k
                {RPCResult::Type::ARR, "mempool_health_statistics", /*optional=*/true, "Health statistics for the most recently mined blocks tracked by the mempool fee rate estimator (only present when verbosity >= 2)",
83
2.75k
                    {
84
2.75k
                        {RPCResult::Type::OBJ, "", "",
85
2.75k
                            {
86
2.75k
                                {RPCResult::Type::NUM, "block_height", "Block height"},
87
2.75k
                                {RPCResult::Type::NUM, "block_weight", "Total weight of non-coinbase transactions in the block"},
88
2.75k
                                {RPCResult::Type::NUM, "mempool_txs_weight", "Total weight of transactions removed from the mempool for this block"},
89
2.75k
                            }},
90
2.75k
                    }},
91
2.75k
        }},
92
2.75k
        RPCExamples{
93
2.75k
            HelpExampleCli("estimatesmartfee", "6") +
94
2.75k
            HelpExampleRpc("estimatesmartfee", "6")
95
2.75k
        },
96
2.75k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
97
2.75k
        {
98
210
            FeeRateEstimatorManager& fee_estimator_man = EnsureAnyFeeEstimatorMan(request.context);
99
210
            const NodeContext& node = EnsureAnyNodeContext(request.context);
100
210
            const CTxMemPool& mempool = EnsureMemPool(node);
101
102
210
            CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
103
210
            unsigned int max_target = fee_estimator_man.MaximumTarget();
104
210
            unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
105
210
            FeeEstimateMode fee_mode;
106
210
            if (!FeeModeFromString(self.Arg<std::string_view>("estimate_mode"), fee_mode)) {
107
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
108
1
            }
109
209
            const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]};
110
209
            RPCTypeCheckObj(options,
111
209
                            {
112
209
                                {"fee_rate_estimator", UniValueType(UniValue::VSTR)},
113
209
                                {"verbosity", UniValueType(UniValue::VNUM)},
114
209
                            }, /*fAllowNull=*/true, /*fStrict=*/true);
115
209
            const auto parsed_fee_rate_estimator{FeeRateEstimatorTypeFromString(
116
209
                options["fee_rate_estimator"].isNull() ? "auto" : options["fee_rate_estimator"].get_str())};
117
209
            if (!parsed_fee_rate_estimator) {
118
3
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid fee_rate_estimator parameter, must be one of: \"auto\", \"block_policy\", \"mempool_policy\"");
119
3
            }
120
206
            const FeeRateEstimatorType fee_rate_estimator{*parsed_fee_rate_estimator};
121
206
            bool conservative{fee_mode == FeeEstimateMode::CONSERVATIVE};
122
206
            int verbosity{ParseVerbosity(options["verbosity"], /*default_verbosity=*/1, /*allow_bool=*/false)};
123
206
            UniValue result(UniValue::VOBJ);
124
206
            UniValue errors(UniValue::VARR);
125
206
            const auto estimate{fee_estimator_man.GetFeeRateEstimate(fee_rate_estimator, conf_target, conservative)};
126
206
            if (estimate) {
127
183
                const CFeeRate min_mempool_feerate{mempool.GetMinFee()};
128
183
                const CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
129
183
                const auto fee_rate{std::max({CFeeRate(estimate->feerate), min_mempool_feerate, min_relay_feerate})};
130
183
                result.pushKV("feerate", ValueFromAmount(fee_rate.GetFeePerK()));
131
183
            } else {
132
23
                errors.push_back(estimate.error().reason);
133
23
                result.pushKV("errors", std::move(errors));
134
23
            }
135
206
            if (estimate && fee_rate_estimator == FeeRateEstimatorType::AUTO) {
136
14
                result.pushKV("estimator", FeeRateEstimatorTypeToString(estimate->feerate_estimator));
137
14
            }
138
206
            const FeeRateEstimation& estimation{FeeRateEstimationRef(estimate)};
139
206
            result.pushKV("blocks", estimation.returned_target);
140
206
            if (verbosity >= 2) {
141
8
                UniValue mempool_health_stats(UniValue::VARR);
142
8
                const auto blocks_data = fee_estimator_man.MempoolPolicyEstimatorBlocksStats();
143
32
                for (auto it = blocks_data.rbegin(); it != blocks_data.rend(); ++it) {
144
24
                    UniValue entry(UniValue::VOBJ);
145
24
                    entry.pushKV("block_height", it->m_height);
146
24
                    entry.pushKV("block_weight", it->m_block_weight);
147
24
                    entry.pushKV("mempool_txs_weight", it->m_removed_block_txs_weight);
148
24
                    mempool_health_stats.push_back(std::move(entry));
149
24
                }
150
8
                result.pushKV("mempool_health_statistics", std::move(mempool_health_stats));
151
8
            }
152
206
            return result;
153
209
        },
154
2.75k
    };
155
2.75k
}
156
157
static std::vector<RPCResult> FeeRateBucketDoc(bool elide = false)
158
15.9k
{
159
15.9k
    auto fields = std::vector<RPCResult>{
160
15.9k
        {RPCResult::Type::NUM, "startrange", "start of feerate range"},
161
15.9k
        {RPCResult::Type::NUM, "endrange", "end of feerate range"},
162
15.9k
        {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
163
15.9k
        {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
164
15.9k
        {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
165
15.9k
        {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
166
15.9k
    };
167
15.9k
    return elide ? ElideGroup(std::move(fields)) : fields;
168
15.9k
}
169
170
static std::vector<RPCResult> FeeEstimateHorizonDoc(bool elide = false)
171
7.97k
{
172
7.97k
    auto fields = std::vector<RPCResult>{
173
7.97k
        {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
174
7.97k
        {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
175
7.97k
        {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
176
7.97k
        {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", FeeRateBucketDoc()},
177
7.97k
        {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", FeeRateBucketDoc(/*elide=*/true)},
178
7.97k
        {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
179
7.97k
        {
180
7.97k
            {RPCResult::Type::STR, "", "error"},
181
7.97k
        }},
182
7.97k
    };
183
7.97k
    return elide ? ElideGroup(std::move(fields)) : fields;
184
7.97k
}
185
186
static RPCMethod estimaterawfee()
187
2.65k
{
188
2.65k
    return RPCMethod{
189
2.65k
        "estimaterawfee",
190
2.65k
        "WARNING: This interface is unstable and may disappear or change!\n"
191
2.65k
        "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
192
2.65k
        "implementation of fee estimation. The parameters it can be called with\n"
193
2.65k
        "and the results it returns will change if the internal implementation changes.\n"
194
2.65k
        "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
195
2.65k
        "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
196
2.65k
        "defined in BIP 141 (witness data is discounted).\n",
197
2.65k
        {
198
2.65k
            {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
199
2.65k
            {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n"
200
2.65k
            "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n"
201
2.65k
            "lower buckets."},
202
2.65k
        },
203
2.65k
        RPCResult{
204
2.65k
            RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
205
2.65k
            {
206
2.65k
                {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
207
2.65k
                    FeeEstimateHorizonDoc()},
208
2.65k
                {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
209
2.65k
                    FeeEstimateHorizonDoc(/*elide=*/true)},
210
2.65k
                {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
211
2.65k
                    FeeEstimateHorizonDoc(/*elide=*/true)},
212
2.65k
            }},
213
2.65k
        RPCExamples{
214
2.65k
            HelpExampleCli("estimaterawfee", "6 0.9")
215
2.65k
        },
216
2.65k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
217
2.65k
        {
218
129
            FeeRateEstimatorManager& fee_estimator_man = EnsureAnyFeeEstimatorMan(request.context);
219
129
            const NodeContext& node = EnsureAnyNodeContext(request.context);
220
221
129
            CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
222
129
            unsigned int max_target = fee_estimator_man.MaximumTarget();
223
129
            unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
224
129
            double threshold = 0.95;
225
129
            if (!request.params[1].isNull()) {
226
1
                threshold = request.params[1].get_real();
227
1
            }
228
129
            if (threshold < 0 || threshold > 1) {
229
0
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
230
0
            }
231
232
129
            UniValue result(UniValue::VOBJ);
233
234
384
            for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
235
384
                CFeeRate feeRate;
236
384
                EstimationResult buckets;
237
238
                // Only output results for horizons which track the target
239
384
                if (conf_target > fee_estimator_man.BlockPolicyHighestTargetTracked(horizon)) continue;
240
241
319
                feeRate = fee_estimator_man.BlockPolicyEstimateRawFee(conf_target, threshold, horizon, &buckets);
242
319
                UniValue horizon_result(UniValue::VOBJ);
243
319
                UniValue errors(UniValue::VARR);
244
319
                UniValue passbucket(UniValue::VOBJ);
245
319
                passbucket.pushKV("startrange", round(buckets.pass.start));
246
319
                passbucket.pushKV("endrange", round(buckets.pass.end));
247
319
                passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
248
319
                passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
249
319
                passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
250
319
                passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
251
319
                UniValue failbucket(UniValue::VOBJ);
252
319
                failbucket.pushKV("startrange", round(buckets.fail.start));
253
319
                failbucket.pushKV("endrange", round(buckets.fail.end));
254
319
                failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
255
319
                failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
256
319
                failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
257
319
                failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
258
259
                // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
260
319
                if (feeRate != CFeeRate(0)) {
261
310
                    horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
262
310
                    horizon_result.pushKV("decay", buckets.decay);
263
310
                    horizon_result.pushKV("scale", buckets.scale);
264
310
                    horizon_result.pushKV("pass", std::move(passbucket));
265
                    // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
266
310
                    if (buckets.fail.start != -1) horizon_result.pushKV("fail", std::move(failbucket));
267
310
                } else {
268
                    // Output only information that is still meaningful in the event of error
269
9
                    horizon_result.pushKV("decay", buckets.decay);
270
9
                    horizon_result.pushKV("scale", buckets.scale);
271
9
                    horizon_result.pushKV("fail", std::move(failbucket));
272
9
                    errors.push_back("Insufficient data or no feerate found which meets threshold");
273
9
                    horizon_result.pushKV("errors", std::move(errors));
274
9
                }
275
319
                result.pushKV(StringForFeeEstimateHorizon(horizon), std::move(horizon_result));
276
319
            }
277
129
            return result;
278
129
        },
279
2.65k
    };
280
2.65k
}
281
282
void RegisterFeeRPCCommands(CRPCTable& t)
283
1.40k
{
284
1.40k
    static const CRPCCommand commands[]{
285
1.40k
        {"util", &estimatesmartfee},
286
1.40k
        {"hidden", &estimaterawfee},
287
1.40k
    };
288
2.81k
    for (const auto& c : commands) {
289
2.81k
        t.appendCommand(c.name, &c);
290
2.81k
    }
291
1.40k
}