Coverage Report

Created: 2026-07-23 20:35

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 <rpc/protocol.h>
12
#include <rpc/request.h>
13
#include <rpc/server.h>
14
#include <rpc/server_util.h>
15
#include <rpc/util.h>
16
#include <txmempool.h>
17
#include <univalue.h>
18
#include <util/fees.h>
19
#include <validationinterface.h>
20
21
#include <algorithm>
22
#include <array>
23
#include <cmath>
24
#include <string>
25
#include <string_view>
26
27
using common::FeeModeFromString;
28
using common::FeeModesDetail;
29
using common::InvalidEstimateModeErrorMessage;
30
using node::NodeContext;
31
32
static RPCMethod estimatesmartfee()
33
2.55k
{
34
2.55k
    return RPCMethod{
35
2.55k
        "estimatesmartfee",
36
2.55k
        "Estimates the approximate fee per kilobyte needed for a transaction to begin\n"
37
2.55k
        "confirmation within conf_target blocks if possible and return the number of blocks\n"
38
2.55k
        "for which the estimate is valid. Uses virtual transaction size as defined\n"
39
2.55k
        "in BIP 141 (witness data is discounted).\n",
40
2.55k
        {
41
2.55k
            {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
42
2.55k
            {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
43
2.55k
              + FeeModesDetail(std::string("default mode will be used"))},
44
2.55k
        },
45
2.55k
        RPCResult{
46
2.55k
            RPCResult::Type::OBJ, "", "",
47
2.55k
            {
48
2.55k
                {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
49
2.55k
                {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
50
2.55k
                    {
51
2.55k
                        {RPCResult::Type::STR, "", "error"},
52
2.55k
                    }},
53
2.55k
                {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n"
54
2.55k
                "The request target will be clamped between 2 and the highest target\n"
55
2.55k
                "fee estimation is able to return based on how long it has been running.\n"
56
2.55k
                "An error is returned if not enough transactions and blocks\n"
57
2.55k
                "have been observed to make an estimate for any number of blocks."},
58
2.55k
        }},
59
2.55k
        RPCExamples{
60
2.55k
            HelpExampleCli("estimatesmartfee", "6") +
61
2.55k
            HelpExampleRpc("estimatesmartfee", "6")
62
2.55k
        },
63
2.55k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
64
2.55k
        {
65
173
            CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
66
173
            const NodeContext& node = EnsureAnyNodeContext(request.context);
67
173
            const CTxMemPool& mempool = EnsureMemPool(node);
68
69
173
            CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
70
173
            unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
71
173
            unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
72
173
            FeeEstimateMode fee_mode;
73
173
            if (!FeeModeFromString(self.Arg<std::string_view>("estimate_mode"), fee_mode)) {
74
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
75
1
            }
76
77
172
            UniValue result(UniValue::VOBJ);
78
172
            UniValue errors(UniValue::VARR);
79
172
            FeeCalculation feeCalc;
80
172
            bool conservative{fee_mode == FeeEstimateMode::CONSERVATIVE};
81
172
            CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
82
172
            if (feeRate != CFeeRate(0)) {
83
163
                CFeeRate min_mempool_feerate{mempool.GetMinFee()};
84
163
                CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
85
163
                feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
86
163
                result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
87
163
            } else {
88
9
                errors.push_back("Insufficient data or no feerate found");
89
9
                result.pushKV("errors", std::move(errors));
90
9
            }
91
172
            result.pushKV("blocks", feeCalc.returnedTarget);
92
172
            return result;
93
173
        },
94
2.55k
    };
95
2.55k
}
96
97
static std::vector<RPCResult> FeeRateBucketDoc(bool elide = false)
98
14.9k
{
99
14.9k
    auto fields = std::vector<RPCResult>{
100
14.9k
        {RPCResult::Type::NUM, "startrange", "start of feerate range"},
101
14.9k
        {RPCResult::Type::NUM, "endrange", "end of feerate range"},
102
14.9k
        {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
103
14.9k
        {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
104
14.9k
        {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
105
14.9k
        {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
106
14.9k
    };
107
14.9k
    return elide ? ElideGroup(std::move(fields)) : fields;
108
14.9k
}
109
110
static std::vector<RPCResult> FeeEstimateHorizonDoc(bool elide = false)
111
7.49k
{
112
7.49k
    auto fields = std::vector<RPCResult>{
113
7.49k
        {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
114
7.49k
        {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
115
7.49k
        {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
116
7.49k
        {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", FeeRateBucketDoc()},
117
7.49k
        {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", FeeRateBucketDoc(/*elide=*/true)},
118
7.49k
        {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
119
7.49k
        {
120
7.49k
            {RPCResult::Type::STR, "error", ""},
121
7.49k
        }},
122
7.49k
    };
123
7.49k
    return elide ? ElideGroup(std::move(fields)) : fields;
124
7.49k
}
125
126
static RPCMethod estimaterawfee()
127
2.49k
{
128
2.49k
    return RPCMethod{
129
2.49k
        "estimaterawfee",
130
2.49k
        "WARNING: This interface is unstable and may disappear or change!\n"
131
2.49k
        "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
132
2.49k
        "implementation of fee estimation. The parameters it can be called with\n"
133
2.49k
        "and the results it returns will change if the internal implementation changes.\n"
134
2.49k
        "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
135
2.49k
        "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
136
2.49k
        "defined in BIP 141 (witness data is discounted).\n",
137
2.49k
        {
138
2.49k
            {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
139
2.49k
            {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n"
140
2.49k
            "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n"
141
2.49k
            "lower buckets."},
142
2.49k
        },
143
2.49k
        RPCResult{
144
2.49k
            RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
145
2.49k
            {
146
2.49k
                {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
147
2.49k
                    FeeEstimateHorizonDoc()},
148
2.49k
                {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
149
2.49k
                    FeeEstimateHorizonDoc(/*elide=*/true)},
150
2.49k
                {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
151
2.49k
                    FeeEstimateHorizonDoc(/*elide=*/true)},
152
2.49k
            }},
153
2.49k
        RPCExamples{
154
2.49k
            HelpExampleCli("estimaterawfee", "6 0.9")
155
2.49k
        },
156
2.49k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
157
2.49k
        {
158
129
            CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
159
129
            const NodeContext& node = EnsureAnyNodeContext(request.context);
160
161
129
            CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
162
129
            unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
163
129
            unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
164
129
            double threshold = 0.95;
165
129
            if (!request.params[1].isNull()) {
166
1
                threshold = request.params[1].get_real();
167
1
            }
168
129
            if (threshold < 0 || threshold > 1) {
169
0
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
170
0
            }
171
172
129
            UniValue result(UniValue::VOBJ);
173
174
384
            for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
175
384
                CFeeRate feeRate;
176
384
                EstimationResult buckets;
177
178
                // Only output results for horizons which track the target
179
384
                if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
180
181
319
                feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
182
319
                UniValue horizon_result(UniValue::VOBJ);
183
319
                UniValue errors(UniValue::VARR);
184
319
                UniValue passbucket(UniValue::VOBJ);
185
319
                passbucket.pushKV("startrange", round(buckets.pass.start));
186
319
                passbucket.pushKV("endrange", round(buckets.pass.end));
187
319
                passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
188
319
                passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
189
319
                passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
190
319
                passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
191
319
                UniValue failbucket(UniValue::VOBJ);
192
319
                failbucket.pushKV("startrange", round(buckets.fail.start));
193
319
                failbucket.pushKV("endrange", round(buckets.fail.end));
194
319
                failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
195
319
                failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
196
319
                failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
197
319
                failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
198
199
                // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
200
319
                if (feeRate != CFeeRate(0)) {
201
310
                    horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
202
310
                    horizon_result.pushKV("decay", buckets.decay);
203
310
                    horizon_result.pushKV("scale", buckets.scale);
204
310
                    horizon_result.pushKV("pass", std::move(passbucket));
205
                    // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
206
310
                    if (buckets.fail.start != -1) horizon_result.pushKV("fail", std::move(failbucket));
207
310
                } else {
208
                    // Output only information that is still meaningful in the event of error
209
9
                    horizon_result.pushKV("decay", buckets.decay);
210
9
                    horizon_result.pushKV("scale", buckets.scale);
211
9
                    horizon_result.pushKV("fail", std::move(failbucket));
212
9
                    errors.push_back("Insufficient data or no feerate found which meets threshold");
213
9
                    horizon_result.pushKV("errors", std::move(errors));
214
9
                }
215
319
                result.pushKV(StringForFeeEstimateHorizon(horizon), std::move(horizon_result));
216
319
            }
217
129
            return result;
218
129
        },
219
2.49k
    };
220
2.49k
}
221
222
void RegisterFeeRPCCommands(CRPCTable& t)
223
1.30k
{
224
1.30k
    static const CRPCCommand commands[]{
225
1.30k
        {"util", &estimatesmartfee},
226
1.30k
        {"hidden", &estimaterawfee},
227
1.30k
    };
228
2.60k
    for (const auto& c : commands) {
229
2.60k
        t.appendCommand(c.name, &c);
230
2.60k
    }
231
1.30k
}