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