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