mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 23:03:45 +01:00
a6b0c1fcc0doc: add releases notes for 25504 (listsinceblock updates) (Antoine Poinsot)0fd2d14454rpc: add an include_change parameter to listsinceblock (Antoine Poinsot)55f98d087erpc: output parent wallet descriptors for coins in listunspent (Antoine Poinsot)b724476158rpc: output wallet descriptors for received entries in listsinceblock (Antoine Poinsot)55a82eaf91wallet: allow to fetch the wallet descriptors for a given Script (Antoine Poinsot) Pull request description: Wallet descriptors are useful for applications using the Bitcoin Core wallet as a backend for tracking coins, as they allow to track coins for multiple descriptors in a single wallet. However there is no information currently given for such applications to link a coin with an imported descriptor, severely limiting the possibilities for such applications of using multiple descriptors in a single wallet. This PR outputs the matching imported descriptor(s) for a given received coin in `listsinceblock` (and friends). It comes from a need for an application i'm working on, but i think it's something any software using `bitcoind` to track multiple descriptors in a single wallet would have eventually. For instance i'm thinking about the BDK project. Currently, the way to achieve this is to import raw addresses with labels and to have your application be responsible for wallet things like the gap limit. I'll add this to the output of `listunspent` too if this gets a few Concept ACKs. ACKs for top commit: instagibbs: ACKa6b0c1fcc0achow101: re-ACKa6b0c1fcc0Tree-SHA512: 7a5850e8de98b439ddede2cb72de0208944f8cda67272e8b8037678738d55b7a5272375be808b0f7d15def4904430e089dafdcc037436858ff3292c5f8b75e37
298 lines
11 KiB
C++
298 lines
11 KiB
C++
// Copyright (c) 2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2021 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/client.h>
|
|
#include <util/system.h>
|
|
|
|
#include <set>
|
|
#include <stdint.h>
|
|
|
|
class CRPCConvertParam
|
|
{
|
|
public:
|
|
std::string methodName; //!< method whose params want conversion
|
|
int paramIdx; //!< 0-based idx of param to convert
|
|
std::string paramName; //!< parameter name
|
|
};
|
|
|
|
// clang-format off
|
|
/**
|
|
* Specify a (method, idx, name) here if the argument is a non-string RPC
|
|
* argument and needs to be converted from JSON.
|
|
*
|
|
* @note Parameter indexes start from 0.
|
|
*/
|
|
static const CRPCConvertParam vRPCConvertParams[] =
|
|
{
|
|
{ "setmocktime", 0, "timestamp" },
|
|
{ "mockscheduler", 0, "delta_time" },
|
|
{ "utxoupdatepsbt", 1, "descriptors" },
|
|
{ "generatetoaddress", 0, "nblocks" },
|
|
{ "generatetoaddress", 2, "maxtries" },
|
|
{ "generatetodescriptor", 0, "num_blocks" },
|
|
{ "generatetodescriptor", 2, "maxtries" },
|
|
{ "generateblock", 1, "transactions" },
|
|
{ "getnetworkhashps", 0, "nblocks" },
|
|
{ "getnetworkhashps", 1, "height" },
|
|
{ "sendtoaddress", 1, "amount" },
|
|
{ "sendtoaddress", 4, "subtractfeefromamount" },
|
|
{ "sendtoaddress", 5 , "replaceable" },
|
|
{ "sendtoaddress", 6 , "conf_target" },
|
|
{ "sendtoaddress", 8, "avoid_reuse" },
|
|
{ "sendtoaddress", 9, "fee_rate"},
|
|
{ "sendtoaddress", 10, "verbose"},
|
|
{ "settxfee", 0, "amount" },
|
|
{ "sethdseed", 0, "newkeypool" },
|
|
{ "getreceivedbyaddress", 1, "minconf" },
|
|
{ "getreceivedbyaddress", 2, "include_immature_coinbase" },
|
|
{ "getreceivedbylabel", 1, "minconf" },
|
|
{ "getreceivedbylabel", 2, "include_immature_coinbase" },
|
|
{ "listreceivedbyaddress", 0, "minconf" },
|
|
{ "listreceivedbyaddress", 1, "include_empty" },
|
|
{ "listreceivedbyaddress", 2, "include_watchonly" },
|
|
{ "listreceivedbyaddress", 4, "include_immature_coinbase" },
|
|
{ "listreceivedbylabel", 0, "minconf" },
|
|
{ "listreceivedbylabel", 1, "include_empty" },
|
|
{ "listreceivedbylabel", 2, "include_watchonly" },
|
|
{ "listreceivedbylabel", 3, "include_immature_coinbase" },
|
|
{ "getbalance", 1, "minconf" },
|
|
{ "getbalance", 2, "include_watchonly" },
|
|
{ "getbalance", 3, "avoid_reuse" },
|
|
{ "getblockfrompeer", 1, "peer_id" },
|
|
{ "getblockhash", 0, "height" },
|
|
{ "waitforblockheight", 0, "height" },
|
|
{ "waitforblockheight", 1, "timeout" },
|
|
{ "waitforblock", 1, "timeout" },
|
|
{ "waitfornewblock", 0, "timeout" },
|
|
{ "listtransactions", 1, "count" },
|
|
{ "listtransactions", 2, "skip" },
|
|
{ "listtransactions", 3, "include_watchonly" },
|
|
{ "walletpassphrase", 1, "timeout" },
|
|
{ "getblocktemplate", 0, "template_request" },
|
|
{ "listsinceblock", 1, "target_confirmations" },
|
|
{ "listsinceblock", 2, "include_watchonly" },
|
|
{ "listsinceblock", 3, "include_removed" },
|
|
{ "listsinceblock", 4, "include_change" },
|
|
{ "sendmany", 1, "amounts" },
|
|
{ "sendmany", 2, "minconf" },
|
|
{ "sendmany", 4, "subtractfeefrom" },
|
|
{ "sendmany", 5 , "replaceable" },
|
|
{ "sendmany", 6 , "conf_target" },
|
|
{ "sendmany", 8, "fee_rate"},
|
|
{ "sendmany", 9, "verbose" },
|
|
{ "deriveaddresses", 1, "range" },
|
|
{ "scantxoutset", 1, "scanobjects" },
|
|
{ "addmultisigaddress", 0, "nrequired" },
|
|
{ "addmultisigaddress", 1, "keys" },
|
|
{ "createmultisig", 0, "nrequired" },
|
|
{ "createmultisig", 1, "keys" },
|
|
{ "listunspent", 0, "minconf" },
|
|
{ "listunspent", 1, "maxconf" },
|
|
{ "listunspent", 2, "addresses" },
|
|
{ "listunspent", 3, "include_unsafe" },
|
|
{ "listunspent", 4, "query_options" },
|
|
{ "getblock", 1, "verbosity" },
|
|
{ "getblock", 1, "verbose" },
|
|
{ "getblockheader", 1, "verbose" },
|
|
{ "getchaintxstats", 0, "nblocks" },
|
|
{ "gettransaction", 1, "include_watchonly" },
|
|
{ "gettransaction", 2, "verbose" },
|
|
{ "getrawtransaction", 1, "verbose" },
|
|
{ "createrawtransaction", 0, "inputs" },
|
|
{ "createrawtransaction", 1, "outputs" },
|
|
{ "createrawtransaction", 2, "locktime" },
|
|
{ "createrawtransaction", 3, "replaceable" },
|
|
{ "decoderawtransaction", 1, "iswitness" },
|
|
{ "signrawtransactionwithkey", 1, "privkeys" },
|
|
{ "signrawtransactionwithkey", 2, "prevtxs" },
|
|
{ "signrawtransactionwithwallet", 1, "prevtxs" },
|
|
{ "sendrawtransaction", 1, "maxfeerate" },
|
|
{ "testmempoolaccept", 0, "rawtxs" },
|
|
{ "testmempoolaccept", 1, "maxfeerate" },
|
|
{ "submitpackage", 0, "package" },
|
|
{ "combinerawtransaction", 0, "txs" },
|
|
{ "fundrawtransaction", 1, "options" },
|
|
{ "fundrawtransaction", 2, "iswitness" },
|
|
{ "walletcreatefundedpsbt", 0, "inputs" },
|
|
{ "walletcreatefundedpsbt", 1, "outputs" },
|
|
{ "walletcreatefundedpsbt", 2, "locktime" },
|
|
{ "walletcreatefundedpsbt", 3, "options" },
|
|
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
|
{ "walletprocesspsbt", 1, "sign" },
|
|
{ "walletprocesspsbt", 3, "bip32derivs" },
|
|
{ "walletprocesspsbt", 4, "finalize" },
|
|
{ "createpsbt", 0, "inputs" },
|
|
{ "createpsbt", 1, "outputs" },
|
|
{ "createpsbt", 2, "locktime" },
|
|
{ "createpsbt", 3, "replaceable" },
|
|
{ "combinepsbt", 0, "txs"},
|
|
{ "joinpsbts", 0, "txs"},
|
|
{ "finalizepsbt", 1, "extract"},
|
|
{ "converttopsbt", 1, "permitsigdata"},
|
|
{ "converttopsbt", 2, "iswitness"},
|
|
{ "gettxout", 1, "n" },
|
|
{ "gettxout", 2, "include_mempool" },
|
|
{ "gettxoutproof", 0, "txids" },
|
|
{ "gettxoutsetinfo", 1, "hash_or_height" },
|
|
{ "gettxoutsetinfo", 2, "use_index"},
|
|
{ "lockunspent", 0, "unlock" },
|
|
{ "lockunspent", 1, "transactions" },
|
|
{ "lockunspent", 2, "persistent" },
|
|
{ "send", 0, "outputs" },
|
|
{ "send", 1, "conf_target" },
|
|
{ "send", 3, "fee_rate"},
|
|
{ "send", 4, "options" },
|
|
{ "sendall", 0, "recipients" },
|
|
{ "sendall", 1, "conf_target" },
|
|
{ "sendall", 3, "fee_rate"},
|
|
{ "sendall", 4, "options" },
|
|
{ "simulaterawtransaction", 0, "rawtxs" },
|
|
{ "simulaterawtransaction", 1, "options" },
|
|
{ "importprivkey", 2, "rescan" },
|
|
{ "importaddress", 2, "rescan" },
|
|
{ "importaddress", 3, "p2sh" },
|
|
{ "importpubkey", 2, "rescan" },
|
|
{ "importmulti", 0, "requests" },
|
|
{ "importmulti", 1, "options" },
|
|
{ "importdescriptors", 0, "requests" },
|
|
{ "listdescriptors", 0, "private" },
|
|
{ "verifychain", 0, "checklevel" },
|
|
{ "verifychain", 1, "nblocks" },
|
|
{ "getblockstats", 0, "hash_or_height" },
|
|
{ "getblockstats", 1, "stats" },
|
|
{ "pruneblockchain", 0, "height" },
|
|
{ "keypoolrefill", 0, "newsize" },
|
|
{ "getrawmempool", 0, "verbose" },
|
|
{ "getrawmempool", 1, "mempool_sequence" },
|
|
{ "estimatesmartfee", 0, "conf_target" },
|
|
{ "estimaterawfee", 0, "conf_target" },
|
|
{ "estimaterawfee", 1, "threshold" },
|
|
{ "prioritisetransaction", 1, "dummy" },
|
|
{ "prioritisetransaction", 2, "fee_delta" },
|
|
{ "setban", 2, "bantime" },
|
|
{ "setban", 3, "absolute" },
|
|
{ "setnetworkactive", 0, "state" },
|
|
{ "setwalletflag", 1, "value" },
|
|
{ "getmempoolancestors", 1, "verbose" },
|
|
{ "getmempooldescendants", 1, "verbose" },
|
|
{ "gettxspendingprevout", 0, "outputs" },
|
|
{ "bumpfee", 1, "options" },
|
|
{ "psbtbumpfee", 1, "options" },
|
|
{ "logging", 0, "include" },
|
|
{ "logging", 1, "exclude" },
|
|
{ "disconnectnode", 1, "nodeid" },
|
|
{ "upgradewallet", 0, "version" },
|
|
// Echo with conversion (For testing only)
|
|
{ "echojson", 0, "arg0" },
|
|
{ "echojson", 1, "arg1" },
|
|
{ "echojson", 2, "arg2" },
|
|
{ "echojson", 3, "arg3" },
|
|
{ "echojson", 4, "arg4" },
|
|
{ "echojson", 5, "arg5" },
|
|
{ "echojson", 6, "arg6" },
|
|
{ "echojson", 7, "arg7" },
|
|
{ "echojson", 8, "arg8" },
|
|
{ "echojson", 9, "arg9" },
|
|
{ "rescanblockchain", 0, "start_height"},
|
|
{ "rescanblockchain", 1, "stop_height"},
|
|
{ "createwallet", 1, "disable_private_keys"},
|
|
{ "createwallet", 2, "blank"},
|
|
{ "createwallet", 4, "avoid_reuse"},
|
|
{ "createwallet", 5, "descriptors"},
|
|
{ "createwallet", 6, "load_on_startup"},
|
|
{ "createwallet", 7, "external_signer"},
|
|
{ "restorewallet", 2, "load_on_startup"},
|
|
{ "loadwallet", 1, "load_on_startup"},
|
|
{ "unloadwallet", 1, "load_on_startup"},
|
|
{ "getnodeaddresses", 0, "count"},
|
|
{ "addpeeraddress", 1, "port"},
|
|
{ "addpeeraddress", 2, "tried"},
|
|
{ "stop", 0, "wait" },
|
|
};
|
|
// clang-format on
|
|
|
|
class CRPCConvertTable
|
|
{
|
|
private:
|
|
std::set<std::pair<std::string, int>> members;
|
|
std::set<std::pair<std::string, std::string>> membersByName;
|
|
|
|
public:
|
|
CRPCConvertTable();
|
|
|
|
bool convert(const std::string& method, int idx) {
|
|
return (members.count(std::make_pair(method, idx)) > 0);
|
|
}
|
|
bool convert(const std::string& method, const std::string& name) {
|
|
return (membersByName.count(std::make_pair(method, name)) > 0);
|
|
}
|
|
};
|
|
|
|
CRPCConvertTable::CRPCConvertTable()
|
|
{
|
|
for (const auto& cp : vRPCConvertParams) {
|
|
members.emplace(cp.methodName, cp.paramIdx);
|
|
membersByName.emplace(cp.methodName, cp.paramName);
|
|
}
|
|
}
|
|
|
|
static CRPCConvertTable rpcCvtTable;
|
|
|
|
/** Non-RFC4627 JSON parser, accepts internal values (such as numbers, true, false, null)
|
|
* as well as objects and arrays.
|
|
*/
|
|
UniValue ParseNonRFCJSONValue(const std::string& strVal)
|
|
{
|
|
UniValue jVal;
|
|
if (!jVal.read(std::string("[")+strVal+std::string("]")) ||
|
|
!jVal.isArray() || jVal.size()!=1)
|
|
throw std::runtime_error(std::string("Error parsing JSON: ") + strVal);
|
|
return jVal[0];
|
|
}
|
|
|
|
UniValue RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams)
|
|
{
|
|
UniValue params(UniValue::VARR);
|
|
|
|
for (unsigned int idx = 0; idx < strParams.size(); idx++) {
|
|
const std::string& strVal = strParams[idx];
|
|
|
|
if (!rpcCvtTable.convert(strMethod, idx)) {
|
|
// insert string value directly
|
|
params.push_back(strVal);
|
|
} else {
|
|
// parse string as JSON, insert bool/number/object/etc. value
|
|
params.push_back(ParseNonRFCJSONValue(strVal));
|
|
}
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<std::string> &strParams)
|
|
{
|
|
UniValue params(UniValue::VOBJ);
|
|
|
|
for (const std::string &s: strParams) {
|
|
size_t pos = s.find('=');
|
|
if (pos == std::string::npos) {
|
|
throw(std::runtime_error("No '=' in named argument '"+s+"', this needs to be present for every argument (even if it is empty)"));
|
|
}
|
|
|
|
std::string name = s.substr(0, pos);
|
|
std::string value = s.substr(pos+1);
|
|
|
|
if (!rpcCvtTable.convert(strMethod, name)) {
|
|
// insert string value directly
|
|
params.pushKV(name, value);
|
|
} else {
|
|
// parse string as JSON, insert bool/number/object/etc. value
|
|
params.pushKV(name, ParseNonRFCJSONValue(value));
|
|
}
|
|
}
|
|
|
|
return params;
|
|
}
|