aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/fees.cpp
diff options
context:
space:
mode:
authorMacroFake <falke.marco@gmail.com>2022-05-03 08:17:40 +0200
committerMacroFake <falke.marco@gmail.com>2022-05-03 08:17:50 +0200
commit2c56404088f7b17ee9cea05ae43315ade35718bc (patch)
tree42d9ddb80f0d3e74cb2505e1825e60d264f429ab /src/rpc/fees.cpp
parent037c5e511fe2185d244049cae25a98f99b878787 (diff)
parentfa753abd7cffa05548ad5f21f2e8f9f6b06a7b04 (diff)
downloadbitcoin-2c56404088f7b17ee9cea05ae43315ade35718bc.tar.xz
Merge bitcoin/bitcoin#25029: rpc: Move fee estimation RPCs to separate file
fa753abd7cffa05548ad5f21f2e8f9f6b06a7b04 rpc: Move fee estimation RPCs to separate file (MacroFake) Pull request description: Fee estimation is generally used by wallets when creating txs. It doesn't have anything to do with creating or submitting blocks. ACKs for top commit: pk-b2: ACK https://github.com/bitcoin/bitcoin/pull/25029/commits/fa753abd7cffa05548ad5f21f2e8f9f6b06a7b04 brunoerg: crACK fa753abd7cffa05548ad5f21f2e8f9f6b06a7b04 Tree-SHA512: 81e0edc936198a0baf0f5bfa8cfedc12db51759c7873bb0082dfc5f0040d7f275b35f639c6f5b86fa1ea03397b0d5e757c2ce1b6b16f1029880a39b9c3aaceda
Diffstat (limited to 'src/rpc/fees.cpp')
-rw-r--r--src/rpc/fees.cpp236
1 files changed, 236 insertions, 0 deletions
diff --git a/src/rpc/fees.cpp b/src/rpc/fees.cpp
new file mode 100644
index 0000000000..bfec0780aa
--- /dev/null
+++ b/src/rpc/fees.cpp
@@ -0,0 +1,236 @@
+// Copyright (c) 2010 Satoshi Nakamoto
+// Copyright (c) 2009-2021 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <core_io.h>
+#include <policy/feerate.h>
+#include <policy/fees.h>
+#include <policy/policy.h>
+#include <rpc/protocol.h>
+#include <rpc/request.h>
+#include <rpc/server.h>
+#include <rpc/server_util.h>
+#include <rpc/util.h>
+#include <txmempool.h>
+#include <univalue.h>
+#include <util/fees.h>
+#include <util/system.h>
+#include <validation.h>
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <string>
+
+namespace node {
+struct NodeContext;
+}
+
+using node::NodeContext;
+
+static RPCHelpMan estimatesmartfee()
+{
+ return RPCHelpMan{"estimatesmartfee",
+ "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
+ "confirmation within conf_target blocks if possible and return the number of blocks\n"
+ "for which the estimate is valid. Uses virtual transaction size as defined\n"
+ "in BIP 141 (witness data is discounted).\n",
+ {
+ {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
+ {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"conservative"}, "The fee estimate mode.\n"
+ "Whether to return a more conservative estimate which also satisfies\n"
+ "a longer history. A conservative estimate potentially returns a\n"
+ "higher feerate and is more likely to be sufficient for the desired\n"
+ "target, but is not as responsive to short term drops in the\n"
+ "prevailing fee market. Must be one of (case insensitive):\n"
+ "\"" + FeeModes("\"\n\"") + "\""},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB (only present if no errors were encountered)"},
+ {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
+ {
+ {RPCResult::Type::STR, "", "error"},
+ }},
+ {RPCResult::Type::NUM, "blocks", "block number where estimate was found\n"
+ "The request target will be clamped between 2 and the highest target\n"
+ "fee estimation is able to return based on how long it has been running.\n"
+ "An error is returned if not enough transactions and blocks\n"
+ "have been observed to make an estimate for any number of blocks."},
+ }},
+ RPCExamples{
+ HelpExampleCli("estimatesmartfee", "6") +
+ HelpExampleRpc("estimatesmartfee", "6")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
+ const NodeContext& node = EnsureAnyNodeContext(request.context);
+ const CTxMemPool& mempool = EnsureMemPool(node);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
+ bool conservative = true;
+ if (!request.params[1].isNull()) {
+ FeeEstimateMode fee_mode;
+ if (!FeeModeFromString(request.params[1].get_str(), fee_mode)) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());
+ }
+ if (fee_mode == FeeEstimateMode::ECONOMICAL) conservative = false;
+ }
+
+ UniValue result(UniValue::VOBJ);
+ UniValue errors(UniValue::VARR);
+ FeeCalculation feeCalc;
+ CFeeRate feeRate{fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative)};
+ if (feeRate != CFeeRate(0)) {
+ CFeeRate min_mempool_feerate{mempool.GetMinFee(gArgs.GetIntArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000)};
+ CFeeRate min_relay_feerate{::minRelayTxFee};
+ feeRate = std::max({feeRate, min_mempool_feerate, min_relay_feerate});
+ result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
+ } else {
+ errors.push_back("Insufficient data or no feerate found");
+ result.pushKV("errors", errors);
+ }
+ result.pushKV("blocks", feeCalc.returnedTarget);
+ return result;
+ },
+ };
+}
+
+static RPCHelpMan estimaterawfee()
+{
+ return RPCHelpMan{"estimaterawfee",
+ "\nWARNING: This interface is unstable and may disappear or change!\n"
+ "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
+ "implementation of fee estimation. The parameters it can be called with\n"
+ "and the results it returns will change if the internal implementation changes.\n"
+ "\nEstimates the approximate fee per kilobyte needed for a transaction to begin\n"
+ "confirmation within conf_target blocks if possible. Uses virtual transaction size as\n"
+ "defined in BIP 141 (witness data is discounted).\n",
+ {
+ {"conf_target", RPCArg::Type::NUM, RPCArg::Optional::NO, "Confirmation target in blocks (1 - 1008)"},
+ {"threshold", RPCArg::Type::NUM, RPCArg::Default{0.95}, "The proportion of transactions in a given feerate range that must have been\n"
+ "confirmed within conf_target in order to consider those feerates as high enough and proceed to check\n"
+ "lower buckets."},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "Results are returned for any horizon which tracks blocks up to the confirmation target",
+ {
+ {RPCResult::Type::OBJ, "short", /*optional=*/true, "estimate for short time horizon",
+ {
+ {RPCResult::Type::NUM, "feerate", /*optional=*/true, "estimate fee rate in " + CURRENCY_UNIT + "/kvB"},
+ {RPCResult::Type::NUM, "decay", "exponential decay (per block) for historical moving average of confirmation data"},
+ {RPCResult::Type::NUM, "scale", "The resolution of confirmation targets at this time horizon"},
+ {RPCResult::Type::OBJ, "pass", /*optional=*/true, "information about the lowest range of feerates to succeed in meeting the threshold",
+ {
+ {RPCResult::Type::NUM, "startrange", "start of feerate range"},
+ {RPCResult::Type::NUM, "endrange", "end of feerate range"},
+ {RPCResult::Type::NUM, "withintarget", "number of txs over history horizon in the feerate range that were confirmed within target"},
+ {RPCResult::Type::NUM, "totalconfirmed", "number of txs over history horizon in the feerate range that were confirmed at any point"},
+ {RPCResult::Type::NUM, "inmempool", "current number of txs in mempool in the feerate range unconfirmed for at least target blocks"},
+ {RPCResult::Type::NUM, "leftmempool", "number of txs over history horizon in the feerate range that left mempool unconfirmed after target"},
+ }},
+ {RPCResult::Type::OBJ, "fail", /*optional=*/true, "information about the highest range of feerates to fail to meet the threshold",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ {RPCResult::Type::ARR, "errors", /*optional=*/true, "Errors encountered during processing (if there are any)",
+ {
+ {RPCResult::Type::STR, "error", ""},
+ }},
+ }},
+ {RPCResult::Type::OBJ, "medium", /*optional=*/true, "estimate for medium time horizon",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ {RPCResult::Type::OBJ, "long", /*optional=*/true, "estimate for long time horizon",
+ {
+ {RPCResult::Type::ELISION, "", ""},
+ }},
+ }},
+ RPCExamples{
+ HelpExampleCli("estimaterawfee", "6 0.9")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
+ RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
+
+ CBlockPolicyEstimator& fee_estimator = EnsureAnyFeeEstimator(request.context);
+
+ unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
+ unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
+ double threshold = 0.95;
+ if (!request.params[1].isNull()) {
+ threshold = request.params[1].get_real();
+ }
+ if (threshold < 0 || threshold > 1) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
+ }
+
+ UniValue result(UniValue::VOBJ);
+
+ for (const FeeEstimateHorizon horizon : ALL_FEE_ESTIMATE_HORIZONS) {
+ CFeeRate feeRate;
+ EstimationResult buckets;
+
+ // Only output results for horizons which track the target
+ if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
+
+ feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
+ UniValue horizon_result(UniValue::VOBJ);
+ UniValue errors(UniValue::VARR);
+ UniValue passbucket(UniValue::VOBJ);
+ passbucket.pushKV("startrange", round(buckets.pass.start));
+ passbucket.pushKV("endrange", round(buckets.pass.end));
+ passbucket.pushKV("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0);
+ passbucket.pushKV("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0);
+ passbucket.pushKV("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0);
+ passbucket.pushKV("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0);
+ UniValue failbucket(UniValue::VOBJ);
+ failbucket.pushKV("startrange", round(buckets.fail.start));
+ failbucket.pushKV("endrange", round(buckets.fail.end));
+ failbucket.pushKV("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0);
+ failbucket.pushKV("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0);
+ failbucket.pushKV("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0);
+ failbucket.pushKV("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0);
+
+ // CFeeRate(0) is used to indicate error as a return value from estimateRawFee
+ if (feeRate != CFeeRate(0)) {
+ horizon_result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
+ horizon_result.pushKV("decay", buckets.decay);
+ horizon_result.pushKV("scale", (int)buckets.scale);
+ horizon_result.pushKV("pass", passbucket);
+ // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
+ if (buckets.fail.start != -1) horizon_result.pushKV("fail", failbucket);
+ } else {
+ // Output only information that is still meaningful in the event of error
+ horizon_result.pushKV("decay", buckets.decay);
+ horizon_result.pushKV("scale", (int)buckets.scale);
+ horizon_result.pushKV("fail", failbucket);
+ errors.push_back("Insufficient data or no feerate found which meets threshold");
+ horizon_result.pushKV("errors", errors);
+ }
+ result.pushKV(StringForFeeEstimateHorizon(horizon), horizon_result);
+ }
+ return result;
+ },
+ };
+}
+
+void RegisterFeeRPCCommands(CRPCTable& t)
+{
+ static const CRPCCommand commands[]{
+ {"util", &estimatesmartfee},
+ {"hidden", &estimaterawfee},
+ };
+ for (const auto& c : commands) {
+ t.appendCommand(c.name, &c);
+ }
+}