Changeset View
Changeset View
Standalone View
Standalone View
src/rpc/blockchain.cpp
Show All 13 Lines | |||||
#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 <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 <script/descriptor.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> | ||||
▲ Show 20 Lines • Show All 2,245 Lines • ▼ Show 20 Lines | public: | ||||
~CoinsViewScanReserver() { | ~CoinsViewScanReserver() { | ||||
if (m_could_reserve) { | if (m_could_reserve) { | ||||
std::lock_guard<std::mutex> lock(g_utxosetscan); | std::lock_guard<std::mutex> lock(g_utxosetscan); | ||||
g_scan_in_progress = false; | 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); | |||||
} | |||||
} | |||||
static UniValue scantxoutset(const Config &config, | static UniValue scantxoutset(const Config &config, | ||||
const JSONRPCRequest &request) { | const JSONRPCRequest &request) { | ||||
if (request.fHelp || request.params.size() < 1 || | if (request.fHelp || request.params.size() < 1 || | ||||
request.params.size() > 2) { | request.params.size() > 2) { | ||||
throw std::runtime_error( | throw std::runtime_error( | ||||
"scantxoutset <action> ( <scanobjects> )\n" | "scantxoutset <action> ( <scanobjects> )\n" | ||||
"\nScans the unspent transaction output set for possible entries " | "\nEXPERIMENTAL warning: this call may be removed or changed in " | ||||
"that matches common scripts of given public keys.\n" | "future releases.\n" | ||||
"Using addresses as scanobjects will _not_ detect unspent P2PK " | "\nScans the unspent transaction output set for entries that match " | ||||
"txouts\n" | "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 and P2PKH 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 " | |||||
"at TODO\n" | |||||
"\nArguments:\n" | "\nArguments:\n" | ||||
"1. \"action\" (string, required) The action " | "1. \"action\" (string, required) The action " | ||||
"to execute\n" | "to execute\n" | ||||
" \"start\" for starting a " | " \"start\" for starting a " | ||||
"scan\n" | "scan\n" | ||||
" \"abort\" for aborting the " | " \"abort\" for aborting the " | ||||
"current scan (returns true when abort was successful)\n" | "current scan (returns true when abort was successful)\n" | ||||
" \"status\" for progress " | " \"status\" for progress " | ||||
"report (in %) of the current scan\n" | "report (in %) of the current scan\n" | ||||
"2. \"scanobjects\" (array, optional) Array of " | "2. \"scanobjects\" (array, required) Array of " | ||||
"scan objects (only one object type per scan object allowed)\n" | "scan objects\n" | ||||
" [\n" | " [ Every scan object is either a " | ||||
" { \"address\" : \"<address>\" }, (string, optional) " | "string descriptor or an object:\n" | ||||
"Bitcoin address\n" | " \"descriptor\", (string, optional) An output " | ||||
" { \"script\" : \"<scriptPubKey>\" }, (string, optional) " | "descriptor\n" | ||||
"HEX encoded script (scriptPubKey)\n" | " { (object, optional) An object " | ||||
" { \"pubkey\" : (object, optional) " | "with output descriptor and metadata\n" | ||||
"Public key\n" | " \"desc\": \"descriptor\", (string, required) An " | ||||
" {\n" | "output descriptor\n" | ||||
" \"pubkey\" : \"<pubkey\">, (string, required) " | " \"range\": n, (numeric, optional) Up to " | ||||
"HEX encoded public key\n" | "what child index HD chains should be explored (default: 1000)\n" | ||||
" \"script_types\" : [ ... ], (array, optional) " | |||||
"Array of script-types to derive from the pubkey (possible values: " | |||||
"\"P2PK\", \"P2PKH\")\n" | |||||
" }\n" | |||||
" },\n" | " },\n" | ||||
" ...\n" | |||||
" ]\n" | " ]\n" | ||||
"\nResult:\n" | "\nResult:\n" | ||||
"{\n" | "{\n" | ||||
" \"unspents\": [\n" | " \"unspents\": [\n" | ||||
" {\n" | " {\n" | ||||
" \"txid\" : \"transactionid\", (string) The transaction " | " \"txid\" : \"transactionid\", (string) The transaction " | ||||
"id\n" | "id\n" | ||||
" \"vout\": n, (numeric) the vout value\n" | " \"vout\": n, (numeric) the vout value\n" | ||||
" \"scriptPubKey\" : \"script\", (string) the script key\n" | " \"scriptPubKey\" : \"script\", (string) the script key\n" | ||||
Show All 40 Lines | if (request.params[0].get_str() == "status") { | ||||
"Scan already in progress, use action \"abort\" or \"status\""); | "Scan already in progress, use action \"abort\" or \"status\""); | ||||
} | } | ||||
std::set<CScript> needles; | std::set<CScript> needles; | ||||
Amount total_in = Amount::zero(); | Amount total_in = Amount::zero(); | ||||
// loop through the scan objects | // loop through the scan objects | ||||
for (const UniValue &scanobject : | for (const UniValue &scanobject : | ||||
request.params[1].get_array().getValues()) { | request.params[1].get_array().getValues()) { | ||||
if (!scanobject.isObject()) { | std::string desc_str; | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | int range = 1000; | ||||
"Invalid scan object"); | if (scanobject.isStr()) { | ||||
} | desc_str = scanobject.get_str(); | ||||
UniValue address_uni = find_value(scanobject, "address"); | } else if (scanobject.isObject()) { | ||||
UniValue pubkey_uni = find_value(scanobject, "pubkey"); | UniValue desc_uni = find_value(scanobject, "desc"); | ||||
UniValue script_uni = find_value(scanobject, "script"); | if (desc_uni.isNull()) { | ||||
// make sure only one object type is present | |||||
if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + | |||||
!script_uni.isNull()) { | |||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_PARAMETER, | RPC_INVALID_PARAMETER, | ||||
"Only one object type is allowed per scan object"); | "Descriptor needs to be provided in 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); | desc_str = desc_uni.get_str(); | ||||
assert(!script.empty()); | UniValue range_uni = find_value(scanobject, "range"); | ||||
needles.insert(script); | if (!range_uni.isNull()) { | ||||
} else if (pubkey_uni.isObject()) { | range = range_uni.get_int(); | ||||
// type: pubkey | if (range < 0 || range > 1000000) { | ||||
// 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, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"script_types must be an array"); | "range out of range"); | ||||
} 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); | |||||
} | } | ||||
} | } | ||||
} else { | |||||
// check the acctual pubkey | throw JSONRPCError( | ||||
if (!pubkeydata_uni.isStr() || | RPC_INVALID_PARAMETER, | ||||
!IsHex(pubkeydata_uni.get_str())) { | "Scan object needs to be either a string or an object"); | ||||
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 | FlatSigningProvider provider; | ||||
for (const UniValue &script_type_uni : | auto desc = Parse(desc_str, provider); | ||||
script_types_uni.get_array().getValues()) { | if (!desc) { | ||||
OutputScriptType script_type = | throw JSONRPCError( | ||||
GetOutputScriptTypeFromString( | RPC_INVALID_ADDRESS_OR_KEY, | ||||
script_type_uni.get_str()); | strprintf("Invalid descriptor '%s'", desc_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()); | if (!desc->IsRange()) { | ||||
needles.insert(script); | 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)); | |||||
} | } | ||||
} else if (script_uni.isStr()) { | needles.insert(scripts.begin(), scripts.end()); | ||||
// 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 | // Scan the unspent transaction output set for inputs | ||||
UniValue unspents(UniValue::VARR); | UniValue unspents(UniValue::VARR); | ||||
std::vector<CTxOut> input_txos; | std::vector<CTxOut> input_txos; | ||||
std::map<COutPoint, Coin> coins; | std::map<COutPoint, Coin> coins; | ||||
g_should_abort_scan = false; | g_should_abort_scan = false; | ||||
▲ Show 20 Lines • Show All 85 Lines • Show Last 20 Lines |