fees.cpp raw

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