Coverage Report

Created: 2026-09-02 14:16

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