Skip to content

Commit f030410

Browse files
committed
Merge #13697: Support output descriptors in scantxoutset
f6b7fc3 Support h instead of ' in hardened descriptor paths (Pieter Wuille) fddea67 Add experimental warning to scantxoutset (Jonas Schnelli) 6495849 [QA] Extend tests to more combinations (Pieter Wuille) 1af237f [QA] Add xpub range tests in scantxoutset tests (Jonas Schnelli) 151600b Swap in descriptors support into scantxoutset (Pieter Wuille) 0652c32 Descriptor tests (Pieter Wuille) fe8a7dc Output descriptors module (Pieter Wuille) e54d760 Add simple FlatSigningProvider (Pieter Wuille) 29943a9 Add more methods to Span class (Pieter Wuille) Pull request description: As promised, here is an implementation of my output descriptor concept (https://gist.github.com/sipa/e3d23d498c430bb601c5bca83523fa82) and integration within the `scantxoutset` RPC that was just added through #12196. It changes the RPC to use descriptors for everything; I hope the interface is simple enough to encompass all use cases. It includes support for P2PK, P2PKH, P2WPKH, P2SH, P2WSH, multisig, xpubs, xprvs, and chains of keys - combined in every possible way. Tree-SHA512: 63b54a96e7a72f5b04a8d645b8517d43ecd6a65a41f9f4e593931ce725a8845ab0baa1e9db6a7243190d8ac841f6e7e2f520d98c539312d78f7fd687d2c7b88f
2 parents c88529a + f6b7fc3 commit f030410

File tree

10 files changed

+994
-131
lines changed

10 files changed

+994
-131
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ BITCOIN_CORE_H = \
158158
rpc/register.h \
159159
rpc/util.h \
160160
scheduler.h \
161+
script/descriptor.h \
161162
script/ismine.h \
162163
script/sigcache.h \
163164
script/sign.h \
@@ -387,6 +388,7 @@ libbitcoin_common_a_SOURCES = \
387388
policy/feerate.cpp \
388389
protocol.cpp \
389390
scheduler.cpp \
391+
script/descriptor.cpp \
390392
script/ismine.cpp \
391393
script/sign.cpp \
392394
script/standard.cpp \

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ BITCOIN_TESTS =\
4747
test/crypto_tests.cpp \
4848
test/cuckoocache_tests.cpp \
4949
test/denialofservice_tests.cpp \
50+
test/descriptor_tests.cpp \
5051
test/getarg_tests.cpp \
5152
test/hash_tests.cpp \
5253
test/key_io_tests.cpp \

src/rpc/blockchain.cpp

Lines changed: 48 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <policy/policy.h>
2121
#include <primitives/transaction.h>
2222
#include <rpc/server.h>
23+
#include <script/descriptor.h>
2324
#include <streams.h>
2425
#include <sync.h>
2526
#include <txdb.h>
@@ -1984,67 +1985,38 @@ class CoinsViewScanReserver
19841985
}
19851986
};
19861987

1987-
static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" };
1988-
1989-
enum class OutputScriptType {
1990-
UNKNOWN,
1991-
P2PK,
1992-
P2PKH,
1993-
P2SH_P2WPKH,
1994-
P2WPKH
1995-
};
1996-
1997-
static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype)
1998-
{
1999-
if (outputtype == "P2PK") return OutputScriptType::P2PK;
2000-
else if (outputtype == "P2PKH") return OutputScriptType::P2PKH;
2001-
else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH;
2002-
else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH;
2003-
else return OutputScriptType::UNKNOWN;
2004-
}
2005-
2006-
CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type)
2007-
{
2008-
switch (type) {
2009-
case OutputScriptType::P2PKH: return key.GetID();
2010-
case OutputScriptType::P2SH_P2WPKH:
2011-
case OutputScriptType::P2WPKH: {
2012-
if (!key.IsCompressed()) return key.GetID();
2013-
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
2014-
if (type == OutputScriptType::P2SH_P2WPKH) {
2015-
CScript witprog = GetScriptForDestination(witdest);
2016-
return CScriptID(witprog);
2017-
} else {
2018-
return witdest;
2019-
}
2020-
}
2021-
default: assert(false);
2022-
}
2023-
}
2024-
20251988
UniValue scantxoutset(const JSONRPCRequest& request)
20261989
{
20271990
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
20281991
throw std::runtime_error(
20291992
"scantxoutset <action> ( <scanobjects> )\n"
2030-
"\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n"
2031-
"Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n"
1993+
"\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
1994+
"\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
1995+
"Examples of output descriptors are:\n"
1996+
" addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
1997+
" raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
1998+
" combo(<pubkey>) P2PK, P2PKH, P2WPKH, and P2SH-P2WPKH outputs for the given pubkey\n"
1999+
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
2000+
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
2001+
"\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
2002+
"or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
2003+
"unhardened or hardened child keys.\n"
2004+
"In the latter case, a range needs to be specified by below if different from 1000.\n"
2005+
"For more information on output descriptors, see the documentation at TODO\n"
20322006
"\nArguments:\n"
20332007
"1. \"action\" (string, required) The action to execute\n"
20342008
" \"start\" for starting a scan\n"
20352009
" \"abort\" for aborting the current scan (returns true when abort was successful)\n"
20362010
" \"status\" for progress report (in %) of the current scan\n"
2037-
"2. \"scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n"
2038-
" [\n"
2039-
" { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n"
2040-
" { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n"
2041-
" { \"pubkey\" : (object, optional) Public key\n"
2042-
" {\n"
2043-
" \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n"
2044-
" \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n"
2045-
" }\n"
2011+
"2. \"scanobjects\" (array, required) Array of scan objects\n"
2012+
" [ Every scan object is either a string descriptor or an object:\n"
2013+
" \"descriptor\", (string, optional) An output descriptor\n"
2014+
" { (object, optional) An object with output descriptor and metadata\n"
2015+
" \"desc\": \"descriptor\", (string, required) An output descriptor\n"
2016+
" \"range\": n, (numeric, optional) Up to what child index HD chains should be explored (default: 1000)\n"
20462017
" },\n"
2047-
" ]\n"
2018+
" ...\n"
2019+
" ]\n"
20482020
"\nResult:\n"
20492021
"{\n"
20502022
" \"unspents\": [\n"
@@ -2090,79 +2062,35 @@ UniValue scantxoutset(const JSONRPCRequest& request)
20902062

20912063
// loop through the scan objects
20922064
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
2093-
if (!scanobject.isObject()) {
2094-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object");
2095-
}
2096-
UniValue address_uni = find_value(scanobject, "address");
2097-
UniValue pubkey_uni = find_value(scanobject, "pubkey");
2098-
UniValue script_uni = find_value(scanobject, "script");
2099-
2100-
// make sure only one object type is present
2101-
if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) {
2102-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object");
2103-
} else if (!address_uni.isNull() && !address_uni.isStr()) {
2104-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value");
2105-
} else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) {
2106-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value");
2107-
} else if (!script_uni.isNull() && !script_uni.isStr()) {
2108-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value");
2109-
} else if (address_uni.isStr()) {
2110-
// type: address
2111-
// decode destination and derive the scriptPubKey
2112-
// add the script to the scan containers
2113-
CTxDestination dest = DecodeDestination(address_uni.get_str());
2114-
if (!IsValidDestination(dest)) {
2115-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
2116-
}
2117-
CScript script = GetScriptForDestination(dest);
2118-
assert(!script.empty());
2119-
needles.insert(script);
2120-
} else if (pubkey_uni.isObject()) {
2121-
// type: pubkey
2122-
// derive script(s) according to the script_type parameter
2123-
UniValue script_types_uni = find_value(pubkey_uni, "script_types");
2124-
UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey");
2125-
2126-
// check the script types and use the default if not provided
2127-
if (!script_types_uni.isNull() && !script_types_uni.isArray()) {
2128-
throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array");
2129-
} else if (script_types_uni.isNull()) {
2130-
// use the default script types
2131-
script_types_uni = UniValue(UniValue::VARR);
2132-
for (const char *t : g_default_scantxoutset_script_types) {
2133-
script_types_uni.push_back(t);
2134-
}
2135-
}
2136-
2137-
// check the acctual pubkey
2138-
if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) {
2139-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded");
2140-
}
2141-
CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey"));
2142-
if (!pubkey.IsFullyValid()) {
2143-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key");
2065+
std::string desc_str;
2066+
int range = 1000;
2067+
if (scanobject.isStr()) {
2068+
desc_str = scanobject.get_str();
2069+
} else if (scanobject.isObject()) {
2070+
UniValue desc_uni = find_value(scanobject, "desc");
2071+
if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object");
2072+
desc_str = desc_uni.get_str();
2073+
UniValue range_uni = find_value(scanobject, "range");
2074+
if (!range_uni.isNull()) {
2075+
range = range_uni.get_int();
2076+
if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
21442077
}
2078+
} else {
2079+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
2080+
}
21452081

2146-
// loop through the script types and derive the script
2147-
for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) {
2148-
OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str());
2149-
if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type");
2150-
CScript script;
2151-
if (script_type == OutputScriptType::P2PK) {
2152-
// support legacy P2PK scripts
2153-
script << ToByteVector(pubkey) << OP_CHECKSIG;
2154-
} else {
2155-
script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type));
2156-
}
2157-
assert(!script.empty());
2158-
needles.insert(script);
2082+
FlatSigningProvider provider;
2083+
auto desc = Parse(desc_str, provider);
2084+
if (!desc) {
2085+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
2086+
}
2087+
if (!desc->IsRange()) range = 0;
2088+
for (int i = 0; i <= range; ++i) {
2089+
std::vector<CScript> scripts;
2090+
if (!desc->Expand(i, provider, scripts, provider)) {
2091+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
21592092
}
2160-
} else if (script_uni.isStr()) {
2161-
// type: script
2162-
// check and add the script to the scan containers (needles array)
2163-
CScript script(ParseHexV(script_uni, "script"));
2164-
// TODO: check script: max length, has OP, is unspenable etc.
2165-
needles.insert(script);
2093+
needles.insert(scripts.begin(), scripts.end());
21662094
}
21672095
}
21682096

0 commit comments

Comments
 (0)