Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/blockchain.cpp
Show All 9 Lines | |||||
#include <chainparams.h> | #include <chainparams.h> | ||||
#include <checkpoints.h> | #include <checkpoints.h> | ||||
#include <coins.h> | #include <coins.h> | ||||
#include <config.h> | #include <config.h> | ||||
#include <consensus/validation.h> | #include <consensus/validation.h> | ||||
#include <core_io.h> | #include <core_io.h> | ||||
#include <hash.h> | #include <hash.h> | ||||
#include <index/txindex.h> | #include <index/txindex.h> | ||||
#include <key_io.h> | |||||
#include <policy/policy.h> | #include <policy/policy.h> | ||||
#include <primitives/transaction.h> | #include <primitives/transaction.h> | ||||
#include <rpc/server.h> | #include <rpc/server.h> | ||||
#include <streams.h> | #include <streams.h> | ||||
#include <sync.h> | #include <sync.h> | ||||
#include <txdb.h> | #include <txdb.h> | ||||
#include <txmempool.h> | #include <txmempool.h> | ||||
#include <util/strencodings.h> | #include <util/strencodings.h> | ||||
#include <util/system.h> | #include <util/system.h> | ||||
#include <validation.h> | #include <validation.h> | ||||
#include <validationinterface.h> | #include <validationinterface.h> | ||||
#include <warnings.h> | #include <warnings.h> | ||||
#include <boost/algorithm/string.hpp> | #include <boost/algorithm/string.hpp> | ||||
#include <boost/thread/thread.hpp> // boost::thread::interrupt | #include <boost/thread/thread.hpp> // boost::thread::interrupt | ||||
#include <cassert> | |||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <cstdint> | #include <cstdint> | ||||
#include <mutex> | #include <mutex> | ||||
struct CUpdatedBlock { | struct CUpdatedBlock { | ||||
uint256 hash; | uint256 hash; | ||||
int height; | int height; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 2,164 Lines • ▼ Show 20 Lines | static UniValue savemempool(const Config &config, | ||||
if (!DumpMempool(::g_mempool)) { | if (!DumpMempool(::g_mempool)) { | ||||
throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); | throw JSONRPCError(RPC_MISC_ERROR, "Unable to dump mempool to disk"); | ||||
} | } | ||||
return NullUniValue; | 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 | |||||
const TxId &txid = key.GetTxId(); | |||||
uint32_t high = 0x100 * *txid.begin() + *(txid.begin() + 1); | |||||
scan_progress = int(high * 100.0 / 65536.0 + 0.5); | |||||
} | |||||
if (needles.count(coin.GetTxOut().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; | |||||
} | |||||
} | |||||
}; | |||||
static const char *g_default_scantxoutset_script_types[] = {"P2PKH"}; | |||||
enum class OutputScriptType { UNKNOWN, P2PK, P2PKH }; | |||||
static inline OutputScriptType | |||||
GetOutputScriptTypeFromString(const std::string &outputtype) { | |||||
if (outputtype == "P2PK") { | |||||
return OutputScriptType::P2PK; | |||||
} else if (outputtype == "P2PKH") { | |||||
return OutputScriptType::P2PKH; | |||||
} else { | |||||
return OutputScriptType::UNKNOWN; | |||||
} | |||||
} | |||||
CTxDestination GetDestinationForKey(const CPubKey &key, OutputScriptType type) { | |||||
switch (type) { | |||||
case OutputScriptType::P2PKH: | |||||
return key.GetID(); | |||||
default: | |||||
assert(false); | |||||
} | |||||
} | |||||
UniValue scantxoutset(const Config &config, const JSONRPCRequest &request) { | |||||
if (request.fHelp || request.params.size() < 1 || | |||||
request.params.size() > 2) { | |||||
throw std::runtime_error( | |||||
"scantxoutset <action> ( <scanobjects> )\n" | |||||
"\nScans the unspent transaction output set for possible entries " | |||||
"that matches common scripts of given public keys.\n" | |||||
"Using addresses as scanobjects will _not_ detect unspent P2PK " | |||||
"txouts\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, optional) Array of " | |||||
"scan objects (only one object type per scan object allowed)\n" | |||||
" [\n" | |||||
" { \"address\" : \"<address>\" }, (string, optional) " | |||||
"Bitcoin address\n" | |||||
" { \"script\" : \"<scriptPubKey>\" }, (string, optional) " | |||||
"HEX encoded script (scriptPubKey)\n" | |||||
" { \"pubkey\" : (object, optional) " | |||||
"Public key\n" | |||||
" {\n" | |||||
" \"pubkey\" : \"<pubkey\">, (string, required) " | |||||
"HEX encoded public key\n" | |||||
" \"script_types\" : [ ... ], (array, optional) " | |||||
"Array of script-types to derive from the pubkey (possible values: " | |||||
"\"P2PK\", \"P2PKH\")\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; | |||||
Amount total_in = Amount::zero(); | |||||
// loop through the scan objects | |||||
for (const UniValue &scanobject : | |||||
request.params[1].get_array().getValues()) { | |||||
if (!scanobject.isObject()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Invalid scan object"); | |||||
} | |||||
UniValue address_uni = find_value(scanobject, "address"); | |||||
UniValue pubkey_uni = find_value(scanobject, "pubkey"); | |||||
UniValue script_uni = find_value(scanobject, "script"); | |||||
// make sure only one object type is present | |||||
if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + | |||||
!script_uni.isNull()) { | |||||
throw JSONRPCError( | |||||
RPC_INVALID_PARAMETER, | |||||
"Only one object type is allowed per scan object"); | |||||
} else if (!address_uni.isNull() && !address_uni.isStr()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Scanobject \"address\" must contain a " | |||||
"single string as value"); | |||||
} else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) { | |||||
throw JSONRPCError( | |||||
RPC_INVALID_PARAMETER, | |||||
"Scanobject \"pubkey\" must contain an object as value"); | |||||
} else if (!script_uni.isNull() && !script_uni.isStr()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Scanobject \"script\" must contain a " | |||||
"single string as value"); | |||||
} else if (address_uni.isStr()) { | |||||
// type: address | |||||
// decode destination and derive the scriptPubKey | |||||
// add the script to the scan containers | |||||
CTxDestination dest = DecodeDestination( | |||||
address_uni.get_str(), config.GetChainParams()); | |||||
if (!IsValidDestination(dest)) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | |||||
"Invalid address"); | |||||
} | |||||
CScript script = GetScriptForDestination(dest); | |||||
assert(!script.empty()); | |||||
needles.insert(script); | |||||
} else if (pubkey_uni.isObject()) { | |||||
// type: pubkey | |||||
// derive script(s) according to the script_type parameter | |||||
UniValue script_types_uni = | |||||
find_value(pubkey_uni, "script_types"); | |||||
UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey"); | |||||
// check the script types and use the default if not provided | |||||
if (!script_types_uni.isNull() && !script_types_uni.isArray()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"script_types must be an array"); | |||||
} else if (script_types_uni.isNull()) { | |||||
// use the default script types | |||||
script_types_uni = UniValue(UniValue::VARR); | |||||
for (const char *t : g_default_scantxoutset_script_types) { | |||||
script_types_uni.push_back(t); | |||||
} | |||||
} | |||||
// check the acctual pubkey | |||||
if (!pubkeydata_uni.isStr() || | |||||
!IsHex(pubkeydata_uni.get_str())) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | |||||
"Public key must be hex encoded"); | |||||
} | |||||
CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey")); | |||||
if (!pubkey.IsFullyValid()) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | |||||
"Invalid public key"); | |||||
} | |||||
// loop through the script types and derive the script | |||||
for (const UniValue &script_type_uni : | |||||
script_types_uni.get_array().getValues()) { | |||||
OutputScriptType script_type = | |||||
GetOutputScriptTypeFromString( | |||||
script_type_uni.get_str()); | |||||
if (script_type == OutputScriptType::UNKNOWN) | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Invalid script type"); | |||||
CScript script; | |||||
if (script_type == OutputScriptType::P2PK) { | |||||
// support legacy P2PK scripts | |||||
script << ToByteVector(pubkey) << OP_CHECKSIG; | |||||
} else { | |||||
script = GetScriptForDestination( | |||||
GetDestinationForKey(pubkey, script_type)); | |||||
} | |||||
assert(!script.empty()); | |||||
needles.insert(script); | |||||
} | |||||
} else if (script_uni.isStr()) { | |||||
// type: script | |||||
// check and add the script to the scan containers (needles | |||||
// array) | |||||
CScript script(ParseHexV(script_uni, "script")); | |||||
// TODO: check script: max length, has OP, is unspenable etc. | |||||
needles.insert(script); | |||||
} | |||||
} | |||||
// 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.GetTxOut(); | |||||
input_txos.push_back(txo); | |||||
total_in += txo.nValue; | |||||
UniValue unspent(UniValue::VOBJ); | |||||
unspent.pushKV("txid", outpoint.GetTxId().GetHex()); | |||||
unspent.pushKV("vout", int32_t(outpoint.GetN())); | |||||
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), | |||||
txo.scriptPubKey.end())); | |||||
unspent.pushKV("amount", ValueFromAmount(txo.nValue)); | |||||
unspent.pushKV("height", int32_t(coin.GetHeight())); | |||||
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; | |||||
} | |||||
// clang-format off | // clang-format off | ||||
static const ContextFreeRPCCommand commands[] = { | static const ContextFreeRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "blockchain", "getbestblockhash", getbestblockhash, {} }, | { "blockchain", "getbestblockhash", getbestblockhash, {} }, | ||||
{ "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, | { "blockchain", "getblock", getblock, {"blockhash","verbosity|verbose"} }, | ||||
{ "blockchain", "getblockchaininfo", getblockchaininfo, {} }, | { "blockchain", "getblockchaininfo", getblockchaininfo, {} }, | ||||
{ "blockchain", "getblockcount", getblockcount, {} }, | { "blockchain", "getblockcount", getblockcount, {} }, | ||||
Show All 9 Lines | static const ContextFreeRPCCommand commands[] = { | ||||
{ "blockchain", "getmempoolinfo", getmempoolinfo, {} }, | { "blockchain", "getmempoolinfo", getmempoolinfo, {} }, | ||||
{ "blockchain", "getrawmempool", getrawmempool, {"verbose"} }, | { "blockchain", "getrawmempool", getrawmempool, {"verbose"} }, | ||||
{ "blockchain", "gettxout", gettxout, {"txid","n","include_mempool"} }, | { "blockchain", "gettxout", gettxout, {"txid","n","include_mempool"} }, | ||||
{ "blockchain", "gettxoutsetinfo", gettxoutsetinfo, {} }, | { "blockchain", "gettxoutsetinfo", gettxoutsetinfo, {} }, | ||||
{ "blockchain", "pruneblockchain", pruneblockchain, {"height"} }, | { "blockchain", "pruneblockchain", pruneblockchain, {"height"} }, | ||||
{ "blockchain", "savemempool", savemempool, {} }, | { "blockchain", "savemempool", savemempool, {} }, | ||||
{ "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, | { "blockchain", "verifychain", verifychain, {"checklevel","nblocks"} }, | ||||
{ "blockchain", "preciousblock", preciousblock, {"blockhash"} }, | { "blockchain", "preciousblock", preciousblock, {"blockhash"} }, | ||||
{ "blockchain", "scantxoutset", scantxoutset, {"action", "scanobjects"} }, | |||||
/* Not shown in help */ | /* Not shown in help */ | ||||
{ "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} }, | { "hidden", "getfinalizedblockhash", getfinalizedblockhash, {} }, | ||||
{ "hidden", "finalizeblock", finalizeblock, {"blockhash"} }, | { "hidden", "finalizeblock", finalizeblock, {"blockhash"} }, | ||||
{ "hidden", "invalidateblock", invalidateblock, {"blockhash"} }, | { "hidden", "invalidateblock", invalidateblock, {"blockhash"} }, | ||||
{ "hidden", "parkblock", parkblock, {"blockhash"} }, | { "hidden", "parkblock", parkblock, {"blockhash"} }, | ||||
{ "hidden", "reconsiderblock", reconsiderblock, {"blockhash"} }, | { "hidden", "reconsiderblock", reconsiderblock, {"blockhash"} }, | ||||
{ "hidden", "syncwithvalidationinterfacequeue", syncwithvalidationinterfacequeue, {} }, | { "hidden", "syncwithvalidationinterfacequeue", syncwithvalidationinterfacequeue, {} }, | ||||
Show All 12 Lines |