LCOV - code coverage report
Current view: top level - src/rpc - fees.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 66 142 46.5 %
Date: 2023-11-06 23:13:05 Functions: 3 8 37.5 %
Branches: 135 524 25.8 %

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

Generated by: LCOV version 1.14