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>
@@ -1945,6 +1949,246 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>&
1945
1949
return true ;
1946
1950
}
1947
1951
1952
+ /* * RAII object to prevent concurrency issue when scanning the txout set */
1953
+ static std::mutex g_utxosetscan;
1954
+ static std::atomic<int > g_scan_progress;
1955
+ static std::atomic<bool > g_scan_in_progress;
1956
+ static std::atomic<bool > g_should_abort_scan;
1957
+ class CoinsViewScanReserver
1958
+ {
1959
+ private:
1960
+ bool m_could_reserve;
1961
+ public:
1962
+ explicit CoinsViewScanReserver () : m_could_reserve(false ) {}
1963
+
1964
+ bool reserve () {
1965
+ assert (!m_could_reserve);
1966
+ std::lock_guard<std::mutex> lock (g_utxosetscan);
1967
+ if (g_scan_in_progress) {
1968
+ return false ;
1969
+ }
1970
+ g_scan_in_progress = true ;
1971
+ m_could_reserve = true ;
1972
+ return true ;
1973
+ }
1974
+
1975
+ ~CoinsViewScanReserver () {
1976
+ if (m_could_reserve) {
1977
+ std::lock_guard<std::mutex> lock (g_utxosetscan);
1978
+ g_scan_in_progress = false ;
1979
+ }
1980
+ }
1981
+ };
1982
+
1983
+ static const char *g_default_scantxoutset_script_types[] = { " P2PKH" , " P2SH_P2WPKH" , " P2WPKH" };
1984
+
1985
+ enum class OutputScriptType {
1986
+ UNKNOWN,
1987
+ P2PK,
1988
+ P2PKH,
1989
+ P2SH_P2WPKH,
1990
+ P2WPKH
1991
+ };
1992
+
1993
+ static inline OutputScriptType GetOutputScriptTypeFromString (const std::string& outputtype)
1994
+ {
1995
+ if (outputtype == " P2PK" ) return OutputScriptType::P2PK;
1996
+ else if (outputtype == " P2PKH" ) return OutputScriptType::P2PKH;
1997
+ else if (outputtype == " P2SH_P2WPKH" ) return OutputScriptType::P2SH_P2WPKH;
1998
+ else if (outputtype == " P2WPKH" ) return OutputScriptType::P2WPKH;
1999
+ else return OutputScriptType::UNKNOWN;
2000
+ }
2001
+
2002
+ CTxDestination GetDestinationForKey (const CPubKey& key, OutputScriptType type)
2003
+ {
2004
+ switch (type) {
2005
+ case OutputScriptType::P2PKH: return key.GetID ();
2006
+ case OutputScriptType::P2SH_P2WPKH:
2007
+ case OutputScriptType::P2WPKH: {
2008
+ if (!key.IsCompressed ()) return key.GetID ();
2009
+ CTxDestination witdest = WitnessV0KeyHash (key.GetID ());
2010
+ if (type == OutputScriptType::P2SH_P2WPKH) {
2011
+ CScript witprog = GetScriptForDestination (witdest);
2012
+ return CScriptID (witprog);
2013
+ } else {
2014
+ return witdest;
2015
+ }
2016
+ }
2017
+ default : assert (false );
2018
+ }
2019
+ }
2020
+
2021
+ UniValue scantxoutset (const JSONRPCRequest& request)
2022
+ {
2023
+ if (request.fHelp || request.params .size () < 1 || request.params .size () > 2 )
2024
+ throw std::runtime_error (
2025
+ " scantxoutset <action> ( <scanobjects> )\n "
2026
+ " \n Scans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n "
2027
+ " \n Arguments:\n "
2028
+ " 1. \" action\" (string, required) The action to execute\n "
2029
+ " \" start\" for starting a scan\n "
2030
+ " \" abort\" for aborting the current scan (returns true when abort was successful)\n "
2031
+ " \" status\" for progress report (in %) of the current scan\n "
2032
+ " 2. \" scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n "
2033
+ " [\n "
2034
+ " { \" address\" : \" <address>\" }, (string, optional) Bitcoin address\n "
2035
+ " { \" pubkey\" : (object, optional) Public key\n "
2036
+ " {\n "
2037
+ " \" pubkey\" : \" <pubkey\" >, (string, required) HEX encoded public key\n "
2038
+ " \" script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \" P2PKH\" , \" P2SH-P2WPKH\" , \" P2WPKH\" )\n "
2039
+ " }\n "
2040
+ " },\n "
2041
+ " ]\n "
2042
+ " \n Result:\n "
2043
+ " {\n "
2044
+ " \" unspents\" : [\n "
2045
+ " {\n "
2046
+ " \" txid\" : \" transactionid\" , (string) The transaction id\n "
2047
+ " \" vout\" : n, (numeric) the vout value\n "
2048
+ " \" scriptPubKey\" : \" script\" , (string) the script key\n "
2049
+ " \" amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n "
2050
+ " \" height\" : n, (numeric) Height of the unspent transaction output\n "
2051
+ " }\n "
2052
+ " ,...], \n "
2053
+ " \" total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + " \n "
2054
+ " ]\n "
2055
+ );
2056
+
2057
+ RPCTypeCheck (request.params , {UniValue::VSTR, UniValue::VARR});
2058
+
2059
+ UniValue result (UniValue::VOBJ);
2060
+ if (request.params [0 ].get_str () == " status" ) {
2061
+ CoinsViewScanReserver reserver;
2062
+ if (reserver.reserve ()) {
2063
+ // no scan in progress
2064
+ return NullUniValue;
2065
+ }
2066
+ result.pushKV (" progress" , g_scan_progress);
2067
+ return result;
2068
+ } else if (request.params [0 ].get_str () == " abort" ) {
2069
+ CoinsViewScanReserver reserver;
2070
+ if (reserver.reserve ()) {
2071
+ // reserve was possible which means no scan was running
2072
+ return false ;
2073
+ }
2074
+ // set the abort flag
2075
+ g_should_abort_scan = true ;
2076
+ return true ;
2077
+ } else if (request.params [0 ].get_str () == " start" ) {
2078
+ CoinsViewScanReserver reserver;
2079
+ if (!reserver.reserve ()) {
2080
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scan already in progress, use action \" abort\" or \" status\" " );
2081
+ }
2082
+ std::set<CScript> needles;
2083
+ CAmount total_in = 0 ;
2084
+
2085
+ // loop through the scan objects
2086
+ for (const UniValue& scanobject : request.params [1 ].get_array ().getValues ()) {
2087
+ if (!scanobject.isObject ()) {
2088
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid scan object" );
2089
+ }
2090
+ UniValue address_uni = find_value (scanobject, " address" );
2091
+ UniValue pubkey_uni = find_value (scanobject, " pubkey" );
2092
+
2093
+ // make sure only one object type is present
2094
+ if (1 != !address_uni.isNull () + !pubkey_uni.isNull ()) {
2095
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Only one object type is allowed per scan object" );
2096
+ } else if (!address_uni.isNull () && !address_uni.isStr ()) {
2097
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scanobject \" address\" must contain a single string as value" );
2098
+ } else if (!pubkey_uni.isNull () && !pubkey_uni.isObject ()) {
2099
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Scanobject \" pubkey\" must contain an object as value" );
2100
+ } else if (address_uni.isStr ()) {
2101
+ // type: address
2102
+ // decode destination and derive the scriptPubKey
2103
+ // add the script to the scan containers
2104
+ CTxDestination dest = DecodeDestination (address_uni.get_str ());
2105
+ if (!IsValidDestination (dest)) {
2106
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Invalid address" );
2107
+ }
2108
+ CScript script = GetScriptForDestination (dest);
2109
+ assert (!script.empty ());
2110
+ needles.insert (script);
2111
+ } else if (pubkey_uni.isObject ()) {
2112
+ // type: pubkey
2113
+ // derive script(s) according to the script_type parameter
2114
+ UniValue script_types_uni = find_value (pubkey_uni, " script_types" );
2115
+ UniValue pubkeydata_uni = find_value (pubkey_uni, " pubkey" );
2116
+
2117
+ // check the script types and use the default if not provided
2118
+ if (!script_types_uni.isNull () && !script_types_uni.isArray ()) {
2119
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " script_types must be an array" );
2120
+ }
2121
+ else if (script_types_uni.isNull ()) {
2122
+ // use the default script types
2123
+ script_types_uni = UniValue (UniValue::VARR);
2124
+ for (const char *t : g_default_scantxoutset_script_types) {
2125
+ script_types_uni.push_back (t);
2126
+ }
2127
+ }
2128
+
2129
+ // check the acctual pubkey
2130
+ if (!pubkeydata_uni.isStr () || !IsHex (pubkeydata_uni.get_str ())) {
2131
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Public key must be hex encoded" );
2132
+ }
2133
+ CPubKey pubkey (ParseHexV (pubkeydata_uni, " pubkey" ));
2134
+ if (!pubkey.IsFullyValid ()) {
2135
+ throw JSONRPCError (RPC_INVALID_ADDRESS_OR_KEY, " Invalid public key" );
2136
+ }
2137
+
2138
+ // loop through the script types and derive the script
2139
+ for (const UniValue& script_type_uni : script_types_uni.get_array ().getValues ()) {
2140
+ OutputScriptType script_type = GetOutputScriptTypeFromString (script_type_uni.get_str ());
2141
+ if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid script type" );
2142
+
2143
+ CScript script = GetScriptForDestination (GetDestinationForKey (pubkey, script_type));
2144
+ assert (!script.empty ());
2145
+ needles.insert (script);
2146
+ }
2147
+ }
2148
+ }
2149
+
2150
+ // Scan the unspent transaction output set for inputs
2151
+ UniValue unspents (UniValue::VARR);
2152
+ std::vector<CTxOut> input_txos;
2153
+ std::map<COutPoint, Coin> coins;
2154
+ g_should_abort_scan = false ;
2155
+ g_scan_progress = 0 ;
2156
+ int64_t count = 0 ;
2157
+ std::unique_ptr<CCoinsViewCursor> pcursor;
2158
+ {
2159
+ LOCK (cs_main);
2160
+ FlushStateToDisk ();
2161
+ pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor ());
2162
+ assert (pcursor);
2163
+ }
2164
+ bool res = FindScriptPubKey (g_scan_progress, g_should_abort_scan, count, pcursor.get (), needles, coins);
2165
+ result.pushKV (" success" , res);
2166
+ result.pushKV (" searched_items" , count);
2167
+
2168
+ for (const auto & it : coins) {
2169
+ const COutPoint& outpoint = it.first ;
2170
+ const Coin& coin = it.second ;
2171
+ const CTxOut& txo = coin.out ;
2172
+ input_txos.push_back (txo);
2173
+ total_in += txo.nValue ;
2174
+
2175
+ UniValue unspent (UniValue::VOBJ);
2176
+ unspent.pushKV (" txid" , outpoint.hash .GetHex ());
2177
+ unspent.pushKV (" vout" , (int32_t )outpoint.n );
2178
+ unspent.pushKV (" scriptPubKey" , HexStr (txo.scriptPubKey .begin (), txo.scriptPubKey .end ()));
2179
+ unspent.pushKV (" amount" , ValueFromAmount (txo.nValue ));
2180
+ unspent.pushKV (" height" , (int32_t )coin.nHeight );
2181
+
2182
+ unspents.push_back (unspent);
2183
+ }
2184
+ result.pushKV (" unspents" , unspents);
2185
+ result.pushKV (" total_amount" , ValueFromAmount (total_in));
2186
+ } else {
2187
+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid command" );
2188
+ }
2189
+ return result;
2190
+ }
2191
+
1948
2192
static const CRPCCommand commands[] =
1949
2193
{ // category name actor (function) argNames
1950
2194
// --------------------- ------------------------ ----------------------- ----------
@@ -1970,6 +2214,7 @@ static const CRPCCommand commands[] =
1970
2214
{ " blockchain" , " verifychain" , &verifychain, {" checklevel" ," nblocks" } },
1971
2215
1972
2216
{ " blockchain" , " preciousblock" , &preciousblock, {" blockhash" } },
2217
+ { " blockchain" , " scantxoutset" , &scantxoutset, {" action" , " scanobjects" } },
1973
2218
1974
2219
/* Not shown in help */
1975
2220
{ " hidden" , " invalidateblock" , &invalidateblock, {" blockhash" } },
0 commit comments