6
6
#include < rpc/blockchain.h>
7
7
8
8
#include < amount.h>
9
+ #include < base58.h>
10
+ #include < chain.h>
9
11
#include < chainparams.h>
10
12
#include < checkpoints.h>
11
13
#include < coins.h>
12
14
#include < consensus/validation.h>
13
15
#include < validation.h>
14
16
#include < core_io.h>
15
17
#include < index/txindex.h>
18
+ #include < key_io.h>
16
19
#include < policy/feerate.h>
17
20
#include < policy/policy.h>
18
21
#include < primitives/transaction.h>
27
30
#include < validationinterface.h>
28
31
#include < warnings.h>
29
32
33
+ #include < assert.h>
30
34
#include < stdint.h>
31
35
32
36
#include < univalue.h>
@@ -1920,6 +1924,290 @@ static UniValue savemempool(const JSONRPCRequest& request)
1920
1924
return NullUniValue;
1921
1925
}
1922
1926
1927
+ // ! Search for a given set of pubkey scripts
1928
+ 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) {
1929
+ scan_progress = 0 ;
1930
+ count = 0 ;
1931
+ while (cursor->Valid ()) {
1932
+ COutPoint key;
1933
+ Coin coin;
1934
+ if (!cursor->GetKey (key) || !cursor->GetValue (coin)) return false ;
1935
+ if (++count % 8192 == 0 ) {
1936
+ boost::this_thread::interruption_point ();
1937
+ if (should_abort) {
1938
+ // allow to abort the scan via the abort reference
1939
+ return false ;
1940
+ }
1941
+ }
1942
+ if (count % 256 == 0 ) {
1943
+ // update progress reference every 256 item
1944
+ uint32_t high = 0x100 * *key.hash .begin () + *(key.hash .begin () + 1 );
1945
+ scan_progress = (int )(high * 100.0 / 65536.0 + 0.5 );
1946
+ }
1947
+ if (needles.count (coin.out .scriptPubKey )) {
1948
+ out_results.emplace (key, coin);
1949
+ }
1950
+ cursor->Next ();
1951
+ }
1952
+ scan_progress = 100 ;
1953
+ return true ;
1954
+ }
1955
+
1956
+ /* * RAII object to prevent concurrency issue when scanning the txout set */
1957
+ static std::mutex g_utxosetscan;
1958
+ static std::atomic<int > g_scan_progress;
1959
+ static std::atomic<bool > g_scan_in_progress;
1960
+ static std::atomic<bool > g_should_abort_scan;
1961
+ class CoinsViewScanReserver
1962
+ {
1963
+ private:
1964
+ bool m_could_reserve;
1965
+ public:
1966
+ explicit CoinsViewScanReserver () : m_could_reserve(false ) {}
1967
+
1968
+ bool reserve () {
1969
+ assert (!m_could_reserve);
1970
+ std::lock_guard<std::mutex> lock (g_utxosetscan);
1971
+ if (g_scan_in_progress) {
1972
+ return false ;
1973
+ }
1974
+ g_scan_in_progress = true ;
1975
+ m_could_reserve = true ;
1976
+ return true ;
1977
+ }
1978
+
1979
+ ~CoinsViewScanReserver () {
1980
+ if (m_could_reserve) {
1981
+ std::lock_guard<std::mutex> lock (g_utxosetscan);
1982
+ g_scan_in_progress = false ;
1983
+ }
1984
+ }
1985
+ };
1986
+
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
+ UniValue scantxoutset (const JSONRPCRequest& request)
2026
+ {
2027
+ if (request.fHelp || request.params .size () < 1 || request.params .size () > 2 )
2028
+ throw std::runtime_error (
2029
+ " scantxoutset <action> ( <scanobjects> )\n "
2030
+ " \n Scans 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 "
2032
+ " \n Arguments:\n "
2033
+ " 1. \" action\" (string, required) The action to execute\n "
2034
+ " \" start\" for starting a scan\n "
2035
+ " \" abort\" for aborting the current scan (returns true when abort was successful)\n "
2036
+ " \" 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 "
2046
+ " },\n "
2047
+ " ]\n "
2048
+ " \n Result:\n "
2049
+ " {\n "
2050
+ " \" unspents\" : [\n "
2051
+ " {\n "
2052
+ " \" txid\" : \" transactionid\" , (string) The transaction id\n "
2053
+ " \" vout\" : n, (numeric) the vout value\n "
2054
+ " \" scriptPubKey\" : \" script\" , (string) the script key\n "
2055
+ " \" amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n "
2056
+ " \" height\" : n, (numeric) Height of the unspent transaction output\n "
2057
+ " }\n "
2058
+ " ,...], \n "
2059
+ " \" total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + " \n "
2060
+ " ]\n "
2061
+ );
2062
+
2063
+ RPCTypeCheck (request.params , {UniValue::VSTR, UniValue::VARR});
2064
+
2065
+ UniValue result (UniValue::VOBJ);
2066
+ if (request.params [0 ].get_str () == " status" ) {
2067
+ CoinsViewScanReserver reserver;
2068
+ if (reserver.reserve ()) {
2069
+ // no scan in progress
2070
+ return NullUniValue;
2071
+ }
2072
+ result.pushKV (" progress" , g_scan_progress);
2073
+ return result;
2074
+ } else if (request.params [0 ].get_str () == " abort" ) {
2075
+ CoinsViewScanReserver reserver;
2076
+ if (reserver.reserve ()) {
2077
+ // reserve was possible which means no scan was running
2078
+ return false ;
2079
+ }
2080
+ // set the abort flag
2081
+ g_should_abort_scan = true ;
2082
+ return true ;
2083
+ } else if (request.params [0 ].get_str () == " start" ) {
2084
+ CoinsViewScanReserver reserver;
2085
+ if (!reserver.reserve ()) {
2086
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scan already in progress, use action \" abort\" or \" status\" " );
2087
+ }
2088
+ std::set<CScript> needles;
2089
+ CAmount total_in = 0 ;
2090
+
2091
+ // loop through the scan objects
2092
+ 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" );
2144
+ }
2145
+
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);
2159
+ }
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);
2166
+ }
2167
+ }
2168
+
2169
+ // Scan the unspent transaction output set for inputs
2170
+ UniValue unspents (UniValue::VARR);
2171
+ std::vector<CTxOut> input_txos;
2172
+ std::map<COutPoint, Coin> coins;
2173
+ g_should_abort_scan = false ;
2174
+ g_scan_progress = 0 ;
2175
+ int64_t count = 0 ;
2176
+ std::unique_ptr<CCoinsViewCursor> pcursor;
2177
+ {
2178
+ LOCK (cs_main);
2179
+ FlushStateToDisk ();
2180
+ pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor ());
2181
+ assert (pcursor);
2182
+ }
2183
+ bool res = FindScriptPubKey (g_scan_progress, g_should_abort_scan, count, pcursor.get (), needles, coins);
2184
+ result.pushKV (" success" , res);
2185
+ result.pushKV (" searched_items" , count);
2186
+
2187
+ for (const auto & it : coins) {
2188
+ const COutPoint& outpoint = it.first ;
2189
+ const Coin& coin = it.second ;
2190
+ const CTxOut& txo = coin.out ;
2191
+ input_txos.push_back (txo);
2192
+ total_in += txo.nValue ;
2193
+
2194
+ UniValue unspent (UniValue::VOBJ);
2195
+ unspent.pushKV (" txid" , outpoint.hash .GetHex ());
2196
+ unspent.pushKV (" vout" , (int32_t )outpoint.n );
2197
+ unspent.pushKV (" scriptPubKey" , HexStr (txo.scriptPubKey .begin (), txo.scriptPubKey .end ()));
2198
+ unspent.pushKV (" amount" , ValueFromAmount (txo.nValue ));
2199
+ unspent.pushKV (" height" , (int32_t )coin.nHeight );
2200
+
2201
+ unspents.push_back (unspent);
2202
+ }
2203
+ result.pushKV (" unspents" , unspents);
2204
+ result.pushKV (" total_amount" , ValueFromAmount (total_in));
2205
+ } else {
2206
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid command" );
2207
+ }
2208
+ return result;
2209
+ }
2210
+
1923
2211
static const CRPCCommand commands[] =
1924
2212
{ // category name actor (function) argNames
1925
2213
// --------------------- ------------------------ ----------------------- ----------
@@ -1945,6 +2233,7 @@ static const CRPCCommand commands[] =
1945
2233
{ " blockchain" , " verifychain" , &verifychain, {" checklevel" ," nblocks" } },
1946
2234
1947
2235
{ " blockchain" , " preciousblock" , &preciousblock, {" blockhash" } },
2236
+ { " blockchain" , " scantxoutset" , &scantxoutset, {" action" , " scanobjects" } },
1948
2237
1949
2238
/* Not shown in help */
1950
2239
{ " hidden" , " invalidateblock" , &invalidateblock, {" blockhash" } },
0 commit comments