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