Skip to content

Commit 2c56404

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#25029: rpc: Move fee estimation RPCs to separate file
fa753ab 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 bitcoin/bitcoin@fa753ab brunoerg: crACK fa753ab Tree-SHA512: 81e0edc936198a0baf0f5bfa8cfedc12db51759c7873bb0082dfc5f0040d7f275b35f639c6f5b86fa1ea03397b0d5e757c2ce1b6b16f1029880a39b9c3aaceda
2 parents 037c5e5 + fa753ab commit 2c56404

File tree

5 files changed

+242
-203
lines changed

5 files changed

+242
-203
lines changed

ci/test/06_script_b.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ if [ "${RUN_TIDY}" = "true" ]; then
4141
CI_EXEC "python3 ${DIR_IWYU}/include-what-you-use/iwyu_tool.py"\
4242
" src/compat"\
4343
" src/init"\
44+
" src/rpc/fees.cpp"\
4445
" src/rpc/signmessage.cpp"\
4546
" -p . ${MAKEJOBS} -- -Xiwyu --cxx17ns -Xiwyu --mapping_file=${BASE_BUILD_DIR}/bitcoin-$HOST/contrib/devtools/iwyu/bitcoin.core.imp"
4647
fi

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ libbitcoin_node_a_SOURCES = \
379379
pow.cpp \
380380
rest.cpp \
381381
rpc/blockchain.cpp \
382+
rpc/fees.cpp \
382383
rpc/mempool.cpp \
383384
rpc/mining.cpp \
384385
rpc/misc.cpp \

src/rpc/fees.cpp

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

0 commit comments

Comments
 (0)