aboutsummaryrefslogtreecommitdiff
path: root/src/rpc/blockchain.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/rpc/blockchain.cpp')
-rw-r--r--src/rpc/blockchain.cpp303
1 files changed, 288 insertions, 15 deletions
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index d9d803ac7d..6fb04ab005 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1,11 +1,13 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2017 The Bitcoin Core developers
+// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <rpc/blockchain.h>
#include <amount.h>
+#include <base58.h>
+#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <coins.h>
@@ -13,10 +15,13 @@
#include <validation.h>
#include <core_io.h>
#include <index/txindex.h>
+#include <key_io.h>
#include <policy/feerate.h>
#include <policy/policy.h>
+#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
+#include <script/descriptor.h>
#include <streams.h>
#include <sync.h>
#include <txdb.h>
@@ -27,6 +32,7 @@
#include <validationinterface.h>
#include <warnings.h>
+#include <assert.h>
#include <stdint.h>
#include <univalue.h>
@@ -44,7 +50,7 @@ struct CUpdatedBlock
int height;
};
-static std::mutex cs_blockchange;
+static Mutex cs_blockchange;
static std::condition_variable cond_blockchange;
static CUpdatedBlock latestblock;
@@ -219,7 +225,7 @@ static UniValue waitfornewblock(const JSONRPCRequest& request)
CUpdatedBlock block;
{
- std::unique_lock<std::mutex> lock(cs_blockchange);
+ WAIT_LOCK(cs_blockchange, lock);
block = latestblock;
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&block]{return latestblock.height != block.height || latestblock.hash != block.hash || !IsRPCRunning(); });
@@ -261,7 +267,7 @@ static UniValue waitforblock(const JSONRPCRequest& request)
CUpdatedBlock block;
{
- std::unique_lock<std::mutex> lock(cs_blockchange);
+ WAIT_LOCK(cs_blockchange, lock);
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&hash]{return latestblock.hash == hash || !IsRPCRunning();});
else
@@ -304,7 +310,7 @@ static UniValue waitforblockheight(const JSONRPCRequest& request)
CUpdatedBlock block;
{
- std::unique_lock<std::mutex> lock(cs_blockchange);
+ WAIT_LOCK(cs_blockchange, lock);
if(timeout)
cond_blockchange.wait_for(lock, std::chrono::milliseconds(timeout), [&height]{return latestblock.height >= height || !IsRPCRunning();});
else
@@ -374,7 +380,8 @@ static std::string EntryDescriptionString()
" ... ]\n"
" \"spentby\" : [ (array) unconfirmed transactions spending outputs from this transaction\n"
" \"transactionid\", (string) child transaction id\n"
- " ... ]\n";
+ " ... ]\n"
+ " \"bip125-replaceable\" : true|false, (boolean) Whether this transaction could be replaced due to BIP125 (replace-by-fee)\n";
}
static void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCKS_REQUIRED(::mempool.cs)
@@ -419,11 +426,22 @@ static void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) EXCLUSIVE_LOCK
UniValue spent(UniValue::VARR);
const CTxMemPool::txiter &it = mempool.mapTx.find(tx.GetHash());
const CTxMemPool::setEntries &setChildren = mempool.GetMemPoolChildren(it);
- for (const CTxMemPool::txiter &childiter : setChildren) {
+ for (CTxMemPool::txiter childiter : setChildren) {
spent.push_back(childiter->GetTx().GetHash().ToString());
}
info.pushKV("spentby", spent);
+
+ // Add opt-in RBF status
+ bool rbfStatus = false;
+ RBFTransactionState rbfState = IsRBFOptIn(tx, mempool);
+ if (rbfState == RBFTransactionState::UNKNOWN) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not in mempool");
+ } else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) {
+ rbfStatus = true;
+ }
+
+ info.pushKV("bip125-replaceable", rbfStatus);
}
UniValue mempoolToJSON(bool fVerbose)
@@ -1635,6 +1653,35 @@ static T CalculateTruncatedMedian(std::vector<T>& scores)
}
}
+void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight)
+{
+ if (scores.empty()) {
+ return;
+ }
+
+ std::sort(scores.begin(), scores.end());
+
+ // 10th, 25th, 50th, 75th, and 90th percentile weight units.
+ const double weights[NUM_GETBLOCKSTATS_PERCENTILES] = {
+ total_weight / 10.0, total_weight / 4.0, total_weight / 2.0, (total_weight * 3.0) / 4.0, (total_weight * 9.0) / 10.0
+ };
+
+ int64_t next_percentile_index = 0;
+ int64_t cumulative_weight = 0;
+ for (const auto& element : scores) {
+ cumulative_weight += element.second;
+ while (next_percentile_index < NUM_GETBLOCKSTATS_PERCENTILES && cumulative_weight >= weights[next_percentile_index]) {
+ result[next_percentile_index] = element.first;
+ ++next_percentile_index;
+ }
+ }
+
+ // Fill any remaining percentiles with the last value.
+ for (int64_t i = next_percentile_index; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
+ result[i] = scores.back().first;
+ }
+}
+
template<typename T>
static inline bool SetHasKeys(const std::set<T>& set) {return false;}
template<typename T, typename Tk, typename... Args>
@@ -1668,13 +1715,19 @@ static UniValue getblockstats(const JSONRPCRequest& request)
" \"avgfeerate\": xxxxx, (numeric) Average feerate (in satoshis per virtual byte)\n"
" \"avgtxsize\": xxxxx, (numeric) Average transaction size\n"
" \"blockhash\": xxxxx, (string) The block hash (to check for potential reorgs)\n"
+ " \"feerate_percentiles\": [ (array of numeric) Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)\n"
+ " \"10th_percentile_feerate\", (numeric) The 10th percentile feerate\n"
+ " \"25th_percentile_feerate\", (numeric) The 25th percentile feerate\n"
+ " \"50th_percentile_feerate\", (numeric) The 50th percentile feerate\n"
+ " \"75th_percentile_feerate\", (numeric) The 75th percentile feerate\n"
+ " \"90th_percentile_feerate\", (numeric) The 90th percentile feerate\n"
+ " ],\n"
" \"height\": xxxxx, (numeric) The height of the block\n"
" \"ins\": xxxxx, (numeric) The number of inputs (excluding coinbase)\n"
" \"maxfee\": xxxxx, (numeric) Maximum fee in the block\n"
" \"maxfeerate\": xxxxx, (numeric) Maximum feerate (in satoshis per virtual byte)\n"
" \"maxtxsize\": xxxxx, (numeric) Maximum transaction size\n"
" \"medianfee\": xxxxx, (numeric) Truncated median fee in the block\n"
- " \"medianfeerate\": xxxxx, (numeric) Truncated median feerate (in satoshis per virtual byte)\n"
" \"mediantime\": xxxxx, (numeric) The block median time past\n"
" \"mediantxsize\": xxxxx, (numeric) Truncated median transaction size\n"
" \"minfee\": xxxxx, (numeric) Minimum fee in the block\n"
@@ -1742,13 +1795,13 @@ static UniValue getblockstats(const JSONRPCRequest& request)
const bool do_all = stats.size() == 0; // Calculate everything if nothing selected (default)
const bool do_mediantxsize = do_all || stats.count("mediantxsize") != 0;
const bool do_medianfee = do_all || stats.count("medianfee") != 0;
- const bool do_medianfeerate = do_all || stats.count("medianfeerate") != 0;
- const bool loop_inputs = do_all || do_medianfee || do_medianfeerate ||
+ const bool do_feerate_percentiles = do_all || stats.count("feerate_percentiles") != 0;
+ const bool loop_inputs = do_all || do_medianfee || do_feerate_percentiles ||
SetHasKeys(stats, "utxo_size_inc", "totalfee", "avgfee", "avgfeerate", "minfee", "maxfee", "minfeerate", "maxfeerate");
const bool loop_outputs = do_all || loop_inputs || stats.count("total_out");
const bool do_calculate_size = do_mediantxsize ||
SetHasKeys(stats, "total_size", "avgtxsize", "mintxsize", "maxtxsize", "swtotal_size");
- const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "medianfeerate", "minfeerate", "maxfeerate");
+ const bool do_calculate_weight = do_all || SetHasKeys(stats, "total_weight", "avgfeerate", "swtotal_weight", "avgfeerate", "feerate_percentiles", "minfeerate", "maxfeerate");
const bool do_calculate_sw = do_all || SetHasKeys(stats, "swtxs", "swtotal_size", "swtotal_weight");
CAmount maxfee = 0;
@@ -1768,7 +1821,7 @@ static UniValue getblockstats(const JSONRPCRequest& request)
int64_t total_weight = 0;
int64_t utxo_size_inc = 0;
std::vector<CAmount> fee_array;
- std::vector<CAmount> feerate_array;
+ std::vector<std::pair<CAmount, int64_t>> feerate_array;
std::vector<int64_t> txsize_array;
for (const auto& tx : block.vtx) {
@@ -1843,26 +1896,34 @@ static UniValue getblockstats(const JSONRPCRequest& request)
// New feerate uses satoshis per virtual byte instead of per serialized byte
CAmount feerate = weight ? (txfee * WITNESS_SCALE_FACTOR) / weight : 0;
- if (do_medianfeerate) {
- feerate_array.push_back(feerate);
+ if (do_feerate_percentiles) {
+ feerate_array.emplace_back(std::make_pair(feerate, weight));
}
maxfeerate = std::max(maxfeerate, feerate);
minfeerate = std::min(minfeerate, feerate);
}
}
+ CAmount feerate_percentiles[NUM_GETBLOCKSTATS_PERCENTILES] = { 0 };
+ CalculatePercentilesByWeight(feerate_percentiles, feerate_array, total_weight);
+
+ UniValue feerates_res(UniValue::VARR);
+ for (int64_t i = 0; i < NUM_GETBLOCKSTATS_PERCENTILES; i++) {
+ feerates_res.push_back(feerate_percentiles[i]);
+ }
+
UniValue ret_all(UniValue::VOBJ);
ret_all.pushKV("avgfee", (block.vtx.size() > 1) ? totalfee / (block.vtx.size() - 1) : 0);
ret_all.pushKV("avgfeerate", total_weight ? (totalfee * WITNESS_SCALE_FACTOR) / total_weight : 0); // Unit: sat/vbyte
ret_all.pushKV("avgtxsize", (block.vtx.size() > 1) ? total_size / (block.vtx.size() - 1) : 0);
ret_all.pushKV("blockhash", pindex->GetBlockHash().GetHex());
+ ret_all.pushKV("feerate_percentiles", feerates_res);
ret_all.pushKV("height", (int64_t)pindex->nHeight);
ret_all.pushKV("ins", inputs);
ret_all.pushKV("maxfee", maxfee);
ret_all.pushKV("maxfeerate", maxfeerate);
ret_all.pushKV("maxtxsize", maxtxsize);
ret_all.pushKV("medianfee", CalculateTruncatedMedian(fee_array));
- ret_all.pushKV("medianfeerate", CalculateTruncatedMedian(feerate_array));
ret_all.pushKV("mediantime", pindex->GetMedianTimePast());
ret_all.pushKV("mediantxsize", CalculateTruncatedMedian(txsize_array));
ret_all.pushKV("minfee", (minfee == MAX_MONEY) ? 0 : minfee);
@@ -1920,6 +1981,217 @@ static UniValue savemempool(const JSONRPCRequest& request)
return NullUniValue;
}
+//! Search for a given set of pubkey scripts
+bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
+ scan_progress = 0;
+ count = 0;
+ while (cursor->Valid()) {
+ COutPoint key;
+ Coin coin;
+ if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
+ if (++count % 8192 == 0) {
+ boost::this_thread::interruption_point();
+ if (should_abort) {
+ // allow to abort the scan via the abort reference
+ return false;
+ }
+ }
+ if (count % 256 == 0) {
+ // update progress reference every 256 item
+ uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1);
+ scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
+ }
+ if (needles.count(coin.out.scriptPubKey)) {
+ out_results.emplace(key, coin);
+ }
+ cursor->Next();
+ }
+ scan_progress = 100;
+ return true;
+}
+
+/** RAII object to prevent concurrency issue when scanning the txout set */
+static std::mutex g_utxosetscan;
+static std::atomic<int> g_scan_progress;
+static std::atomic<bool> g_scan_in_progress;
+static std::atomic<bool> g_should_abort_scan;
+class CoinsViewScanReserver
+{
+private:
+ bool m_could_reserve;
+public:
+ explicit CoinsViewScanReserver() : m_could_reserve(false) {}
+
+ bool reserve() {
+ assert (!m_could_reserve);
+ std::lock_guard<std::mutex> lock(g_utxosetscan);
+ if (g_scan_in_progress) {
+ return false;
+ }
+ g_scan_in_progress = true;
+ m_could_reserve = true;
+ return true;
+ }
+
+ ~CoinsViewScanReserver() {
+ if (m_could_reserve) {
+ std::lock_guard<std::mutex> lock(g_utxosetscan);
+ g_scan_in_progress = false;
+ }
+ }
+};
+
+UniValue scantxoutset(const JSONRPCRequest& request)
+{
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
+ throw std::runtime_error(
+ "scantxoutset <action> ( <scanobjects> )\n"
+ "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
+ "\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
+ "Examples of output descriptors are:\n"
+ " addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
+ " raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
+ " combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
+ " pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
+ " sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
+ "\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
+ "or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
+ "unhardened or hardened child keys.\n"
+ "In the latter case, a range needs to be specified by below if different from 1000.\n"
+ "For more information on output descriptors, see the documentation in the doc/descriptors.md file.\n"
+ "\nArguments:\n"
+ "1. \"action\" (string, required) The action to execute\n"
+ " \"start\" for starting a scan\n"
+ " \"abort\" for aborting the current scan (returns true when abort was successful)\n"
+ " \"status\" for progress report (in %) of the current scan\n"
+ "2. \"scanobjects\" (array, required) Array of scan objects\n"
+ " [ Every scan object is either a string descriptor or an object:\n"
+ " \"descriptor\", (string, optional) An output descriptor\n"
+ " { (object, optional) An object with output descriptor and metadata\n"
+ " \"desc\": \"descriptor\", (string, required) An output descriptor\n"
+ " \"range\": n, (numeric, optional) Up to what child index HD chains should be explored (default: 1000)\n"
+ " },\n"
+ " ...\n"
+ " ]\n"
+ "\nResult:\n"
+ "{\n"
+ " \"unspents\": [\n"
+ " {\n"
+ " \"txid\" : \"transactionid\", (string) The transaction id\n"
+ " \"vout\": n, (numeric) the vout value\n"
+ " \"scriptPubKey\" : \"script\", (string) the script key\n"
+ " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
+ " \"height\" : n, (numeric) Height of the unspent transaction output\n"
+ " }\n"
+ " ,...], \n"
+ " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
+ "]\n"
+ );
+
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
+
+ UniValue result(UniValue::VOBJ);
+ if (request.params[0].get_str() == "status") {
+ CoinsViewScanReserver reserver;
+ if (reserver.reserve()) {
+ // no scan in progress
+ return NullUniValue;
+ }
+ result.pushKV("progress", g_scan_progress);
+ return result;
+ } else if (request.params[0].get_str() == "abort") {
+ CoinsViewScanReserver reserver;
+ if (reserver.reserve()) {
+ // reserve was possible which means no scan was running
+ return false;
+ }
+ // set the abort flag
+ g_should_abort_scan = true;
+ return true;
+ } else if (request.params[0].get_str() == "start") {
+ CoinsViewScanReserver reserver;
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
+ }
+ std::set<CScript> needles;
+ CAmount total_in = 0;
+
+ // loop through the scan objects
+ for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
+ std::string desc_str;
+ int range = 1000;
+ if (scanobject.isStr()) {
+ desc_str = scanobject.get_str();
+ } else if (scanobject.isObject()) {
+ UniValue desc_uni = find_value(scanobject, "desc");
+ if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object");
+ desc_str = desc_uni.get_str();
+ UniValue range_uni = find_value(scanobject, "range");
+ if (!range_uni.isNull()) {
+ range = range_uni.get_int();
+ if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
+ }
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
+ }
+
+ FlatSigningProvider provider;
+ auto desc = Parse(desc_str, provider);
+ if (!desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
+ }
+ if (!desc->IsRange()) range = 0;
+ for (int i = 0; i <= range; ++i) {
+ std::vector<CScript> scripts;
+ if (!desc->Expand(i, provider, scripts, provider)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
+ }
+ needles.insert(scripts.begin(), scripts.end());
+ }
+ }
+
+ // Scan the unspent transaction output set for inputs
+ UniValue unspents(UniValue::VARR);
+ std::vector<CTxOut> input_txos;
+ std::map<COutPoint, Coin> coins;
+ g_should_abort_scan = false;
+ g_scan_progress = 0;
+ int64_t count = 0;
+ std::unique_ptr<CCoinsViewCursor> pcursor;
+ {
+ LOCK(cs_main);
+ FlushStateToDisk();
+ pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor());
+ assert(pcursor);
+ }
+ bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
+ result.pushKV("success", res);
+ result.pushKV("searched_items", count);
+
+ for (const auto& it : coins) {
+ const COutPoint& outpoint = it.first;
+ const Coin& coin = it.second;
+ const CTxOut& txo = coin.out;
+ input_txos.push_back(txo);
+ total_in += txo.nValue;
+
+ UniValue unspent(UniValue::VOBJ);
+ unspent.pushKV("txid", outpoint.hash.GetHex());
+ unspent.pushKV("vout", (int32_t)outpoint.n);
+ unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end()));
+ unspent.pushKV("amount", ValueFromAmount(txo.nValue));
+ unspent.pushKV("height", (int32_t)coin.nHeight);
+
+ unspents.push_back(unspent);
+ }
+ result.pushKV("unspents", unspents);
+ result.pushKV("total_amount", ValueFromAmount(total_in));
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
+ }
+ return result;
+}
+
static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
@@ -1945,6 +2217,7 @@ static const CRPCCommand commands[] =
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
{ "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
+ { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
/* Not shown in help */
{ "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },