mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-12 19:20:42 +02:00
rest: add endpoint for estimatesmartfee
* write REST interface for getting estimated fee * update docs about REST interface for fee estimation * add test
This commit is contained in:
parent
1248d0da22
commit
bada19a8e8
@ -144,6 +144,12 @@ Refer to the `getrawmempool` RPC help for details. Defaults to setting
|
||||
*Query parameters for `verbose` and `mempool_sequence` available in 25.0 and up.*
|
||||
|
||||
|
||||
#### Fees
|
||||
`GET /rest/fee/<MODE>/<TARGET>.json`
|
||||
|
||||
Returns fee and blocknumber where estimation was found. `<MODE>` should be one of `<unset|conservative|economical>`.
|
||||
`<TARGET>` is the desired confirmation time (in block height).
|
||||
|
||||
Risks
|
||||
-------------
|
||||
Running a web browser on the same node with a REST enabled bitcoind can be a risk. Accessing prepared XSS websites could read out tx/block data of your node by placing links like `<script src="http://127.0.0.1:8332/rest/tx/1234567890.json">` which might break the nodes privacy.
|
||||
|
67
src/rest.cpp
67
src/rest.cpp
@ -10,6 +10,7 @@
|
||||
#include <blockfilter.h>
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <common/messages.h>
|
||||
#include <core_io.h>
|
||||
#include <flatfile.h>
|
||||
#include <httpserver.h>
|
||||
@ -31,6 +32,7 @@
|
||||
#include <util/check.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <validation.h>
|
||||
#include <policy/fees.h>
|
||||
|
||||
#include <any>
|
||||
#include <vector>
|
||||
@ -1003,6 +1005,70 @@ static bool rest_blockhash_by_height(const std::any& context, HTTPRequest* req,
|
||||
}
|
||||
}
|
||||
|
||||
static bool rest_getfee(const std::any& context, HTTPRequest* req, const std::string& strURIPart) {
|
||||
if (!CheckWarmup(req)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const NodeContext* const node = GetNodeContext(context, req);
|
||||
if (!node) return false;
|
||||
const CBlockPolicyEstimator* const fee_estimator = node->fee_estimator.get();
|
||||
if (!fee_estimator) return false;
|
||||
|
||||
std::string param;
|
||||
const RESTResponseFormat rf = ParseDataFormat(param, strURIPart);
|
||||
switch (rf) {
|
||||
case RESTResponseFormat::JSON: {
|
||||
std::vector<std::string> path = SplitString(param, '/');
|
||||
path.erase(path.begin());
|
||||
// check url scheme is correct
|
||||
if (path.size() != 2) {
|
||||
return RESTERR(req, HTTP_BAD_REQUEST, "Path must be /rest/fee/<MODE>/<TARGET>.json");
|
||||
}
|
||||
// check estimation mode is valid
|
||||
const auto modestr = ToUpper(path[0]);
|
||||
FeeEstimateMode mode;
|
||||
if (!common::FeeModeFromString(modestr, mode)){
|
||||
return RESTERR(req, HTTP_BAD_REQUEST, "<MODE> must be one of <unset|economical|conservative>");
|
||||
}
|
||||
|
||||
// type conversions for estimateSmartFee
|
||||
bool conservative = mode == FeeEstimateMode::CONSERVATIVE;
|
||||
const auto parsed_conf_target{ToIntegral<int64_t>(path[1])};
|
||||
if (!parsed_conf_target.has_value()) {
|
||||
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Unable to parse confirmation target to int"));
|
||||
}
|
||||
int64_t conf_target{*parsed_conf_target};
|
||||
unsigned int max_target = fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
|
||||
if (conf_target < 1 || (unsigned int)conf_target > max_target) {
|
||||
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Invalid confirmation target, must be in between %u - %u", 1, max_target));
|
||||
}
|
||||
|
||||
// perform fee estimation
|
||||
FeeCalculation feeCalc;
|
||||
CFeeRate estimatedfee = fee_estimator->estimateSmartFee(conf_target, &feeCalc, conservative);
|
||||
|
||||
// create json for replying
|
||||
UniValue feejson(UniValue::VOBJ);
|
||||
if (estimatedfee != CFeeRate(0)) {
|
||||
feejson.pushKV("feerate", ValueFromAmount(estimatedfee.GetFeePerK()));
|
||||
} else {
|
||||
return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Insufficient data or no feerate found");
|
||||
}
|
||||
feejson.pushKV("blocks", feeCalc.returnedTarget);
|
||||
|
||||
// reply
|
||||
std::string strJSON = feejson.write() + "\n";
|
||||
req->WriteHeader("Content-Type", "application/json");
|
||||
req->WriteReply(HTTP_OK, strJSON);
|
||||
return true;
|
||||
}
|
||||
default: {
|
||||
return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char* prefix;
|
||||
bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq);
|
||||
@ -1019,6 +1085,7 @@ static const struct {
|
||||
{"/rest/deploymentinfo/", rest_deploymentinfo},
|
||||
{"/rest/deploymentinfo", rest_deploymentinfo},
|
||||
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
|
||||
{"/rest/fee", rest_getfee},
|
||||
};
|
||||
|
||||
void StartREST(const std::any& context)
|
||||
|
@ -49,6 +49,9 @@ def filter_output_indices_by_value(vouts, value):
|
||||
yield vout['n']
|
||||
|
||||
class RESTTest (BitcoinTestFramework):
|
||||
def add_options(self, parser):
|
||||
self.add_wallet_options(parser)
|
||||
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.extra_args = [["-rest", "-blockfilterindex=1"], []]
|
||||
@ -440,5 +443,23 @@ class RESTTest (BitcoinTestFramework):
|
||||
resp = self.test_rest_request(f"/deploymentinfo/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400)
|
||||
assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}")
|
||||
|
||||
if self.is_wallet_compiled():
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
|
||||
# Random address so node1's balance doesn't increase
|
||||
not_related_address = "2MxqoHEdNQTyYeX1mHcbrrpzgojbosTpCvJ"
|
||||
|
||||
# Prepare for Fee estimation
|
||||
for i in range(18):
|
||||
self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
|
||||
self.sync_all()
|
||||
self.generatetoaddress(self.nodes[1], 1, not_related_address)
|
||||
self.sync_all()
|
||||
|
||||
json_obj = self.test_rest_request("/fee/conservative/1")
|
||||
assert_greater_than(float(json_obj["feerate"]), 0)
|
||||
assert_greater_than(int(json_obj["blocks"]), 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RESTTest(__file__).main()
|
||||
|
Loading…
Reference in New Issue
Block a user