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.*
|
*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
|
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.
|
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 <blockfilter.h>
|
||||||
#include <chain.h>
|
#include <chain.h>
|
||||||
#include <chainparams.h>
|
#include <chainparams.h>
|
||||||
|
#include <common/messages.h>
|
||||||
#include <core_io.h>
|
#include <core_io.h>
|
||||||
#include <flatfile.h>
|
#include <flatfile.h>
|
||||||
#include <httpserver.h>
|
#include <httpserver.h>
|
||||||
@ -31,6 +32,7 @@
|
|||||||
#include <util/check.h>
|
#include <util/check.h>
|
||||||
#include <util/strencodings.h>
|
#include <util/strencodings.h>
|
||||||
#include <validation.h>
|
#include <validation.h>
|
||||||
|
#include <policy/fees.h>
|
||||||
|
|
||||||
#include <any>
|
#include <any>
|
||||||
#include <vector>
|
#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 {
|
static const struct {
|
||||||
const char* prefix;
|
const char* prefix;
|
||||||
bool (*handler)(const std::any& context, HTTPRequest* req, const std::string& strReq);
|
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/deploymentinfo", rest_deploymentinfo},
|
{"/rest/deploymentinfo", rest_deploymentinfo},
|
||||||
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
|
{"/rest/blockhashbyheight/", rest_blockhash_by_height},
|
||||||
|
{"/rest/fee", rest_getfee},
|
||||||
};
|
};
|
||||||
|
|
||||||
void StartREST(const std::any& context)
|
void StartREST(const std::any& context)
|
||||||
|
@ -49,6 +49,9 @@ def filter_output_indices_by_value(vouts, value):
|
|||||||
yield vout['n']
|
yield vout['n']
|
||||||
|
|
||||||
class RESTTest (BitcoinTestFramework):
|
class RESTTest (BitcoinTestFramework):
|
||||||
|
def add_options(self, parser):
|
||||||
|
self.add_wallet_options(parser)
|
||||||
|
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 2
|
self.num_nodes = 2
|
||||||
self.extra_args = [["-rest", "-blockfilterindex=1"], []]
|
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)
|
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}")
|
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__':
|
if __name__ == '__main__':
|
||||||
RESTTest(__file__).main()
|
RESTTest(__file__).main()
|
||||||
|
Loading…
Reference in New Issue
Block a user