/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 <rpc/register.h> // IWYU pragma: associated |
7 | | |
8 | | #include <common/messages.h> |
9 | | #include <core_io.h> |
10 | | #include <node/context.h> |
11 | | #include <policy/feerate.h> |
12 | | #include <policy/fees/block_policy_estimator.h> |
13 | | #include <policy/fees/estimator_man.h> |
14 | | #include <policy/fees/mempool_estimator.h> |
15 | | #include <rpc/protocol.h> |
16 | | #include <rpc/request.h> |
17 | | #include <rpc/server.h> |
18 | | #include <rpc/server_util.h> |
19 | | #include <rpc/util.h> |
20 | | #include <txmempool.h> |
21 | | #include <univalue.h> |
22 | | #include <util/check.h> |
23 | | #include <util/expected.h> |
24 | | #include <util/fees.h> |
25 | | #include <validationinterface.h> |
26 | | |
27 | | #include <algorithm> |
28 | | #include <array> |
29 | | #include <cmath> |
30 | | #include <map> |
31 | | #include <string> |
32 | | #include <string_view> |
33 | | #include <utility> |
34 | | #include <vector> |
35 | | |
36 | | using common::FeeModeFromString; |
37 | | using common::FeeModesDetail; |
38 | | using common::InvalidEstimateModeErrorMessage; |
39 | | using node::NodeContext; |
40 | | |
41 | | static RPCMethod estimatesmartfee() |
42 | 2.66k | { |
43 | 2.66k | return RPCMethod{ |
44 | 2.66k | "estimatesmartfee", |
45 | 2.66k | "Estimates the approximate fee per kilobyte needed for a transaction to begin\n" |
46 | 2.66k | "confirmation within conf_target blocks if possible and return the number of blocks\n" |
47 | 2.66k | "for which the estimate is valid. Uses virtual transaction size as defined\n" |
48 | 2.66k | "in BIP 141 (witness data is discounted).\n", |
49 | 2.66k | { |
50 | 2.66k | {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, |
51 | 2.66k | {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n" |
52 | 2.66k | + FeeModesDetail(std::string("default mode will be used"))}, |
53 | 2.66k | {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", |
54 | 2.66k | { |
55 | 2.66k | {"fee_rate_estimator", RPCArg::Type::STR, RPCArg::Default{"none"}, |
56 | 2.66k | "Selects which fee rate estimator to use.\n" |
57 | 2.66k | "\"none\" returns the lower of the block policy and mempool estimates. If the mempool\n" |
58 | 2.66k | "estimate is unavailable, it returns that error instead of falling back to the block\n" |
59 | 2.66k | "policy estimate; use \"block_policy\" in that case to get the block policy estimate.\n" |
60 | 2.66k | "\"block_policy\" uses only the block policy fee rate estimator.\n" |
61 | 2.66k | "\"mempool_policy\" uses only the mempool fee rate estimator.\n" |
62 | 2.66k | "Unknown values are treated as \"none\"."}, |
63 | 2.66k | {"verbosity", RPCArg::Type::NUM, RPCArg::Default{1}, |
64 | 2.66k | "1 returns feerate or errors. 2 also returns \"mempool_health_statistics\"."}, |
65 | 2.66k | }, |
66 | 2.66k | }, |
67 | 2.66k | }, |
68 | 2.66k | RPCResult{ |
69 | 2.66k | RPCResult::Type::OBJ, "", "", |
70 | 2.66k | { |
71 | 2.66k | {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"}, |
72 | 2.66k | {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\")"}, |
73 | 2.66k | {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", |
74 | 2.66k | { |
75 | 2.66k | {RPCResult::Type::STR, "", "error"}, |
76 | 2.66k | }}, |
77 | 2.66k | {RPCResult::Type::NUM, "blocks", "the confirmation target in blocks for the returned fee rate estimate.\n" |
78 | 2.66k | "For the block policy fee rate estimator, this is the target the estimate was found at, clamped to at\n" |
79 | 2.66k | "least 2 and at most the estimator's maximum usable target. For the mempool fee rate\n" |
80 | 2.66k | "estimator, it is always 2."}, |
81 | 2.66k | {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)", |
82 | 2.66k | { |
83 | 2.66k | {RPCResult::Type::OBJ, "", "", |
84 | 2.66k | { |
85 | 2.66k | {RPCResult::Type::NUM, "block_height", "Block height"}, |
86 | 2.66k | {RPCResult::Type::NUM, "block_weight", "Total weight of non-coinbase transactions in the block"}, |
87 | 2.66k | {RPCResult::Type::NUM, "mempool_txs_weight", "Total weight of transactions removed from the mempool for this block"}, |
88 | 2.66k | }}, |
89 | 2.66k | }}, |
90 | 2.66k | }}, |
91 | 2.66k | RPCExamples{ |
92 | 2.66k | HelpExampleCli("estimatesmartfee", "6") + |
93 | 2.66k | HelpExampleRpc("estimatesmartfee", "6") |
94 | 2.66k | }, |
95 | 2.66k | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
96 | 2.66k | { |
97 | 192 | FeeRateEstimatorManager& fee_estimator_man = EnsureAnyFeeEstimatorMan(request.context); |
98 | 192 | const NodeContext& node = EnsureAnyNodeContext(request.context); |
99 | 192 | const CTxMemPool& mempool = EnsureMemPool(node); |
100 | | |
101 | 192 | CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue(); |
102 | 192 | unsigned int max_target = fee_estimator_man.MaximumTarget(); |
103 | 192 | unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); |
104 | 192 | FeeEstimateMode fee_mode; |
105 | 192 | if (!FeeModeFromString(self.Arg<std::string_view>("estimate_mode"), fee_mode)) { |
106 | 1 | throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); |
107 | 1 | } |
108 | 191 | const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]}; |
109 | 191 | RPCTypeCheckObj(options, |
110 | 191 | { |
111 | 191 | {"fee_rate_estimator", UniValueType(UniValue::VSTR)}, |
112 | 191 | {"verbosity", UniValueType(UniValue::VNUM)}, |
113 | 191 | }, /*fAllowNull=*/true, /*fStrict=*/true); |
114 | 191 | const auto fee_rate_estimator{FeeRateEstimatorTypeFromString( |
115 | 191 | options["fee_rate_estimator"].isNull() ? "none" : options["fee_rate_estimator"].get_str())}; |
116 | 191 | bool conservative{fee_mode == FeeEstimateMode::CONSERVATIVE}; |
117 | 191 | int verbosity{ParseVerbosity(options["verbosity"], /*default_verbosity=*/1, /*allow_bool=*/false)}; |
118 | 191 | UniValue result(UniValue::VOBJ); |
119 | 191 | UniValue errors(UniValue::VARR); |
120 | 191 | const auto estimate{fee_estimator_man.GetFeeRateEstimate(fee_rate_estimator, conf_target, conservative)}; |
121 | 191 | if (estimate) { |
122 | 172 | const CFeeRate min_mempool_feerate{mempool.GetMinFee()}; |
123 | 172 | const CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate}; |
124 | 172 | const auto fee_rate{std::max({CFeeRate(estimate->feerate), min_mempool_feerate, min_relay_feerate})}; |
125 | 172 | result.pushKV("feerate", ValueFromAmount(fee_rate.GetFeePerK())); |
126 | 172 | } else { |
127 | 19 | errors.push_back(estimate.error().reason); |
128 | 19 | result.pushKV("errors", std::move(errors)); |
129 | 19 | } |
130 | 191 | if (estimate && fee_rate_estimator == FeeRateEstimatorType::NONE) { |
131 | 5 | result.pushKV("estimator", FeeRateEstimatorTypeToString(estimate->feerate_estimator)); |
132 | 5 | } |
133 | 191 | const FeeRateEstimation& estimation{FeeRateEstimationRef(estimate)}; |
134 | 191 | result.pushKV("blocks", estimation.returned_target); |
135 | 191 | if (verbosity >= 2) { |
136 | 3 | UniValue mempool_health_stats(UniValue::VARR); |
137 | 3 | const auto blocks_data = fee_estimator_man.MempoolPolicyEstimatorBlocksStats(); |
138 | 15 | for (auto it = blocks_data.rbegin(); it != blocks_data.rend(); ++it) { |
139 | 12 | UniValue entry(UniValue::VOBJ); |
140 | 12 | entry.pushKV("block_height", it->m_height); |
141 | 12 | entry.pushKV("block_weight", it->m_block_weight); |
142 | 12 | entry.pushKV("mempool_txs_weight", it->m_removed_block_txs_weight); |
143 | 12 | mempool_health_stats.push_back(std::move(entry)); |
144 | 12 | } |
145 | 3 | result.pushKV("mempool_health_statistics", std::move(mempool_health_stats)); |
146 | 3 | } |
147 | 191 | return result; |
148 | 192 | }, |
149 | 2.66k | }; |
150 | 2.66k | } |
151 | | |
152 | | static std::vector<RPCResult> FeeRateBucketDoc(bool elide = false) |
153 | 15.5k | { |
154 | 15.5k | auto fields = std::vector<RPCResult>{ |
155 | 15.5k | {RPCResult::Type::NUM, "startrange", "start of feerate range"}, |
156 | 15.5k | {RPCResult::Type::NUM, "endrange", "end of feerate range"}, |
157 | 15.5k | {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"}, |
158 | 15.5k | {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"}, |
159 | 15.5k | {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"}, |
160 | 15.5k | {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"}, |
161 | 15.5k | }; |
162 | 15.5k | return elide ? ElideGroup(std::move(fields)) : fields; |
163 | 15.5k | } |
164 | | |
165 | | static std::vector<RPCResult> FeeEstimateHorizonDoc(bool elide = false) |
166 | 7.76k | { |
167 | 7.76k | auto fields = std::vector<RPCResult>{ |
168 | 7.76k | {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"}, |
169 | 7.76k | {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"}, |
170 | 7.76k | {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"}, |
171 | 7.76k | {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", FeeRateBucketDoc()}, |
172 | 7.76k | {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", FeeRateBucketDoc(/*elide=*/true)}, |
173 | 7.76k | {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)", |
174 | 7.76k | { |
175 | 7.76k | {RPCResult::Type::STR, "", "error"}, |
176 | 7.76k | }}, |
177 | 7.76k | }; |
178 | 7.76k | return elide ? ElideGroup(std::move(fields)) : fields; |
179 | 7.76k | } |
180 | | |
181 | | static RPCMethod estimaterawfee() |
182 | 2.58k | { |
183 | 2.58k | return RPCMethod{ |
184 | 2.58k | "estimaterawfee", |
185 | 2.58k | "WARNING: This interface is unstable and may disappear or change!\n" |
186 | 2.58k | "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" |
187 | 2.58k | "implementation of fee estimation. The parameters it can be called with\n" |
188 | 2.58k | "and the results it returns will change if the internal implementation changes.\n" |
189 | 2.58k | "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n" |
190 | 2.58k | "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n" |
191 | 2.58k | "defined in BIP 141 (witness data is discounted).\n", |
192 | 2.58k | { |
193 | 2.58k | {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"}, |
194 | 2.58k | {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n" |
195 | 2.58k | "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n" |
196 | 2.58k | "lower buckets."}, |
197 | 2.58k | }, |
198 | 2.58k | RPCResult{ |
199 | 2.58k | RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target", |
200 | 2.58k | { |
201 | 2.58k | {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon", |
202 | 2.58k | FeeEstimateHorizonDoc()}, |
203 | 2.58k | {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon", |
204 | 2.58k | FeeEstimateHorizonDoc(/*elide=*/true)}, |
205 | 2.58k | {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon", |
206 | 2.58k | FeeEstimateHorizonDoc(/*elide=*/true)}, |
207 | 2.58k | }}, |
208 | 2.58k | RPCExamples{ |
209 | 2.58k | HelpExampleCli("estimaterawfee", "6 0.9") |
210 | 2.58k | }, |
211 | 2.58k | [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue |
212 | 2.58k | { |
213 | 129 | FeeRateEstimatorManager& fee_estimator_man = EnsureAnyFeeEstimatorMan(request.context); |
214 | 129 | const NodeContext& node = EnsureAnyNodeContext(request.context); |
215 | | |
216 | 129 | CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue(); |
217 | 129 | unsigned int max_target = fee_estimator_man.MaximumTarget(); |
218 | 129 | unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target); |
219 | 129 | double threshold = 0.95; |
220 | 129 | if (!request.params[1].isNull()) { |
221 | 1 | threshold = request.params[1].get_real(); |
222 | 1 | } |
223 | 129 | if (threshold < 0 || threshold > 1) { |
224 | 0 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); |
225 | 0 | } |
226 | | |
227 | 129 | UniValue result(UniValue::VOBJ); |
228 | | |
229 | 384 | for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) { |
230 | 384 | CFeeRate feeRate; |
231 | 384 | EstimationResult buckets; |
232 | | |
233 | | // Only output results for horizons which track the target |
234 | 384 | if (conf_target > fee_estimator_man.BlockPolicyHighestTargetTracked(horizon)) continue; |
235 | | |
236 | 319 | feeRate = fee_estimator_man.BlockPolicyEstimateRawFee(conf_target, threshold, horizon, &buckets); |
237 | 319 | UniValue horizon_result(UniValue::VOBJ); |
238 | 319 | UniValue errors(UniValue::VARR); |
239 | 319 | UniValue passbucket(UniValue::VOBJ); |
240 | 319 | passbucket.pushKV("startrange", round(buckets.pass.start)); |
241 | 319 | passbucket.pushKV("endrange", round(buckets.pass.end)); |
242 | 319 | passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0); |
243 | 319 | passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0); |
244 | 319 | passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0); |
245 | 319 | passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0); |
246 | 319 | UniValue failbucket(UniValue::VOBJ); |
247 | 319 | failbucket.pushKV("startrange", round(buckets.fail.start)); |
248 | 319 | failbucket.pushKV("endrange", round(buckets.fail.end)); |
249 | 319 | failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0); |
250 | 319 | failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0); |
251 | 319 | failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0); |
252 | 319 | failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0); |
253 | | |
254 | | // CFeeRate(0) is used to indicate error as a return value from estimateRawFee |
255 | 319 | if (feeRate != CFeeRate(0)) { |
256 | 310 | horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK())); |
257 | 310 | horizon_result.pushKV("decay", buckets.decay); |
258 | 310 | horizon_result.pushKV("scale", buckets.scale); |
259 | 310 | horizon_result.pushKV("pass", std::move(passbucket)); |
260 | | // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output |
261 | 310 | if (buckets.fail.start != -1) horizon_result.pushKV("fail", std::move(failbucket)); |
262 | 310 | } else { |
263 | | // Output only information that is still meaningful in the event of error |
264 | 9 | horizon_result.pushKV("decay", buckets.decay); |
265 | 9 | horizon_result.pushKV("scale", buckets.scale); |
266 | 9 | horizon_result.pushKV("fail", std::move(failbucket)); |
267 | 9 | errors.push_back("Insufficient data or no feerate found which meets threshold"); |
268 | 9 | horizon_result.pushKV("errors", std::move(errors)); |
269 | 9 | } |
270 | 319 | result.pushKV(StringForFeeEstimateHorizon(horizon), std::move(horizon_result)); |
271 | 319 | } |
272 | 129 | return result; |
273 | 129 | }, |
274 | 2.58k | }; |
275 | 2.58k | } |
276 | | |
277 | | void RegisterFeeRPCCommands(CRPCTable& t) |
278 | 1.36k | { |
279 | 1.36k | static const CRPCCommand commands[]{ |
280 | 1.36k | {"util", &estimatesmartfee}, |
281 | 1.36k | {"hidden", &estimaterawfee}, |
282 | 1.36k | }; |
283 | 2.72k | for (const auto& c : commands) { |
284 | 2.72k | t.appendCommand(c.name, &c); |
285 | 2.72k | } |
286 | 1.36k | } |