|
20 | 20 | #include <policy/policy.h>
|
21 | 21 | #include <primitives/transaction.h>
|
22 | 22 | #include <rpc/server.h>
|
| 23 | +#include <script/descriptor.h> |
23 | 24 | #include <streams.h>
|
24 | 25 | #include <sync.h>
|
25 | 26 | #include <txdb.h>
|
@@ -1984,67 +1985,38 @@ class CoinsViewScanReserver
|
1984 | 1985 | }
|
1985 | 1986 | };
|
1986 | 1987 |
|
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 |
| - |
2025 | 1988 | UniValue scantxoutset(const JSONRPCRequest& request)
|
2026 | 1989 | {
|
2027 | 1990 | if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
2028 | 1991 | throw std::runtime_error(
|
2029 | 1992 | "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" |
2032 | 2006 | "\nArguments:\n"
|
2033 | 2007 | "1. \"action\" (string, required) The action to execute\n"
|
2034 | 2008 | " \"start\" for starting a scan\n"
|
2035 | 2009 | " \"abort\" for aborting the current scan (returns true when abort was successful)\n"
|
2036 | 2010 | " \"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" |
2046 | 2017 | " },\n"
|
2047 |
| - " ]\n" |
| 2018 | + " ...\n" |
| 2019 | + " ]\n" |
2048 | 2020 | "\nResult:\n"
|
2049 | 2021 | "{\n"
|
2050 | 2022 | " \"unspents\": [\n"
|
@@ -2090,79 +2062,35 @@ UniValue scantxoutset(const JSONRPCRequest& request)
|
2090 | 2062 |
|
2091 | 2063 | // loop through the scan objects
|
2092 | 2064 | 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"); |
2144 | 2077 | }
|
| 2078 | + } else { |
| 2079 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object"); |
| 2080 | + } |
2145 | 2081 |
|
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)); |
2159 | 2092 | }
|
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()); |
2166 | 2094 | }
|
2167 | 2095 | }
|
2168 | 2096 |
|
|
0 commit comments