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