Skip to content

Commit b27b004

Browse files
committed
Merge #10543: Change API to estimaterawfee
5e3b7b5 Improve error reporting for estimaterawfee (Alex Morcos) 1fafd70 Add function to report highest estimate target tracked per horizon (Alex Morcos) 9c85b91 Change API to estimaterawfee (Alex Morcos) Tree-SHA512: e624c6e7967e9e48abe49f5818bd674e5710e571cc093029d2f90d39fdfba3c1f30e83bf89f6dce97052b59a7d9636a64642ccfb26effd149c417d0afbed0c0b
2 parents cef4b5c + 5e3b7b5 commit b27b004

File tree

5 files changed

+112
-53
lines changed

5 files changed

+112
-53
lines changed

src/policy/feerate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class CFeeRate
4040
friend bool operator==(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK == b.nSatoshisPerK; }
4141
friend bool operator<=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK <= b.nSatoshisPerK; }
4242
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
43+
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
4344
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
4445
std::string ToString() const;
4546

src/policy/fees.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,19 @@
1616

1717
static constexpr double INF_FEERATE = 1e99;
1818

19+
std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon) {
20+
static const std::map<FeeEstimateHorizon, std::string> horizon_strings = {
21+
{FeeEstimateHorizon::SHORT_HALFLIFE, "short"},
22+
{FeeEstimateHorizon::MED_HALFLIFE, "medium"},
23+
{FeeEstimateHorizon::LONG_HALFLIFE, "long"},
24+
};
25+
auto horizon_string = horizon_strings.find(horizon);
26+
27+
if (horizon_string == horizon_strings.end()) return "unknown";
28+
29+
return horizon_string->second;
30+
}
31+
1932
std::string StringForFeeReason(FeeReason reason) {
2033
static const std::map<FeeReason, std::string> fee_reason_strings = {
2134
{FeeReason::NONE, "None"},
@@ -685,7 +698,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr
685698
break;
686699
}
687700
default: {
688-
return CFeeRate(0);
701+
throw std::out_of_range("CBlockPoicyEstimator::estimateRawFee unknown FeeEstimateHorizon");
689702
}
690703
}
691704

@@ -704,6 +717,24 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr
704717
return CFeeRate(median);
705718
}
706719

720+
unsigned int CBlockPolicyEstimator::HighestTargetTracked(FeeEstimateHorizon horizon) const
721+
{
722+
switch (horizon) {
723+
case FeeEstimateHorizon::SHORT_HALFLIFE: {
724+
return shortStats->GetMaxConfirms();
725+
}
726+
case FeeEstimateHorizon::MED_HALFLIFE: {
727+
return feeStats->GetMaxConfirms();
728+
}
729+
case FeeEstimateHorizon::LONG_HALFLIFE: {
730+
return longStats->GetMaxConfirms();
731+
}
732+
default: {
733+
throw std::out_of_range("CBlockPoicyEstimator::HighestTargetTracked unknown FeeEstimateHorizon");
734+
}
735+
}
736+
}
737+
707738
unsigned int CBlockPolicyEstimator::BlockSpan() const
708739
{
709740
if (firstRecordedHeight == 0) return 0;

src/policy/fees.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ enum FeeEstimateHorizon {
7474
LONG_HALFLIFE = 2
7575
};
7676

77+
std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon);
78+
7779
/* Enumeration of reason for returned fee estimate */
7880
enum class FeeReason {
7981
NONE,
@@ -223,6 +225,9 @@ class CBlockPolicyEstimator
223225
/** Empty mempool transactions on shutdown to record failure to confirm for txs still in mempool */
224226
void FlushUnconfirmed(CTxMemPool& pool);
225227

228+
/** Calculation of highest target that estimates are tracked for */
229+
unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const;
230+
226231
private:
227232
unsigned int nBestSeenHeight;
228233
unsigned int firstRecordedHeight;

src/rpc/client.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
117117
{ "estimatesmartfee", 1, "conservative" },
118118
{ "estimaterawfee", 0, "nblocks" },
119119
{ "estimaterawfee", 1, "threshold" },
120-
{ "estimaterawfee", 2, "horizon" },
121120
{ "prioritisetransaction", 1, "dummy" },
122121
{ "prioritisetransaction", 2, "fee_delta" },
123122
{ "setban", 2, "bantime" },

src/rpc/mining.cpp

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -838,9 +838,9 @@ UniValue estimatesmartfee(const JSONRPCRequest& request)
838838

839839
UniValue estimaterawfee(const JSONRPCRequest& request)
840840
{
841-
if (request.fHelp || request.params.size() < 1|| request.params.size() > 3)
841+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
842842
throw std::runtime_error(
843-
"estimaterawfee nblocks (threshold horizon)\n"
843+
"estimaterawfee nblocks (threshold)\n"
844844
"\nWARNING: This interface is unstable and may disappear or change!\n"
845845
"\nWARNING: This is an advanced API call that is tightly coupled to the specific\n"
846846
" implementation of fee estimation. The parameters it can be called with\n"
@@ -849,72 +849,95 @@ UniValue estimaterawfee(const JSONRPCRequest& request)
849849
"confirmation within nblocks blocks if possible. Uses virtual transaction size as defined\n"
850850
"in BIP 141 (witness data is discounted).\n"
851851
"\nArguments:\n"
852-
"1. nblocks (numeric)\n"
852+
"1. nblocks (numeric) Confirmation target in blocks (1 - 1008)\n"
853853
"2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n"
854854
" confirmed within nblocks in order to consider those feerates as high enough and proceed to check\n"
855855
" lower buckets. Default: 0.95\n"
856-
"3. horizon (numeric, optional) How long a history of estimates to consider. 0=short, 1=medium, 2=long.\n"
857-
" Default: 1\n"
858856
"\nResult:\n"
859857
"{\n"
860-
" \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n"
861-
" \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n"
862-
" \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n"
863-
" \"pass\" : { (json object) information about the lowest range of feerates to succeed in meeting the threshold\n"
864-
" \"startrange\" : x.x, (numeric) start of feerate range\n"
865-
" \"endrange\" : x.x, (numeric) end of feerate range\n"
866-
" \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n"
867-
" \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n"
868-
" \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n"
869-
" \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n"
870-
" }\n"
871-
" \"fail\" : { ... } (json object) information about the highest range of feerates to fail to meet the threshold\n"
858+
" \"short\" : { (json object, optional) estimate for short time horizon\n"
859+
" \"feerate\" : x.x, (numeric, optional) estimate fee-per-kilobyte (in BTC)\n"
860+
" \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n"
861+
" \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n"
862+
" \"pass\" : { (json object, optional) information about the lowest range of feerates to succeed in meeting the threshold\n"
863+
" \"startrange\" : x.x, (numeric) start of feerate range\n"
864+
" \"endrange\" : x.x, (numeric) end of feerate range\n"
865+
" \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n"
866+
" \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n"
867+
" \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n"
868+
" \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n"
869+
" },\n"
870+
" \"fail\" : { ... }, (json object, optional) information about the highest range of feerates to fail to meet the threshold\n"
871+
" \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n"
872+
" },\n"
873+
" \"medium\" : { ... }, (json object, optional) estimate for medium time horizon\n"
874+
" \"long\" : { ... } (json object) estimate for long time horizon\n"
872875
"}\n"
873876
"\n"
874-
"A negative feerate is returned if no answer can be given.\n"
877+
"Results are returned for any horizon which tracks blocks up to the confirmation target.\n"
875878
"\nExample:\n"
876-
+ HelpExampleCli("estimaterawfee", "6 0.9 1")
879+
+ HelpExampleCli("estimaterawfee", "6 0.9")
877880
);
878881

879882
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM, UniValue::VNUM}, true);
880883
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
881884
int nBlocks = request.params[0].get_int();
885+
if (nBlocks < 1 || (unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE)) {
886+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid nblocks");
887+
}
882888
double threshold = 0.95;
883-
if (!request.params[1].isNull())
889+
if (!request.params[1].isNull()) {
884890
threshold = request.params[1].get_real();
885-
FeeEstimateHorizon horizon = FeeEstimateHorizon::MED_HALFLIFE;
886-
if (!request.params[2].isNull()) {
887-
int horizonInt = request.params[2].get_int();
888-
if (horizonInt < 0 || horizonInt > 2) {
889-
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid horizon for fee estimates");
890-
} else {
891-
horizon = (FeeEstimateHorizon)horizonInt;
892-
}
893891
}
892+
if (threshold < 0 || threshold > 1) {
893+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold");
894+
}
895+
894896
UniValue result(UniValue::VOBJ);
895-
CFeeRate feeRate;
896-
EstimationResult buckets;
897-
feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets);
898897

899-
result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK())));
900-
result.push_back(Pair("decay", buckets.decay));
901-
result.push_back(Pair("scale", (int)buckets.scale));
902-
UniValue passbucket(UniValue::VOBJ);
903-
passbucket.push_back(Pair("startrange", round(buckets.pass.start)));
904-
passbucket.push_back(Pair("endrange", round(buckets.pass.end)));
905-
passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0));
906-
passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0));
907-
passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0));
908-
passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0));
909-
result.push_back(Pair("pass", passbucket));
910-
UniValue failbucket(UniValue::VOBJ);
911-
failbucket.push_back(Pair("startrange", round(buckets.fail.start)));
912-
failbucket.push_back(Pair("endrange", round(buckets.fail.end)));
913-
failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0));
914-
failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0));
915-
failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0));
916-
failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0));
917-
result.push_back(Pair("fail", failbucket));
898+
for (FeeEstimateHorizon horizon : {FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}) {
899+
CFeeRate feeRate;
900+
EstimationResult buckets;
901+
902+
// Only output results for horizons which track the target
903+
if ((unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(horizon)) continue;
904+
905+
feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets);
906+
UniValue horizon_result(UniValue::VOBJ);
907+
UniValue errors(UniValue::VARR);
908+
UniValue passbucket(UniValue::VOBJ);
909+
passbucket.push_back(Pair("startrange", round(buckets.pass.start)));
910+
passbucket.push_back(Pair("endrange", round(buckets.pass.end)));
911+
passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0));
912+
passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0));
913+
passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0));
914+
passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0));
915+
UniValue failbucket(UniValue::VOBJ);
916+
failbucket.push_back(Pair("startrange", round(buckets.fail.start)));
917+
failbucket.push_back(Pair("endrange", round(buckets.fail.end)));
918+
failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0));
919+
failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0));
920+
failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0));
921+
failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0));
922+
923+
// CFeeRate(0) is used to indicate error as a return value from estimateRawFee
924+
if (feeRate != CFeeRate(0)) {
925+
horizon_result.push_back(Pair("feerate", ValueFromAmount(feeRate.GetFeePerK())));
926+
horizon_result.push_back(Pair("decay", buckets.decay));
927+
horizon_result.push_back(Pair("scale", (int)buckets.scale));
928+
horizon_result.push_back(Pair("pass", passbucket));
929+
// buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output
930+
if (buckets.fail.start != -1) horizon_result.push_back(Pair("fail", failbucket));
931+
} else {
932+
// Output only information that is still meaningful in the event of error
933+
horizon_result.push_back(Pair("decay", buckets.decay));
934+
horizon_result.push_back(Pair("scale", (int)buckets.scale));
935+
horizon_result.push_back(Pair("fail", failbucket));
936+
errors.push_back("Insufficient data or no feerate found which meets threshold");
937+
horizon_result.push_back(Pair("errors",errors));
938+
}
939+
result.push_back(Pair(StringForFeeEstimateHorizon(horizon), horizon_result));
940+
}
918941
return result;
919942
}
920943

@@ -932,7 +955,7 @@ static const CRPCCommand commands[] =
932955
{ "util", "estimatefee", &estimatefee, true, {"nblocks"} },
933956
{ "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks", "conservative"} },
934957

935-
{ "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold", "horizon"} },
958+
{ "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold"} },
936959
};
937960

938961
void RegisterMiningRPCCommands(CRPCTable &t)

0 commit comments

Comments
 (0)