diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/bench/bech32.cpp | 1 | ||||
-rw-r--r-- | src/chainparamsbase.cpp | 1 | ||||
-rw-r--r-- | src/interfaces/handler.cpp | 2 | ||||
-rw-r--r-- | src/net.cpp | 3 | ||||
-rw-r--r-- | src/rpc/blockchain.cpp | 289 | ||||
-rw-r--r-- | src/rpc/client.cpp | 1 | ||||
-rw-r--r-- | src/util.h | 11 | ||||
-rw-r--r-- | src/utilmemory.h | 19 | ||||
-rw-r--r-- | src/validation.cpp | 12 |
10 files changed, 321 insertions, 19 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1dd202085a..2603dbae56 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -179,6 +179,7 @@ BITCOIN_CORE_H = \ ui_interface.h \ undo.h \ util.h \ + utilmemory.h \ utilmoneystr.h \ utiltime.h \ validation.h \ diff --git a/src/bench/bech32.cpp b/src/bench/bech32.cpp index ff655bded0..8b80e17391 100644 --- a/src/bench/bech32.cpp +++ b/src/bench/bech32.cpp @@ -27,7 +27,6 @@ static void Bech32Encode(benchmark::State& state) static void Bech32Decode(benchmark::State& state) { std::string addr = "bc1qkallence7tjawwvy0dwt4twc62qjgaw8f4vlhyd006d99f09"; - std::vector<unsigned char> vch; while (state.KeepRunning()) { bech32::Decode(addr); } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 787d8d8f6a..da9f3e3209 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -7,6 +7,7 @@ #include <tinyformat.h> #include <util.h> +#include <utilmemory.h> #include <assert.h> diff --git a/src/interfaces/handler.cpp b/src/interfaces/handler.cpp index 8e45faa2a5..80f461f4d3 100644 --- a/src/interfaces/handler.cpp +++ b/src/interfaces/handler.cpp @@ -4,7 +4,7 @@ #include <interfaces/handler.h> -#include <util.h> +#include <utilmemory.h> #include <boost/signals2/connection.hpp> #include <utility> diff --git a/src/net.cpp b/src/net.cpp index e44aa1fdb4..34dc4aec5c 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2254,7 +2254,8 @@ bool CConnman::InitBinds(const std::vector<CService>& binds, const std::vector<C if (binds.empty() && whiteBinds.empty()) { struct in_addr inaddr_any; inaddr_any.s_addr = INADDR_ANY; - fBound |= Bind(CService((in6_addr)IN6ADDR_ANY_INIT, GetListenPort()), BF_NONE); + struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT; + fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE); fBound |= Bind(CService(inaddr_any, GetListenPort()), !fBound ? BF_REPORT_ERROR : BF_NONE); } return fBound; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d9d803ac7d..012e3e3ac1 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -6,6 +6,8 @@ #include <rpc/blockchain.h> #include <amount.h> +#include <base58.h> +#include <chain.h> #include <chainparams.h> #include <checkpoints.h> #include <coins.h> @@ -13,6 +15,7 @@ #include <validation.h> #include <core_io.h> #include <index/txindex.h> +#include <key_io.h> #include <policy/feerate.h> #include <policy/policy.h> #include <primitives/transaction.h> @@ -27,6 +30,7 @@ #include <validationinterface.h> #include <warnings.h> +#include <assert.h> #include <stdint.h> #include <univalue.h> @@ -1920,6 +1924,290 @@ static UniValue savemempool(const JSONRPCRequest& request) return NullUniValue; } +//! Search for a given set of pubkey scripts +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) { + scan_progress = 0; + count = 0; + while (cursor->Valid()) { + COutPoint key; + Coin coin; + if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false; + if (++count % 8192 == 0) { + boost::this_thread::interruption_point(); + if (should_abort) { + // allow to abort the scan via the abort reference + return false; + } + } + if (count % 256 == 0) { + // update progress reference every 256 item + uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1); + scan_progress = (int)(high * 100.0 / 65536.0 + 0.5); + } + if (needles.count(coin.out.scriptPubKey)) { + out_results.emplace(key, coin); + } + cursor->Next(); + } + scan_progress = 100; + return true; +} + +/** RAII object to prevent concurrency issue when scanning the txout set */ +static std::mutex g_utxosetscan; +static std::atomic<int> g_scan_progress; +static std::atomic<bool> g_scan_in_progress; +static std::atomic<bool> g_should_abort_scan; +class CoinsViewScanReserver +{ +private: + bool m_could_reserve; +public: + explicit CoinsViewScanReserver() : m_could_reserve(false) {} + + bool reserve() { + assert (!m_could_reserve); + std::lock_guard<std::mutex> lock(g_utxosetscan); + if (g_scan_in_progress) { + return false; + } + g_scan_in_progress = true; + m_could_reserve = true; + return true; + } + + ~CoinsViewScanReserver() { + if (m_could_reserve) { + std::lock_guard<std::mutex> lock(g_utxosetscan); + g_scan_in_progress = false; + } + } +}; + +static const char *g_default_scantxoutset_script_types[] = { "P2PKH", "P2SH_P2WPKH", "P2WPKH" }; + +enum class OutputScriptType { + UNKNOWN, + P2PK, + P2PKH, + P2SH_P2WPKH, + P2WPKH +}; + +static inline OutputScriptType GetOutputScriptTypeFromString(const std::string& outputtype) +{ + if (outputtype == "P2PK") return OutputScriptType::P2PK; + else if (outputtype == "P2PKH") return OutputScriptType::P2PKH; + else if (outputtype == "P2SH_P2WPKH") return OutputScriptType::P2SH_P2WPKH; + else if (outputtype == "P2WPKH") return OutputScriptType::P2WPKH; + else return OutputScriptType::UNKNOWN; +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputScriptType type) +{ + switch (type) { + case OutputScriptType::P2PKH: return key.GetID(); + case OutputScriptType::P2SH_P2WPKH: + case OutputScriptType::P2WPKH: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + if (type == OutputScriptType::P2SH_P2WPKH) { + CScript witprog = GetScriptForDestination(witdest); + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +UniValue scantxoutset(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "scantxoutset <action> ( <scanobjects> )\n" + "\nScans the unspent transaction output set for possible entries that matches common scripts of given public keys.\n" + "Using addresses as scanobjects will _not_ detect unspent P2PK txouts\n" + "\nArguments:\n" + "1. \"action\" (string, required) The action to execute\n" + " \"start\" for starting a scan\n" + " \"abort\" for aborting the current scan (returns true when abort was successful)\n" + " \"status\" for progress report (in %) of the current scan\n" + "2. \"scanobjects\" (array, optional) Array of scan objects (only one object type per scan object allowed)\n" + " [\n" + " { \"address\" : \"<address>\" }, (string, optional) Bitcoin address\n" + " { \"script\" : \"<scriptPubKey>\" }, (string, optional) HEX encoded script (scriptPubKey)\n" + " { \"pubkey\" : (object, optional) Public key\n" + " {\n" + " \"pubkey\" : \"<pubkey\">, (string, required) HEX encoded public key\n" + " \"script_types\" : [ ... ], (array, optional) Array of script-types to derive from the pubkey (possible values: \"P2PK\", \"P2PKH\", \"P2SH-P2WPKH\", \"P2WPKH\")\n" + " }\n" + " },\n" + " ]\n" + "\nResult:\n" + "{\n" + " \"unspents\": [\n" + " {\n" + " \"txid\" : \"transactionid\", (string) The transaction id\n" + " \"vout\": n, (numeric) the vout value\n" + " \"scriptPubKey\" : \"script\", (string) the script key\n" + " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n" + " \"height\" : n, (numeric) Height of the unspent transaction output\n" + " }\n" + " ,...], \n" + " \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n" + "]\n" + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); + + UniValue result(UniValue::VOBJ); + if (request.params[0].get_str() == "status") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // no scan in progress + return NullUniValue; + } + result.pushKV("progress", g_scan_progress); + return result; + } else if (request.params[0].get_str() == "abort") { + CoinsViewScanReserver reserver; + if (reserver.reserve()) { + // reserve was possible which means no scan was running + return false; + } + // set the abort flag + g_should_abort_scan = true; + return true; + } else if (request.params[0].get_str() == "start") { + CoinsViewScanReserver reserver; + if (!reserver.reserve()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\""); + } + std::set<CScript> needles; + CAmount total_in = 0; + + // loop through the scan objects + for (const UniValue& scanobject : request.params[1].get_array().getValues()) { + if (!scanobject.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid scan object"); + } + UniValue address_uni = find_value(scanobject, "address"); + UniValue pubkey_uni = find_value(scanobject, "pubkey"); + UniValue script_uni = find_value(scanobject, "script"); + + // make sure only one object type is present + if (1 != !address_uni.isNull() + !pubkey_uni.isNull() + !script_uni.isNull()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Only one object type is allowed per scan object"); + } else if (!address_uni.isNull() && !address_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"address\" must contain a single string as value"); + } else if (!pubkey_uni.isNull() && !pubkey_uni.isObject()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"pubkey\" must contain an object as value"); + } else if (!script_uni.isNull() && !script_uni.isStr()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Scanobject \"script\" must contain a single string as value"); + } else if (address_uni.isStr()) { + // type: address + // decode destination and derive the scriptPubKey + // add the script to the scan containers + CTxDestination dest = DecodeDestination(address_uni.get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } + CScript script = GetScriptForDestination(dest); + assert(!script.empty()); + needles.insert(script); + } else if (pubkey_uni.isObject()) { + // type: pubkey + // derive script(s) according to the script_type parameter + UniValue script_types_uni = find_value(pubkey_uni, "script_types"); + UniValue pubkeydata_uni = find_value(pubkey_uni, "pubkey"); + + // check the script types and use the default if not provided + if (!script_types_uni.isNull() && !script_types_uni.isArray()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "script_types must be an array"); + } else if (script_types_uni.isNull()) { + // use the default script types + script_types_uni = UniValue(UniValue::VARR); + for (const char *t : g_default_scantxoutset_script_types) { + script_types_uni.push_back(t); + } + } + + // check the acctual pubkey + if (!pubkeydata_uni.isStr() || !IsHex(pubkeydata_uni.get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Public key must be hex encoded"); + } + CPubKey pubkey(ParseHexV(pubkeydata_uni, "pubkey")); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key"); + } + + // loop through the script types and derive the script + for (const UniValue& script_type_uni : script_types_uni.get_array().getValues()) { + OutputScriptType script_type = GetOutputScriptTypeFromString(script_type_uni.get_str()); + if (script_type == OutputScriptType::UNKNOWN) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid script type"); + CScript script; + if (script_type == OutputScriptType::P2PK) { + // support legacy P2PK scripts + script << ToByteVector(pubkey) << OP_CHECKSIG; + } else { + script = GetScriptForDestination(GetDestinationForKey(pubkey, script_type)); + } + assert(!script.empty()); + needles.insert(script); + } + } else if (script_uni.isStr()) { + // type: script + // check and add the script to the scan containers (needles array) + CScript script(ParseHexV(script_uni, "script")); + // TODO: check script: max length, has OP, is unspenable etc. + needles.insert(script); + } + } + + // Scan the unspent transaction output set for inputs + UniValue unspents(UniValue::VARR); + std::vector<CTxOut> input_txos; + std::map<COutPoint, Coin> coins; + g_should_abort_scan = false; + g_scan_progress = 0; + int64_t count = 0; + std::unique_ptr<CCoinsViewCursor> pcursor; + { + LOCK(cs_main); + FlushStateToDisk(); + pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor()); + assert(pcursor); + } + bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); + result.pushKV("success", res); + result.pushKV("searched_items", count); + + for (const auto& it : coins) { + const COutPoint& outpoint = it.first; + const Coin& coin = it.second; + const CTxOut& txo = coin.out; + input_txos.push_back(txo); + total_in += txo.nValue; + + UniValue unspent(UniValue::VOBJ); + unspent.pushKV("txid", outpoint.hash.GetHex()); + unspent.pushKV("vout", (int32_t)outpoint.n); + unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey.begin(), txo.scriptPubKey.end())); + unspent.pushKV("amount", ValueFromAmount(txo.nValue)); + unspent.pushKV("height", (int32_t)coin.nHeight); + + unspents.push_back(unspent); + } + result.pushKV("unspents", unspents); + result.pushKV("total_amount", ValueFromAmount(total_in)); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); + } + return result; +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -1945,6 +2233,7 @@ static const CRPCCommand commands[] = { "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} }, { "blockchain", "preciousblock", &preciousblock, {"blockhash"} }, + { "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} }, /* Not shown in help */ { "hidden", "invalidateblock", &invalidateblock, {"blockhash"} }, diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 0f35fd3770..ce608631ff 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -78,6 +78,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendmany", 4, "subtractfeefrom" }, { "sendmany", 5 , "replaceable" }, { "sendmany", 6 , "conf_target" }, + { "scantxoutset", 1, "scanobjects" }, { "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 1, "keys" }, { "createmultisig", 0, "nrequired" }, diff --git a/src/util.h b/src/util.h index 8094d72d6b..f8bcc0192c 100644 --- a/src/util.h +++ b/src/util.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2017 The Bitcoin Core developers +// Copyright (c) 2009-2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -20,11 +20,11 @@ #include <sync.h> #include <tinyformat.h> #include <utiltime.h> +#include <utilmemory.h> #include <atomic> #include <exception> #include <map> -#include <memory> #include <set> #include <stdint.h> #include <string> @@ -346,13 +346,6 @@ template <typename Callable> void TraceThread(const char* name, Callable func) std::string CopyrightHolders(const std::string& strPrefix); -//! Substitute for C++14 std::make_unique. -template <typename T, typename... Args> -std::unique_ptr<T> MakeUnique(Args&&... args) -{ - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. diff --git a/src/utilmemory.h b/src/utilmemory.h new file mode 100644 index 0000000000..e71fe92284 --- /dev/null +++ b/src/utilmemory.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_UTILMEMORY_H +#define BITCOIN_UTILMEMORY_H + +#include <memory> +#include <utility> + +//! Substitute for C++14 std::make_unique. +template <typename T, typename... Args> +std::unique_ptr<T> MakeUnique(Args&&... args) +{ + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} + +#endif diff --git a/src/validation.cpp b/src/validation.cpp index 811530d40e..a30f1fd0ce 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -806,13 +806,11 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool // be increased is also an easy-to-reason about way to prevent // DoS attacks via replacements. // - // The mining code doesn't (currently) take children into - // account (CPFP) so we only consider the feerates of - // transactions being directly replaced, not their indirect - // descendants. While that does mean high feerate children are - // ignored when deciding whether or not to replace, we do - // require the replacement to pay more overall fees too, - // mitigating most cases. + // We only consider the feerates of transactions being directly + // replaced, not their indirect descendants. While that does + // mean high feerate children are ignored when deciding whether + // or not to replace, we do require the replacement to pay more + // overall fees too, mitigating most cases. CFeeRate oldFeeRate(mi->GetModifiedFee(), mi->GetTxSize()); if (newFeeRate <= oldFeeRate) { |