Coverage Report

Created: 2025-06-10 13:21

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