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