Branch data Line data Source code
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 <core_io.h>
7 : : #include <policy/feerate.h>
8 : : #include <policy/fees.h>
9 : : #include <rpc/protocol.h>
10 : : #include <rpc/request.h>
11 : : #include <rpc/server.h>
12 : : #include <rpc/server_util.h>
13 : : #include <rpc/util.h>
14 : : #include <txmempool.h>
15 : : #include <univalue.h>
16 : : #include <util/fees.h>
17 : : #include <validationinterface.h>
18 : :
19 : : #include <algorithm>
20 : : #include <array>
21 : : #include <cmath>
22 : : #include <string>
23 : :
24 : : namespace node {
25 : : struct NodeContext;
26 : : }
27 : 2 :
28 : : using node::NodeContext;
29 : :
30 : 0 : static RPCHelpMan estimatesmartfee()
31 : : {
32 [ # # ][ # # ]: 0 : return RPCHelpMan{"estimatesmartfee",
[ # # ][ # # ]
[ # # ]
33 [ # # ]: 0 : "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
34 : : "confirmation within conf_target blocks if possible and return the number of blocks\n"
35 : : "for which the estimate is valid. Uses virtual transaction size as defined\n"
36 : : "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{"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 [ # # ][ # # ]: 0 : "\"" + FeeModes("\"\n\"") + "\""},
[ # # ][ # # ]
46 : : },
47 [ # # ][ # # ]: 0 : RPCResult{
48 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "",
49 [ # # ]: 0 : {
50 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
[ # # ][ # # ]
51 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
[ # # ]
52 [ # # ]: 0 : {
53 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "", "error"},
[ # # ]
54 : : }},
55 [ # # ][ # # ]: 0 : {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 [ # # ]: 0 : RPCExamples{
62 [ # # ][ # # ]: 0 : HelpExampleCli("estimatesmartfee", "6") +
[ # # ][ # # ]
63 [ # # ][ # # ]: 0 : HelpExampleRpc("estimatesmartfee", "6")
[ # # ]
64 : : },
65 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
66 : : {
67 : 0 : CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
68 : 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
69 : 0 : const CTxMemPool& mempool = EnsureMemPool(node);
70 : :
71 : 0 : SyncWithValidationInterfaceQueue();
72 : 0 : unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
73 : 0 : unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
74 : 0 : bool conservative = true;
75 [ # # ]: 0 : if (!request.params[1].isNull()) {
76 : : FeeEstimateMode fee_mode;
77 [ # # ]: 0 : if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
78 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
[ # # ][ # # ]
79 : : }
80 [ # # ]: 0 : if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
81 : 0 : }
82 : :
83 [ # # ]: 0 : UniValue result(UniValue::VOBJ);
84 [ # # ]: 0 : UniValue errors(UniValue::VARR);
85 : 0 : FeeCalculation feeCalc;
86 [ # # ]: 0 : CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
87 [ # # ][ # # ]: 0 : if (feeRate != CFeeRate(0)) {
[ # # ]
88 [ # # ]: 0 : CFeeRate min_mempool_feerate{mempool.GetMinFee()};
89 : 0 : CFeeRate min_relay_feerate{mempool.m_min_relay_feerate};
90 [ # # ]: 0 : feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
91 [ # # ][ # # ]: 0 : result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
[ # # ][ # # ]
92 : 0 : } else {
93 [ # # ][ # # ]: 0 : errors.push_back("Insufficient data or no feerate found");
94 [ # # ][ # # ]: 0 : result.pushKV("errors", errors);
[ # # ]
95 : : }
96 [ # # ][ # # ]: 0 : result.pushKV("blocks", feeCalc.returnedTarget);
[ # # ]
97 : 0 : return result;
98 [ # # ]: 0 : },
99 : : };
100 : 0 : }
101 : :
102 : 0 : static RPCHelpMan estimaterawfee()
103 : : {
104 [ # # ][ # # ]: 0 : return RPCHelpMan{"estimaterawfee",
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ][ # # ]
105 [ # # ]: 0 : "\nWARNING: This interface is unstable and may disappear or change!\n"
106 : : "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
107 : : "implementation of fee estimation. The parameters it can be called with\n"
108 : : "and the results it returns will change if the internal implementation changes.\n"
109 : : "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
110 : : "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
111 : : "defined in BIP 141 (witness data is discounted).\n",
112 [ # # ]: 0 : {
113 [ # # ][ # # ]: 0 : {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
[ # # ]
114 [ # # ][ # # ]: 0 : {"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 [ # # ][ # # ]: 0 : RPCResult{
119 [ # # ][ # # ]: 0 : RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
120 [ # # ]: 0 : {
121 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
[ # # ]
122 [ # # ]: 0 : {
123 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
[ # # ][ # # ]
124 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
[ # # ]
125 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
[ # # ]
126 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold",
[ # # ]
127 [ # # ]: 0 : {
128 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "startrange", "start of feerate range"},
[ # # ]
129 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "endrange", "end of feerate range"},
[ # # ]
130 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
[ # # ]
131 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
[ # # ]
132 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
[ # # ]
133 [ # # ][ # # ]: 0 : {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
[ # # ]
134 : : }},
135 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold",
[ # # ]
136 [ # # ]: 0 : {
137 [ # # ][ # # ]: 0 : {RPCResult::Type::ELISION, "", ""},
[ # # ]
138 : : }},
139 [ # # ][ # # ]: 0 : {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
[ # # ]
140 [ # # ]: 0 : {
141 [ # # ][ # # ]: 0 : {RPCResult::Type::STR, "error", ""},
[ # # ]
142 : : }},
143 : : }},
144 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
[ # # ]
145 [ # # ]: 0 : {
146 [ # # ][ # # ]: 0 : {RPCResult::Type::ELISION, "", ""},
[ # # ]
147 : : }},
148 [ # # ][ # # ]: 0 : {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
[ # # ]
149 [ # # ]: 0 : {
150 [ # # ][ # # ]: 0 : {RPCResult::Type::ELISION, "", ""},
[ # # ]
151 : : }},
152 : : }},
153 [ # # ]: 0 : RPCExamples{
154 [ # # ][ # # ]: 0 : HelpExampleCli("estimaterawfee", "6 0.9")
[ # # ]
155 : : },
156 : 0 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
157 : : {
158 : 0 : CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
159 : :
160 : 0 : SyncWithValidationInterfaceQueue();
161 : 0 : unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
162 : 0 : unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
163 : 0 : double threshold = 0.95;
164 [ # # ]: 0 : if (!request.params[1].isNull()) {
165 : 0 : threshold = request.params[1].get_real();
166 : 0 : }
167 [ # # ]: 0 : if (threshold < 0 || threshold > 1) {
168 [ # # ][ # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
[ # # ][ # # ]
169 : : }
170 : :
171 [ # # ]: 0 : UniValue result(UniValue::VOBJ);
172 : :
173 [ # # ]: 0 : for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
174 [ # # ]: 0 : CFeeRate feeRate;
175 : 0 : EstimationResult buckets;
176 : :
177 : : // Only output results for horizons which track the target
178 [ # # ][ # # ]: 0 : if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
179 : :
180 [ # # ]: 0 : feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
181 [ # # ]: 0 : UniValue horizon_result(UniValue::VOBJ);
182 [ # # ]: 0 : UniValue errors(UniValue::VARR);
183 [ # # ]: 0 : UniValue passbucket(UniValue::VOBJ);
184 [ # # ][ # # ]: 0 : passbucket.pushKV("startrange", round(buckets.pass.start));
[ # # ]
185 [ # # ][ # # ]: 0 : passbucket.pushKV("endrange", round(buckets.pass.end));
[ # # ]
186 [ # # ][ # # ]: 0 : passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
[ # # ]
187 [ # # ][ # # ]: 0 : passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
[ # # ]
188 [ # # ][ # # ]: 0 : passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
[ # # ]
189 [ # # ][ # # ]: 0 : passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
[ # # ]
190 [ # # ]: 0 : UniValue failbucket(UniValue::VOBJ);
191 [ # # ][ # # ]: 0 : failbucket.pushKV("startrange", round(buckets.fail.start));
[ # # ]
192 [ # # ][ # # ]: 0 : failbucket.pushKV("endrange", round(buckets.fail.end));
[ # # ]
193 [ # # ][ # # ]: 0 : failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
[ # # ]
194 [ # # ][ # # ]: 0 : failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
[ # # ]
195 [ # # ][ # # ]: 0 : failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
[ # # ]
196 [ # # ][ # # ]: 0 : failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
[ # # ]
197 : :
198 : : // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
199 [ # # ][ # # ]: 0 : if (feeRate != CFeeRate(0)) {
200 [ # # ][ # # ]: 0 : horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
[ # # ]
201 [ # # ][ # # ]: 0 : horizon_result.pushKV("decay", buckets.decay);
[ # # ]
202 [ # # ][ # # ]: 0 : horizon_result.pushKV("scale", (int)buckets.scale);
[ # # ]
203 [ # # ][ # # ]: 0 : horizon_result.pushKV("pass", passbucket);
[ # # ]
204 : : // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
205 [ # # ][ # # ]: 0 : if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket);
[ # # ][ # # ]
206 : 0 : } else {
207 : : // Output only information that is still meaningful in the event of error
208 [ # # ][ # # ]: 0 : horizon_result.pushKV("decay", buckets.decay);
[ # # ]
209 [ # # ][ # # ]: 0 : horizon_result.pushKV("scale", (int)buckets.scale);
[ # # ]
210 [ # # ][ # # ]: 0 : horizon_result.pushKV("fail", failbucket);
[ # # ]
211 [ # # ][ # # ]: 0 : errors.push_back("Insufficient data or no feerate found which meets threshold");
212 [ # # ][ # # ]: 0 : horizon_result.pushKV("errors", errors);
[ # # ]
213 : : }
214 [ # # ][ # # ]: 0 : result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result);
[ # # ]
215 : 0 : }
216 : 0 : return result;
217 [ # # ]: 0 : },
218 : : };
219 : 0 : }
220 : :
221 : 0 : void RegisterFeeRPCCommands(CRPCTable& t)
222 : : {
223 [ # # ][ # # ]: 0 : static const CRPCCommand commands[]{
[ # # ]
224 [ # # ][ # # ]: 0 : {"util", &estimatesmartfee},
225 [ # # ][ # # ]: 0 : {"hidden", &estimaterawfee},
226 : : };
227 [ # # ]: 0 : for (const auto& c : commands) {
228 : 0 : t.appendCommand(c.name, &c);
229 : : }
230 : 0 : }
|