Coverage Report

Created: 2026-09-14 20:36

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