Coverage Report

Created: 2026-07-01 15:07

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