Branch data Line data Source code
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 : 80 : static RPCMethod estimatesmartfee()
33 : : {
34 : 80 : return RPCMethod{
35 : 80 : "estimatesmartfee",
36 [ + - ]: 160 : "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 [ + - + - ]: 160 : {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
42 [ + - + - ]: 160 : {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"economical"}, "The fee estimate mode.\n"
43 [ + - + - : 160 : + FeeModesDetail(std::string("default mode will be used"))},
+ - ]
44 : : },
45 [ + - ]: 160 : RPCResult{
46 [ + - + - ]: 160 : RPCResult::Type::OBJ, "", "",
47 : : {
48 [ + - + - ]: 160 : {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
49 [ + - + - ]: 160 : {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
50 : : {
51 [ + - + - ]: 160 : {RPCResult::Type::STR, "", "error"},
52 : : }},
53 [ + - + - ]: 160 : {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 [ + - + - : 880 : }},
+ - + + +
+ - - -
- ]
59 : 80 : RPCExamples{
60 [ + - + - : 160 : HelpExampleCli("estimatesmartfee", "6") +
+ - ]
61 [ + - + - : 240 : HelpExampleRpc("estimatesmartfee", "6")
+ - + - ]
62 [ + - ]: 80 : },
63 : 80 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
64 : : {
65 : 0 : CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
66 : 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
67 : 0 : const CTxMemPool& mempool = EnsureMemPool(node);
68 : :
69 : 0 : CHECK_NONFATAL(mempool.m_opts.signals)->SyncWithValidationInterfaceQueue();
70 : 0 : unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
71 : 0 : unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
72 : 0 : FeeEstimateMode fee_mode;
73 [ # # ]: 0 : if (!FeeModeFromString(self.Arg<std::string_view>("estimate_mode"), fee_mode)) {
74 [ # # # # ]: 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
75 : : }
76 : :
77 : 0 : UniValue result(UniValue::VOBJ);
78 : 0 : UniValue errors(UniValue::VARR);
79 : 0 : FeeCalculation feeCalc;
80 : 0 : bool conservative{fee_mode == FeeEstimateMode::CONSERVATIVE};
81 [ # # ]: 0 : CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
82 [ # # ]: 0 : if (feeRate != CFeeRate(0)) {
83 [ # # ]: 0 : CFeeRate min_mempool_feerate{mempool.GetMinFee()};
84 : 0 : CFeeRate min_relay_feerate{mempool.m_opts.min_relay_feerate};
85 : 0 : feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
86 [ # # # # : 0 : result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
# # ]
87 : : } else {
88 [ # # # # ]: 0 : errors.push_back("Insufficient data or no feerate found");
89 [ # # # # ]: 0 : result.pushKV("errors", std::move(errors));
90 : : }
91 [ # # # # : 0 : result.pushKV("blocks", feeCalc.returnedTarget);
# # ]
92 : 0 : return result;
93 : 0 : },
94 [ + - + - : 480 : };
+ + - - ]
95 [ + - + - : 960 : }
+ - + - +
- + - - -
- - ]
96 : :
97 : 480 : static std::vector<RPCResult> FeeRateBucketDoc(bool elide = false)
98 : : {
99 : 480 : auto fields = std::vector<RPCResult>{
100 [ + - + - ]: 960 : {RPCResult::Type::NUM, "startrange", "start of feerate range"},
101 [ + - + - ]: 960 : {RPCResult::Type::NUM, "endrange", "end of feerate range"},
102 [ + - + - ]: 960 : {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
103 [ + - + - ]: 960 : {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
104 [ + - + - ]: 960 : {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
105 [ + - + - ]: 960 : {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
106 [ + - + + : 6720 : };
- - ]
107 [ + + + - : 1200 : return elide ? ElideGroup(std::move(fields)) : fields;
+ - + - +
+ - - ]
108 [ + - + - : 6240 : }
+ - + - +
- + - -
- ]
109 : :
110 : 240 : static std::vector<RPCResult> FeeEstimateHorizonDoc(bool elide = false)
111 : : {
112 : 240 : auto fields = std::vector<RPCResult>{
113 [ + - + - ]: 480 : {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
114 [ + - + - ]: 480 : {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
115 [ + - + - ]: 480 : {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
116 [ + - + - : 480 : {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold", FeeRateBucketDoc()},
+ - ]
117 [ + - + - : 480 : {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold", FeeRateBucketDoc(/*elide=*/true)},
+ - ]
118 [ + - + - ]: 480 : {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
119 : : {
120 [ + - + - ]: 480 : {RPCResult::Type::STR, "error", ""},
121 : : }},
122 [ + - + - : 3600 : };
+ + + + -
- - - ]
123 [ + + + - : 640 : return elide ? ElideGroup(std::move(fields)) : fields;
+ - + - +
+ - - ]
124 [ + - + - : 3600 : }
+ - + - +
- + - + -
- - ]
125 : :
126 : 80 : static RPCMethod estimaterawfee()
127 : : {
128 : 80 : return RPCMethod{
129 : 80 : "estimaterawfee",
130 [ + - ]: 160 : "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 [ + - + - ]: 160 : {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
139 [ + - + - : 240 : {"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 [ + - ]: 160 : RPCResult{
144 [ + - + - ]: 160 : RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
145 : : {
146 [ + - + - ]: 160 : {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
147 [ + - ]: 160 : FeeEstimateHorizonDoc()},
148 [ + - + - ]: 160 : {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
149 [ + - ]: 160 : FeeEstimateHorizonDoc(/*elide=*/true)},
150 [ + - + - ]: 160 : {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
151 [ + - ]: 160 : FeeEstimateHorizonDoc(/*elide=*/true)},
152 [ + - + - : 400 : }},
+ + - - ]
153 : 80 : RPCExamples{
154 [ + - + - : 160 : HelpExampleCli("estimaterawfee", "6 0.9")
+ - ]
155 [ + - ]: 80 : },
156 : 80 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
157 : : {
158 : 0 : CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
159 : 0 : const NodeContext& node = EnsureAnyNodeContext(request.context);
160 : :
161 : 0 : CHECK_NONFATAL(node.validation_signals)->SyncWithValidationInterfaceQueue();
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 : : }
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", buckets.scale);
# # ]
204 [ # # # # ]: 0 : 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 [ # # # # : 0 : 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 [ # # # # : 0 : horizon_result.pushKV("decay", buckets.decay);
# # ]
210 [ # # # # : 0 : horizon_result.pushKV("scale", buckets.scale);
# # ]
211 [ # # # # ]: 0 : horizon_result.pushKV("fail", std::move(failbucket));
212 [ # # # # ]: 0 : errors.push_back("Insufficient data or no feerate found which meets threshold");
213 [ # # # # ]: 0 : horizon_result.pushKV("errors", std::move(errors));
214 : : }
215 [ # # # # ]: 0 : result.pushKV(StringForFeeEstimateHorizon(horizon), std::move(horizon_result));
216 : 0 : }
217 : 0 : return result;
218 : 0 : },
219 [ + - + - : 480 : };
+ + - - ]
220 [ + - + - : 800 : }
+ - + - +
- - - -
- ]
221 : :
222 : 156 : void RegisterFeeRPCCommands(CRPCTable& t)
223 : : {
224 : 156 : static const CRPCCommand commands[]{
225 [ + - ]: 80 : {"util", &estimatesmartfee},
226 [ + - ]: 80 : {"hidden", &estimaterawfee},
227 [ + + + - : 236 : };
+ - + - -
- ]
228 [ + + ]: 468 : for (const auto& c : commands) {
229 : 312 : t.appendCommand(c.name, &c);
230 : : }
231 : 156 : }
|