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