Skip to content

Commit 1d82dd8

Browse files
committed
merge bitcoin#25029: Move fee estimation RPCs to separate file
1 parent ad94a30 commit 1d82dd8

File tree

4 files changed

+234
-201
lines changed

4 files changed

+234
-201
lines changed

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,7 @@ libbitcoin_server_a_SOURCES = \
529529
rpc/blockchain.cpp \
530530
rpc/coinjoin.cpp \
531531
rpc/evo.cpp \
532+
rpc/fees.cpp \
532533
rpc/index_util.cpp \
533534
rpc/masternode.cpp \
534535
rpc/mempool.cpp \

src/rpc/fees.cpp

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

0 commit comments

Comments
 (0)