fees.cpp raw

   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