From 9c85b91dc1566f7c27c5b8a1a9c7aebe0e3a83cf Mon Sep 17 00:00:00 2001 From: Alex Morcos Date: Tue, 6 Jun 2017 13:08:27 -0400 Subject: Change API to estimaterawfee Report results for all 3 possible time horizons instead of specifying time horizon as an argument. --- src/rpc/client.cpp | 1 - src/rpc/mining.cpp | 100 +++++++++++++++++++++++++++-------------------------- 2 files changed, 51 insertions(+), 50 deletions(-) (limited to 'src/rpc') diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8fac8af3ec..4c5eb6dc69 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -113,7 +113,6 @@ static const CRPCConvertParam vRPCConvertParams[] = { "estimatesmartfee", 1, "conservative" }, { "estimaterawfee", 0, "nblocks" }, { "estimaterawfee", 1, "threshold" }, - { "estimaterawfee", 2, "horizon" }, { "prioritisetransaction", 1, "dummy" }, { "prioritisetransaction", 2, "fee_delta" }, { "setban", 2, "bantime" }, diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index e50742f36e..a15931386b 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -838,9 +838,9 @@ UniValue estimatesmartfee(const JSONRPCRequest& request) UniValue estimaterawfee(const JSONRPCRequest& request) { - if (request.fHelp || request.params.size() < 1|| request.params.size() > 3) + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) throw std::runtime_error( - "estimaterawfee nblocks (threshold horizon)\n" + "estimaterawfee nblocks (threshold)\n" "\nWARNING: This interface is unstable and may disappear or change!\n" "\nWARNING: This is an advanced API call that is tightly coupled to the specific\n" " implementation of fee estimation. The parameters it can be called with\n" @@ -853,68 +853,70 @@ UniValue estimaterawfee(const JSONRPCRequest& request) "2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n" " confirmed within nblocks in order to consider those feerates as high enough and proceed to check\n" " lower buckets. Default: 0.95\n" - "3. horizon (numeric, optional) How long a history of estimates to consider. 0=short, 1=medium, 2=long.\n" - " Default: 1\n" "\nResult:\n" "{\n" - " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n" - " \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n" - " \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n" - " \"pass\" : { (json object) information about the lowest range of feerates to succeed in meeting the threshold\n" - " \"startrange\" : x.x, (numeric) start of feerate range\n" - " \"endrange\" : x.x, (numeric) end of feerate range\n" - " \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n" - " \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n" - " \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n" - " \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n" - " }\n" - " \"fail\" : { ... } (json object) information about the highest range of feerates to fail to meet the threshold\n" + " \"short\" : { (json object) estimate for short time horizon\n" + " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n" + " \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n" + " \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n" + " \"pass\" : { (json object) information about the lowest range of feerates to succeed in meeting the threshold\n" + " \"startrange\" : x.x, (numeric) start of feerate range\n" + " \"endrange\" : x.x, (numeric) end of feerate range\n" + " \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n" + " \"totalconfirmed\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed at any point\n" + " \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n" + " \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n" + " },\n" + " \"fail\" : { ... }, (json object) information about the highest range of feerates to fail to meet the threshold\n" + " },\n" + " \"medium\" : { ... }, (json object) estimate for medium time horizon\n" + " \"long\" : { ... } (json object) estimate for long time horizon\n" "}\n" "\n" "A negative feerate is returned if no answer can be given.\n" "\nExample:\n" - + HelpExampleCli("estimaterawfee", "6 0.9 1") + + HelpExampleCli("estimaterawfee", "6 0.9") ); RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); int nBlocks = request.params[0].get_int(); double threshold = 0.95; - if (!request.params[1].isNull()) + if (!request.params[1].isNull()) { threshold = request.params[1].get_real(); - FeeEstimateHorizon horizon = FeeEstimateHorizon::MED_HALFLIFE; - if (!request.params[2].isNull()) { - int horizonInt = request.params[2].get_int(); - if (horizonInt < 0 || horizonInt > 2) { - throw JSONRPCError(RPC_TYPE_ERROR, "Invalid horizon for fee estimates"); - } else { - horizon = (FeeEstimateHorizon)horizonInt; - } } + UniValue result(UniValue::VOBJ); - CFeeRate feeRate; - EstimationResult buckets; - feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets); - result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); - result.push_back(Pair("decay", buckets.decay)); - result.push_back(Pair("scale", (int)buckets.scale)); - UniValue passbucket(UniValue::VOBJ); - passbucket.push_back(Pair("startrange", round(buckets.pass.start))); - passbucket.push_back(Pair("endrange", round(buckets.pass.end))); - passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0)); - passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0)); - passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0)); - passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0)); - result.push_back(Pair("pass", passbucket)); - UniValue failbucket(UniValue::VOBJ); - failbucket.push_back(Pair("startrange", round(buckets.fail.start))); - failbucket.push_back(Pair("endrange", round(buckets.fail.end))); - failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0)); - failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0)); - failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0)); - failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0)); - result.push_back(Pair("fail", failbucket)); + for (FeeEstimateHorizon horizon : {FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}) { + CFeeRate feeRate; + EstimationResult buckets; + feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets); + + UniValue horizon_result(UniValue::VOBJ); + horizon_result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); + if (!(feeRate == CFeeRate(0))) { + horizon_result.push_back(Pair("decay", buckets.decay)); + horizon_result.push_back(Pair("scale", (int)buckets.scale)); + UniValue passbucket(UniValue::VOBJ); + passbucket.push_back(Pair("startrange", round(buckets.pass.start))); + passbucket.push_back(Pair("endrange", round(buckets.pass.end))); + passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0)); + passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0)); + passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0)); + passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0)); + horizon_result.push_back(Pair("pass", passbucket)); + UniValue failbucket(UniValue::VOBJ); + failbucket.push_back(Pair("startrange", round(buckets.fail.start))); + failbucket.push_back(Pair("endrange", round(buckets.fail.end))); + failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0)); + failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0)); + failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0)); + failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0)); + horizon_result.push_back(Pair("fail", failbucket)); + } + result.push_back(Pair(StringForFeeEstimateHorizon(horizon), horizon_result)); + } return result; } @@ -932,7 +934,7 @@ static const CRPCCommand commands[] = { "util", "estimatefee", &estimatefee, true, {"nblocks"} }, { "util", "estimatesmartfee", &estimatesmartfee, true, {"nblocks", "conservative"} }, - { "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold", "horizon"} }, + { "hidden", "estimaterawfee", &estimaterawfee, true, {"nblocks", "threshold"} }, }; void RegisterMiningRPCCommands(CRPCTable &t) -- cgit v1.2.3 From 5e3b7b5686d326814c40e5b70da6697971d27092 Mon Sep 17 00:00:00 2001 From: Alex Morcos Date: Wed, 28 Jun 2017 10:50:32 -0400 Subject: Improve error reporting for estimaterawfee --- src/rpc/mining.cpp | 71 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 25 deletions(-) (limited to 'src/rpc') diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index a15931386b..046c85a76e 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -849,17 +849,17 @@ UniValue estimaterawfee(const JSONRPCRequest& request) "confirmation within nblocks blocks if possible. Uses virtual transaction size as defined\n" "in BIP 141 (witness data is discounted).\n" "\nArguments:\n" - "1. nblocks (numeric)\n" + "1. nblocks (numeric) Confirmation target in blocks (1 - 1008)\n" "2. threshold (numeric, optional) The proportion of transactions in a given feerate range that must have been\n" " confirmed within nblocks in order to consider those feerates as high enough and proceed to check\n" " lower buckets. Default: 0.95\n" "\nResult:\n" "{\n" - " \"short\" : { (json object) estimate for short time horizon\n" - " \"feerate\" : x.x, (numeric) estimate fee-per-kilobyte (in BTC)\n" + " \"short\" : { (json object, optional) estimate for short time horizon\n" + " \"feerate\" : x.x, (numeric, optional) estimate fee-per-kilobyte (in BTC)\n" " \"decay\" : x.x, (numeric) exponential decay (per block) for historical moving average of confirmation data\n" " \"scale\" : x, (numeric) The resolution of confirmation targets at this time horizon\n" - " \"pass\" : { (json object) information about the lowest range of feerates to succeed in meeting the threshold\n" + " \"pass\" : { (json object, optional) information about the lowest range of feerates to succeed in meeting the threshold\n" " \"startrange\" : x.x, (numeric) start of feerate range\n" " \"endrange\" : x.x, (numeric) end of feerate range\n" " \"withintarget\" : x.x, (numeric) number of txs over history horizon in the feerate range that were confirmed within target\n" @@ -867,13 +867,14 @@ UniValue estimaterawfee(const JSONRPCRequest& request) " \"inmempool\" : x.x, (numeric) current number of txs in mempool in the feerate range unconfirmed for at least target blocks\n" " \"leftmempool\" : x.x, (numeric) number of txs over history horizon in the feerate range that left mempool unconfirmed after target\n" " },\n" - " \"fail\" : { ... }, (json object) information about the highest range of feerates to fail to meet the threshold\n" + " \"fail\" : { ... }, (json object, optional) information about the highest range of feerates to fail to meet the threshold\n" + " \"errors\": [ str... ] (json array of strings, optional) Errors encountered during processing\n" " },\n" - " \"medium\" : { ... }, (json object) estimate for medium time horizon\n" - " \"long\" : { ... } (json object) estimate for long time horizon\n" + " \"medium\" : { ... }, (json object, optional) estimate for medium time horizon\n" + " \"long\" : { ... } (json object) estimate for long time horizon\n" "}\n" "\n" - "A negative feerate is returned if no answer can be given.\n" + "Results are returned for any horizon which tracks blocks up to the confirmation target.\n" "\nExample:\n" + HelpExampleCli("estimaterawfee", "6 0.9") ); @@ -881,39 +882,59 @@ UniValue estimaterawfee(const JSONRPCRequest& request) RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM, UniValue::VNUM}, true); RPCTypeCheckArgument(request.params[0], UniValue::VNUM); int nBlocks = request.params[0].get_int(); + if (nBlocks < 1 || (unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid nblocks"); + } double threshold = 0.95; if (!request.params[1].isNull()) { threshold = request.params[1].get_real(); } + if (threshold < 0 || threshold > 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid threshold"); + } UniValue result(UniValue::VOBJ); for (FeeEstimateHorizon horizon : {FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}) { CFeeRate feeRate; EstimationResult buckets; - feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets); + // Only output results for horizons which track the target + if ((unsigned int)nBlocks > ::feeEstimator.HighestTargetTracked(horizon)) continue; + + feeRate = ::feeEstimator.estimateRawFee(nBlocks, threshold, horizon, &buckets); UniValue horizon_result(UniValue::VOBJ); - horizon_result.push_back(Pair("feerate", feeRate == CFeeRate(0) ? -1.0 : ValueFromAmount(feeRate.GetFeePerK()))); - if (!(feeRate == CFeeRate(0))) { + UniValue errors(UniValue::VARR); + UniValue passbucket(UniValue::VOBJ); + passbucket.push_back(Pair("startrange", round(buckets.pass.start))); + passbucket.push_back(Pair("endrange", round(buckets.pass.end))); + passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0)); + passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0)); + passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0)); + passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0)); + UniValue failbucket(UniValue::VOBJ); + failbucket.push_back(Pair("startrange", round(buckets.fail.start))); + failbucket.push_back(Pair("endrange", round(buckets.fail.end))); + failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0)); + failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0)); + failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0)); + failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0)); + + // CFeeRate(0) is used to indicate error as a return value from estimateRawFee + if (feeRate != CFeeRate(0)) { + horizon_result.push_back(Pair("feerate", ValueFromAmount(feeRate.GetFeePerK()))); horizon_result.push_back(Pair("decay", buckets.decay)); horizon_result.push_back(Pair("scale", (int)buckets.scale)); - UniValue passbucket(UniValue::VOBJ); - passbucket.push_back(Pair("startrange", round(buckets.pass.start))); - passbucket.push_back(Pair("endrange", round(buckets.pass.end))); - passbucket.push_back(Pair("withintarget", round(buckets.pass.withinTarget * 100.0) / 100.0)); - passbucket.push_back(Pair("totalconfirmed", round(buckets.pass.totalConfirmed * 100.0) / 100.0)); - passbucket.push_back(Pair("inmempool", round(buckets.pass.inMempool * 100.0) / 100.0)); - passbucket.push_back(Pair("leftmempool", round(buckets.pass.leftMempool * 100.0) / 100.0)); horizon_result.push_back(Pair("pass", passbucket)); - UniValue failbucket(UniValue::VOBJ); - failbucket.push_back(Pair("startrange", round(buckets.fail.start))); - failbucket.push_back(Pair("endrange", round(buckets.fail.end))); - failbucket.push_back(Pair("withintarget", round(buckets.fail.withinTarget * 100.0) / 100.0)); - failbucket.push_back(Pair("totalconfirmed", round(buckets.fail.totalConfirmed * 100.0) / 100.0)); - failbucket.push_back(Pair("inmempool", round(buckets.fail.inMempool * 100.0) / 100.0)); - failbucket.push_back(Pair("leftmempool", round(buckets.fail.leftMempool * 100.0) / 100.0)); + // buckets.fail.start == -1 indicates that all buckets passed, there is no fail bucket to output + if (buckets.fail.start != -1) horizon_result.push_back(Pair("fail", failbucket)); + } else { + // Output only information that is still meaningful in the event of error + horizon_result.push_back(Pair("decay", buckets.decay)); + horizon_result.push_back(Pair("scale", (int)buckets.scale)); horizon_result.push_back(Pair("fail", failbucket)); + errors.push_back("Insufficient data or no feerate found which meets threshold"); + horizon_result.push_back(Pair("errors",errors)); } result.push_back(Pair(StringForFeeEstimateHorizon(horizon), horizon_result)); } -- cgit v1.2.3