/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 | } |