diff options
Diffstat (limited to 'src')
184 files changed, 3170 insertions, 1345 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 619f968bc9..cbe5479956 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -119,6 +119,7 @@ BITCOIN_CORE_H = \ compat.h \ compat/assumptions.h \ compat/byteswap.h \ + compat/cpuid.h \ compat/endian.h \ compat/sanity.h \ compressor.h \ @@ -146,12 +147,14 @@ BITCOIN_CORE_H = \ dbwrapper.h \ limitedmap.h \ logging.h \ + logging/timer.h \ memusage.h \ merkleblock.h \ miner.h \ net.h \ net_permissions.h \ net_processing.h \ + net_types.h \ netaddress.h \ netbase.h \ netmessagemaker.h \ @@ -160,6 +163,7 @@ BITCOIN_CORE_H = \ node/context.h \ node/psbt.h \ node/transaction.h \ + node/utxo_snapshot.h \ noui.h \ optional.h \ outputtype.h \ @@ -172,6 +176,7 @@ BITCOIN_CORE_H = \ protocol.h \ psbt.h \ random.h \ + randomenv.h \ reverse_iterator.h \ reverselock.h \ rpc/blockchain.h \ @@ -216,6 +221,7 @@ BITCOIN_CORE_H = \ util/memory.h \ util/moneystr.h \ util/rbf.h \ + util/settings.h \ util/string.h \ util/threadnames.h \ util/time.h \ @@ -499,6 +505,7 @@ libbitcoin_util_a_SOURCES = \ interfaces/handler.cpp \ logging.cpp \ random.cpp \ + randomenv.cpp \ rpc/request.cpp \ support/cleanse.cpp \ sync.cpp \ @@ -510,6 +517,7 @@ libbitcoin_util_a_SOURCES = \ util/system.cpp \ util/moneystr.cpp \ util/rbf.cpp \ + util/settings.cpp \ util/threadnames.cpp \ util/spanparsing.cpp \ util/strencodings.cpp \ diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index 38143e32b9..c9e4fcc4bc 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -30,6 +30,7 @@ bench_bench_bitcoin_SOURCES = \ bench/gcs_filter.cpp \ bench/merkle_root.cpp \ bench/mempool_eviction.cpp \ + bench/mempool_stress.cpp \ bench/rpc_blockchain.cpp \ bench/rpc_mempool.cpp \ bench/util_time.cpp \ @@ -39,10 +40,10 @@ bench_bench_bitcoin_SOURCES = \ bench/lockedpool.cpp \ bench/poly1305.cpp \ bench/prevector.cpp \ - test/lib/transaction_utils.h \ - test/lib/transaction_utils.cpp \ - test/setup_common.h \ - test/setup_common.cpp \ + test/util/transaction_utils.h \ + test/util/transaction_utils.cpp \ + test/util/setup_common.h \ + test/util/setup_common.cpp \ test/util.h \ test/util.cpp diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index c309340fd7..562b393b22 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -27,10 +27,10 @@ TEST_QT_H = \ qt/test/wallettests.h TEST_BITCOIN_CPP = \ - test/setup_common.cpp + test/util/setup_common.cpp TEST_BITCOIN_H = \ - test/setup_common.h + test/util/setup_common.h qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_TEST_INCLUDES) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index df49cc1f5e..c5353c6554 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -7,6 +7,7 @@ FUZZ_TARGETS = \ test/fuzz/address_deserialize \ test/fuzz/addrman_deserialize \ test/fuzz/banentry_deserialize \ + test/fuzz/bech32 \ test/fuzz/block_deserialize \ test/fuzz/blockheader_deserialize \ test/fuzz/blocklocator_deserialize \ @@ -57,18 +58,26 @@ RAW_TEST_FILES = GENERATED_TEST_FILES = $(JSON_TEST_FILES:.json=.json.h) $(RAW_TEST_FILES:.raw=.raw.h) BITCOIN_TEST_SUITE = \ - test/lib/transaction_utils.h \ - test/lib/transaction_utils.cpp \ + test/util/blockfilter.cpp \ + test/util/blockfilter.h \ + test/util/logging.cpp \ + test/util/logging.h \ + test/util/transaction_utils.cpp \ + test/util/transaction_utils.h \ test/main.cpp \ - test/setup_common.h \ - test/setup_common.cpp + test/util/setup_common.h \ + test/util/setup_common.cpp \ + test/util/str.h \ + test/util/str.cpp FUZZ_SUITE = \ - test/setup_common.h \ - test/setup_common.cpp \ test/fuzz/fuzz.cpp \ test/fuzz/fuzz.h \ - test/fuzz/FuzzedDataProvider.h + test/fuzz/FuzzedDataProvider.h \ + test/util/setup_common.cpp \ + test/util/setup_common.h \ + test/util/str.cpp \ + test/util/str.h FUZZ_SUITE_LD_COMMON = \ $(LIBBITCOIN_SERVER) \ @@ -119,6 +128,7 @@ BITCOIN_TESTS =\ test/key_io_tests.cpp \ test/key_tests.cpp \ test/limitedmap_tests.cpp \ + test/logging_tests.cpp \ test/dbwrapper_tests.cpp \ test/validation_tests.cpp \ test/mempool_tests.cpp \ @@ -143,6 +153,7 @@ BITCOIN_TESTS =\ test/script_standard_tests.cpp \ test/scriptnum_tests.cpp \ test/serialize_tests.cpp \ + test/settings_tests.cpp \ test/sighash_tests.cpp \ test/sigopcount_tests.cpp \ test/skiplist_tests.cpp \ @@ -241,6 +252,12 @@ test_fuzz_banentry_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_banentry_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_banentry_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_bech32_SOURCES = $(FUZZ_SUITE) test/fuzz/bech32.cpp +test_fuzz_bech32_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_bech32_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_bech32_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_bech32_LDADD = $(FUZZ_SUITE_LD_COMMON) + test_fuzz_txundo_deserialize_SOURCES = $(FUZZ_SUITE) test/fuzz/deserialize.cpp test_fuzz_txundo_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DTXUNDO_DESERIALIZE=1 test_fuzz_txundo_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) diff --git a/src/addrdb.h b/src/addrdb.h index 290b63dd12..ad85224d1f 100644 --- a/src/addrdb.h +++ b/src/addrdb.h @@ -7,6 +7,7 @@ #define BITCOIN_ADDRDB_H #include <fs.h> +#include <net_types.h> // For banmap_t #include <serialize.h> #include <string> @@ -79,8 +80,6 @@ public: } }; -typedef std::map<CSubNet, CBanEntry> banmap_t; - /** Access to the (IP) address database (peers.dat) */ class CAddrDB { diff --git a/src/banman.h b/src/banman.h index 9d45bf0559..7943f666e8 100644 --- a/src/banman.h +++ b/src/banman.h @@ -10,6 +10,7 @@ #include <addrdb.h> #include <fs.h> +#include <net_types.h> // For banmap_t #include <sync.h> // NOTE: When adjusting this, update rpcnet:setban's help ("24h") diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp index f2b520e893..cc159eb191 100644 --- a/src/bench/bench.cpp +++ b/src/bench/bench.cpp @@ -5,7 +5,7 @@ #include <bench/bench.h> #include <chainparams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <validation.h> #include <algorithm> @@ -112,7 +112,7 @@ void benchmark::BenchRunner::RunAll(Printer& printer, uint64_t num_evals, double printer.header(); for (const auto& p : benchmarks()) { - TestingSetup test{CBaseChainParams::REGTEST}; + RegTestingSetup test{}; { LOCK(cs_main); assert(::ChainActive().Height() == 0); diff --git a/src/bench/mempool_stress.cpp b/src/bench/mempool_stress.cpp new file mode 100644 index 0000000000..389e2c096f --- /dev/null +++ b/src/bench/mempool_stress.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2011-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bench/bench.h> +#include <policy/policy.h> +#include <txmempool.h> + +#include <vector> + +static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) +{ + int64_t nTime = 0; + unsigned int nHeight = 1; + bool spendsCoinbase = false; + unsigned int sigOpCost = 4; + LockPoints lp; + pool.addUnchecked(CTxMemPoolEntry(tx, 1000, nTime, nHeight, spendsCoinbase, sigOpCost, lp)); +} + +struct Available { + CTransactionRef ref; + size_t vin_left{0}; + size_t tx_count; + Available(CTransactionRef& ref, size_t tx_count) : ref(ref), tx_count(tx_count){} +}; + +static void ComplexMemPool(benchmark::State& state) +{ + FastRandomContext det_rand{true}; + std::vector<Available> available_coins; + std::vector<CTransactionRef> ordered_coins; + // Create some base transactions + size_t tx_counter = 1; + for (auto x = 0; x < 100; ++x) { + CMutableTransaction tx = CMutableTransaction(); + tx.vin.resize(1); + tx.vin[0].scriptSig = CScript() << CScriptNum(tx_counter); + tx.vin[0].scriptWitness.stack.push_back(CScriptNum(x).getvch()); + tx.vout.resize(det_rand.randrange(10)+2); + for (auto& out : tx.vout) { + out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; + out.nValue = 10 * COIN; + } + ordered_coins.emplace_back(MakeTransactionRef(tx)); + available_coins.emplace_back(ordered_coins.back(), tx_counter++); + } + for (auto x = 0; x < 800 && !available_coins.empty(); ++x) { + CMutableTransaction tx = CMutableTransaction(); + size_t n_ancestors = det_rand.randrange(10)+1; + for (size_t ancestor = 0; ancestor < n_ancestors && !available_coins.empty(); ++ancestor){ + size_t idx = det_rand.randrange(available_coins.size()); + Available coin = available_coins[idx]; + uint256 hash = coin.ref->GetHash(); + // biased towards taking just one ancestor, but maybe more + size_t n_to_take = det_rand.randrange(2) == 0 ? 1 : 1+det_rand.randrange(coin.ref->vout.size() - coin.vin_left); + for (size_t i = 0; i < n_to_take; ++i) { + tx.vin.emplace_back(); + tx.vin.back().prevout = COutPoint(hash, coin.vin_left++); + tx.vin.back().scriptSig = CScript() << coin.tx_count; + tx.vin.back().scriptWitness.stack.push_back(CScriptNum(coin.tx_count).getvch()); + } + if (coin.vin_left == coin.ref->vin.size()) { + coin = available_coins.back(); + available_coins.pop_back(); + } + tx.vout.resize(det_rand.randrange(10)+2); + for (auto& out : tx.vout) { + out.scriptPubKey = CScript() << CScriptNum(tx_counter) << OP_EQUAL; + out.nValue = 10 * COIN; + } + } + ordered_coins.emplace_back(MakeTransactionRef(tx)); + available_coins.emplace_back(ordered_coins.back(), tx_counter++); + } + CTxMemPool pool; + LOCK2(cs_main, pool.cs); + while (state.KeepRunning()) { + for (auto& tx : ordered_coins) { + AddTx(tx, pool); + } + pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); + pool.TrimToSize(GetVirtualTransactionSize(*ordered_coins.front())); + } +} + +BENCHMARK(ComplexMemPool, 1); diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index c9947f192e..1c025e29d3 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -10,7 +10,7 @@ #include <script/script.h> #include <script/standard.h> #include <streams.h> -#include <test/lib/transaction_utils.h> +#include <test/util/transaction_utils.h> #include <array> diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index d7b6891503..592fcbe8dd 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -265,7 +265,7 @@ public: result.pushKV("proxy", batch[ID_NETWORKINFO]["result"]["networks"][0]["proxy"]); result.pushKV("difficulty", batch[ID_BLOCKCHAININFO]["result"]["difficulty"]); result.pushKV("chain", UniValue(batch[ID_BLOCKCHAININFO]["result"]["chain"])); - if (!batch[ID_WALLETINFO].isNull()) { + if (!batch[ID_WALLETINFO]["result"].isNull()) { result.pushKV("walletversion", batch[ID_WALLETINFO]["result"]["walletversion"]); result.pushKV("balance", batch[ID_WALLETINFO]["result"]["balance"]); result.pushKV("keypoololdest", batch[ID_WALLETINFO]["result"]["keypoololdest"]); diff --git a/src/bloom.h b/src/bloom.h index 7d3aa878b0..c3f64ba4bc 100644 --- a/src/bloom.h +++ b/src/bloom.h @@ -115,9 +115,6 @@ public: class CRollingBloomFilter { public: - // A random bloom filter calls GetRand() at creation time. - // Don't create global CRollingBloomFilter objects, as they may be - // constructed before the randomizer is properly initialized. CRollingBloomFilter(const unsigned int nElements, const double nFPRate); void insert(const std::vector<unsigned char>& vKey); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index dd4d3e97ac..0a1cb858ef 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -71,7 +71,7 @@ public: consensus.BIP66Height = 363725; // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 consensus.CSVHeight = 419328; // 000000000000000004a1b34462cb8aeebd5799177f7a29cf28f2d1961716b5b5 consensus.SegwitHeight = 481824; // 0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893 - consensus.MinBIP9WarningHeight = consensus.SegwitHeight + consensus.nMinerConfirmationWindow; + consensus.MinBIP9WarningHeight = 483840; // segwit activation height + miner confirmation window consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; @@ -178,7 +178,7 @@ public: consensus.BIP66Height = 330776; // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 consensus.CSVHeight = 770112; // 00000000025e930139bac5c6c31a403776da130831ab85be56578f3fa75369bb consensus.SegwitHeight = 834624; // 00000000002b980fcd729daaa248fd9316a5200e9b367f4ff2c42453e84201ca - consensus.MinBIP9WarningHeight = consensus.SegwitHeight + consensus.nMinerConfirmationWindow; + consensus.MinBIP9WarningHeight = 836640; // segwit activation height + miner confirmation window consensus.powLimit = uint256S("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks consensus.nPowTargetSpacing = 10 * 60; diff --git a/src/chainparams.h b/src/chainparams.h index 8f1d27e03c..6be066806b 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -75,7 +75,7 @@ public: uint64_t AssumedChainStateSize() const { return m_assumed_chain_state_size; } /** Whether it is possible to mine blocks on demand (no retargeting) */ bool MineBlocksOnDemand() const { return consensus.fPowNoRetargeting; } - /** Return the BIP70 network string (main, test or regtest) */ + /** Return the network string */ std::string NetworkIDString() const { return strNetworkID; } /** Return the list of hostnames to look up for DNS seeds */ const std::vector<std::string>& DNSSeeds() const { return vSeeds; } @@ -120,7 +120,7 @@ std::unique_ptr<const CChainParams> CreateChainParams(const std::string& chain); const CChainParams &Params(); /** - * Sets the params returned by Params() to those for the given BIP70 chain name. + * Sets the params returned by Params() to those for the given chain name. * @throws std::runtime_error when the chain is not supported. */ void SelectParams(const std::string& chain); diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index f34646f7ac..69fe2438f3 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -15,10 +15,12 @@ class CBaseChainParams { public: - /** BIP70 chain name strings (main, test or regtest) */ + ///@{ + /** Chain name strings */ static const std::string MAIN; static const std::string TESTNET; static const std::string REGTEST; + ///@} const std::string& DataDir() const { return strDataDir; } int RPCPort() const { return nRPCPort; } diff --git a/src/compat/cpuid.h b/src/compat/cpuid.h new file mode 100644 index 0000000000..0877ad47d3 --- /dev/null +++ b/src/compat/cpuid.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017-2019 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_COMPAT_CPUID_H +#define BITCOIN_COMPAT_CPUID_H + +#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) +#define HAVE_GETCPUID + +#include <cpuid.h> + +// We can't use cpuid.h's __get_cpuid as it does not support subleafs. +void static inline GetCPUID(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) +{ +#ifdef __GNUC__ + __cpuid_count(leaf, subleaf, a, b, c, d); +#else + __asm__ ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(leaf), "2"(subleaf)); +#endif +} + +#endif // defined(__x86_64__) || defined(__amd64__) || defined(__i386__) +#endif // BITCOIN_COMPAT_CPUID_H diff --git a/src/crypto/sha256.cpp b/src/crypto/sha256.cpp index 3257ee7f97..dda7e5230f 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -8,9 +8,10 @@ #include <assert.h> #include <string.h> +#include <compat/cpuid.h> + #if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) #if defined(USE_ASM) -#include <cpuid.h> namespace sha256_sse4 { void Transform(uint32_t* s, const unsigned char* chunk, size_t blocks); @@ -546,18 +547,7 @@ bool SelfTest() { return true; } - #if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) -// We can't use cpuid.h's __get_cpuid as it does not support subleafs. -void inline cpuid(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) -{ -#ifdef __GNUC__ - __cpuid_count(leaf, subleaf, a, b, c, d); -#else - __asm__ ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(leaf), "2"(subleaf)); -#endif -} - /** Check whether the OS has enabled AVX registers. */ bool AVXEnabled() { @@ -572,7 +562,7 @@ bool AVXEnabled() std::string SHA256AutoDetect() { std::string ret = "standard"; -#if defined(USE_ASM) && (defined(__x86_64__) || defined(__amd64__) || defined(__i386__)) +#if defined(USE_ASM) && defined(HAVE_GETCPUID) bool have_sse4 = false; bool have_xsave = false; bool have_avx = false; @@ -589,7 +579,7 @@ std::string SHA256AutoDetect() (void)enabled_avx; uint32_t eax, ebx, ecx, edx; - cpuid(1, 0, eax, ebx, ecx, edx); + GetCPUID(1, 0, eax, ebx, ecx, edx); have_sse4 = (ecx >> 19) & 1; have_xsave = (ecx >> 27) & 1; have_avx = (ecx >> 28) & 1; @@ -597,7 +587,7 @@ std::string SHA256AutoDetect() enabled_avx = AVXEnabled(); } if (have_sse4) { - cpuid(7, 0, eax, ebx, ecx, edx); + GetCPUID(7, 0, eax, ebx, ecx, edx); have_avx2 = (ebx >> 5) & 1; have_shani = (ebx >> 29) & 1; } diff --git a/src/crypto/sha512.h b/src/crypto/sha512.h index 4118ac1b18..fc7dd1b87e 100644 --- a/src/crypto/sha512.h +++ b/src/crypto/sha512.h @@ -23,6 +23,7 @@ public: CSHA512& Write(const unsigned char* data, size_t len); void Finalize(unsigned char hash[OUTPUT_SIZE]); CSHA512& Reset(); + uint64_t Size() const { return bytes; } }; #endif // BITCOIN_CRYPTO_SHA512_H diff --git a/src/init.cpp b/src/init.cpp index 2a23f99b75..2abdf7dbc4 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -372,7 +372,7 @@ void SetupServerArgs() gArgs.AddArg("-debuglogfile=<file>", strprintf("Specify location of debug log file. Relative paths will be prefixed by a net-specific datadir location. (-nodebuglogfile to disable; default: %s)", DEFAULT_DEBUGLOGFILE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-feefilter", strprintf("Tell other nodes to filter invs to us by our mempool min fee (default: %u)", DEFAULT_FEEFILTER), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-includeconf=<file>", "Specify additional configuration file, relative to the -datadir path (only useable from configuration file, not command line)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); - gArgs.AddArg("-loadblock=<file>", "Imports blocks from external blk000??.dat file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); + gArgs.AddArg("-loadblock=<file>", "Imports blocks from external file on startup", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-maxmempool=<n>", strprintf("Keep the transaction memory pool below <n> megabytes (default: %u)", DEFAULT_MAX_MEMPOOL_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-maxorphantx=<n>", strprintf("Keep at most <n> unconnectable transactions in memory (default: %u)", DEFAULT_MAX_ORPHAN_TRANSACTIONS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); gArgs.AddArg("-mempoolexpiry=<n>", strprintf("Do not keep transactions in the mempool longer than <n> hours (default: %u)", DEFAULT_MEMPOOL_EXPIRY), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); @@ -480,7 +480,7 @@ void SetupServerArgs() "(0-4, default: %u)", DEFAULT_CHECKLEVEL), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkblockindex", strprintf("Do a consistency check for the block tree, chainstate, and other validation data structures occasionally. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-checkmempool=<n>", strprintf("Run checks every <n> transactions (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); - gArgs.AddArg("-checkpoints", strprintf("Disable expensive verification for known chain history (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + gArgs.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block 295000 (default: %u)", DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-deprecatedrpc=<method>", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-dropmessagestest=<n>", "Randomly drop 1 of every <n> network messages", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); gArgs.AddArg("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", DEFAULT_STOPAFTERBLOCKIMPORT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); @@ -684,20 +684,6 @@ static void ThreadImport(std::vector<fs::path> vImportFiles) LoadGenesisBlock(chainparams); } - // hardcoded $DATADIR/bootstrap.dat - fs::path pathBootstrap = GetDataDir() / "bootstrap.dat"; - if (fs::exists(pathBootstrap)) { - FILE *file = fsbridge::fopen(pathBootstrap, "rb"); - if (file) { - fs::path pathBootstrapOld = GetDataDir() / "bootstrap.dat.old"; - LogPrintf("Importing bootstrap.dat...\n"); - LoadExternalBlockFile(chainparams, file); - RenameOver(pathBootstrap, pathBootstrapOld); - } else { - LogPrintf("Warning: Could not open bootstrap file %s\n", pathBootstrap.string()); - } - } - // -loadblock= for (const fs::path& path : vImportFiles) { FILE *file = fsbridge::fopen(path, "rb"); @@ -1061,15 +1047,6 @@ bool AppInitParameterInteraction() incrementalRelayFee = CFeeRate(n); } - // -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency - nScriptCheckThreads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); - if (nScriptCheckThreads <= 0) - nScriptCheckThreads += GetNumCores(); - if (nScriptCheckThreads <= 1) - nScriptCheckThreads = 0; - else if (nScriptCheckThreads > MAX_SCRIPTCHECK_THREADS) - nScriptCheckThreads = MAX_SCRIPTCHECK_THREADS; - // block pruning; get the amount of disk space (in MiB) to allot for block & undo files int64_t nPruneArg = gArgs.GetArg("-prune", 0); if (nPruneArg < 0) { @@ -1256,16 +1233,36 @@ bool AppInitMain(NodeContext& node) InitSignatureCache(); InitScriptExecutionCache(); - LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); - if (nScriptCheckThreads) { - for (int i=0; i<nScriptCheckThreads-1; i++) + int script_threads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS); + if (script_threads <= 0) { + // -par=0 means autodetect (number of cores - 1 script threads) + // -par=-n means "leave n cores free" (number of cores - n - 1 script threads) + script_threads += GetNumCores(); + } + + // Subtract 1 because the main thread counts towards the par threads + script_threads = std::max(script_threads - 1, 0); + + // Number of script-checking threads <= MAX_SCRIPTCHECK_THREADS + script_threads = std::min(script_threads, MAX_SCRIPTCHECK_THREADS); + + LogPrintf("Script verification uses %d additional threads\n", script_threads); + if (script_threads >= 1) { + g_parallel_script_checks = true; + for (int i = 0; i < script_threads; ++i) { threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); + } } // Start the lightweight task scheduler thread CScheduler::Function serviceLoop = std::bind(&CScheduler::serviceQueue, &scheduler); threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>, "scheduler", serviceLoop)); + // Gather some entropy once per minute. + scheduler.scheduleEvery([]{ + RandAddPeriodic(); + }, 60000); + GetMainSignals().RegisterBackgroundSignalScheduler(scheduler); GetMainSignals().RegisterWithMempoolSignals(mempool); diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 23099a7799..26856a00d3 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -58,12 +58,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection> } return nullopt; } - int getBlockDepth(const uint256& hash) override - { - const Optional<int> tip_height = getHeight(); - const Optional<int> height = getBlockHeight(hash); - return tip_height && height ? *tip_height - *height + 1 : 0; - } uint256 getBlockHash(int height) override { LockAssertion lock(::cs_main); @@ -182,11 +176,11 @@ public: const CBlockIndex* index, const std::vector<CTransactionRef>& tx_conflicted) override { - m_notifications->BlockConnected(*block, tx_conflicted); + m_notifications->BlockConnected(*block, tx_conflicted, index->nHeight); } - void BlockDisconnected(const std::shared_ptr<const CBlock>& block) override + void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override { - m_notifications->BlockDisconnected(*block); + m_notifications->BlockDisconnected(*block, index->nHeight); } void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override { @@ -353,13 +347,11 @@ public: { return MakeUnique<NotificationsHandlerImpl>(*this, notifications); } - void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) override + void waitForNotificationsIfTipChanged(const uint256& old_tip) override { if (!old_tip.IsNull()) { LOCK(::cs_main); if (old_tip == ::ChainActive().Tip()->GetBlockHash()) return; - CBlockIndex* block = LookupBlockIndex(old_tip); - if (block && block->GetAncestor(::ChainActive().Height()) == ::ChainActive().Tip()) return; } SyncWithValidationInterfaceQueue(); } diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 82eeba1160..349af152d5 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -76,10 +76,6 @@ public: //! included in the current chain. virtual Optional<int> getBlockHeight(const uint256& hash) = 0; - //! Get block depth. Returns 1 for chain tip, 2 for preceding block, and - //! so on. Returns 0 for a block not included in the current chain. - virtual int getBlockDepth(const uint256& hash) = 0; - //! Get block hash. Height must be valid or this function will abort. virtual uint256 getBlockHash(int height) = 0; @@ -226,8 +222,8 @@ public: virtual ~Notifications() {} virtual void TransactionAddedToMempool(const CTransactionRef& tx) {} virtual void TransactionRemovedFromMempool(const CTransactionRef& ptx) {} - virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted) {} - virtual void BlockDisconnected(const CBlock& block) {} + virtual void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& tx_conflicted, int height) {} + virtual void BlockDisconnected(const CBlock& block, int height) {} virtual void UpdatedBlockTip() {} virtual void ChainStateFlushed(const CBlockLocator& locator) {} }; @@ -236,9 +232,8 @@ public: virtual std::unique_ptr<Handler> handleNotifications(Notifications& notifications) = 0; //! Wait for pending notifications to be processed unless block hash points to the current - //! chain tip, or to a possible descendant of the current chain tip that isn't currently - //! connected. - virtual void waitForNotificationsIfNewBlocksConnected(const uint256& old_tip) = 0; + //! chain tip. + virtual void waitForNotificationsIfTipChanged(const uint256& old_tip) = 0; //! Register handler for RPC. Command is not copied, so reference //! needs to remain valid until Handler is disconnected. diff --git a/src/interfaces/node.h b/src/interfaces/node.h index c29037f2e3..adf3de7b07 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -5,9 +5,9 @@ #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H -#include <addrdb.h> // For banmap_t #include <amount.h> // For CAmount #include <net.h> // For CConnman::NumConnections +#include <net_types.h> // For banmap_t #include <netaddress.h> // For Network #include <support/allocators/secure.h> // For SecureString diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index b6ede08b14..701a748e55 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -31,7 +31,7 @@ namespace interfaces { namespace { //! Construct wallet tx struct. -WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, const CWalletTx& wtx) +WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) { WalletTx result; result.tx = wtx.tx; @@ -49,7 +49,7 @@ WalletTx MakeWalletTx(interfaces::Chain::Lock& locked_chain, CWallet& wallet, co wallet.IsMine(result.txout_address.back()) : ISMINE_NO); } - result.credit = wtx.GetCredit(locked_chain, ISMINE_ALL); + result.credit = wtx.GetCredit(ISMINE_ALL); result.debit = wtx.GetDebit(ISMINE_ALL); result.change = wtx.GetChange(); result.time = wtx.GetTxTime(); @@ -63,21 +63,20 @@ WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const C { WalletTxStatus result; result.block_height = locked_chain.getBlockHeight(wtx.m_confirm.hashBlock).get_value_or(std::numeric_limits<int>::max()); - result.blocks_to_maturity = wtx.GetBlocksToMaturity(locked_chain); - result.depth_in_main_chain = wtx.GetDepthInMainChain(locked_chain); + result.blocks_to_maturity = wtx.GetBlocksToMaturity(); + result.depth_in_main_chain = wtx.GetDepthInMainChain(); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_final = locked_chain.checkFinalTx(*wtx.tx); result.is_trusted = wtx.IsTrusted(locked_chain); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); - result.is_in_main_chain = wtx.IsInMainChain(locked_chain); + result.is_in_main_chain = wtx.IsInMainChain(); return result; } //! Construct wallet TxOut struct. -WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, - CWallet& wallet, +WalletTxOut MakeWalletTxOut(CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) @@ -86,7 +85,7 @@ WalletTxOut MakeWalletTxOut(interfaces::Chain::Lock& locked_chain, result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; - result.is_spent = wallet.IsSpent(locked_chain, wtx.GetHash(), n); + result.is_spent = wallet.IsSpent(wtx.GetHash(), n); return result; } @@ -170,12 +169,14 @@ public: bool addDestData(const CTxDestination& dest, const std::string& key, const std::string& value) override { LOCK(m_wallet->cs_wallet); - return m_wallet->AddDestData(dest, key, value); + WalletBatch batch{m_wallet->GetDatabase()}; + return m_wallet->AddDestData(batch, dest, key, value); } bool eraseDestData(const CTxDestination& dest, const std::string& key) override { LOCK(m_wallet->cs_wallet); - return m_wallet->EraseDestData(dest, key); + WalletBatch batch{m_wallet->GetDatabase()}; + return m_wallet->EraseDestData(batch, dest, key); } std::vector<std::string> getDestValues(const std::string& prefix) override { @@ -235,7 +236,7 @@ public: { auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); - return m_wallet->AbandonTransaction(*locked_chain, txid); + return m_wallet->AbandonTransaction(txid); } bool transactionCanBeBumped(const uint256& txid) override { @@ -282,7 +283,7 @@ public: LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { - return MakeWalletTx(*locked_chain, *m_wallet, mi->second); + return MakeWalletTx(*m_wallet, mi->second); } return {}; } @@ -293,7 +294,7 @@ public: std::vector<WalletTx> result; result.reserve(m_wallet->mapWallet.size()); for (const auto& entry : m_wallet->mapWallet) { - result.emplace_back(MakeWalletTx(*locked_chain, *m_wallet, entry.second)); + result.emplace_back(MakeWalletTx(*m_wallet, entry.second)); } return result; } @@ -338,7 +339,7 @@ public: in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(*locked_chain, mi->second); - return MakeWalletTx(*locked_chain, *m_wallet, mi->second); + return MakeWalletTx(*m_wallet, mi->second); } return {}; } @@ -407,7 +408,7 @@ public: auto& group = result[entry.first]; for (const auto& coin : entry.second) { group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), - MakeWalletTxOut(*locked_chain, *m_wallet, *coin.tx, coin.i, coin.nDepth)); + MakeWalletTxOut(*m_wallet, *coin.tx, coin.i, coin.nDepth)); } } return result; @@ -422,9 +423,9 @@ public: result.emplace_back(); auto it = m_wallet->mapWallet.find(output.hash); if (it != m_wallet->mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(*locked_chain); + int depth = it->second.GetDepthInMainChain(); if (depth >= 0) { - result.back() = MakeWalletTxOut(*locked_chain, *m_wallet, it->second, output.n, depth); + result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth); } } } diff --git a/src/logging.cpp b/src/logging.cpp index 60ab486198..9f6b5ede12 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -67,6 +67,9 @@ bool BCLog::Logger::StartLogging() if (m_print_to_file) FileWriteStr(s, m_fileout); if (m_print_to_console) fwrite(s.data(), 1, s.size(), stdout); + for (const auto& cb : m_print_callbacks) { + cb(s); + } m_msgs_before_open.pop_front(); } @@ -81,6 +84,7 @@ void BCLog::Logger::DisconnectTestLogger() m_buffering = true; if (m_fileout != nullptr) fclose(m_fileout); m_fileout = nullptr; + m_print_callbacks.clear(); } void BCLog::Logger::EnableCategory(BCLog::LogFlags flag) @@ -91,7 +95,15 @@ void BCLog::Logger::EnableCategory(BCLog::LogFlags flag) bool BCLog::Logger::EnableCategory(const std::string& str) { BCLog::LogFlags flag; - if (!GetLogCategory(flag, str)) return false; + if (!GetLogCategory(flag, str)) { + if (str == "db") { + // DEPRECATION: Added in 0.20, should start returning an error in 0.21 + LogPrintf("Warning: logging category 'db' is deprecated, use 'walletdb' instead\n"); + EnableCategory(BCLog::WALLETDB); + return true; + } + return false; + } EnableCategory(flag); return true; } @@ -135,7 +147,7 @@ const CLogCategoryDesc LogCategories[] = {BCLog::HTTP, "http"}, {BCLog::BENCH, "bench"}, {BCLog::ZMQ, "zmq"}, - {BCLog::DB, "db"}, + {BCLog::WALLETDB, "walletdb"}, {BCLog::RPC, "rpc"}, {BCLog::ESTIMATEFEE, "estimatefee"}, {BCLog::ADDRMAN, "addrman"}, @@ -270,6 +282,9 @@ void BCLog::Logger::LogPrintStr(const std::string& str) fwrite(str_prefixed.data(), 1, str_prefixed.size(), stdout); fflush(stdout); } + for (const auto& cb : m_print_callbacks) { + cb(str_prefixed); + } if (m_print_to_file) { assert(m_fileout != nullptr); diff --git a/src/logging.h b/src/logging.h index 75cd5353c0..a2caef51a8 100644 --- a/src/logging.h +++ b/src/logging.h @@ -39,7 +39,7 @@ namespace BCLog { HTTP = (1 << 3), BENCH = (1 << 4), ZMQ = (1 << 5), - DB = (1 << 6), + WALLETDB = (1 << 6), RPC = (1 << 7), ESTIMATEFEE = (1 << 8), ADDRMAN = (1 << 9), @@ -77,6 +77,9 @@ namespace BCLog { std::string LogTimestampStr(const std::string& str); + /** Slots that connect to the print signal */ + std::list<std::function<void(const std::string&)>> m_print_callbacks /* GUARDED_BY(m_cs) */ {}; + public: bool m_print_to_console = false; bool m_print_to_file = false; @@ -95,7 +98,22 @@ namespace BCLog { bool Enabled() const { std::lock_guard<std::mutex> scoped_lock(m_cs); - return m_buffering || m_print_to_console || m_print_to_file; + return m_buffering || m_print_to_console || m_print_to_file || !m_print_callbacks.empty(); + } + + /** Connect a slot to the print signal and return the connection */ + std::list<std::function<void(const std::string&)>>::iterator PushBackCallback(std::function<void(const std::string&)> fun) + { + std::lock_guard<std::mutex> scoped_lock(m_cs); + m_print_callbacks.push_back(std::move(fun)); + return --m_print_callbacks.end(); + } + + /** Delete a connection */ + void DeleteCallback(std::list<std::function<void(const std::string&)>>::iterator it) + { + std::lock_guard<std::mutex> scoped_lock(m_cs); + m_print_callbacks.erase(it); } /** Start logging (and flush all buffered messages) */ @@ -155,12 +173,13 @@ static inline void LogPrintf(const char* fmt, const Args&... args) } } -template <typename... Args> -static inline void LogPrint(const BCLog::LogFlags& category, const Args&... args) -{ - if (LogAcceptCategory((category))) { - LogPrintf(args...); - } -} +// Use a macro instead of a function for conditional logging to prevent +// evaluating arguments when logging for the category is not enabled. +#define LogPrint(category, ...) \ + do { \ + if (LogAcceptCategory((category))) { \ + LogPrintf(__VA_ARGS__); \ + } \ + } while (0) #endif // BITCOIN_LOGGING_H diff --git a/src/logging/timer.h b/src/logging/timer.h new file mode 100644 index 0000000000..34dbb942c5 --- /dev/null +++ b/src/logging/timer.h @@ -0,0 +1,104 @@ +// 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_LOGGING_TIMER_H +#define BITCOIN_LOGGING_TIMER_H + +#include <logging.h> +#include <util/macros.h> +#include <util/time.h> + +#include <chrono> +#include <string> + + +namespace BCLog { + +//! RAII-style object that outputs timing information to logs. +template <typename TimeType> +class Timer +{ +public: + //! If log_category is left as the default, end_msg will log unconditionally + //! (instead of being filtered by category). + Timer( + std::string prefix, + std::string end_msg, + BCLog::LogFlags log_category = BCLog::LogFlags::ALL) : + m_prefix(std::move(prefix)), + m_title(std::move(end_msg)), + m_log_category(log_category) + { + this->Log(strprintf("%s started", m_title)); + m_start_t = GetTime<std::chrono::microseconds>(); + } + + ~Timer() + { + this->Log(strprintf("%s completed", m_title)); + } + + void Log(const std::string& msg) + { + const std::string full_msg = this->LogMsg(msg); + + if (m_log_category == BCLog::LogFlags::ALL) { + LogPrintf("%s\n", full_msg); + } else { + LogPrint(m_log_category, "%s\n", full_msg); + } + } + + std::string LogMsg(const std::string& msg) + { + const auto end_time = GetTime<std::chrono::microseconds>() - m_start_t; + if (m_start_t.count() <= 0) { + return strprintf("%s: %s", m_prefix, msg); + } + + std::string units = ""; + float divisor = 1; + + if (std::is_same<TimeType, std::chrono::microseconds>::value) { + units = "μs"; + } else if (std::is_same<TimeType, std::chrono::milliseconds>::value) { + units = "ms"; + divisor = 1000.; + } else if (std::is_same<TimeType, std::chrono::seconds>::value) { + units = "s"; + divisor = 1000. * 1000.; + } + + const float time_ms = end_time.count() / divisor; + return strprintf("%s: %s (%.2f%s)", m_prefix, msg, time_ms, units); + } + +private: + std::chrono::microseconds m_start_t{}; + + //! Log prefix; usually the name of the function this was created in. + const std::string m_prefix{}; + + //! A descriptive message of what is being timed. + const std::string m_title{}; + + //! Forwarded on to LogPrint if specified - has the effect of only + //! outputing the timing log when a particular debug= category is specified. + const BCLog::LogFlags m_log_category{}; + +}; + +} // namespace BCLog + + +#define LOG_TIME_MICROS(end_msg, ...) \ + BCLog::Timer<std::chrono::microseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, ## __VA_ARGS__) +#define LOG_TIME_MILLIS(end_msg, ...) \ + BCLog::Timer<std::chrono::milliseconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, ## __VA_ARGS__) +#define LOG_TIME_SECONDS(end_msg, ...) \ + BCLog::Timer<std::chrono::seconds> PASTE2(logging_timer, __COUNTER__)(__func__, end_msg, ## __VA_ARGS__) + + +#endif // BITCOIN_LOGGING_TIMER_H diff --git a/src/net.cpp b/src/net.cpp index 674f2ecf24..84692d2a79 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2666,11 +2666,10 @@ CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn, int nMyStartingHeightIn addrBind(addrBindIn), fInbound(fInboundIn), nKeyedNetGroup(nKeyedNetGroupIn), - addrKnown(5000, 0.001), // Don't relay addr messages to peers that we connect to as block-relay-only // peers (to prevent adversaries from inferring these links from addr // traffic). - m_addr_relay_peer(!block_relay_only), + m_addr_known{block_relay_only ? nullptr : MakeUnique<CRollingBloomFilter>(5000, 0.001)}, id(idIn), nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn), @@ -776,13 +776,12 @@ public: // flood relay std::vector<CAddress> vAddrToSend; - CRollingBloomFilter addrKnown; + const std::unique_ptr<CRollingBloomFilter> m_addr_known; bool fGetAddr{false}; int64_t nNextAddrSend GUARDED_BY(cs_sendProcessing){0}; int64_t nNextLocalAddrSend GUARDED_BY(cs_sendProcessing){0}; - const bool m_addr_relay_peer; - bool IsAddrRelayPeer() const { return m_addr_relay_peer; } + bool IsAddrRelayPeer() const { return m_addr_known != nullptr; } // List of block ids we still have announce. // There is no final sorting before sending, as they are always sent immediately @@ -809,7 +808,7 @@ public: bool fSendMempool GUARDED_BY(cs_tx_inventory){false}; // Last time a "MEMPOOL" request was serviced. std::atomic<std::chrono::seconds> m_last_mempool_req{std::chrono::seconds{0}}; - int64_t nNextInvSend{0}; + std::chrono::microseconds nNextInvSend{0}; CCriticalSection cs_feeFilter; // Minimum fee rate with which to filter inv's to this node @@ -931,7 +930,8 @@ public: void AddAddressKnown(const CAddress& _addr) { - addrKnown.insert(_addr.GetKey()); + assert(m_addr_known); + m_addr_known->insert(_addr.GetKey()); } void PushAddress(const CAddress& _addr, FastRandomContext &insecure_rand) @@ -939,7 +939,8 @@ public: // Known checking here is only to save space from duplicates. // SendMessages will filter it again for knowns that were added // after addresses were pushed. - if (_addr.IsValid() && !addrKnown.contains(_addr.GetKey())) { + assert(m_addr_known); + if (_addr.IsValid() && !m_addr_known->contains(_addr.GetKey())) { if (vAddrToSend.size() >= MAX_ADDR_TO_SEND) { vAddrToSend[insecure_rand.randrange(vAddrToSend.size())] = _addr; } else { @@ -990,11 +991,13 @@ public: void MaybeSetAddrName(const std::string& addrNameIn); }; - - - - /** Return a timestamp in the future (in microseconds) for exponentially distributed events. */ int64_t PoissonNextSend(int64_t now, int average_interval_seconds); +/** Wrapper to return mockable type */ +inline std::chrono::microseconds PoissonNextSend(std::chrono::microseconds now, std::chrono::seconds average_interval) +{ + return std::chrono::microseconds{PoissonNextSend(now.count(), average_interval.count())}; +} + #endif // BITCOIN_NET_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index d03817834d..f42a26ca3e 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -1340,7 +1340,7 @@ static void RelayAddress(const CAddress& addr, bool fReachable, CConnman* connma // Relay to a limited number of other nodes // Use deterministic randomness to send to the same nodes for 24 hours - // at a time so the addrKnowns of the chosen nodes prevent repeats + // at a time so the m_addr_knowns of the chosen nodes prevent repeats uint64_t hashAddr = addr.GetHash(); const CSipHasher hasher = connman->GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY).Write(hashAddr << 32).Write((GetTime() + hashAddr) / (24*60*60)); FastRandomContext insecure_rand; @@ -3575,6 +3575,8 @@ bool PeerLogicValidation::SendMessages(CNode* pto) // Address refresh broadcast int64_t nNow = GetTimeMicros(); + auto current_time = GetTime<std::chrono::microseconds>(); + if (pto->IsAddrRelayPeer() && !::ChainstateActive().IsInitialBlockDownload() && pto->nNextLocalAddrSend < nNow) { AdvertiseLocal(pto); pto->nNextLocalAddrSend = PoissonNextSend(nNow, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL); @@ -3587,11 +3589,12 @@ bool PeerLogicValidation::SendMessages(CNode* pto) pto->nNextAddrSend = PoissonNextSend(nNow, AVG_ADDRESS_BROADCAST_INTERVAL); std::vector<CAddress> vAddr; vAddr.reserve(pto->vAddrToSend.size()); + assert(pto->m_addr_known); for (const CAddress& addr : pto->vAddrToSend) { - if (!pto->addrKnown.contains(addr.GetKey())) + if (!pto->m_addr_known->contains(addr.GetKey())) { - pto->addrKnown.insert(addr.GetKey()); + pto->m_addr_known->insert(addr.GetKey()); vAddr.push_back(addr); // receiver rejects addr messages larger than 1000 if (vAddr.size() >= 1000) @@ -3795,13 +3798,13 @@ bool PeerLogicValidation::SendMessages(CNode* pto) LOCK(pto->m_tx_relay->cs_tx_inventory); // Check whether periodic sends should happen bool fSendTrickle = pto->HasPermission(PF_NOBAN); - if (pto->m_tx_relay->nNextInvSend < nNow) { + if (pto->m_tx_relay->nNextInvSend < current_time) { fSendTrickle = true; if (pto->fInbound) { - pto->m_tx_relay->nNextInvSend = connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL); + pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{connman->PoissonNextSendInbound(nNow, INVENTORY_BROADCAST_INTERVAL)}; } else { // Use half the delay for outbound peers, as there is less privacy concern for them. - pto->m_tx_relay->nNextInvSend = PoissonNextSend(nNow, INVENTORY_BROADCAST_INTERVAL >> 1); + pto->m_tx_relay->nNextInvSend = PoissonNextSend(current_time, std::chrono::seconds{INVENTORY_BROADCAST_INTERVAL >> 1}); } } @@ -3916,7 +3919,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto) connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv)); // Detect whether we're stalling - const auto current_time = GetTime<std::chrono::microseconds>(); + current_time = GetTime<std::chrono::microseconds>(); // nNow is the current system time (GetTimeMicros is not mockable) and // should be replaced by the mockable current_time eventually nNow = GetTimeMicros(); diff --git a/src/net_types.h b/src/net_types.h new file mode 100644 index 0000000000..d55a8cde6c --- /dev/null +++ b/src/net_types.h @@ -0,0 +1,15 @@ +// Copyright (c) 2019 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_NET_TYPES_H +#define BITCOIN_NET_TYPES_H + +#include <map> + +class CBanEntry; +class CSubNet; + +using banmap_t = std::map<CSubNet, CBanEntry>; + +#endif // BITCOIN_NET_TYPES_H diff --git a/src/netbase.cpp b/src/netbase.cpp index 0148aea428..d1cde8c40f 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -605,7 +605,7 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg * @param nTimeout Wait this many milliseconds for the connection to be * established. * @param manual_connection Whether or not the connection was manually requested - * (e.g. thru the addnode RPC) + * (e.g. through the addnode RPC) * * @returns Whether or not a connection was successfully made. */ @@ -709,7 +709,7 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) { /** * Set the name proxy to use for all connections to nodes specified by a - * hostname. After setting this proxy, connecting to a node sepcified by a + * hostname. After setting this proxy, connecting to a node specified by a * hostname won't result in a local lookup of said hostname, rather, connect to * the node by asking the name proxy for a proxy connection to the hostname, * effectively delegating the hostname lookup to the specified proxy. diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index 57fa158ad2..a818f06d51 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -14,9 +14,6 @@ #include <map> -#include <boost/thread.hpp> - - static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) { assert(!outputs.empty()); @@ -38,6 +35,7 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, //! Calculate statistics about the unspent transaction output set bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) { + stats = CCoinsStats(); std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); assert(pcursor); @@ -51,7 +49,6 @@ bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) uint256 prevkey; std::map<uint32_t, Coin> outputs; while (pcursor->Valid()) { - boost::this_thread::interruption_point(); COutPoint key; Coin coin; if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { @@ -61,6 +58,7 @@ bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) } prevkey = key.hash; outputs[key.n] = std::move(coin); + stats.coins_count++; } else { return error("%s: unable to read value", __func__); } diff --git a/src/node/coinstats.h b/src/node/coinstats.h index 7c11aab8bd..a19af0fd1b 100644 --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -15,16 +15,17 @@ class CCoinsView; struct CCoinsStats { - int nHeight; - uint256 hashBlock; - uint64_t nTransactions; - uint64_t nTransactionOutputs; - uint64_t nBogoSize; - uint256 hashSerialized; - uint64_t nDiskSize; - CAmount nTotalAmount; - - CCoinsStats() : nHeight(0), nTransactions(0), nTransactionOutputs(0), nBogoSize(0), nDiskSize(0), nTotalAmount(0) {} + int nHeight{0}; + uint256 hashBlock{}; + uint64_t nTransactions{0}; + uint64_t nTransactionOutputs{0}; + uint64_t nBogoSize{0}; + uint256 hashSerialized{}; + uint64_t nDiskSize{0}; + CAmount nTotalAmount{0}; + + //! The number of coins contained. + uint64_t coins_count{0}; }; //! Calculate statistics about the unspent transaction output set diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 2da3ecd8e3..3c0df2b26e 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -31,7 +31,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); - // IsSpent doesnt mean the coin is spent, it means the output doesnt' exist. + // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. // So if the output does exist, then this transaction exists in the chain. if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; } diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h new file mode 100644 index 0000000000..702a0cbe53 --- /dev/null +++ b/src/node/utxo_snapshot.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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_NODE_UTXO_SNAPSHOT_H +#define BITCOIN_NODE_UTXO_SNAPSHOT_H + +#include <uint256.h> +#include <serialize.h> + +//! Metadata describing a serialized version of a UTXO set from which an +//! assumeutxo CChainState can be constructed. +class SnapshotMetadata +{ +public: + //! The hash of the block that reflects the tip of the chain for the + //! UTXO set contained in this snapshot. + uint256 m_base_blockhash; + + //! The number of coins in the UTXO set contained in this snapshot. Used + //! during snapshot load to estimate progress of UTXO set reconstruction. + uint64_t m_coins_count = 0; + + //! Necessary to "fake" the base nChainTx so that we can estimate progress during + //! initial block download for the assumeutxo chainstate. + unsigned int m_nchaintx = 0; + + SnapshotMetadata() { } + SnapshotMetadata( + const uint256& base_blockhash, + uint64_t coins_count, + unsigned int nchaintx) : + m_base_blockhash(base_blockhash), + m_coins_count(coins_count), + m_nchaintx(nchaintx) { } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_base_blockhash); + READWRITE(m_coins_count); + READWRITE(m_nchaintx); + } + +}; + +#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H diff --git a/src/noui.cpp b/src/noui.cpp index 11c8f1e13d..a5b7a2d591 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -66,28 +66,31 @@ void noui_connect() noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessage); } -bool noui_ThreadSafeMessageBoxSuppressed(const std::string& message, const std::string& caption, unsigned int style) +bool noui_ThreadSafeMessageBoxRedirect(const std::string& message, const std::string& caption, unsigned int style) { + LogPrintf("%s: %s\n", caption, message); return false; } -bool noui_ThreadSafeQuestionSuppressed(const std::string& /* ignored interactive message */, const std::string& message, const std::string& caption, unsigned int style) +bool noui_ThreadSafeQuestionRedirect(const std::string& /* ignored interactive message */, const std::string& message, const std::string& caption, unsigned int style) { + LogPrintf("%s: %s\n", caption, message); return false; } -void noui_InitMessageSuppressed(const std::string& message) +void noui_InitMessageRedirect(const std::string& message) { + LogPrintf("init message: %s\n", message); } -void noui_suppress() +void noui_test_redirect() { noui_ThreadSafeMessageBoxConn.disconnect(); noui_ThreadSafeQuestionConn.disconnect(); noui_InitMessageConn.disconnect(); - noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBoxSuppressed); - noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestionSuppressed); - noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessageSuppressed); + noui_ThreadSafeMessageBoxConn = uiInterface.ThreadSafeMessageBox_connect(noui_ThreadSafeMessageBoxRedirect); + noui_ThreadSafeQuestionConn = uiInterface.ThreadSafeQuestion_connect(noui_ThreadSafeQuestionRedirect); + noui_InitMessageConn = uiInterface.InitMessage_connect(noui_InitMessageRedirect); } void noui_reconnect() diff --git a/src/noui.h b/src/noui.h index 854aeeacca..621e9c2798 100644 --- a/src/noui.h +++ b/src/noui.h @@ -17,10 +17,10 @@ void noui_InitMessage(const std::string& message); /** Connect all bitcoind signal handlers */ void noui_connect(); -/** Suppress all bitcoind signal handlers. Used to suppress output during test runs that produce expected errors */ -void noui_suppress(); +/** Redirect all bitcoind signal handlers to LogPrintf. Used to check or suppress output during test runs that produce expected errors */ +void noui_test_redirect(); -/** Reconnects the regular Non-GUI handlers after having used noui_suppress */ +/** Reconnects the regular Non-GUI handlers after having used noui_test_redirect */ void noui_reconnect(); #endif // BITCOIN_NOUI_H diff --git a/src/psbt.h b/src/psbt.h index 9d996171bb..6a5c468058 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -401,7 +401,6 @@ struct PartiallySignedTransaction bool AddInput(const CTxIn& txin, PSBTInput& psbtin); bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout); PartiallySignedTransaction() {} - PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {} explicit PartiallySignedTransaction(const CMutableTransaction& tx); /** * Finds the UTXO for a given input index diff --git a/src/qt/bantablemodel.cpp b/src/qt/bantablemodel.cpp index ae11b80347..48201b420e 100644 --- a/src/qt/bantablemodel.cpp +++ b/src/qt/bantablemodel.cpp @@ -4,9 +4,9 @@ #include <qt/bantablemodel.h> -#include <qt/clientmodel.h> - #include <interfaces/node.h> +#include <net_types.h> // For banmap_t +#include <qt/clientmodel.h> #include <algorithm> diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 02a2a01bdd..676c15ea43 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -309,7 +309,7 @@ void BitcoinApplication::requestShutdown() // rescanning a wallet. m_node.startShutdown(); // Unsetting the client model can cause the current thread to wait for node - // to complete an operation, like wait for a RPC execution to complate. + // to complete an operation, like wait for a RPC execution to complete. window->setClientModel(nullptr); pollShutdownTimer->stop(); @@ -430,16 +430,19 @@ int GuiMain(int argc, char* argv[]) BitcoinApplication app(*node); - // Register meta types used for QMetaObject::invokeMethod - qRegisterMetaType< bool* >(); + // Register meta types used for QMetaObject::invokeMethod and Qt::QueuedConnection + qRegisterMetaType<bool*>(); #ifdef ENABLE_WALLET qRegisterMetaType<WalletModel*>(); #endif - // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) - // IMPORTANT if it is no longer a typedef use the normal variant above - qRegisterMetaType< CAmount >("CAmount"); - qRegisterMetaType< std::function<void()> >("std::function<void()>"); + // Register typedefs (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) + // IMPORTANT: if CAmount is no longer a typedef use the normal variant above (see https://doc.qt.io/qt-5/qmetatype.html#qRegisterMetaType-1) + qRegisterMetaType<CAmount>("CAmount"); + qRegisterMetaType<size_t>("size_t"); + + qRegisterMetaType<std::function<void()>>("std::function<void()>"); qRegisterMetaType<QMessageBox::Icon>("QMessageBox::Icon"); + /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: node->setupServerArgs(); diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 386d559281..7190d59240 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -797,7 +797,7 @@ <item> <widget class="QPushButton" name="buttonMinimizeFee"> <property name="toolTip"> - <string>collapse fee-settings</string> + <string>Hide transaction fee settings</string> </property> <property name="text"> <string>Hide</string> diff --git a/src/qt/networkstyle.h b/src/qt/networkstyle.h index bb12dd1b6e..1367261325 100644 --- a/src/qt/networkstyle.h +++ b/src/qt/networkstyle.h @@ -13,7 +13,7 @@ class NetworkStyle { public: - /** Get style associated with provided BIP70 network id, or 0 if not known */ + /** Get style associated with provided network id, or 0 if not known */ static const NetworkStyle* instantiate(const std::string& networkId); const QString &getAppName() const { return appName; } diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 8b32b70d1e..6e8d383847 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -1,6 +1,6 @@ #include <qt/test/addressbooktests.h> #include <qt/test/util.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <interfaces/chain.h> #include <interfaces/node.h> diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index e730c8f6d5..664826ecf2 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -11,7 +11,7 @@ #include <qt/networkstyle.h> #include <qt/rpcconsole.h> #include <shutdown.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <univalue.h> #include <validation.h> diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 1772de4c1b..971d9f4a7c 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -7,7 +7,7 @@ #include <interfaces/node.h> #include <rpc/server.h> #include <qt/rpcconsole.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <univalue.h> #include <util/system.h> diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index e6870cf1be..243c10d7da 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -12,7 +12,7 @@ #include <qt/test/rpcnestedtests.h> #include <qt/test/uritests.h> #include <qt/test/compattests.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #ifdef ENABLE_WALLET #include <qt/test/addressbooktests.h> diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 881653cdac..980de711db 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -13,7 +13,7 @@ #include <qt/transactionview.h> #include <qt/walletmodel.h> #include <key_io.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <validation.h> #include <wallet/wallet.h> #include <qt/overviewpage.h> @@ -139,10 +139,12 @@ void TestGUI(interfaces::Node& node) wallet->LoadWallet(firstRun); { auto spk_man = wallet->GetLegacyScriptPubKeyMan(); + auto locked_chain = wallet->chain().lock(); LOCK(wallet->cs_wallet); AssertLockHeld(spk_man->cs_wallet); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); + wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash()); } { auto locked_chain = wallet->chain().lock(); diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index 095c98d26f..220e41b383 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -34,14 +34,6 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo ui->setupUi(this); QString version = QString{PACKAGE_NAME} + " " + tr("version") + " " + QString::fromStdString(FormatFullVersion()); - /* On x86 add a bit specifier to the version so that users can distinguish between - * 32 and 64 bit builds. On other architectures, 32/64 bit may be more ambiguous. - */ -#if defined(__x86_64__) - version += " " + tr("(%1-bit)").arg(64); -#elif defined(__i386__ ) - version += " " + tr("(%1-bit)").arg(32); -#endif if (about) { diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index d7f0617315..4b2b475883 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -61,11 +61,6 @@ void WalletFrame::addWallet(WalletModel *walletModel) walletStack->addWidget(walletView); mapWalletViews[walletModel] = walletView; - // Ensure a walletView is able to show the main window - connect(walletView, &WalletView::showNormalIfMinimized, [this]{ - gui->showNormalIfMinimized(); - }); - connect(walletView, &WalletView::outOfSyncWarningClicked, this, &WalletFrame::outOfSyncWarningClicked); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index d21dec118a..a873519a34 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -64,8 +64,8 @@ public: CAmount amount; // If from a payment request, this is used for storing the memo QString message; - // If building with BIP70 is disabled, keep the payment request around as - // serialized string to ensure load/store is lossless + // Keep the payment request around as a serialized string to ensure + // load/store is lossless. std::string sPaymentRequest; // Empty if no authentication or invalid signature/cert/etc. QString authenticatedMerchant; diff --git a/src/qt/walletview.h b/src/qt/walletview.h index e29c4c52f5..8d5a301cdb 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -115,8 +115,6 @@ public Q_SLOTS: void requestedSyncWarningInfo(); Q_SIGNALS: - /** Signal that we want to show the main window */ - void showNormalIfMinimized(); /** Fired when a message should be reported to the user */ void message(const QString &title, const QString &message, unsigned int style); /** Encryption status of wallet changed */ diff --git a/src/random.cpp b/src/random.cpp index 48d20d7d72..3e6398f7b4 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -5,19 +5,22 @@ #include <random.h> +#include <compat/cpuid.h> #include <crypto/sha512.h> #include <support/cleanse.h> #ifdef WIN32 #include <compat.h> // for Windows API #include <wincrypt.h> #endif -#include <logging.h> // for LogPrint() -#include <sync.h> // for WAIT_LOCK +#include <logging.h> // for LogPrintf() +#include <sync.h> // for Mutex #include <util/time.h> // for GetTime() #include <stdlib.h> #include <thread> +#include <randomenv.h> + #include <support/allocators/secure.h> #ifndef WIN32 @@ -40,11 +43,6 @@ #include <sys/sysctl.h> #endif - -#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) -#include <cpuid.h> -#endif - #include <openssl/err.h> #include <openssl/rand.h> #include <openssl/conf.h> @@ -75,7 +73,7 @@ static inline int64_t GetPerformanceCounter() noexcept #endif } -#if defined(__x86_64__) || defined(__amd64__) || defined(__i386__) +#ifdef HAVE_GETCPUID static bool g_rdrand_supported = false; static bool g_rdseed_supported = false; static constexpr uint32_t CPUID_F1_ECX_RDRAND = 0x40000000; @@ -86,15 +84,6 @@ static_assert(CPUID_F1_ECX_RDRAND == bit_RDRND, "Unexpected value for bit_RDRND" #ifdef bit_RDSEED static_assert(CPUID_F7_EBX_RDSEED == bit_RDSEED, "Unexpected value for bit_RDSEED"); #endif -static void inline GetCPUID(uint32_t leaf, uint32_t subleaf, uint32_t& a, uint32_t& b, uint32_t& c, uint32_t& d) -{ - // We can't use __get_cpuid as it doesn't support subleafs. -#ifdef __GNUC__ - __cpuid_count(leaf, subleaf, a, b, c, d); -#else - __asm__ ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(leaf), "2"(subleaf)); -#endif -} static void InitHardwareRand() { @@ -263,44 +252,6 @@ static void Strengthen(const unsigned char (&seed)[32], int microseconds, CSHA51 memory_cleanse(buffer, sizeof(buffer)); } -static void RandAddSeedPerfmon(CSHA512& hasher) -{ -#ifdef WIN32 - // Don't need this on Linux, OpenSSL automatically uses /dev/urandom - // Seed with the entire set of perfmon data - - // This can take up to 2 seconds, so only do it every 10 minutes - static int64_t nLastPerfmon; - if (GetTime() < nLastPerfmon + 10 * 60) - return; - nLastPerfmon = GetTime(); - - std::vector<unsigned char> vData(250000, 0); - long ret = 0; - unsigned long nSize = 0; - const size_t nMaxSize = 10000000; // Bail out at more than 10MB of performance data - while (true) { - nSize = vData.size(); - ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", nullptr, nullptr, vData.data(), &nSize); - if (ret != ERROR_MORE_DATA || vData.size() >= nMaxSize) - break; - vData.resize(std::max((vData.size() * 3) / 2, nMaxSize)); // Grow size of buffer exponentially - } - RegCloseKey(HKEY_PERFORMANCE_DATA); - if (ret == ERROR_SUCCESS) { - hasher.Write(vData.data(), nSize); - memory_cleanse(vData.data(), nSize); - } else { - // Performance data is only a best-effort attempt at improving the - // situation when the OS randomness (and other sources) aren't - // adequate. As a result, failure to read it is isn't considered critical, - // so we don't call RandFailure(). - // TODO: Add logging when the logger is made functional before global - // constructors have been invoked. - } -#endif -} - #ifndef WIN32 /** Fallback: get 32 bytes of system entropy from /dev/urandom. The most * compatible way to get cryptographic randomness on UNIX-ish platforms. @@ -556,22 +507,16 @@ static void SeedSlow(CSHA512& hasher) noexcept } /** Extract entropy from rng, strengthen it, and feed it into hasher. */ -static void SeedStrengthen(CSHA512& hasher, RNGState& rng) noexcept +static void SeedStrengthen(CSHA512& hasher, RNGState& rng, int microseconds) noexcept { - static std::atomic<int64_t> last_strengthen{0}; - int64_t last_time = last_strengthen.load(); - int64_t current_time = GetTimeMicros(); - if (current_time > last_time + 60000000) { // Only run once a minute - // Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher. - unsigned char strengthen_seed[32]; - rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false); - // Strengthen it for 10ms (100ms on first run), and feed it into hasher. - Strengthen(strengthen_seed, last_time == 0 ? 100000 : 10000, hasher); - last_strengthen = current_time; - } + // Generate 32 bytes of entropy from the RNG, and a copy of the entropy already in hasher. + unsigned char strengthen_seed[32]; + rng.MixExtract(strengthen_seed, sizeof(strengthen_seed), CSHA512(hasher), false); + // Strengthen the seed, and feed it into hasher. + Strengthen(strengthen_seed, microseconds, hasher); } -static void SeedSleep(CSHA512& hasher, RNGState& rng) +static void SeedPeriodic(CSHA512& hasher, RNGState& rng) { // Everything that the 'fast' seeder includes SeedFast(hasher); @@ -579,17 +524,13 @@ static void SeedSleep(CSHA512& hasher, RNGState& rng) // High-precision timestamp SeedTimestamp(hasher); - // Sleep for 1ms - MilliSleep(1); - - // High-precision timestamp after sleeping (as we commit to both the time before and after, this measures the delay) - SeedTimestamp(hasher); - - // Windows performance monitor data (once every 10 minutes) - RandAddSeedPerfmon(hasher); + // Dynamic environment data (performance monitoring, ...) + auto old_size = hasher.Size(); + RandAddDynamicEnv(hasher); + LogPrintf("Feeding %i bytes of dynamic environment data into RNG\n", hasher.Size() - old_size); - // Strengthen every minute - SeedStrengthen(hasher, rng); + // Strengthen for 10 ms + SeedStrengthen(hasher, rng, 10000); } static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept @@ -600,17 +541,22 @@ static void SeedStartup(CSHA512& hasher, RNGState& rng) noexcept // Everything that the 'slow' seeder includes. SeedSlow(hasher); - // Windows performance monitor data. - RandAddSeedPerfmon(hasher); + // Dynamic environment data (performance monitoring, ...) + auto old_size = hasher.Size(); + RandAddDynamicEnv(hasher); + + // Static environment data + RandAddStaticEnv(hasher); + LogPrintf("Feeding %i bytes of environment data into RNG\n", hasher.Size() - old_size); - // Strengthen - SeedStrengthen(hasher, rng); + // Strengthen for 100 ms + SeedStrengthen(hasher, rng, 100000); } enum class RNGLevel { FAST, //!< Automatically called by GetRandBytes SLOW, //!< Automatically called by GetStrongRandBytes - SLEEP, //!< Called by RandAddSeedSleep() + PERIODIC, //!< Called by RandAddPeriodic() }; static void ProcRand(unsigned char* out, int num, RNGLevel level) @@ -628,8 +574,8 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) case RNGLevel::SLOW: SeedSlow(hasher); break; - case RNGLevel::SLEEP: - SeedSleep(hasher, rng); + case RNGLevel::PERIODIC: + SeedPeriodic(hasher, rng); break; } @@ -652,7 +598,7 @@ static void ProcRand(unsigned char* out, int num, RNGLevel level) void GetRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::FAST); } void GetStrongRandBytes(unsigned char* buf, int num) noexcept { ProcRand(buf, num, RNGLevel::SLOW); } -void RandAddSeedSleep() { ProcRand(nullptr, 0, RNGLevel::SLEEP); } +void RandAddPeriodic() { ProcRand(nullptr, 0, RNGLevel::PERIODIC); } bool g_mock_deterministic_tests{false}; @@ -716,7 +662,7 @@ bool Random_SanityCheck() uint64_t start = GetPerformanceCounter(); /* This does not measure the quality of randomness, but it does test that - * OSRandom() overwrites all 32 bytes of the output given a maximum + * GetOSRand() overwrites all 32 bytes of the output given a maximum * number of tries. */ static const ssize_t MAX_TRIES = 1024; diff --git a/src/random.h b/src/random.h index 22801ec155..7768f9d3c5 100644 --- a/src/random.h +++ b/src/random.h @@ -52,7 +52,6 @@ * sources used in the 'slow' seeder are included, but also: * - 256 bits from the hardware RNG (rdseed or rdrand) when available. * - (On Windows) Performance monitoring data from the OS. - * - (On Windows) Through OpenSSL, the screen contents. * - Strengthen the entropy for 100 ms using repeated SHA512. * * When mixing in new entropy, H = SHA512(entropy || old_rng_state) is computed, and @@ -85,11 +84,11 @@ uint256 GetRandHash() noexcept; void GetStrongRandBytes(unsigned char* buf, int num) noexcept; /** - * Sleep for 1ms, gather entropy from various sources, and feed them to the PRNG state. + * Gather entropy from various expensive sources, and feed them to the PRNG state. * * Thread-safe. */ -void RandAddSeedSleep(); +void RandAddPeriodic(); /** * Fast randomness source. This is seeded once with secure random data, but @@ -166,6 +165,7 @@ public: /** Generate a random integer in the range [0..range). */ uint64_t randrange(uint64_t range) noexcept { + assert(range); --range; int bits = CountBits(range); while (true) { diff --git a/src/randomenv.cpp b/src/randomenv.cpp new file mode 100644 index 0000000000..603c88eaab --- /dev/null +++ b/src/randomenv.cpp @@ -0,0 +1,508 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <randomenv.h> + +#include <clientversion.h> +#include <compat/cpuid.h> +#include <crypto/sha512.h> +#include <support/cleanse.h> +#include <util/time.h> // for GetTime() +#ifdef WIN32 +#include <compat.h> // for Windows API +#endif + +#include <algorithm> +#include <atomic> +#include <chrono> +#include <climits> +#include <thread> +#include <vector> + +#include <stdint.h> +#include <string.h> +#ifndef WIN32 +#include <sys/types.h> // must go before a number of other headers +#include <fcntl.h> +#include <netinet/in.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/utsname.h> +#include <unistd.h> +#endif +#ifdef __MACH__ +#include <mach/clock.h> +#include <mach/mach.h> +#include <mach/mach_time.h> +#endif +#if HAVE_DECL_GETIFADDRS +#include <ifaddrs.h> +#endif +#if HAVE_SYSCTL +#include <sys/sysctl.h> +#if HAVE_VM_VM_PARAM_H +#include <vm/vm_param.h> +#endif +#if HAVE_SYS_RESOURCES_H +#include <sys/resources.h> +#endif +#if HAVE_SYS_VMMETER_H +#include <sys/vmmeter.h> +#endif +#endif +#ifdef __linux__ +#include <sys/auxv.h> +#endif + +//! Necessary on some platforms +extern char** environ; + +namespace { + +void RandAddSeedPerfmon(CSHA512& hasher) +{ +#ifdef WIN32 + // Don't need this on Linux, OpenSSL automatically uses /dev/urandom + // Seed with the entire set of perfmon data + + // This can take up to 2 seconds, so only do it every 10 minutes + static std::atomic<std::chrono::seconds> last_perfmon{std::chrono::seconds{0}}; + auto last_time = last_perfmon.load(); + auto current_time = GetTime<std::chrono::seconds>(); + if (current_time < last_time + std::chrono::minutes{10}) return; + last_perfmon = current_time; + + std::vector<unsigned char> vData(250000, 0); + long ret = 0; + unsigned long nSize = 0; + const size_t nMaxSize = 10000000; // Bail out at more than 10MB of performance data + while (true) { + nSize = vData.size(); + ret = RegQueryValueExA(HKEY_PERFORMANCE_DATA, "Global", nullptr, nullptr, vData.data(), &nSize); + if (ret != ERROR_MORE_DATA || vData.size() >= nMaxSize) + break; + vData.resize(std::max((vData.size() * 3) / 2, nMaxSize)); // Grow size of buffer exponentially + } + RegCloseKey(HKEY_PERFORMANCE_DATA); + if (ret == ERROR_SUCCESS) { + hasher.Write(vData.data(), nSize); + memory_cleanse(vData.data(), nSize); + } else { + // Performance data is only a best-effort attempt at improving the + // situation when the OS randomness (and other sources) aren't + // adequate. As a result, failure to read it is isn't considered critical, + // so we don't call RandFailure(). + // TODO: Add logging when the logger is made functional before global + // constructors have been invoked. + } +#endif +} + +/** Helper to easily feed data into a CSHA512. + * + * Note that this does not serialize the passed object (like stream.h's << operators do). + * Its raw memory representation is used directly. + */ +template<typename T> +CSHA512& operator<<(CSHA512& hasher, const T& data) { + static_assert(!std::is_same<typename std::decay<T>::type, char*>::value, "Calling operator<<(CSHA512, char*) is probably not what you want"); + static_assert(!std::is_same<typename std::decay<T>::type, unsigned char*>::value, "Calling operator<<(CSHA512, unsigned char*) is probably not what you want"); + static_assert(!std::is_same<typename std::decay<T>::type, const char*>::value, "Calling operator<<(CSHA512, const char*) is probably not what you want"); + static_assert(!std::is_same<typename std::decay<T>::type, const unsigned char*>::value, "Calling operator<<(CSHA512, const unsigned char*) is probably not what you want"); + hasher.Write((const unsigned char*)&data, sizeof(data)); + return hasher; +} + +#ifndef WIN32 +void AddSockaddr(CSHA512& hasher, const struct sockaddr *addr) +{ + if (addr == nullptr) return; + switch (addr->sa_family) { + case AF_INET: + hasher.Write((const unsigned char*)addr, sizeof(sockaddr_in)); + break; + case AF_INET6: + hasher.Write((const unsigned char*)addr, sizeof(sockaddr_in6)); + break; + default: + hasher.Write((const unsigned char*)&addr->sa_family, sizeof(addr->sa_family)); + } +} + +void AddFile(CSHA512& hasher, const char *path) +{ + struct stat sb = {}; + int f = open(path, O_RDONLY); + size_t total = 0; + if (f != -1) { + unsigned char fbuf[4096]; + int n; + hasher.Write((const unsigned char*)&f, sizeof(f)); + if (fstat(f, &sb) == 0) hasher << sb; + do { + n = read(f, fbuf, sizeof(fbuf)); + if (n > 0) hasher.Write(fbuf, n); + total += n; + /* not bothering with EINTR handling. */ + } while (n == sizeof(fbuf) && total < 1048576); // Read only the first 1 Mbyte + close(f); + } +} + +void AddPath(CSHA512& hasher, const char *path) +{ + struct stat sb = {}; + if (stat(path, &sb) == 0) { + hasher.Write((const unsigned char*)path, strlen(path) + 1); + hasher << sb; + } +} +#endif + +#if HAVE_SYSCTL +template<int... S> +void AddSysctl(CSHA512& hasher) +{ + int CTL[sizeof...(S)] = {S...}; + unsigned char buffer[65536]; + size_t siz = 65536; + int ret = sysctl(CTL, sizeof...(S), buffer, &siz, nullptr, 0); + if (ret == 0 || (ret == -1 && errno == ENOMEM)) { + hasher << sizeof(CTL); + hasher.Write((const unsigned char*)CTL, sizeof(CTL)); + if (siz > sizeof(buffer)) siz = sizeof(buffer); + hasher << siz; + hasher.Write(buffer, siz); + } +} +#endif + +#ifdef HAVE_GETCPUID +void inline AddCPUID(CSHA512& hasher, uint32_t leaf, uint32_t subleaf, uint32_t& ax, uint32_t& bx, uint32_t& cx, uint32_t& dx) +{ + GetCPUID(leaf, subleaf, ax, bx, cx, dx); + hasher << leaf << subleaf << ax << bx << cx << dx; +} + +void AddAllCPUID(CSHA512& hasher) +{ + uint32_t ax, bx, cx, dx; + // Iterate over all standard leaves + AddCPUID(hasher, 0, 0, ax, bx, cx, dx); // Returns max leaf in ax + uint32_t max = ax; + for (uint32_t leaf = 1; leaf <= max; ++leaf) { + for (uint32_t subleaf = 0;; ++subleaf) { + AddCPUID(hasher, leaf, subleaf, ax, bx, cx, dx); + // Iterate over subleaves for leaf 4, 11, 13 + if (leaf != 4 && leaf != 11 && leaf != 13) break; + if ((leaf == 4 || leaf == 13) && ax == 0) break; + if (leaf == 11 && (cx & 0xFF00) == 0) break; + } + } + // Iterate over all extended leaves + AddCPUID(hasher, 0x80000000, 0, ax, bx, cx, dx); // Returns max extended leaf in ax + uint32_t ext_max = ax; + for (uint32_t leaf = 0x80000001; leaf <= ext_max; ++leaf) { + AddCPUID(hasher, leaf, 0, ax, bx, cx, dx); + } +} +#endif +} // namespace + +void RandAddDynamicEnv(CSHA512& hasher) +{ + RandAddSeedPerfmon(hasher); + + // Various clocks +#ifdef WIN32 + FILETIME ftime; + GetSystemTimeAsFileTime(&ftime); + hasher << ftime; +#else +# ifndef __MACH__ + // On non-MacOS systems, use various clock_gettime() calls. + struct timespec ts = {}; +# ifdef CLOCK_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, &ts); + hasher << ts; +# endif +# ifdef CLOCK_REALTIME + clock_gettime(CLOCK_REALTIME, &ts); + hasher << ts; +# endif +# ifdef CLOCK_BOOTTIME + clock_gettime(CLOCK_BOOTTIME, &ts); + hasher << ts; +# endif +# else + // On MacOS use mach_absolute_time (number of CPU ticks since boot) as a replacement for CLOCK_MONOTONIC, + // and clock_get_time for CALENDAR_CLOCK as a replacement for CLOCK_REALTIME. + hasher << mach_absolute_time(); + // From https://gist.github.com/jbenet/1087739 + clock_serv_t cclock; + mach_timespec_t mts = {}; + if (host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock) == KERN_SUCCESS && clock_get_time(cclock, &mts) == KERN_SUCCESS) { + hasher << mts; + mach_port_deallocate(mach_task_self(), cclock); + } +# endif + // gettimeofday is available on all UNIX systems, but only has microsecond precision. + struct timeval tv = {}; + gettimeofday(&tv, nullptr); + hasher << tv; +#endif + // Probably redundant, but also use all the clocks C++11 provides: + hasher << std::chrono::system_clock::now().time_since_epoch().count(); + hasher << std::chrono::steady_clock::now().time_since_epoch().count(); + hasher << std::chrono::high_resolution_clock::now().time_since_epoch().count(); + +#ifndef WIN32 + // Current resource usage. + struct rusage usage = {}; + if (getrusage(RUSAGE_SELF, &usage) == 0) hasher << usage; +#endif + +#ifdef __linux__ + AddFile(hasher, "/proc/diskstats"); + AddFile(hasher, "/proc/vmstat"); + AddFile(hasher, "/proc/schedstat"); + AddFile(hasher, "/proc/zoneinfo"); + AddFile(hasher, "/proc/meminfo"); + AddFile(hasher, "/proc/softirqs"); + AddFile(hasher, "/proc/stat"); + AddFile(hasher, "/proc/self/schedstat"); + AddFile(hasher, "/proc/self/status"); +#endif + +#if HAVE_SYSCTL +# ifdef CTL_KERN +# if defined(KERN_PROC) && defined(KERN_PROC_ALL) + AddSysctl<CTL_KERN, KERN_PROC, KERN_PROC_ALL>(hasher); +# endif +# endif +# ifdef CTL_HW +# ifdef HW_DISKSTATS + AddSysctl<CTL_HW, HW_DISKSTATS>(hasher); +# endif +# endif +# ifdef CTL_VM +# ifdef VM_LOADAVG + AddSysctl<CTL_VM, VM_LOADAVG>(hasher); +# endif +# ifdef VM_TOTAL + AddSysctl<CTL_VM, VM_TOTAL>(hasher); +# endif +# ifdef VM_METER + AddSysctl<CTL_VM, VM_METER>(hasher); +# endif +# endif +#endif + + // Stack and heap location + void* addr = malloc(4097); + hasher << &addr << addr; + free(addr); +} + +void RandAddStaticEnv(CSHA512& hasher) +{ + // Some compile-time static properties + hasher << (CHAR_MIN < 0) << sizeof(void*) << sizeof(long) << sizeof(int); +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) + hasher << __GNUC__ << __GNUC_MINOR__ << __GNUC_PATCHLEVEL__; +#endif +#ifdef _MSC_VER + hasher << _MSC_VER; +#endif + hasher << __cplusplus; +#ifdef _XOPEN_VERSION + hasher << _XOPEN_VERSION; +#endif +#ifdef __VERSION__ + const char* COMPILER_VERSION = __VERSION__; + hasher.Write((const unsigned char*)COMPILER_VERSION, strlen(COMPILER_VERSION) + 1); +#endif + + // Bitcoin client version + hasher << CLIENT_VERSION; + +#ifdef __linux__ + // Information available through getauxval() +# ifdef AT_HWCAP + hasher << getauxval(AT_HWCAP); +# endif +# ifdef AT_HWCAP2 + hasher << getauxval(AT_HWCAP2); +# endif +# ifdef AT_RANDOM + const unsigned char* random_aux = (const unsigned char*)getauxval(AT_RANDOM); + if (random_aux) hasher.Write(random_aux, 16); +# endif +# ifdef AT_PLATFORM + const char* platform_str = (const char*)getauxval(AT_PLATFORM); + if (platform_str) hasher.Write((const unsigned char*)platform_str, strlen(platform_str) + 1); +# endif +# ifdef AT_EXECFN + const char* exec_str = (const char*)getauxval(AT_EXECFN); + if (exec_str) hasher.Write((const unsigned char*)exec_str, strlen(exec_str) + 1); +# endif +#endif // __linux__ + +#ifdef HAVE_GETCPUID + AddAllCPUID(hasher); +#endif + + // Memory locations + hasher << &hasher << &RandAddStaticEnv << &malloc << &errno << &environ; + + // Hostname + char hname[256]; + if (gethostname(hname, 256) == 0) { + hasher.Write((const unsigned char*)hname, strnlen(hname, 256)); + } + +#if HAVE_DECL_GETIFADDRS + // Network interfaces + struct ifaddrs *ifad = NULL; + getifaddrs(&ifad); + struct ifaddrs *ifit = ifad; + while (ifit != NULL) { + hasher.Write((const unsigned char*)&ifit, sizeof(ifit)); + hasher.Write((const unsigned char*)ifit->ifa_name, strlen(ifit->ifa_name) + 1); + hasher.Write((const unsigned char*)&ifit->ifa_flags, sizeof(ifit->ifa_flags)); + AddSockaddr(hasher, ifit->ifa_addr); + AddSockaddr(hasher, ifit->ifa_netmask); + AddSockaddr(hasher, ifit->ifa_dstaddr); + ifit = ifit->ifa_next; + } + freeifaddrs(ifad); +#endif + +#ifndef WIN32 + // UNIX kernel information + struct utsname name; + if (uname(&name) != -1) { + hasher.Write((const unsigned char*)&name.sysname, strlen(name.sysname) + 1); + hasher.Write((const unsigned char*)&name.nodename, strlen(name.nodename) + 1); + hasher.Write((const unsigned char*)&name.release, strlen(name.release) + 1); + hasher.Write((const unsigned char*)&name.version, strlen(name.version) + 1); + hasher.Write((const unsigned char*)&name.machine, strlen(name.machine) + 1); + } + + /* Path and filesystem provided data */ + AddPath(hasher, "/"); + AddPath(hasher, "."); + AddPath(hasher, "/tmp"); + AddPath(hasher, "/home"); + AddPath(hasher, "/proc"); +#ifdef __linux__ + AddFile(hasher, "/proc/cmdline"); + AddFile(hasher, "/proc/cpuinfo"); + AddFile(hasher, "/proc/version"); +#endif + AddFile(hasher, "/etc/passwd"); + AddFile(hasher, "/etc/group"); + AddFile(hasher, "/etc/hosts"); + AddFile(hasher, "/etc/resolv.conf"); + AddFile(hasher, "/etc/timezone"); + AddFile(hasher, "/etc/localtime"); +#endif + + // For MacOS/BSDs, gather data through sysctl instead of /proc. Not all of these + // will exist on every system. +#if HAVE_SYSCTL +# ifdef CTL_HW +# ifdef HW_MACHINE + AddSysctl<CTL_HW, HW_MACHINE>(hasher); +# endif +# ifdef HW_MODEL + AddSysctl<CTL_HW, HW_MODEL>(hasher); +# endif +# ifdef HW_NCPU + AddSysctl<CTL_HW, HW_NCPU>(hasher); +# endif +# ifdef HW_PHYSMEM + AddSysctl<CTL_HW, HW_PHYSMEM>(hasher); +# endif +# ifdef HW_USERMEM + AddSysctl<CTL_HW, HW_USERMEM>(hasher); +# endif +# ifdef HW_MACHINE_ARCH + AddSysctl<CTL_HW, HW_MACHINE_ARCH>(hasher); +# endif +# ifdef HW_REALMEM + AddSysctl<CTL_HW, HW_REALMEM>(hasher); +# endif +# ifdef HW_CPU_FREQ + AddSysctl<CTL_HW, HW_CPU_FREQ>(hasher); +# endif +# ifdef HW_BUS_FREQ + AddSysctl<CTL_HW, HW_BUS_FREQ>(hasher); +# endif +# ifdef HW_CACHELINE + AddSysctl<CTL_HW, HW_CACHELINE>(hasher); +# endif +# endif +# ifdef CTL_KERN +# ifdef KERN_BOOTFILE + AddSysctl<CTL_KERN, KERN_BOOTFILE>(hasher); +# endif +# ifdef KERN_BOOTTIME + AddSysctl<CTL_KERN, KERN_BOOTTIME>(hasher); +# endif +# ifdef KERN_CLOCKRATE + AddSysctl<CTL_KERN, KERN_CLOCKRATE>(hasher); +# endif +# ifdef KERN_HOSTID + AddSysctl<CTL_KERN, KERN_HOSTID>(hasher); +# endif +# ifdef KERN_HOSTUUID + AddSysctl<CTL_KERN, KERN_HOSTUUID>(hasher); +# endif +# ifdef KERN_HOSTNAME + AddSysctl<CTL_KERN, KERN_HOSTNAME>(hasher); +# endif +# ifdef KERN_OSRELDATE + AddSysctl<CTL_KERN, KERN_OSRELDATE>(hasher); +# endif +# ifdef KERN_OSRELEASE + AddSysctl<CTL_KERN, KERN_OSRELEASE>(hasher); +# endif +# ifdef KERN_OSREV + AddSysctl<CTL_KERN, KERN_OSREV>(hasher); +# endif +# ifdef KERN_OSTYPE + AddSysctl<CTL_KERN, KERN_OSTYPE>(hasher); +# endif +# ifdef KERN_POSIX1 + AddSysctl<CTL_KERN, KERN_OSREV>(hasher); +# endif +# ifdef KERN_VERSION + AddSysctl<CTL_KERN, KERN_VERSION>(hasher); +# endif +# endif +#endif + + // Env variables + if (environ) { + for (size_t i = 0; environ[i]; ++i) { + hasher.Write((const unsigned char*)environ[i], strlen(environ[i])); + } + } + + // Process, thread, user, session, group, ... ids. +#ifdef WIN32 + hasher << GetCurrentProcessId() << GetCurrentThreadId(); +#else + hasher << getpid() << getppid() << getsid(0) << getpgid(0) << getuid() << geteuid() << getgid() << getegid(); +#endif + hasher << std::this_thread::get_id(); +} diff --git a/src/randomenv.h b/src/randomenv.h new file mode 100644 index 0000000000..46cea6f6f2 --- /dev/null +++ b/src/randomenv.h @@ -0,0 +1,17 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2019 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_RANDOMENV_H +#define BITCOIN_RANDOMENV_H + +#include <crypto/sha512.h> + +/** Gather non-cryptographic environment data that changes over time. */ +void RandAddDynamicEnv(CSHA512& hasher); + +/** Gather non-cryptographic environment data that does not change over time. */ +void RandAddStaticEnv(CSHA512& hasher); + +#endif diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 4ca8225392..2f4b4412f5 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -15,6 +15,7 @@ #include <hash.h> #include <index/blockfilterindex.h> #include <node/coinstats.h> +#include <node/utxo_snapshot.h> #include <policy/feerate.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -38,8 +39,6 @@ #include <univalue.h> -#include <boost/thread/thread.hpp> // boost::thread::interrupt - #include <condition_variable> #include <memory> #include <mutex> @@ -58,7 +57,7 @@ static CUpdatedBlock latestblock; */ double GetDifficulty(const CBlockIndex* blockindex) { - assert(blockindex); + CHECK_NONFATAL(blockindex); int nShift = (blockindex->nBits >> 24) & 0xff; double dDiff = @@ -957,7 +956,7 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) PruneBlockFilesManual(height); const CBlockIndex* block = ::ChainActive().Tip(); - assert(block); + CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { block = block->pprev; } @@ -1191,7 +1190,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) {}, RPCResult{ "{\n" - " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" + " \"chain\": \"xxxx\", (string) current network name (main, test, regtest)\n" " \"blocks\": xxxxxx, (numeric) the height of the most-work fully-validated chain. The genesis block has height 0\n" " \"headers\": xxxxxx, (numeric) the current number of headers we have validated\n" " \"bestblockhash\": \"...\", (string) the hash of the currently best block\n" @@ -1252,7 +1251,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) obj.pushKV("pruned", fPruneMode); if (fPruneMode) { const CBlockIndex* block = tip; - assert(block); + CHECK_NONFATAL(block); while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { block = block->pprev; } @@ -1598,7 +1597,7 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) } } - assert(pindex != nullptr); + CHECK_NONFATAL(pindex != nullptr); if (request.params[0].isNull()) { blockcount = std::max(0, std::min(blockcount, pindex->nHeight - 1)); @@ -1771,7 +1770,7 @@ static UniValue getblockstats(const JSONRPCRequest& request) } } - assert(pindex != nullptr); + CHECK_NONFATAL(pindex != nullptr); std::set<std::string> stats; if (!request.params[1].isNull()) { @@ -1871,7 +1870,7 @@ static UniValue getblockstats(const JSONRPCRequest& request) } CAmount txfee = tx_total_in - tx_total_out; - assert(MoneyRange(txfee)); + CHECK_NONFATAL(MoneyRange(txfee)); if (do_medianfee) { fee_array.push_back(txfee); } @@ -1975,7 +1974,6 @@ bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& 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; @@ -2008,7 +2006,7 @@ public: explicit CoinsViewScanReserver() : m_could_reserve(false) {} bool reserve() { - assert (!m_could_reserve); + CHECK_NONFATAL(!m_could_reserve); std::lock_guard<std::mutex> lock(g_utxosetscan); if (g_scan_in_progress) { return false; @@ -2135,9 +2133,9 @@ UniValue scantxoutset(const JSONRPCRequest& request) LOCK(cs_main); ::ChainstateActive().ForceFlushStateToDisk(); pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); - assert(pcursor); + CHECK_NONFATAL(pcursor); tip = ::ChainActive().Tip(); - assert(tip); + CHECK_NONFATAL(tip); } bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins); result.pushKV("success", res); @@ -2245,6 +2243,113 @@ static UniValue getblockfilter(const JSONRPCRequest& request) return ret; } +/** + * Serialize the UTXO set to a file for loading elsewhere. + * + * @see SnapshotMetadata + */ +UniValue dumptxoutset(const JSONRPCRequest& request) +{ + RPCHelpMan{ + "dumptxoutset", + "\nWrite the serialized UTXO set to disk.\n" + "Incidentally flushes the latest coinsdb (leveldb) to disk.\n", + { + {"path", + RPCArg::Type::STR, + RPCArg::Optional::NO, + /* default_val */ "", + "path to the output file. If relative, will be prefixed by datadir."}, + }, + RPCResult{ + "{\n" + " \"coins_written\": n, (numeric) the number of coins written in the snapshot\n" + " \"base_hash\": \"...\", (string) the hash of the base of the snapshot\n" + " \"base_height\": n, (string) the height of the base of the snapshot\n" + " \"path\": \"...\" (string) the absolute path that the snapshot was written to\n" + "]\n" + }, + RPCExamples{ + HelpExampleCli("dumptxoutset", "utxo.dat") + } + }.Check(request); + + fs::path path = fs::absolute(request.params[0].get_str(), GetDataDir()); + // Write to a temporary path and then move into `path` on completion + // to avoid confusion due to an interruption. + fs::path temppath = fs::absolute(request.params[0].get_str() + ".incomplete", GetDataDir()); + + if (fs::exists(path)) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + path.string() + " already exists. If you are sure this is what you want, " + "move it out of the way first"); + } + + FILE* file{fsbridge::fopen(temppath, "wb")}; + CAutoFile afile{file, SER_DISK, CLIENT_VERSION}; + std::unique_ptr<CCoinsViewCursor> pcursor; + CCoinsStats stats; + CBlockIndex* tip; + + { + // We need to lock cs_main to ensure that the coinsdb isn't written to + // between (i) flushing coins cache to disk (coinsdb), (ii) getting stats + // based upon the coinsdb, and (iii) constructing a cursor to the + // coinsdb for use below this block. + // + // Cursors returned by leveldb iterate over snapshots, so the contents + // of the pcursor will not be affected by simultaneous writes during + // use below this block. + // + // See discussion here: + // https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369 + // + LOCK(::cs_main); + + ::ChainstateActive().ForceFlushStateToDisk(); + + if (!GetUTXOStats(&::ChainstateActive().CoinsDB(), stats)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); + } + + pcursor = std::unique_ptr<CCoinsViewCursor>(::ChainstateActive().CoinsDB().Cursor()); + tip = LookupBlockIndex(stats.hashBlock); + CHECK_NONFATAL(tip); + } + + SnapshotMetadata metadata{tip->GetBlockHash(), stats.coins_count, tip->nChainTx}; + + afile << metadata; + + COutPoint key; + Coin coin; + unsigned int iter{0}; + + while (pcursor->Valid()) { + if (iter % 5000 == 0 && !IsRPCRunning()) { + throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); + } + ++iter; + if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { + afile << key; + afile << coin; + } + + pcursor->Next(); + } + + afile.fclose(); + fs::rename(temppath, path); + + UniValue result(UniValue::VOBJ); + result.pushKV("coins_written", stats.coins_count); + result.pushKV("base_hash", tip->GetBlockHash().ToString()); + result.pushKV("base_height", tip->nHeight); + result.pushKV("path", path.string()); + return result; +} + // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames @@ -2281,6 +2386,7 @@ static const CRPCCommand commands[] = { "hidden", "waitforblock", &waitforblock, {"blockhash","timeout"} }, { "hidden", "waitforblockheight", &waitforblockheight, {"height","timeout"} }, { "hidden", "syncwithvalidationinterfacequeue", &syncwithvalidationinterfacequeue, {} }, + { "hidden", "dumptxoutset", &dumptxoutset, {"path"} }, }; // clang-format on diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index b3158d1e0c..ab22155651 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -233,7 +233,7 @@ static UniValue getmininginfo(const JSONRPCRequest& request) " \"difficulty\": xxx.xxxxx (numeric) The current difficulty\n" " \"networkhashps\": nnn, (numeric) The network hashes per second\n" " \"pooledtx\": n (numeric) The size of the mempool\n" - " \"chain\": \"xxxx\", (string) current network name as defined in BIP70 (main, test, regtest)\n" + " \"chain\": \"xxxx\", (string) current network name (main, test, regtest)\n" " \"warnings\": \"...\" (string) any network and blockchain warnings\n" "}\n" }, @@ -555,7 +555,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) // Need to update only after we know CreateNewBlock succeeded pindexPrev = pindexPrevNew; } - assert(pindexPrev); + CHECK_NONFATAL(pindexPrev); CBlock* pblock = &pblocktemplate->block; // pointer for convenience const Consensus::Params& consensusParams = Params().GetConsensus(); @@ -597,7 +597,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) entry.pushKV("fee", pblocktemplate->vTxFees[index_in_template]); int64_t nTxSigOps = pblocktemplate->vTxSigOpsCost[index_in_template]; if (fPreSegWit) { - assert(nTxSigOps % WITNESS_SCALE_FACTOR == 0); + CHECK_NONFATAL(nTxSigOps % WITNESS_SCALE_FACTOR == 0); nTxSigOps /= WITNESS_SCALE_FACTOR; } entry.pushKV("sigops", nTxSigOps); @@ -686,9 +686,9 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) int64_t nSigOpLimit = MAX_BLOCK_SIGOPS_COST; int64_t nSizeLimit = MAX_BLOCK_SERIALIZED_SIZE; if (fPreSegWit) { - assert(nSigOpLimit % WITNESS_SCALE_FACTOR == 0); + CHECK_NONFATAL(nSigOpLimit % WITNESS_SCALE_FACTOR == 0); nSigOpLimit /= WITNESS_SCALE_FACTOR; - assert(nSizeLimit % WITNESS_SCALE_FACTOR == 0); + CHECK_NONFATAL(nSizeLimit % WITNESS_SCALE_FACTOR == 0); nSizeLimit /= WITNESS_SCALE_FACTOR; } result.pushKV("sigoplimit", nSigOpLimit); diff --git a/src/rpc/net.cpp b/src/rpc/net.cpp index f443f37c6d..f1dcc9b607 100644 --- a/src/rpc/net.cpp +++ b/src/rpc/net.cpp @@ -8,8 +8,9 @@ #include <clientversion.h> #include <core_io.h> #include <net.h> -#include <net_processing.h> #include <net_permissions.h> +#include <net_processing.h> +#include <net_types.h> // For banmap_t #include <netbase.h> #include <node/context.h> #include <policy/settings.h> diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 17380f113f..983f251d6b 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1620,7 +1620,7 @@ UniValue joinpsbts(const JSONRPCRequest& request) std::vector<int> output_indices(merged_psbt.outputs.size()); std::iota(output_indices.begin(), output_indices.end(), 0); - // Shuffle input and output indicies lists + // Shuffle input and output indices lists Shuffle(input_indices.begin(), input_indices.end(), FastRandomContext()); Shuffle(output_indices.begin(), output_indices.end(), FastRandomContext()); diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 5b92650764..1936998ff3 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -29,7 +29,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const SigningProvider* keysto * Parse a prevtxs UniValue array and get the map of coins from it * * @param prevTxs Array of previous txns outputs that tx depends on but may not yet be in the block chain - * @param keystore A pointer to the temprorary keystore if there is one + * @param keystore A pointer to the temporary keystore if there is one * @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call */ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 653b287e97..cfa3509c65 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -428,7 +428,7 @@ RPCHelpMan::RPCHelpMan(std::string name, std::string description, std::vector<RP std::set<std::string> named_args; for (const auto& arg : m_args) { // Should have unique named arguments - assert(named_args.insert(arg.m_name).second); + CHECK_NONFATAL(named_args.insert(arg.m_name).second); } } @@ -620,11 +620,11 @@ std::string RPCArg::ToStringObj(const bool oneline) const case Type::OBJ: case Type::OBJ_USER_KEYS: // Currently unused, so avoid writing dead code - assert(false); + CHECK_NONFATAL(false); // no default case, so the compiler can warn about missing cases } - assert(false); + CHECK_NONFATAL(false); } std::string RPCArg::ToString(const bool oneline) const @@ -661,7 +661,7 @@ std::string RPCArg::ToString(const bool oneline) const // no default case, so the compiler can warn about missing cases } - assert(false); + CHECK_NONFATAL(false); } static std::pair<int64_t, int64_t> ParseRange(const UniValue& value) diff --git a/src/scheduler.cpp b/src/scheduler.cpp index fdc859b3a0..07a54335ac 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -41,8 +41,6 @@ void CScheduler::serviceQueue() try { if (!shouldStop() && taskQueue.empty()) { reverse_lock<boost::unique_lock<boost::mutex> > rlock(lock); - // Use this chance to get more entropy - RandAddSeedSleep(); } while (!shouldStop() && taskQueue.empty()) { // Wait until there is something to do. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 4b27ef0ca9..13cdd6c61a 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -36,7 +36,7 @@ namespace { // xpubs use other characters too, but already have their own checksum // mechanism. // * Function names like "multi()" use other characters, but mistakes in -// these would generally result in an unparseable descriptor. +// these would generally result in an unparsable descriptor. // * A case error always counts as 1 symbol error. // * Any other 1 character substitution error counts as 1 or 2 symbol errors. // * Any 1 symbol error is always detected. diff --git a/src/test/README.md b/src/test/README.md index 96dcb072bc..731720f654 100644 --- a/src/test/README.md +++ b/src/test/README.md @@ -7,8 +7,8 @@ configure some other framework (we want as few impediments to creating unit tests as possible). The build system is set up to compile an executable called `test_bitcoin` -that runs all of the unit tests. The main source file is called -`setup_common.cpp`. +that runs all of the unit tests. The main source file for the test library is found in +`util/setup_common.cpp`. ### Compiling/running unit tests diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index da0abd495a..c034216bc1 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <addrman.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <string> #include <boost/test/unit_test.hpp> diff --git a/src/test/allocator_tests.cpp b/src/test/allocator_tests.cpp index e333763f27..d33d668a04 100644 --- a/src/test/allocator_tests.cpp +++ b/src/test/allocator_tests.cpp @@ -5,7 +5,7 @@ #include <util/memory.h> #include <util/system.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <memory> diff --git a/src/test/amount_tests.cpp b/src/test/amount_tests.cpp index 378fe285d5..e20900ed13 100644 --- a/src/test/amount_tests.cpp +++ b/src/test/amount_tests.cpp @@ -4,7 +4,7 @@ #include <amount.h> #include <policy/feerate.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 9ac87261b6..3723a48903 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -11,7 +11,7 @@ #include <uint256.h> #include <arith_uint256.h> #include <string> -#include <test/setup_common.h> +#include <test/util/setup_common.h> BOOST_FIXTURE_TEST_SUITE(arith_uint256_tests, BasicTestingSetup) diff --git a/src/test/base32_tests.cpp b/src/test/base32_tests.cpp index b3bed2434c..bd6ece935b 100644 --- a/src/test/base32_tests.cpp +++ b/src/test/base32_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index cb376cddb6..52301f799a 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -5,7 +5,7 @@ #include <test/data/base58_encode_decode.json.h> #include <base58.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/strencodings.h> #include <univalue.h> diff --git a/src/test/base64_tests.cpp b/src/test/base64_tests.cpp index 9ffffb0b7d..a5fed55504 100644 --- a/src/test/base64_tests.cpp +++ b/src/test/base64_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/bech32_tests.cpp b/src/test/bech32_tests.cpp index 0ba492c24e..a2098f4f56 100644 --- a/src/test/bech32_tests.cpp +++ b/src/test/bech32_tests.cpp @@ -3,25 +3,13 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <bech32.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> +#include <test/util/str.h> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(bech32_tests, BasicTestingSetup) -static bool CaseInsensitiveEqual(const std::string &s1, const std::string &s2) -{ - if (s1.size() != s2.size()) return false; - for (size_t i = 0; i < s1.size(); ++i) { - char c1 = s1[i]; - if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a'); - char c2 = s2[i]; - if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a'); - if (c1 != c2) return false; - } - return true; -} - BOOST_AUTO_TEST_CASE(bip173_testvectors_valid) { static const std::string CASES[] = { diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index e46cf624cf..53df032252 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -9,7 +9,7 @@ #include <key_io.h> #include <streams.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <string> #include <vector> diff --git a/src/test/blockchain_tests.cpp b/src/test/blockchain_tests.cpp index ca75563ef0..29935c09f8 100644 --- a/src/test/blockchain_tests.cpp +++ b/src/test/blockchain_tests.cpp @@ -4,7 +4,7 @@ #include <chain.h> #include <rpc/blockchain.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> /* Equality between doubles is imprecise. Comparison should be done * with a small threshold of tolerance, rather than exact equality. diff --git a/src/test/blockencodings_tests.cpp b/src/test/blockencodings_tests.cpp index 5ce8e6feb0..8694891a51 100644 --- a/src/test/blockencodings_tests.cpp +++ b/src/test/blockencodings_tests.cpp @@ -3,22 +3,18 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <blockencodings.h> -#include <consensus/merkle.h> #include <chainparams.h> +#include <consensus/merkle.h> #include <pow.h> #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> std::vector<std::pair<uint256, CTransactionRef>> extra_txn; -struct RegtestingSetup : public TestingSetup { - RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} -}; - -BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegtestingSetup) +BOOST_FIXTURE_TEST_SUITE(blockencodings_tests, RegTestingSetup) static CBlock BuildBlockTestCase() { CBlock block; diff --git a/src/test/blockfilter_index_tests.cpp b/src/test/blockfilter_index_tests.cpp index 4a15bf0c77..2b616f4c2b 100644 --- a/src/test/blockfilter_index_tests.cpp +++ b/src/test/blockfilter_index_tests.cpp @@ -8,8 +8,9 @@ #include <index/blockfilterindex.h> #include <miner.h> #include <pow.h> -#include <test/setup_common.h> #include <script/standard.h> +#include <test/util/blockfilter.h> +#include <test/util/setup_common.h> #include <util/time.h> #include <validation.h> @@ -17,23 +18,6 @@ BOOST_AUTO_TEST_SUITE(blockfilter_index_tests) -static bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, - BlockFilter& filter) -{ - CBlock block; - if (!ReadBlockFromDisk(block, block_index->GetBlockPos(), Params().GetConsensus())) { - return false; - } - - CBlockUndo block_undo; - if (block_index->nHeight > 0 && !UndoReadFromDisk(block_undo, block_index)) { - return false; - } - - filter = BlockFilter(filter_type, block, block_undo); - return true; -} - static bool CheckFilterLookups(BlockFilterIndex& filter_index, const CBlockIndex* block_index, uint256& last_header) { diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index df0a041e0e..e69503ef35 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <test/data/blockfilters.json.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <blockfilter.h> #include <core_io.h> diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index 4421494007..4a7ad9b38b 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -15,7 +15,7 @@ #include <uint256.h> #include <util/system.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> @@ -461,7 +461,7 @@ static std::vector<unsigned char> RandomData() BOOST_AUTO_TEST_CASE(rolling_bloom) { - SeedInsecureRand(/* deterministic */ true); + SeedInsecureRand(SeedRand::ZEROS); g_mock_deterministic_tests = true; // last-100-entry, 1% false positive: diff --git a/src/test/bswap_tests.cpp b/src/test/bswap_tests.cpp index 8fd4e5d5d6..d5e2344a8b 100644 --- a/src/test/bswap_tests.cpp +++ b/src/test/bswap_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <compat/byteswap.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/checkqueue_tests.cpp b/src/test/checkqueue_tests.cpp index d796444419..6745bb9015 100644 --- a/src/test/checkqueue_tests.cpp +++ b/src/test/checkqueue_tests.cpp @@ -5,9 +5,8 @@ #include <util/memory.h> #include <util/system.h> #include <util/time.h> -#include <validation.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <checkqueue.h> #include <boost/test/unit_test.hpp> #include <boost/thread.hpp> @@ -19,11 +18,10 @@ #include <unordered_set> -// BasicTestingSetup not sufficient because nScriptCheckThreads is not set -// otherwise. BOOST_FIXTURE_TEST_SUITE(checkqueue_tests, TestingSetup) static const unsigned int QUEUE_BATCH_SIZE = 128; +static const int SCRIPT_CHECK_THREADS = 3; struct FakeCheck { bool operator()() @@ -149,7 +147,7 @@ static void Correct_Queue_range(std::vector<size_t> range) { auto small_queue = MakeUnique<Correct_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{small_queue->Thread();}); } // Make vChecks here to save on malloc (this test can be slow...) @@ -214,7 +212,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Catches_Failure) auto fail_queue = MakeUnique<Failing_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{fail_queue->Thread();}); } @@ -246,7 +244,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Recovers_From_Failure) { auto fail_queue = MakeUnique<Failing_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{fail_queue->Thread();}); } @@ -274,7 +272,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_UniqueCheck) { auto queue = MakeUnique<Unique_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{queue->Thread();}); } @@ -310,7 +308,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_Memory) { auto queue = MakeUnique<Memory_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{queue->Thread();}); } for (size_t i = 0; i < 1000; ++i) { @@ -342,7 +340,7 @@ BOOST_AUTO_TEST_CASE(test_CheckQueue_FrozenCleanup) auto queue = MakeUnique<FrozenCleanup_Queue>(QUEUE_BATCH_SIZE); boost::thread_group tg; bool fails = false; - for (auto x = 0; x < nScriptCheckThreads; ++x) { + for (auto x = 0; x < SCRIPT_CHECK_THREADS; ++x) { tg.create_thread([&]{queue->Thread();}); } std::thread t0([&]() { diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 948591196c..436c1bffa0 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -7,7 +7,7 @@ #include <coins.h> #include <script/standard.h> #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <uint256.h> #include <undo.h> #include <util/strencodings.h> @@ -279,7 +279,7 @@ UtxoData::iterator FindRandomFrom(const std::set<COutPoint> &utxoSet) { // has the expected effect (the other duplicate is overwritten at all cache levels) BOOST_AUTO_TEST_CASE(updatecoins_simulation_test) { - SeedInsecureRand(/* deterministic */ true); + SeedInsecureRand(SeedRand::ZEROS); g_mock_deterministic_tests = true; bool spent_a_duplicate_coinbase = false; diff --git a/src/test/compilerbug_tests.cpp b/src/test/compilerbug_tests.cpp index 74e1eac3ea..1a6fcda009 100644 --- a/src/test/compilerbug_tests.cpp +++ b/src/test/compilerbug_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> BOOST_FIXTURE_TEST_SUITE(compilerbug_tests, BasicTestingSetup) diff --git a/src/test/compress_tests.cpp b/src/test/compress_tests.cpp index c6a08b293f..22eae91cf0 100644 --- a/src/test/compress_tests.cpp +++ b/src/test/compress_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <compressor.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <script/standard.h> #include <stdint.h> diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 4ac12bf969..591a317d17 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -15,7 +15,7 @@ #include <crypto/sha512.h> #include <random.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> diff --git a/src/test/cuckoocache_tests.cpp b/src/test/cuckoocache_tests.cpp index a3017da3e7..119d4b3295 100644 --- a/src/test/cuckoocache_tests.cpp +++ b/src/test/cuckoocache_tests.cpp @@ -4,7 +4,7 @@ #include <boost/test/unit_test.hpp> #include <cuckoocache.h> #include <script/sigcache.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <random.h> #include <thread> @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_SUITE(cuckoocache_tests); */ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) { - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); CuckooCache::cache<uint256, SignatureCacheHasher> cc{}; size_t megabytes = 4; cc.setup_bytes(megabytes << 20); @@ -47,7 +47,7 @@ BOOST_AUTO_TEST_CASE(test_cuckoocache_no_fakes) template <typename Cache> static double test_cache(size_t megabytes, double load) { - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -118,7 +118,7 @@ template <typename Cache> static void test_cache_erase(size_t megabytes) { double load = 1; - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -181,7 +181,7 @@ template <typename Cache> static void test_cache_erase_parallel(size_t megabytes) { double load = 1; - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); std::vector<uint256> hashes; Cache set{}; size_t bytes = megabytes * (1 << 20); @@ -285,7 +285,7 @@ static void test_cache_generations() // iterations with non-deterministic values, so it isn't "overfit" to the // specific entropy in FastRandomContext(true) and implementation of the // cache. - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); // block_activity models a chunk of network activity. n_insert elements are // added to the cache. The first and last n/4 are stored for removal later diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp index 2ffe4dccdb..57d5b2bb5c 100644 --- a/src/test/dbwrapper_tests.cpp +++ b/src/test/dbwrapper_tests.cpp @@ -4,7 +4,7 @@ #include <dbwrapper.h> #include <uint256.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/memory.h> #include <memory> diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index 1928324b27..632151793e 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -17,7 +17,7 @@ #include <util/time.h> #include <validation.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <stdint.h> diff --git a/src/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index 55726a4a8f..bcce8854e3 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -6,7 +6,7 @@ #include <string> #include <script/sign.h> #include <script/standard.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <script/descriptor.h> #include <util/strencodings.h> diff --git a/src/test/flatfile_tests.cpp b/src/test/flatfile_tests.cpp index 740d805cce..9bb0b3ef02 100644 --- a/src/test/flatfile_tests.cpp +++ b/src/test/flatfile_tests.cpp @@ -5,7 +5,7 @@ #include <clientversion.h> #include <flatfile.h> #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp index b504a3cbb1..d02c3613ba 100644 --- a/src/test/fs_tests.cpp +++ b/src/test/fs_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. // #include <fs.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/fuzz/bech32.cpp b/src/test/fuzz/bech32.cpp new file mode 100644 index 0000000000..8b91f9bc96 --- /dev/null +++ b/src/test/fuzz/bech32.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <bech32.h> +#include <test/fuzz/fuzz.h> +#include <test/util/str.h> +#include <util/strencodings.h> + +#include <cassert> +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + const std::string random_string(buffer.begin(), buffer.end()); + const std::pair<std::string, std::vector<uint8_t>> r1 = bech32::Decode(random_string); + if (r1.first.empty()) { + assert(r1.second.empty()); + } else { + const std::string& hrp = r1.first; + const std::vector<uint8_t>& data = r1.second; + const std::string reencoded = bech32::Encode(hrp, data); + assert(CaseInsensitiveEqual(random_string, reencoded)); + } + + std::vector<unsigned char> input; + ConvertBits<8, 5, true>([&](unsigned char c) { input.push_back(c); }, buffer.begin(), buffer.end()); + const std::string encoded = bech32::Encode("bc", input); + assert(!encoded.empty()); + + const std::pair<std::string, std::vector<uint8_t>> r2 = bech32::Decode(encoded); + if (r2.first.empty()) { + assert(r2.second.empty()); + } else { + const std::string& hrp = r2.first; + const std::vector<uint8_t>& data = r2.second; + assert(hrp == "bc"); + assert(data == input); + } +} diff --git a/src/test/fuzz/eval_script.cpp b/src/test/fuzz/eval_script.cpp index 9444cd489e..7acdd76857 100644 --- a/src/test/fuzz/eval_script.cpp +++ b/src/test/fuzz/eval_script.cpp @@ -2,12 +2,19 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <pubkey.h> #include <script/interpreter.h> -#include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <util/memory.h> #include <limits> +void initialize() +{ + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); +} + void test_one_input(const std::vector<uint8_t>& buffer) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); diff --git a/src/test/fuzz/script_flags.cpp b/src/test/fuzz/script_flags.cpp index 0bf5cd5c72..08622d0979 100644 --- a/src/test/fuzz/script_flags.cpp +++ b/src/test/fuzz/script_flags.cpp @@ -2,8 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <pubkey.h> #include <script/interpreter.h> #include <streams.h> +#include <util/memory.h> #include <version.h> #include <test/fuzz/fuzz.h> @@ -11,6 +13,11 @@ /** Flags that are not forbidden by an assert */ static bool IsValidFlagCombination(unsigned flags); +void initialize() +{ + static const auto verify_handle = MakeUnique<ECCVerifyHandle>(); +} + void test_one_input(const std::vector<uint8_t>& buffer) { CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); diff --git a/src/test/getarg_tests.cpp b/src/test/getarg_tests.cpp index 77304fe918..5abd1087ec 100644 --- a/src/test/getarg_tests.cpp +++ b/src/test/getarg_tests.cpp @@ -4,7 +4,7 @@ #include <util/strencodings.h> #include <util/system.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <string> #include <utility> diff --git a/src/test/hash_tests.cpp b/src/test/hash_tests.cpp index d91fcb0034..b864e6e599 100644 --- a/src/test/hash_tests.cpp +++ b/src/test/hash_tests.cpp @@ -6,7 +6,7 @@ #include <crypto/siphash.h> #include <hash.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index e924f27d1b..b52513f4af 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -9,7 +9,7 @@ #include <key_io.h> #include <script/script.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/key_properties.cpp b/src/test/key_properties.cpp index 95587130fc..0e45a2549d 100644 --- a/src/test/key_properties.cpp +++ b/src/test/key_properties.cpp @@ -4,7 +4,7 @@ #include <key.h> #include <uint256.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> #include <boost/test/unit_test.hpp> diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 3e99dcaa40..85dc961bea 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -8,7 +8,7 @@ #include <uint256.h> #include <util/system.h> #include <util/strencodings.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <string> #include <vector> diff --git a/src/test/limitedmap_tests.cpp b/src/test/limitedmap_tests.cpp index 00b36f51fb..ea18debbd3 100644 --- a/src/test/limitedmap_tests.cpp +++ b/src/test/limitedmap_tests.cpp @@ -4,7 +4,7 @@ #include <limitedmap.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp new file mode 100644 index 0000000000..25655b8894 --- /dev/null +++ b/src/test/logging_tests.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <logging.h> +#include <logging/timer.h> +#include <test/util/setup_common.h> + +#include <chrono> + +#include <boost/test/unit_test.hpp> + +BOOST_FIXTURE_TEST_SUITE(logging_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(logging_timer) +{ + + SetMockTime(1); + auto sec_timer = BCLog::Timer<std::chrono::seconds>("tests", "end_msg"); + SetMockTime(2); + BOOST_CHECK_EQUAL(sec_timer.LogMsg("test secs"), "tests: test secs (1.00s)"); + + SetMockTime(1); + auto ms_timer = BCLog::Timer<std::chrono::milliseconds>("tests", "end_msg"); + SetMockTime(2); + BOOST_CHECK_EQUAL(ms_timer.LogMsg("test ms"), "tests: test ms (1000.00ms)"); + + SetMockTime(1); + auto micro_timer = BCLog::Timer<std::chrono::microseconds>("tests", "end_msg"); + SetMockTime(2); + BOOST_CHECK_EQUAL(micro_timer.LogMsg("test micros"), "tests: test micros (1000000.00μs)"); + + SetMockTime(0); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp index fe5d31b7d3..38fed51af2 100644 --- a/src/test/mempool_tests.cpp +++ b/src/test/mempool_tests.cpp @@ -7,7 +7,7 @@ #include <util/system.h> #include <util/time.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <vector> @@ -749,6 +749,43 @@ BOOST_AUTO_TEST_CASE(MempoolAncestryTests) pool.GetTransactionAncestry(ty6->GetHash(), ancestors, descendants); BOOST_CHECK_EQUAL(ancestors, 9ULL); BOOST_CHECK_EQUAL(descendants, 6ULL); + + /* Ancestors represented more than once ("diamond") */ + // + // [ta].0 <- [tb].0 -----<------- [td].0 + // | | + // \---1 <- [tc].0 --<--/ + // + CTransactionRef ta, tb, tc, td; + ta = make_tx(/* output_values */ {10 * COIN}); + tb = make_tx(/* output_values */ {5 * COIN, 3 * COIN}, /* inputs */ {ta}); + tc = make_tx(/* output_values */ {2 * COIN}, /* inputs */ {tb}, /* input_indices */ {1}); + td = make_tx(/* output_values */ {6 * COIN}, /* inputs */ {tb, tc}, /* input_indices */ {0, 0}); + pool.clear(); + pool.addUnchecked(entry.Fee(10000LL).FromTx(ta)); + pool.addUnchecked(entry.Fee(10000LL).FromTx(tb)); + pool.addUnchecked(entry.Fee(10000LL).FromTx(tc)); + pool.addUnchecked(entry.Fee(10000LL).FromTx(td)); + + // Ancestors / descendants should be: + // transaction ancestors descendants + // ============ =================== =========== + // ta 1 (ta 4 (ta,tb,tc,td) + // tb 2 (ta,tb) 4 (ta,tb,tc,td) + // tc 3 (ta,tb,tc) 4 (ta,tb,tc,td) + // td 4 (ta,tb,tc,td) 4 (ta,tb,tc,td) + pool.GetTransactionAncestry(ta->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 1ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(tb->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 2ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(tc->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 3ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); + pool.GetTransactionAncestry(td->GetHash(), ancestors, descendants); + BOOST_CHECK_EQUAL(ancestors, 4ULL); + BOOST_CHECK_EQUAL(descendants, 4ULL); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/merkle_tests.cpp b/src/test/merkle_tests.cpp index dc38a1a818..03dce552fc 100644 --- a/src/test/merkle_tests.cpp +++ b/src/test/merkle_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/merkle.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> @@ -345,8 +345,8 @@ BOOST_AUTO_TEST_CASE(merkle_test_BlockWitness) hashes[0].SetNull(); hashes[1] = block.vtx[1]->GetHash(); - uint256 merkelRootofHashes = ComputeMerkleRoot(hashes); + uint256 merkleRootofHashes = ComputeMerkleRoot(hashes); - BOOST_CHECK_EQUAL(merkelRootofHashes, blockWitness); + BOOST_CHECK_EQUAL(merkleRootofHashes, blockWitness); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/merkleblock_tests.cpp b/src/test/merkleblock_tests.cpp index eac43471c7..9f8c4ba5c5 100644 --- a/src/test/merkleblock_tests.cpp +++ b/src/test/merkleblock_tests.cpp @@ -4,7 +4,7 @@ #include <merkleblock.h> #include <uint256.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index c9661b730d..6ed7350ea2 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -17,7 +17,7 @@ #include <util/time.h> #include <validation.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <memory> diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 7c60abb93f..97a918da45 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -11,7 +11,7 @@ #include <script/signingprovider.h> #include <tinyformat.h> #include <uint256.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/net_tests.cpp b/src/test/net_tests.cpp index fed65afdbf..daf7fea6ad 100644 --- a/src/test/net_tests.cpp +++ b/src/test/net_tests.cpp @@ -5,7 +5,7 @@ #include <addrdb.h> #include <addrman.h> #include <clientversion.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <string> #include <boost/test/unit_test.hpp> #include <serialize.h> @@ -128,9 +128,8 @@ BOOST_AUTO_TEST_CASE(caddrdb_read) CDataStream ssPeers2 = AddrmanToStream(addrmanUncorrupted); CAddrMan addrman2; - CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(adb.Read(addrman2, ssPeers2)); + BOOST_CHECK(CAddrDB::Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 3); } @@ -160,9 +159,8 @@ BOOST_AUTO_TEST_CASE(caddrdb_read_corrupted) CDataStream ssPeers2 = AddrmanToStream(addrmanCorrupted); CAddrMan addrman2; - CAddrDB adb; BOOST_CHECK(addrman2.size() == 0); - BOOST_CHECK(!adb.Read(addrman2, ssPeers2)); + BOOST_CHECK(!CAddrDB::Read(addrman2, ssPeers2)); BOOST_CHECK(addrman2.size() == 0); } @@ -303,5 +301,19 @@ BOOST_AUTO_TEST_CASE(LocalAddress_BasicLifecycle) BOOST_CHECK_EQUAL(IsLocal(addr), false); } +BOOST_AUTO_TEST_CASE(PoissonNextSend) +{ + g_mock_deterministic_tests = true; + + int64_t now = 5000; + int average_interval_seconds = 600; + + auto poisson = ::PoissonNextSend(now, average_interval_seconds); + std::chrono::microseconds poisson_chrono = ::PoissonNextSend(std::chrono::microseconds{now}, std::chrono::seconds{average_interval_seconds}); + + BOOST_CHECK_EQUAL(poisson, poisson_chrono.count()); + + g_mock_deterministic_tests = false; +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/netbase_tests.cpp b/src/test/netbase_tests.cpp index a3d0831624..78c11ff202 100644 --- a/src/test/netbase_tests.cpp +++ b/src/test/netbase_tests.cpp @@ -4,7 +4,7 @@ #include <netbase.h> #include <net_permissions.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/strencodings.h> #include <string> diff --git a/src/test/pmt_tests.cpp b/src/test/pmt_tests.cpp index c5513ae9fa..bf58bd63b9 100644 --- a/src/test/pmt_tests.cpp +++ b/src/test/pmt_tests.cpp @@ -9,7 +9,7 @@ #include <uint256.h> #include <arith_uint256.h> #include <version.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> @@ -30,7 +30,6 @@ BOOST_FIXTURE_TEST_SUITE(pmt_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(pmt_test1) { - SeedInsecureRand(false); static const unsigned int nTxCounts[] = {1, 4, 7, 17, 56, 100, 127, 256, 312, 513, 1000, 4095}; for (int i = 0; i < 12; i++) { diff --git a/src/test/policyestimator_tests.cpp b/src/test/policyestimator_tests.cpp index 5368f82ffe..025e2b78ca 100644 --- a/src/test/policyestimator_tests.cpp +++ b/src/test/policyestimator_tests.cpp @@ -8,7 +8,7 @@ #include <uint256.h> #include <util/time.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 6c99021d97..0f9872f434 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -5,7 +5,7 @@ #include <chain.h> #include <chainparams.h> #include <pow.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/prevector_tests.cpp b/src/test/prevector_tests.cpp index fc1f946bba..9782b78f2c 100644 --- a/src/test/prevector_tests.cpp +++ b/src/test/prevector_tests.cpp @@ -9,7 +9,7 @@ #include <serialize.h> #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/raii_event_tests.cpp b/src/test/raii_event_tests.cpp index 41ca8029e5..04bf7c20c1 100644 --- a/src/test/raii_event_tests.cpp +++ b/src/test/raii_event_tests.cpp @@ -12,7 +12,7 @@ #include <support/events.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/random_tests.cpp b/src/test/random_tests.cpp index e6fbe2355d..e0df41a971 100644 --- a/src/test/random_tests.cpp +++ b/src/test/random_tests.cpp @@ -4,7 +4,7 @@ #include <random.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/reverselock_tests.cpp b/src/test/reverselock_tests.cpp index 69db9dcf4e..532fe143ae 100644 --- a/src/test/reverselock_tests.cpp +++ b/src/test/reverselock_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <reverselock.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index faff1931cd..52dd22de7e 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -9,7 +9,7 @@ #include <core_io.h> #include <interfaces/chain.h> #include <node/context.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/time.h> #include <boost/algorithm/string.hpp> diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 891aa8e5c3..4d50845256 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -4,7 +4,7 @@ #include <compat/sanity.h> #include <key.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/scheduler_tests.cpp b/src/test/scheduler_tests.cpp index 42242b962b..b292d5b0d0 100644 --- a/src/test/scheduler_tests.cpp +++ b/src/test/scheduler_tests.cpp @@ -5,7 +5,7 @@ #include <random.h> #include <scheduler.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/thread.hpp> #include <boost/test/unit_test.hpp> diff --git a/src/test/script_p2sh_tests.cpp b/src/test/script_p2sh_tests.cpp index f451d80984..8c1e843b0b 100644 --- a/src/test/script_p2sh_tests.cpp +++ b/src/test/script_p2sh_tests.cpp @@ -11,7 +11,7 @@ #include <policy/settings.h> #include <script/sign.h> #include <script/signingprovider.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> @@ -209,20 +209,21 @@ BOOST_AUTO_TEST_CASE(is) p2sh << OP_HASH160 << ToByteVector(dummy) << OP_EQUAL; BOOST_CHECK(p2sh.IsPayToScriptHash()); - // Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes: std::vector<unsigned char> direct = {OP_HASH160, 20}; direct.insert(direct.end(), 20, 0); direct.push_back(OP_EQUAL); BOOST_CHECK(CScript(direct.begin(), direct.end()).IsPayToScriptHash()); + + // Not considered pay-to-script-hash if using one of the OP_PUSHDATA opcodes: std::vector<unsigned char> pushdata1 = {OP_HASH160, OP_PUSHDATA1, 20}; pushdata1.insert(pushdata1.end(), 20, 0); pushdata1.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata1.begin(), pushdata1.end()).IsPayToScriptHash()); - std::vector<unsigned char> pushdata2 = {OP_HASH160, 20, 0}; + std::vector<unsigned char> pushdata2 = {OP_HASH160, OP_PUSHDATA2, 20, 0}; pushdata2.insert(pushdata2.end(), 20, 0); pushdata2.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata2.begin(), pushdata2.end()).IsPayToScriptHash()); - std::vector<unsigned char> pushdata4 = {OP_HASH160, 20, 0, 0, 0}; + std::vector<unsigned char> pushdata4 = {OP_HASH160, OP_PUSHDATA4, 20, 0, 0, 0}; pushdata4.insert(pushdata4.end(), 20, 0); pushdata4.push_back(OP_EQUAL); BOOST_CHECK(!CScript(pushdata4.begin(), pushdata4.end()).IsPayToScriptHash()); diff --git a/src/test/script_standard_tests.cpp b/src/test/script_standard_tests.cpp index 412a57dd9d..de990d9254 100644 --- a/src/test/script_standard_tests.cpp +++ b/src/test/script_standard_tests.cpp @@ -6,7 +6,7 @@ #include <script/script.h> #include <script/signingprovider.h> #include <script/standard.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index caa99805c3..26015ca4c2 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -12,8 +12,8 @@ #include <script/signingprovider.h> #include <util/system.h> #include <util/strencodings.h> -#include <test/lib/transaction_utils.h> -#include <test/setup_common.h> +#include <test/util/transaction_utils.h> +#include <test/util/setup_common.h> #include <rpc/util.h> #include <streams.h> diff --git a/src/test/scriptnum_tests.cpp b/src/test/scriptnum_tests.cpp index e7916f5000..40a6f69668 100644 --- a/src/test/scriptnum_tests.cpp +++ b/src/test/scriptnum_tests.cpp @@ -4,7 +4,7 @@ #include <test/scriptnum10.h> #include <script/script.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <limits.h> diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index b90be15fba..303bb9b88c 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -5,7 +5,7 @@ #include <serialize.h> #include <streams.h> #include <hash.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/strencodings.h> #include <stdint.h> diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp new file mode 100644 index 0000000000..b0ee76ea6b --- /dev/null +++ b/src/test/settings_tests.cpp @@ -0,0 +1,163 @@ +// Copyright (c) 2011-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/settings.h> + +#include <test/util.h> +#include <test/util/setup_common.h> + +#include <boost/test/unit_test.hpp> +#include <univalue.h> +#include <util/strencodings.h> +#include <vector> + +BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup) + +//! Check settings struct contents against expected json strings. +static void CheckValues(const util::Settings& settings, const std::string& single_val, const std::string& list_val) +{ + util::SettingsValue single_value = GetSetting(settings, "section", "name", false, false); + util::SettingsValue list_value(util::SettingsValue::VARR); + for (const auto& item : GetSettingsList(settings, "section", "name", false)) { + list_value.push_back(item); + } + BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val); + BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val); +}; + +// Simple settings merge test case. +BOOST_AUTO_TEST_CASE(Simple) +{ + util::Settings settings; + settings.command_line_options["name"].push_back("val1"); + settings.command_line_options["name"].push_back("val2"); + settings.ro_config["section"]["name"].push_back(2); + + // The last given arg takes precedence when specified via commandline. + CheckValues(settings, R"("val2")", R"(["val1","val2",2])"); + + util::Settings settings2; + settings2.ro_config["section"]["name"].push_back("val2"); + settings2.ro_config["section"]["name"].push_back("val3"); + + // The first given arg takes precedence when specified via config file. + CheckValues(settings2, R"("val2")", R"(["val2","val3"])"); +} + +// Test different ways settings can be merged, and verify results. This test can +// be used to confirm that updates to settings code don't change behavior +// unintentionally. +struct MergeTestingSetup : public BasicTestingSetup { + //! Max number of actions to sequence together. Can decrease this when + //! debugging to make test results easier to understand. + static constexpr int MAX_ACTIONS = 3; + + enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; + using ActionList = Action[MAX_ACTIONS]; + + //! Enumerate all possible test configurations. + template <typename Fn> + void ForEachMergeSetup(Fn&& fn) + { + ActionList arg_actions = {}; + // command_line_options do not have sections. Only iterate over SET and NEGATE + ForEachNoDup(arg_actions, SET, NEGATE, [&]{ + ActionList conf_actions = {}; + ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&]{ + for (bool force_set : {false, true}) { + for (bool ignore_default_section_config : {false, true}) { + fn(arg_actions, conf_actions, force_set, ignore_default_section_config); + } + } + }); + }); + } +}; + +// Regression test covering different ways config settings can be merged. The +// test parses and merges settings, representing the results as strings that get +// compared against an expected hash. To debug, the result strings can be dumped +// to a file (see comments below). +BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) +{ + CHash256 out_sha; + FILE* out_file = nullptr; + if (const char* out_path = getenv("SETTINGS_MERGE_TEST_OUT")) { + out_file = fsbridge::fopen(out_path, "w"); + if (!out_file) throw std::system_error(errno, std::generic_category(), "fopen failed"); + } + + const std::string& network = CBaseChainParams::MAIN; + ForEachMergeSetup([&](const ActionList& arg_actions, const ActionList& conf_actions, bool force_set, + bool ignore_default_section_config) { + std::string desc; + int value_suffix = 0; + util::Settings settings; + + const std::string& name = ignore_default_section_config ? "wallet" : "server"; + auto push_values = [&](Action action, const char* value_prefix, const std::string& name_prefix, + std::vector<util::SettingsValue>& dest) { + if (action == SET || action == SECTION_SET) { + for (int i = 0; i < 2; ++i) { + dest.push_back(value_prefix + std::to_string(++value_suffix)); + desc += " " + name_prefix + name + "=" + dest.back().get_str(); + } + } else if (action == NEGATE || action == SECTION_NEGATE) { + dest.push_back(false); + desc += " " + name_prefix + "no" + name; + } + }; + + if (force_set) { + settings.forced_settings[name] = "forced"; + desc += " " + name + "=forced"; + } + for (Action arg_action : arg_actions) { + push_values(arg_action, "a", "-", settings.command_line_options[name]); + } + for (Action conf_action : conf_actions) { + bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE; + push_values(conf_action, "c", use_section ? network + "." : "", + settings.ro_config[use_section ? network : ""][name]); + } + + desc += " || "; + desc += GetSetting(settings, network, name, ignore_default_section_config, /* get_chain_name= */ false).write(); + desc += " |"; + for (const auto& s : GetSettingsList(settings, network, name, ignore_default_section_config)) { + desc += " "; + desc += s.write(); + } + desc += " |"; + if (OnlyHasDefaultSectionSetting(settings, network, name)) desc += " ignored"; + desc += "\n"; + + out_sha.Write((const unsigned char*)desc.data(), desc.size()); + if (out_file) { + BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); + } + }); + + if (out_file) { + if (fclose(out_file)) throw std::system_error(errno, std::generic_category(), "fclose failed"); + out_file = nullptr; + } + + unsigned char out_sha_bytes[CSHA256::OUTPUT_SIZE]; + out_sha.Finalize(out_sha_bytes); + std::string out_sha_hex = HexStr(std::begin(out_sha_bytes), std::end(out_sha_bytes)); + + // If check below fails, should manually dump the results with: + // + // SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin --run_test=settings_tests/Merge + // + // And verify diff against previous results to make sure the changes are expected. + // + // Results file is formatted like: + // + // <input> || GetSetting() | GetSettingsList() | OnlyHasDefaultSectionSetting() + BOOST_CHECK_EQUAL(out_sha_hex, "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index b18f9df72d..2c56bbdbb0 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -10,7 +10,7 @@ #include <script/script.h> #include <serialize.h> #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <util/strencodings.h> #include <version.h> @@ -119,8 +119,6 @@ BOOST_FIXTURE_TEST_SUITE(sighash_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(sighash_test) { - SeedInsecureRand(false); - #if defined(PRINT_SIGHASH_JSON) std::cout << "[\n"; std::cout << "\t[\"raw_transaction, script, input_index, hashType, signature_hash (result)\"],\n"; diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index a32f2cda92..6462fcefe3 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -9,7 +9,7 @@ #include <script/script.h> #include <script/standard.h> #include <uint256.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> diff --git a/src/test/skiplist_tests.cpp b/src/test/skiplist_tests.cpp index 1cba3a1297..7ede79279f 100644 --- a/src/test/skiplist_tests.cpp +++ b/src/test/skiplist_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <chain.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <vector> diff --git a/src/test/streams_tests.cpp b/src/test/streams_tests.cpp index 6075fbfeca..177d8fda73 100644 --- a/src/test/streams_tests.cpp +++ b/src/test/streams_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <streams.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> @@ -338,7 +338,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file) BOOST_AUTO_TEST_CASE(streams_buffered_file_rand) { // Make this test deterministic. - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); for (int rep = 0; rep < 50; ++rep) { FILE* file = fsbridge::fopen("streams_test_tmp", "w+b"); diff --git a/src/test/sync_tests.cpp b/src/test/sync_tests.cpp index c1399d2dbe..188e9986ff 100644 --- a/src/test/sync_tests.cpp +++ b/src/test/sync_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <sync.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/timedata_tests.cpp b/src/test/timedata_tests.cpp index 7b00222ab7..4721151197 100644 --- a/src/test/timedata_tests.cpp +++ b/src/test/timedata_tests.cpp @@ -5,7 +5,8 @@ #include <netaddress.h> #include <noui.h> -#include <test/setup_common.h> +#include <test/util/logging.h> +#include <test/util/setup_common.h> #include <timedata.h> #include <warnings.h> @@ -59,9 +60,10 @@ BOOST_AUTO_TEST_CASE(addtimedata) MultiAddTimeData(3, DEFAULT_MAX_TIME_ADJUSTMENT + 1); // Filter size is 1 + 3 = 4: It is always initialized with a single element (offset 0) - noui_suppress(); - MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 - noui_reconnect(); + { + ASSERT_DEBUG_LOG("Please check that your computer's date and time are correct!"); + MultiAddTimeData(1, DEFAULT_MAX_TIME_ADJUSTMENT + 1); //filter size 5 + } BOOST_CHECK(GetWarnings("gui").find("clock is wrong") != std::string::npos); diff --git a/src/test/torcontrol_tests.cpp b/src/test/torcontrol_tests.cpp index d846062d9b..41aa17988c 100644 --- a/src/test/torcontrol_tests.cpp +++ b/src/test/torcontrol_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index a8c8918733..eb0050a4a3 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -4,7 +4,7 @@ #include <test/data/tx_invalid.json.h> #include <test/data/tx_valid.json.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <clientversion.h> #include <checkqueue.h> @@ -784,6 +784,19 @@ BOOST_AUTO_TEST_CASE(test_IsStandard) reason.clear(); BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); BOOST_CHECK_EQUAL(reason, "multi-op-return"); + + // Check large scriptSig (non-standard if size is >1650 bytes) + t.vout.resize(1); + t.vout[0].nValue = MAX_MONEY; + t.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key.GetPubKey())); + // OP_PUSHDATA2 with len (3 bytes) + data (1647 bytes) = 1650 bytes + t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(1647, 0); // 1650 + BOOST_CHECK(IsStandardTx(CTransaction(t), reason)); + + t.vin[0].scriptSig = CScript() << std::vector<unsigned char>(1648, 0); // 1651 + reason.clear(); + BOOST_CHECK(!IsStandardTx(CTransaction(t), reason)); + BOOST_CHECK_EQUAL(reason, "scriptsig-size"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/txindex_tests.cpp b/src/test/txindex_tests.cpp index 0ac4b7ebc9..4b0214a15a 100644 --- a/src/test/txindex_tests.cpp +++ b/src/test/txindex_tests.cpp @@ -5,7 +5,7 @@ #include <chainparams.h> #include <index/txindex.h> #include <script/standard.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/time.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/txvalidation_tests.cpp b/src/test/txvalidation_tests.cpp index 391ebfadfb..245c03d774 100644 --- a/src/test/txvalidation_tests.cpp +++ b/src/test/txvalidation_tests.cpp @@ -6,7 +6,7 @@ #include <consensus/validation.h> #include <primitives/transaction.h> #include <script/script.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/txvalidationcache_tests.cpp b/src/test/txvalidationcache_tests.cpp index 144230b114..a5bc15bb9f 100644 --- a/src/test/txvalidationcache_tests.cpp +++ b/src/test/txvalidationcache_tests.cpp @@ -9,7 +9,7 @@ #include <script/standard.h> #include <script/sign.h> #include <script/signingprovider.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> diff --git a/src/test/uint256_tests.cpp b/src/test/uint256_tests.cpp index 33a118c2bb..7293ecd325 100644 --- a/src/test/uint256_tests.cpp +++ b/src/test/uint256_tests.cpp @@ -6,7 +6,7 @@ #include <streams.h> #include <uint256.h> #include <version.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/test/unit_test.hpp> #include <sstream> diff --git a/src/test/util/README.md b/src/test/util/README.md new file mode 100644 index 0000000000..36ad645201 --- /dev/null +++ b/src/test/util/README.md @@ -0,0 +1,11 @@ +# Test library + +This contains files for the test library, which is used by the test binaries (unit tests, benchmarks, fuzzers, gui +tests). + +Generally, the files in this folder should be well-separated modules. New code should be added to existing modules or +(when in doubt) a new module should be created. + +The utilities in here are compiled into a library, which does not hold any state. However, the main file `setup_common` +defines the common test setup for all test binaries. The test binaries will handle the global state when they +instantiate the `BasicTestingSetup` (or one of its derived classes). diff --git a/src/test/util/blockfilter.cpp b/src/test/util/blockfilter.cpp new file mode 100644 index 0000000000..bccff5e5a6 --- /dev/null +++ b/src/test/util/blockfilter.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/util/blockfilter.h> + +#include <chainparams.h> +#include <validation.h> + + +bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, BlockFilter& filter) +{ + CBlock block; + if (!ReadBlockFromDisk(block, block_index->GetBlockPos(), Params().GetConsensus())) { + return false; + } + + CBlockUndo block_undo; + if (block_index->nHeight > 0 && !UndoReadFromDisk(block_undo, block_index)) { + return false; + } + + filter = BlockFilter(filter_type, block, block_undo); + return true; +} + diff --git a/src/test/util/blockfilter.h b/src/test/util/blockfilter.h new file mode 100644 index 0000000000..79d11dcad8 --- /dev/null +++ b/src/test/util/blockfilter.h @@ -0,0 +1,13 @@ +// Copyright (c) 2019 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_TEST_UTIL_BLOCKFILTER_H +#define BITCOIN_TEST_UTIL_BLOCKFILTER_H + +#include <blockfilter.h> +class CBlockIndex; + +bool ComputeFilter(BlockFilterType filter_type, const CBlockIndex* block_index, BlockFilter& filter); + +#endif // BITCOIN_TEST_UTIL_BLOCKFILTER_H diff --git a/src/test/util/logging.cpp b/src/test/util/logging.cpp new file mode 100644 index 0000000000..fe2e69104b --- /dev/null +++ b/src/test/util/logging.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/util/logging.h> + +#include <logging.h> +#include <noui.h> +#include <tinyformat.h> +#include <util/memory.h> + +#include <stdexcept> + +DebugLogHelper::DebugLogHelper(std::string message) + : m_message{std::move(message)} +{ + m_print_connection = LogInstance().PushBackCallback( + [this](const std::string& s) { + if (m_found) return; + m_found = s.find(m_message) != std::string::npos; + }); + noui_test_redirect(); +} + +void DebugLogHelper::check_found() +{ + noui_reconnect(); + LogInstance().DeleteCallback(m_print_connection); + if (!m_found) { + throw std::runtime_error(strprintf("'%s' not found in debug log\n", m_message)); + } +} diff --git a/src/test/util/logging.h b/src/test/util/logging.h new file mode 100644 index 0000000000..45ec44173c --- /dev/null +++ b/src/test/util/logging.h @@ -0,0 +1,29 @@ +// Copyright (c) 2019 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_TEST_UTIL_LOGGING_H +#define BITCOIN_TEST_UTIL_LOGGING_H + +#include <util/macros.h> + +#include <functional> +#include <list> +#include <string> + +class DebugLogHelper +{ + const std::string m_message; + bool m_found{false}; + std::list<std::function<void(const std::string&)>>::iterator m_print_connection; + + void check_found(); + +public: + DebugLogHelper(std::string message); + ~DebugLogHelper() { check_found(); } +}; + +#define ASSERT_DEBUG_LOG(message) DebugLogHelper PASTE2(debugloghelper, __COUNTER__)(message) + +#endif // BITCOIN_TEST_UTIL_LOGGING_H diff --git a/src/test/setup_common.cpp b/src/test/util/setup_common.cpp index 3425bd59c1..0c6ecdf69d 100644 --- a/src/test/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <banman.h> #include <chainparams.h> @@ -34,6 +34,27 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; FastRandomContext g_insecure_rand_ctx; +/** Random context to get unique temp data dirs. Separate from g_insecure_rand_ctx, which can be seeded from a const env var */ +static FastRandomContext g_insecure_rand_ctx_temp_path; + +/** Return the unsigned from the environment var if available, otherwise 0 */ +static uint256 GetUintFromEnv(const std::string& env_name) +{ + const char* num = std::getenv(env_name.c_str()); + if (!num) return {}; + return uint256S(num); +} + +void Seed(FastRandomContext& ctx) +{ + // Should be enough to get the seed once for the process + static uint256 seed{}; + static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"}; + if (seed.IsNull()) seed = GetUintFromEnv(RANDOM_CTX_SEED); + if (seed.IsNull()) seed = GetRandHash(); + LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__, RANDOM_CTX_SEED, seed.GetHex()); + ctx = FastRandomContext(seed); +} std::ostream& operator<<(std::ostream& os, const uint256& num) { @@ -42,12 +63,13 @@ std::ostream& operator<<(std::ostream& os, const uint256& num) } BasicTestingSetup::BasicTestingSetup(const std::string& chainName) - : m_path_root(fs::temp_directory_path() / "test_common_" PACKAGE_NAME / strprintf("%lu_%i", (unsigned long)GetTime(), (int)(InsecureRandRange(1 << 30)))) + : m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME / std::to_string(g_insecure_rand_ctx_temp_path.rand32())} { fs::create_directories(m_path_root); gArgs.ForceSetArg("-datadir", m_path_root.string()); ClearDatadirCache(); SelectParams(chainName); + SeedInsecureRand(); gArgs.ForceSetArg("-printtoconsole", "0"); InitLogging(); LogInstance().StartLogging(); @@ -102,9 +124,12 @@ TestingSetup::TestingSetup(const std::string& chainName) : BasicTestingSetup(cha throw std::runtime_error(strprintf("ActivateBestChain failed. (%s)", FormatStateMessage(state))); } - nScriptCheckThreads = 3; - for (int i = 0; i < nScriptCheckThreads - 1; i++) + // Start script-checking threads. Set g_parallel_script_checks to true so they are used. + constexpr int script_check_threads = 2; + for (int i = 0; i < script_check_threads; ++i) { threadGroup.create_thread([i]() { return ThreadScriptCheck(i); }); + } + g_parallel_script_checks = true; m_node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", nullptr, DEFAULT_MISBEHAVING_BANTIME); m_node.connman = MakeUnique<CConnman>(0x1337, 0x1337); // Deterministic randomness for tests. @@ -124,11 +149,12 @@ TestingSetup::~TestingSetup() pblocktree.reset(); } -TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) +TestChain100Setup::TestChain100Setup() { // CreateAndProcessBlock() does not support building SegWit blocks, so don't activate in these tests. // TODO: fix the code to support SegWit blocks. gArgs.ForceSetArg("-segwitheight", "432"); + // Need to recreate chainparams SelectParams(CBaseChainParams::REGTEST); // Generate a 100-block chain: @@ -142,12 +168,9 @@ TestChain100Setup::TestChain100Setup() : TestingSetup(CBaseChainParams::REGTEST) } } -// // Create a new block with just given transactions, coinbase paying to // scriptPubKey, and try to add it to the current chain. -// -CBlock -TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey) +CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey) { const CChainParams& chainparams = Params(); std::unique_ptr<CBlockTemplate> pblocktemplate = BlockAssembler(chainparams).CreateNewBlock(scriptPubKey); @@ -175,6 +198,7 @@ TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& TestChain100Setup::~TestChain100Setup() { + gArgs.ForceSetArg("-segwitheight", "0"); } diff --git a/src/test/setup_common.h b/src/test/util/setup_common.h index 5731b50e31..1e2e059a56 100644 --- a/src/test/setup_common.h +++ b/src/test/util/setup_common.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_TEST_SETUP_COMMON_H -#define BITCOIN_TEST_SETUP_COMMON_H +#ifndef BITCOIN_TEST_UTIL_SETUP_COMMON_H +#define BITCOIN_TEST_UTIL_SETUP_COMMON_H #include <chainparamsbase.h> #include <fs.h> @@ -39,9 +39,21 @@ extern FastRandomContext g_insecure_rand_ctx; */ extern bool g_mock_deterministic_tests; -static inline void SeedInsecureRand(bool deterministic = false) +enum class SeedRand { + ZEROS, //!< Seed with a compile time constant of zeros + SEED, //!< Call the Seed() helper +}; + +/** Seed the given random ctx or use the seed passed in via an environment var */ +void Seed(FastRandomContext& ctx); + +static inline void SeedInsecureRand(SeedRand seed = SeedRand::SEED) { - g_insecure_rand_ctx = FastRandomContext(deterministic); + if (seed == SeedRand::ZEROS) { + g_insecure_rand_ctx = FastRandomContext(/* deterministic */ true); + } else { + Seed(g_insecure_rand_ctx); + } } static inline uint32_t InsecureRand32() { return g_insecure_rand_ctx.rand32(); } @@ -76,6 +88,12 @@ struct TestingSetup : public BasicTestingSetup { ~TestingSetup(); }; +/** Identical to TestingSetup, but chain set to regtest */ +struct RegTestingSetup : public TestingSetup { + RegTestingSetup() + : TestingSetup{CBaseChainParams::REGTEST} {} +}; + class CBlock; struct CMutableTransaction; class CScript; @@ -84,7 +102,7 @@ class CScript; // Testing fixture that pre-creates a // 100-block REGTEST-mode block chain // -struct TestChain100Setup : public TestingSetup { +struct TestChain100Setup : public RegTestingSetup { TestChain100Setup(); // Create a new block with just given transactions, coinbase paying to diff --git a/src/test/util/str.cpp b/src/test/util/str.cpp new file mode 100644 index 0000000000..c517fe44d9 --- /dev/null +++ b/src/test/util/str.cpp @@ -0,0 +1,21 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/util/str.h> + +#include <cstdint> +#include <string> + +bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2) +{ + if (s1.size() != s2.size()) return false; + for (size_t i = 0; i < s1.size(); ++i) { + char c1 = s1[i]; + if (c1 >= 'A' && c1 <= 'Z') c1 -= ('A' - 'a'); + char c2 = s2[i]; + if (c2 >= 'A' && c2 <= 'Z') c2 -= ('A' - 'a'); + if (c1 != c2) return false; + } + return true; +} diff --git a/src/test/util/str.h b/src/test/util/str.h new file mode 100644 index 0000000000..63629501e8 --- /dev/null +++ b/src/test/util/str.h @@ -0,0 +1,12 @@ +// Copyright (c) 2019 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_TEST_UTIL_STR_H +#define BITCOIN_TEST_UTIL_STR_H + +#include <string> + +bool CaseInsensitiveEqual(const std::string& s1, const std::string& s2); + +#endif // BITCOIN_TEST_UTIL_STR_H diff --git a/src/test/lib/transaction_utils.cpp b/src/test/util/transaction_utils.cpp index 2619fb9006..90b78effb0 100644 --- a/src/test/lib/transaction_utils.cpp +++ b/src/test/util/transaction_utils.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/lib/transaction_utils.h> +#include <test/util/transaction_utils.h> CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey, int nValue) { diff --git a/src/test/lib/transaction_utils.h b/src/test/util/transaction_utils.h index 6f297ac34f..57604646e7 100644 --- a/src/test/lib/transaction_utils.h +++ b/src/test/util/transaction_utils.h @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef BITCOIN_TEST_LIB_TRANSACTION_UTILS_H -#define BITCOIN_TEST_LIB_TRANSACTION_UTILS_H +#ifndef BITCOIN_TEST_UTIL_TRANSACTION_UTILS_H +#define BITCOIN_TEST_UTIL_TRANSACTION_UTILS_H #include <primitives/transaction.h> @@ -16,4 +16,4 @@ CMutableTransaction BuildCreditingTransaction(const CScript& scriptPubKey, int n // 1 output with empty scriptPubKey, full value of referenced transaction] CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CScriptWitness& scriptWitness, const CTransaction& txCredit); -#endif // BITCOIN_TEST_LIB_TRANSACTION_UTILS_H +#endif // BITCOIN_TEST_UTIL_TRANSACTION_UTILS_H diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 569ce53092..b9fcd97a8f 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -6,7 +6,7 @@ #include <clientversion.h> #include <sync.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <test/util.h> #include <util/moneystr.h> #include <util/strencodings.h> @@ -17,6 +17,7 @@ #include <stdint.h> #include <thread> +#include <univalue.h> #include <utility> #include <vector> #ifndef WIN32 @@ -166,14 +167,12 @@ BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) struct TestArgsManager : public ArgsManager { TestArgsManager() { m_network_only_args.clear(); } - std::map<std::string, std::vector<std::string> >& GetOverrideArgs() { return m_override_args; } - std::map<std::string, std::vector<std::string> >& GetConfigArgs() { return m_config_args; } void ReadConfigString(const std::string str_config) { std::istringstream streamConfig(str_config); { LOCK(cs_args); - m_config_args.clear(); + m_settings.ro_config.clear(); m_config_sections.clear(); } std::string error; @@ -193,6 +192,7 @@ struct TestArgsManager : public ArgsManager using ArgsManager::ReadConfigStream; using ArgsManager::cs_args; using ArgsManager::m_network; + using ArgsManager::m_settings; }; BOOST_AUTO_TEST_CASE(util_ParseParameters) @@ -206,31 +206,86 @@ BOOST_AUTO_TEST_CASE(util_ParseParameters) const char *argv_test[] = {"-ignored", "-a", "-b", "-ccc=argument", "-ccc=multiple", "f", "-d=e"}; std::string error; + LOCK(testArgs.cs_args); testArgs.SetupArgs({a, b, ccc, d}); BOOST_CHECK(testArgs.ParseParameters(0, (char**)argv_test, error)); - BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.ParseParameters(1, (char**)argv_test, error)); - BOOST_CHECK(testArgs.GetOverrideArgs().empty() && testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.empty() && testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); // expectation: -ignored is ignored (program name argument), // -a, -b and -ccc end up in map, -d ignored because it is after // a non-option argument (non-GNU option parsing) - BOOST_CHECK(testArgs.GetOverrideArgs().size() == 3 && testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 3 && testArgs.m_settings.ro_config.empty()); BOOST_CHECK(testArgs.IsArgSet("-a") && testArgs.IsArgSet("-b") && testArgs.IsArgSet("-ccc") && !testArgs.IsArgSet("f") && !testArgs.IsArgSet("-d")); - BOOST_CHECK(testArgs.GetOverrideArgs().count("-a") && testArgs.GetOverrideArgs().count("-b") && testArgs.GetOverrideArgs().count("-ccc") - && !testArgs.GetOverrideArgs().count("f") && !testArgs.GetOverrideArgs().count("-d")); - - BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].size() == 1); - BOOST_CHECK(testArgs.GetOverrideArgs()["-a"].front() == ""); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].size() == 2); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].front() == "argument"); - BOOST_CHECK(testArgs.GetOverrideArgs()["-ccc"].back() == "multiple"); + BOOST_CHECK(testArgs.m_settings.command_line_options.count("a") && testArgs.m_settings.command_line_options.count("b") && testArgs.m_settings.command_line_options.count("ccc") + && !testArgs.m_settings.command_line_options.count("f") && !testArgs.m_settings.command_line_options.count("d")); + + BOOST_CHECK(testArgs.m_settings.command_line_options["a"].size() == 1); + BOOST_CHECK(testArgs.m_settings.command_line_options["a"].front().get_str() == ""); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].size() == 2); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].front().get_str() == "argument"); + BOOST_CHECK(testArgs.m_settings.command_line_options["ccc"].back().get_str() == "multiple"); BOOST_CHECK(testArgs.GetArgs("-ccc").size() == 2); } +static void TestParse(const std::string& str, bool expected_bool, int64_t expected_int) +{ + TestArgsManager test; + test.SetupArgs({{"-value", ArgsManager::ALLOW_ANY}}); + std::string arg = "-value=" + str; + const char* argv[] = {"ignored", arg.c_str()}; + std::string error; + BOOST_CHECK(test.ParseParameters(2, (char**)argv, error)); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", false), expected_bool); + BOOST_CHECK_EQUAL(test.GetBoolArg("-value", true), expected_bool); + BOOST_CHECK_EQUAL(test.GetArg("-value", 99998), expected_int); + BOOST_CHECK_EQUAL(test.GetArg("-value", 99999), expected_int); +} + +// Test bool and int parsing. +BOOST_AUTO_TEST_CASE(util_ArgParsing) +{ + // Some of these cases could be ambiguous or surprising to users, and might + // be worth triggering errors or warnings in the future. But for now basic + // test coverage is useful to avoid breaking backwards compatibility + // unintentionally. + TestParse("", true, 0); + TestParse(" ", false, 0); + TestParse("0", false, 0); + TestParse("0 ", false, 0); + TestParse(" 0", false, 0); + TestParse("+0", false, 0); + TestParse("-0", false, 0); + TestParse("5", true, 5); + TestParse("5 ", true, 5); + TestParse(" 5", true, 5); + TestParse("+5", true, 5); + TestParse("-5", true, -5); + TestParse("0 5", false, 0); + TestParse("5 0", true, 5); + TestParse("050", true, 50); + TestParse("0.", false, 0); + TestParse("5.", true, 5); + TestParse("0.0", false, 0); + TestParse("0.5", false, 0); + TestParse("5.0", true, 5); + TestParse("5.5", true, 5); + TestParse("x", false, 0); + TestParse("x0", false, 0); + TestParse("x5", false, 0); + TestParse("0x", false, 0); + TestParse("5x", true, 5); + TestParse("0x5", false, 0); + TestParse("false", false, 0); + TestParse("true", false, 0); + TestParse("yes", false, 0); + TestParse("no", false, 0); +} + BOOST_AUTO_TEST_CASE(util_GetBoolArg) { TestArgsManager testArgs; @@ -244,6 +299,7 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArg) const char *argv_test[] = { "ignored", "-a", "-nob", "-c=0", "-d=1", "-e=false", "-f=true"}; std::string error; + LOCK(testArgs.cs_args); testArgs.SetupArgs({a, b, c, d, e, f}); BOOST_CHECK(testArgs.ParseParameters(7, (char**)argv_test, error)); @@ -252,8 +308,8 @@ BOOST_AUTO_TEST_CASE(util_GetBoolArg) BOOST_CHECK(testArgs.IsArgSet({'-', opt}) || !opt); // Nothing else should be in the map - BOOST_CHECK(testArgs.GetOverrideArgs().size() == 6 && - testArgs.GetConfigArgs().empty()); + BOOST_CHECK(testArgs.m_settings.command_line_options.size() == 6 && + testArgs.m_settings.ro_config.empty()); // The -no prefix should get stripped on the way in. BOOST_CHECK(!testArgs.IsArgSet("-nob")); @@ -349,6 +405,7 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) "iii=2\n"; TestArgsManager test_args; + LOCK(test_args.cs_args); const auto a = std::make_pair("-a", ArgsManager::ALLOW_BOOL); const auto b = std::make_pair("-b", ArgsManager::ALLOW_BOOL); const auto ccc = std::make_pair("-ccc", ArgsManager::ALLOW_STRING); @@ -365,22 +422,25 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) // expectation: a, b, ccc, d, fff, ggg, h, i end up in map // so do sec1.ccc, sec1.d, sec1.h, sec2.ccc, sec2.iii - BOOST_CHECK(test_args.GetOverrideArgs().empty()); - BOOST_CHECK(test_args.GetConfigArgs().size() == 13); - - BOOST_CHECK(test_args.GetConfigArgs().count("-a") - && test_args.GetConfigArgs().count("-b") - && test_args.GetConfigArgs().count("-ccc") - && test_args.GetConfigArgs().count("-d") - && test_args.GetConfigArgs().count("-fff") - && test_args.GetConfigArgs().count("-ggg") - && test_args.GetConfigArgs().count("-h") - && test_args.GetConfigArgs().count("-i") + BOOST_CHECK(test_args.m_settings.command_line_options.empty()); + BOOST_CHECK(test_args.m_settings.ro_config.size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config[""].size() == 8); + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].size() == 3); + BOOST_CHECK(test_args.m_settings.ro_config["sec2"].size() == 2); + + BOOST_CHECK(test_args.m_settings.ro_config[""].count("a") + && test_args.m_settings.ro_config[""].count("b") + && test_args.m_settings.ro_config[""].count("ccc") + && test_args.m_settings.ro_config[""].count("d") + && test_args.m_settings.ro_config[""].count("fff") + && test_args.m_settings.ro_config[""].count("ggg") + && test_args.m_settings.ro_config[""].count("h") + && test_args.m_settings.ro_config[""].count("i") ); - BOOST_CHECK(test_args.GetConfigArgs().count("-sec1.ccc") - && test_args.GetConfigArgs().count("-sec1.h") - && test_args.GetConfigArgs().count("-sec2.ccc") - && test_args.GetConfigArgs().count("-sec2.iii") + BOOST_CHECK(test_args.m_settings.ro_config["sec1"].count("ccc") + && test_args.m_settings.ro_config["sec1"].count("h") + && test_args.m_settings.ro_config["sec2"].count("ccc") + && test_args.m_settings.ro_config["sec2"].count("iii") ); BOOST_CHECK(test_args.IsArgSet("-a") @@ -519,24 +579,25 @@ BOOST_AUTO_TEST_CASE(util_ReadConfigStream) BOOST_AUTO_TEST_CASE(util_GetArg) { TestArgsManager testArgs; - testArgs.GetOverrideArgs().clear(); - testArgs.GetOverrideArgs()["strtest1"] = {"string..."}; + LOCK(testArgs.cs_args); + testArgs.m_settings.command_line_options.clear(); + testArgs.m_settings.command_line_options["strtest1"] = {"string..."}; // strtest2 undefined on purpose - testArgs.GetOverrideArgs()["inttest1"] = {"12345"}; - testArgs.GetOverrideArgs()["inttest2"] = {"81985529216486895"}; + testArgs.m_settings.command_line_options["inttest1"] = {"12345"}; + testArgs.m_settings.command_line_options["inttest2"] = {"81985529216486895"}; // inttest3 undefined on purpose - testArgs.GetOverrideArgs()["booltest1"] = {""}; + testArgs.m_settings.command_line_options["booltest1"] = {""}; // booltest2 undefined on purpose - testArgs.GetOverrideArgs()["booltest3"] = {"0"}; - testArgs.GetOverrideArgs()["booltest4"] = {"1"}; + testArgs.m_settings.command_line_options["booltest3"] = {"0"}; + testArgs.m_settings.command_line_options["booltest4"] = {"1"}; // priorities - testArgs.GetOverrideArgs()["pritest1"] = {"a", "b"}; - testArgs.GetConfigArgs()["pritest2"] = {"a", "b"}; - testArgs.GetOverrideArgs()["pritest3"] = {"a"}; - testArgs.GetConfigArgs()["pritest3"] = {"b"}; - testArgs.GetOverrideArgs()["pritest4"] = {"a","b"}; - testArgs.GetConfigArgs()["pritest4"] = {"c","d"}; + testArgs.m_settings.command_line_options["pritest1"] = {"a", "b"}; + testArgs.m_settings.ro_config[""]["pritest2"] = {"a", "b"}; + testArgs.m_settings.command_line_options["pritest3"] = {"a"}; + testArgs.m_settings.ro_config[""]["pritest3"] = {"b"}; + testArgs.m_settings.command_line_options["pritest4"] = {"a","b"}; + testArgs.m_settings.ro_config[""]["pritest4"] = {"c","d"}; BOOST_CHECK_EQUAL(testArgs.GetArg("strtest1", "default"), "string..."); BOOST_CHECK_EQUAL(testArgs.GetArg("strtest2", "default"), "default"); @@ -890,6 +951,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) desc += " "; desc += argstr + 1; conf += argstr + 1; + conf += "\n"; } std::istringstream conf_stream(conf); BOOST_CHECK(parser.ReadConfigStream(conf_stream, "filepath", error)); @@ -928,7 +990,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) // Results file is formatted like: // // <input> || <output> - BOOST_CHECK_EQUAL(out_sha_hex, "94b4ad55c8ac639a56b93e36f7e32e4c611fd7d7dd7b2be6a71707b1eadcaec7"); + BOOST_CHECK_EQUAL(out_sha_hex, "f0b3a3c29869edc765d579c928f7f1690a71fbb673b49ccf39cbc4de18156a0d"); } BOOST_AUTO_TEST_CASE(util_FormatMoney) @@ -1051,7 +1113,7 @@ BOOST_AUTO_TEST_CASE(util_IsHexNumber) BOOST_AUTO_TEST_CASE(util_seed_insecure_rand) { - SeedInsecureRand(true); + SeedInsecureRand(SeedRand::ZEROS); for (int mod=2;mod<11;mod++) { int mask = 1; diff --git a/src/test/util_threadnames_tests.cpp b/src/test/util_threadnames_tests.cpp index 71c0168ca3..33f840d9c5 100644 --- a/src/test/util_threadnames_tests.cpp +++ b/src/test/util_threadnames_tests.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/threadnames.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <thread> #include <vector> diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index aca9f475ac..b7cf82906a 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -11,20 +11,16 @@ #include <pow.h> #include <random.h> #include <script/standard.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/time.h> #include <validation.h> #include <validationinterface.h> #include <thread> -struct RegtestingSetup : public TestingSetup { - RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} -}; - static const std::vector<unsigned char> V_OP_TRUE{OP_TRUE}; -BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup) +BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegTestingSetup) struct TestSubscriber : public CValidationInterface { uint256 m_expected_tip; @@ -44,9 +40,10 @@ struct TestSubscriber : public CValidationInterface { m_expected_tip = block->GetHash(); } - void BlockDisconnected(const std::shared_ptr<const CBlock>& block) override + void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex) override { BOOST_CHECK_EQUAL(m_expected_tip, block->GetHash()); + BOOST_CHECK_EQUAL(m_expected_tip, pindex->GetBlockHash()); m_expected_tip = block->hashPrevBlock; } diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index 101025d31e..3b961db52d 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -6,7 +6,7 @@ #include <net.h> #include <validation.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <boost/signals2/signal.hpp> #include <boost/test/unit_test.hpp> diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 0ca3a17974..7b59d539a6 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -4,7 +4,7 @@ #include <chain.h> #include <versionbits.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <chainparams.h> #include <validation.h> #include <consensus/params.h> diff --git a/src/txmempool.h b/src/txmempool.h index 9ccede9d4d..4a7640b78a 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -50,8 +50,6 @@ struct LockPoints LockPoints() : height(0), time(0), maxInputBlock(nullptr) { } }; -class CTxMemPool; - /** \class CTxMemPoolEntry * * CTxMemPoolEntry stores data about the corresponding transaction, as well diff --git a/src/univalue/README.md b/src/univalue/README.md index 36aa786a4c..7c62c33970 100644 --- a/src/univalue/README.md +++ b/src/univalue/README.md @@ -12,21 +12,10 @@ an arbitrary depth. This class is aligned with the JSON standard, [RFC 7159](https://tools.ietf.org/html/rfc7159.html). -## Installation +## Library usage -This project is a standard GNU -[autotools](https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html) -project. Build and install instructions are available in the `INSTALL` -file provided with GNU autotools. - -``` -$ ./autogen.sh -$ ./configure -$ make -``` - -## Design - -UniValue provides a single dynamic RAII C++ object class, -and minimizes template use (contra json_spirit). +This is a fork of univalue used by Bitcoin Core. It is not maintained for usage +by other projects. Notably, the API may break in non-backward-compatible ways. +Other projects looking for a maintained library should use the upstream +univalue at https://github.com/jgarzik/univalue. diff --git a/src/univalue/include/univalue.h b/src/univalue/include/univalue.h index 91b104e56e..6080516353 100644 --- a/src/univalue/include/univalue.h +++ b/src/univalue/include/univalue.h @@ -47,7 +47,6 @@ public: std::string s(val_); setStr(s); } - ~UniValue() {} void clear(); diff --git a/src/univalue/lib/univalue_get.cpp b/src/univalue/lib/univalue_get.cpp index eabcf2dad1..0ad6146545 100644 --- a/src/univalue/lib/univalue_get.cpp +++ b/src/univalue/lib/univalue_get.cpp @@ -35,7 +35,7 @@ bool ParseInt32(const std::string& str, int32_t *out) errno = 0; // strtol will not set errno if valid long int n = strtol(str.c_str(), &endp, 10); if(out) *out = (int32_t)n; - // Note that strtol returns a *long int*, so even if strtol doesn't report a over/underflow + // Note that strtol returns a *long int*, so even if strtol doesn't report an over/underflow // we still have to check that the returned value is within the range of an *int32_t*. On 64-bit // platforms the size of these types may be different. return endp && *endp == 0 && !errno && diff --git a/src/util/fees.cpp b/src/util/fees.cpp index cf16d5e44f..41149888d7 100644 --- a/src/util/fees.cpp +++ b/src/util/fees.cpp @@ -3,8 +3,11 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <util/fees.h> + #include <policy/fees.h> +#include <map> #include <string> std::string StringForFeeReason(FeeReason reason) { diff --git a/src/util/settings.cpp b/src/util/settings.cpp new file mode 100644 index 0000000000..af75fef310 --- /dev/null +++ b/src/util/settings.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <util/settings.h> + +#include <univalue.h> + +namespace util { +namespace { + +enum class Source { + FORCED, + COMMAND_LINE, + CONFIG_FILE_NETWORK_SECTION, + CONFIG_FILE_DEFAULT_SECTION +}; + +//! Merge settings from multiple sources in precedence order: +//! Forced config > command line > config file network-specific section > config file default section +//! +//! This function is provided with a callback function fn that contains +//! specific logic for how to merge the sources. +template <typename Fn> +static void MergeSettings(const Settings& settings, const std::string& section, const std::string& name, Fn&& fn) +{ + // Merge in the forced settings + if (auto* value = FindKey(settings.forced_settings, name)) { + fn(SettingsSpan(*value), Source::FORCED); + } + // Merge in the command-line options + if (auto* values = FindKey(settings.command_line_options, name)) { + fn(SettingsSpan(*values), Source::COMMAND_LINE); + } + // Merge in the network-specific section of the config file + if (!section.empty()) { + if (auto* map = FindKey(settings.ro_config, section)) { + if (auto* values = FindKey(*map, name)) { + fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); + } + } + } + // Merge in the default section of the config file + if (auto* map = FindKey(settings.ro_config, "")) { + if (auto* values = FindKey(*map, name)) { + fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); + } + } +} +} // namespace + +SettingsValue GetSetting(const Settings& settings, + const std::string& section, + const std::string& name, + bool ignore_default_section_config, + bool get_chain_name) +{ + SettingsValue result; + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { + // Weird behavior preserved for backwards compatibility: Apply negated + // setting even if non-negated setting would be ignored. A negated + // value in the default section is applied to network specific options, + // even though normal non-negated values there would be ignored. + const bool never_ignore_negated_setting = span.last_negated(); + + // Weird behavior preserved for backwards compatibility: Take first + // assigned value instead of last. In general, later settings take + // precedence over early settings, but for backwards compatibility in + // the config file the precedence is reversed for all settings except + // chain name settings. + const bool reverse_precedence = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !get_chain_name; + + // Weird behavior preserved for backwards compatibility: Negated + // -regtest and -testnet arguments which you would expect to override + // values set in the configuration file are currently accepted but + // silently ignored. It would be better to apply these just like other + // negated values, or at least warn they are ignored. + const bool skip_negated_command_line = get_chain_name; + + // Ignore settings in default config section if requested. + if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION && !never_ignore_negated_setting) return; + + // Skip negated command line settings. + if (skip_negated_command_line && span.last_negated()) return; + + // Stick with highest priority value, keeping result if already set. + if (!result.isNull()) return; + + if (!span.empty()) { + result = reverse_precedence ? span.begin()[0] : span.end()[-1]; + } else if (span.last_negated()) { + result = false; + } + }); + return result; +} + +std::vector<SettingsValue> GetSettingsList(const Settings& settings, + const std::string& section, + const std::string& name, + bool ignore_default_section_config) +{ + std::vector<SettingsValue> result; + bool result_complete = false; + bool prev_negated_empty = false; + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { + // Weird behavior preserved for backwards compatibility: Apply config + // file settings even if negated on command line. Negating a setting on + // command line will ignore earlier settings on the command line and + // ignore settings in the config file, unless the negated command line + // value is followed by non-negated value, in which case config file + // settings will be brought back from the dead (but earlier command + // line settings will still be ignored). + const bool add_zombie_config_values = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !prev_negated_empty; + + // Ignore settings in default config section if requested. + if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) return; + + // Add new settings to the result if isn't already complete, or if the + // values are zombies. + if (!result_complete || add_zombie_config_values) { + for (const auto& value : span) { + if (value.isArray()) { + result.insert(result.end(), value.getValues().begin(), value.getValues().end()); + } else { + result.push_back(value); + } + } + } + + // If a setting was negated, or if a setting was forced, set + // result_complete to true to ignore any later lower priority settings. + result_complete |= span.negated() > 0 || source == Source::FORCED; + + // Update the negated and empty state used for the zombie values check. + prev_negated_empty |= span.last_negated() && result.empty(); + }); + return result; +} + +bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name) +{ + bool has_default_section_setting = false; + bool has_other_setting = false; + MergeSettings(settings, section, name, [&](SettingsSpan span, Source source) { + if (span.empty()) return; + else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) has_default_section_setting = true; + else has_other_setting = true; + }); + // If a value is set in the default section and not explicitly overwritten by the + // user on the command line or in a different section, then we want to enable + // warnings about the value being ignored. + return has_default_section_setting && !has_other_setting; +} + +SettingsSpan::SettingsSpan(const std::vector<SettingsValue>& vec) noexcept : SettingsSpan(vec.data(), vec.size()) {} +const SettingsValue* SettingsSpan::begin() const { return data + negated(); } +const SettingsValue* SettingsSpan::end() const { return data + size; } +bool SettingsSpan::empty() const { return size == 0 || last_negated(); } +bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); } +size_t SettingsSpan::negated() const +{ + for (size_t i = size; i > 0; --i) { + if (data[i - 1].isFalse()) return i; // Return number of negated values (position of last false value) + } + return 0; +} + +} // namespace util diff --git a/src/util/settings.h b/src/util/settings.h new file mode 100644 index 0000000000..17832e4d2c --- /dev/null +++ b/src/util/settings.h @@ -0,0 +1,87 @@ +// Copyright (c) 2019 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_UTIL_SETTINGS_H +#define BITCOIN_UTIL_SETTINGS_H + +#include <map> +#include <string> +#include <vector> + +class UniValue; + +namespace util { + +//! Settings value type (string/integer/boolean/null variant). +//! +//! @note UniValue is used here for convenience and because it can be easily +//! serialized in a readable format. But any other variant type that can +//! be assigned strings, int64_t, and bool values and has get_str(), +//! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and +//! isNull() methods can be substituted if there's a need to move away +//! from UniValue. (An implementation with boost::variant was posted at +//! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) +using SettingsValue = UniValue; + +//! Stored bitcoin settings. This struct combines settings from the command line +//! and a read-only configuration file. +struct Settings { + //! Map of setting name to forced setting value. + std::map<std::string, SettingsValue> forced_settings; + //! Map of setting name to list of command line values. + std::map<std::string, std::vector<SettingsValue>> command_line_options; + //! Map of config section name and setting name to list of config file values. + std::map<std::string, std::map<std::string, std::vector<SettingsValue>>> ro_config; +}; + +//! Get settings value from combined sources: forced settings, command line +//! arguments and the read-only config file. +//! +//! @param ignore_default_section_config - ignore values in the default section +//! of the config file (part before any +//! [section] keywords) +//! @param get_chain_name - enable special backwards compatible behavior +//! for GetChainName +SettingsValue GetSetting(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config, bool get_chain_name); + +//! Get combined setting value similar to GetSetting(), except if setting was +//! specified multiple times, return a list of all the values specified. +std::vector<SettingsValue> GetSettingsList(const Settings& settings, const std::string& section, const std::string& name, bool ignore_default_section_config); + +//! Return true if a setting is set in the default config file section, and not +//! overridden by a higher priority command-line or network section value. +//! +//! This is used to provide user warnings about values that might be getting +//! ignored unintentionally. +bool OnlyHasDefaultSectionSetting(const Settings& settings, const std::string& section, const std::string& name); + +//! Accessor for list of settings that skips negated values when iterated over. +//! The last boolean `false` value in the list and all earlier values are +//! considered negated. +struct SettingsSpan { + explicit SettingsSpan() = default; + explicit SettingsSpan(const SettingsValue& value) noexcept : SettingsSpan(&value, 1) {} + explicit SettingsSpan(const SettingsValue* data, size_t size) noexcept : data(data), size(size) {} + explicit SettingsSpan(const std::vector<SettingsValue>& vec) noexcept; + const SettingsValue* begin() const; //<! Pointer to first non-negated value. + const SettingsValue* end() const; //<! Pointer to end of values. + bool empty() const; //<! True if there are any non-negated values. + bool last_negated() const; //<! True if the last value is negated. + size_t negated() const; //<! Number of negated values. + + const SettingsValue* data = nullptr; + size_t size = 0; +}; + +//! Map lookup helper. +template <typename Map, typename Key> +auto FindKey(Map&& map, Key&& key) -> decltype(&map.at(key)) +{ + auto it = map.find(key); + return it == map.end() ? nullptr : &it->second; +} + +} // namespace util + +#endif // BITCOIN_UTIL_SETTINGS_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 7da408eda5..2a2ae6fdf5 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -63,6 +63,7 @@ #endif #include <thread> +#include <univalue.h> // Application startup time (used for uptime calculation) const int64_t nStartupTime = GetTime(); @@ -161,11 +162,14 @@ static bool InterpretBool(const std::string& strValue) return (atoi(strValue) != 0); } +static std::string SettingName(const std::string& arg) +{ + return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg; +} + /** Internal helper functions for ArgsManager */ class ArgsManagerHelper { public: - typedef std::map<std::string, std::vector<std::string>> MapArgs; - /** Determine whether to use config settings in the default section, * See also comments around ArgsManager::ArgsManager() below. */ static inline bool UseDefaultSection(const ArgsManager& am, const std::string& arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) @@ -173,91 +177,10 @@ public: return (am.m_network == CBaseChainParams::MAIN || am.m_network_only_args.count(arg) == 0); } - /** Convert regular argument into the network-specific setting */ - static inline std::string NetworkArg(const ArgsManager& am, const std::string& arg) - { - assert(arg.length() > 1 && arg[0] == '-'); - return "-" + am.m_network + "." + arg.substr(1); - } - - /** Find arguments in a map and add them to a vector */ - static inline void AddArgs(std::vector<std::string>& res, const MapArgs& map_args, const std::string& arg) - { - auto it = map_args.find(arg); - if (it != map_args.end()) { - res.insert(res.end(), it->second.begin(), it->second.end()); - } - } - - /** Return true/false if an argument is set in a map, and also - * return the first (or last) of the possibly multiple values it has - */ - static inline std::pair<bool,std::string> GetArgHelper(const MapArgs& map_args, const std::string& arg, bool getLast = false) - { - auto it = map_args.find(arg); - - if (it == map_args.end() || it->second.empty()) { - return std::make_pair(false, std::string()); - } - - if (getLast) { - return std::make_pair(true, it->second.back()); - } else { - return std::make_pair(true, it->second.front()); - } - } - - /* Get the string value of an argument, returning a pair of a boolean - * indicating the argument was found, and the value for the argument - * if it was found (or the empty string if not found). - */ - static inline std::pair<bool,std::string> GetArg(const ArgsManager &am, const std::string& arg) + static util::SettingsValue Get(const ArgsManager& am, const std::string& arg) { LOCK(am.cs_args); - std::pair<bool,std::string> found_result(false, std::string()); - - // We pass "true" to GetArgHelper in order to return the last - // argument value seen from the command line (so "bitcoind -foo=bar - // -foo=baz" gives GetArg(am,"foo")=={true,"baz"} - found_result = GetArgHelper(am.m_override_args, arg, true); - if (found_result.first) { - return found_result; - } - - // But in contrast we return the first argument seen in a config file, - // so "foo=bar \n foo=baz" in the config file gives - // GetArg(am,"foo")={true,"bar"} - if (!am.m_network.empty()) { - found_result = GetArgHelper(am.m_config_args, NetworkArg(am, arg)); - if (found_result.first) { - return found_result; - } - } - - if (UseDefaultSection(am, arg)) { - found_result = GetArgHelper(am.m_config_args, arg); - if (found_result.first) { - return found_result; - } - } - - return found_result; - } - - /* Special test for -testnet and -regtest args, because we - * don't want to be confused by craziness like "[regtest] testnet=1" - */ - static inline bool GetNetBoolArg(const ArgsManager &am, const std::string& net_arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) - { - std::pair<bool,std::string> found_result(false,std::string()); - found_result = GetArgHelper(am.m_override_args, net_arg, true); - if (!found_result.first) { - found_result = GetArgHelper(am.m_config_args, net_arg, true); - if (!found_result.first) { - return false; // not set - } - } - return InterpretBool(found_result.second); // is set, so evaluate + return GetSetting(am.m_settings, am.m_network, SettingName(arg), !UseDefaultSection(am, arg), /* get_chain_name= */ false); } }; @@ -268,13 +191,12 @@ public: * checks whether there was a double-negative (-nofoo=0 -> -foo=1). * * If there was not a double negative, it removes the "no" from the key - * and clears the args vector to indicate a negated option. + * and returns false. * - * If there was a double negative, it removes "no" from the key, sets the - * value to "1" and pushes the key and the updated value to the args vector. + * If there was a double negative, it removes "no" from the key, and + * returns true. * - * If there was no "no", it leaves key and value untouched and pushes them - * to the args vector. + * If there was no "no", it returns the string value untouched. * * Where an option was negated can be later checked using the * IsArgNegated() method. One use case for this is to have a way to disable @@ -282,34 +204,39 @@ public: * that debug log output is not sent to any file at all). */ -NODISCARD static bool InterpretOption(std::string key, std::string val, unsigned int flags, - std::map<std::string, std::vector<std::string>>& args, - std::string& error) +static util::SettingsValue InterpretOption(std::string& section, std::string& key, const std::string& value) { - assert(key[0] == '-'); - + // Split section name from key name for keys like "testnet.foo" or "regtest.bar" size_t option_index = key.find('.'); - if (option_index == std::string::npos) { - option_index = 1; - } else { - ++option_index; + if (option_index != std::string::npos) { + section = key.substr(0, option_index); + key.erase(0, option_index + 1); } - if (key.substr(option_index, 2) == "no") { - key.erase(option_index, 2); - if (flags & ArgsManager::ALLOW_BOOL) { - if (InterpretBool(val)) { - args[key].clear(); - return true; - } - // Double negatives like -nofoo=0 are supported (but discouraged) - LogPrintf("Warning: parsed potentially confusing double-negative %s=%s\n", key, val); - val = "1"; - } else { - error = strprintf("Negating of %s is meaningless and therefore forbidden", key); - return false; + if (key.substr(0, 2) == "no") { + key.erase(0, 2); + // Double negatives like -nofoo=0 are supported (but discouraged) + if (!InterpretBool(value)) { + LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key, value); + return true; } + return false; + } + return value; +} + +/** + * Check settings value validity according to flags. + * + * TODO: Add more meaningful error checks here in the future + * See "here's how the flags are meant to behave" in + * https://github.com/bitcoin/bitcoin/pull/16097#issuecomment-514627823 + */ +static bool CheckValid(const std::string& key, const util::SettingsValue& val, unsigned int flags, std::string& error) +{ + if (val.isBool() && !(flags & ArgsManager::ALLOW_BOOL)) { + error = strprintf("Negating of -%s is meaningless and therefore forbidden", key); + return false; } - args[key].push_back(val); return true; } @@ -331,22 +258,9 @@ const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const if (m_network == CBaseChainParams::MAIN) return std::set<std::string> {}; for (const auto& arg : m_network_only_args) { - std::pair<bool, std::string> found_result; - - // if this option is overridden it's fine - found_result = ArgsManagerHelper::GetArgHelper(m_override_args, arg); - if (found_result.first) continue; - - // if there's a network-specific value for this option, it's fine - found_result = ArgsManagerHelper::GetArgHelper(m_config_args, ArgsManagerHelper::NetworkArg(*this, arg)); - if (found_result.first) continue; - - // if there isn't a default value for this option, it's fine - found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg); - if (!found_result.first) continue; - - // otherwise, issue a warning - unsuitables.insert(arg); + if (OnlyHasDefaultSectionSetting(m_settings, m_network, SettingName(arg))) { + unsuitables.insert(arg); + } } return unsuitables; } @@ -375,7 +289,7 @@ void ArgsManager::SelectConfigNetwork(const std::string& network) bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::string& error) { LOCK(cs_args); - m_override_args.clear(); + m_settings.command_line_options.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); @@ -408,49 +322,44 @@ bool ArgsManager::ParseParameters(int argc, const char* const argv[], std::strin if (key.length() > 1 && key[1] == '-') key.erase(0, 1); + // Transform -foo to foo + key.erase(0, 1); + std::string section; + util::SettingsValue value = InterpretOption(section, key, val); const unsigned int flags = FlagsOfKnownArg(key); if (flags) { - if (!InterpretOption(key, val, flags, m_override_args, error)) { + if (!CheckValid(key, value, flags, error)) { return false; } + // Weird behavior preserved for backwards compatibility: command + // line options with section prefixes are allowed but ignored. It + // would be better if these options triggered the Invalid parameter + // error below. + if (section.empty()) { + m_settings.command_line_options[key].push_back(value); + } } else { - error = strprintf("Invalid parameter %s", key); + error = strprintf("Invalid parameter -%s", key); return false; } } // we do not allow -includeconf from command line, so we clear it here - auto it = m_override_args.find("-includeconf"); - if (it != m_override_args.end()) { - if (it->second.size() > 0) { - for (const auto& ic : it->second) { - error += "-includeconf cannot be used from commandline; -includeconf=" + ic + "\n"; - } - return false; + bool success = true; + if (auto* includes = util::FindKey(m_settings.command_line_options, "includeconf")) { + for (const auto& include : util::SettingsSpan(*includes)) { + error += "-includeconf cannot be used from commandline; -includeconf=" + include.get_str() + "\n"; + success = false; } } - return true; + return success; } unsigned int ArgsManager::FlagsOfKnownArg(const std::string& key) const { - assert(key[0] == '-'); - - size_t option_index = key.find('.'); - if (option_index == std::string::npos) { - option_index = 1; - } else { - ++option_index; - } - if (key.substr(option_index, 2) == "no") { - option_index += 2; - } - - const std::string base_arg_name = '-' + key.substr(option_index); - LOCK(cs_args); for (const auto& arg_map : m_available_args) { - const auto search = arg_map.second.find(base_arg_name); + const auto search = arg_map.second.find('-' + key); if (search != arg_map.second.end()) { return search->second.m_flags; } @@ -460,69 +369,42 @@ unsigned int ArgsManager::FlagsOfKnownArg(const std::string& key) const std::vector<std::string> ArgsManager::GetArgs(const std::string& strArg) const { - std::vector<std::string> result = {}; - if (IsArgNegated(strArg)) return result; // special case - LOCK(cs_args); - - ArgsManagerHelper::AddArgs(result, m_override_args, strArg); - if (!m_network.empty()) { - ArgsManagerHelper::AddArgs(result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg)); + bool ignore_default_section_config = !ArgsManagerHelper::UseDefaultSection(*this, strArg); + std::vector<std::string> result; + for (const util::SettingsValue& value : + util::GetSettingsList(m_settings, m_network, SettingName(strArg), ignore_default_section_config)) { + result.push_back(value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str()); } - - if (ArgsManagerHelper::UseDefaultSection(*this, strArg)) { - ArgsManagerHelper::AddArgs(result, m_config_args, strArg); - } - return result; } bool ArgsManager::IsArgSet(const std::string& strArg) const { - if (IsArgNegated(strArg)) return true; // special case - return ArgsManagerHelper::GetArg(*this, strArg).first; + return !ArgsManagerHelper::Get(*this, strArg).isNull(); } bool ArgsManager::IsArgNegated(const std::string& strArg) const { - LOCK(cs_args); - - const auto& ov = m_override_args.find(strArg); - if (ov != m_override_args.end()) return ov->second.empty(); - - if (!m_network.empty()) { - const auto& cfs = m_config_args.find(ArgsManagerHelper::NetworkArg(*this, strArg)); - if (cfs != m_config_args.end()) return cfs->second.empty(); - } - - const auto& cf = m_config_args.find(strArg); - if (cf != m_config_args.end()) return cf->second.empty(); - - return false; + return ArgsManagerHelper::Get(*this, strArg).isFalse(); } std::string ArgsManager::GetArg(const std::string& strArg, const std::string& strDefault) const { - if (IsArgNegated(strArg)) return "0"; - std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) return found_res.second; - return strDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str(); } int64_t ArgsManager::GetArg(const std::string& strArg, int64_t nDefault) const { - if (IsArgNegated(strArg)) return 0; - std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) return atoi64(found_res.second); - return nDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : atoi64(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const { - if (IsArgNegated(strArg)) return false; - std::pair<bool,std::string> found_res = ArgsManagerHelper::GetArg(*this, strArg); - if (found_res.first) return InterpretBool(found_res.second); - return fDefault; + const util::SettingsValue value = ArgsManagerHelper::Get(*this, strArg); + return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); } bool ArgsManager::SoftSetArg(const std::string& strArg, const std::string& strValue) @@ -544,7 +426,7 @@ bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue) void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue) { LOCK(cs_args); - m_override_args[strArg] = {strValue}; + m_settings.forced_settings[SettingName(strArg)] = strValue; } void ArgsManager::AddArg(const std::string& name, const std::string& help, unsigned int flags, const OptionsCategory& cat) @@ -860,12 +742,15 @@ bool ArgsManager::ReadConfigStream(std::istream& stream, const std::string& file return false; } for (const std::pair<std::string, std::string>& option : options) { - const std::string strKey = std::string("-") + option.first; - const unsigned int flags = FlagsOfKnownArg(strKey); + std::string section; + std::string key = option.first; + util::SettingsValue value = InterpretOption(section, key, option.second); + const unsigned int flags = FlagsOfKnownArg(key); if (flags) { - if (!InterpretOption(strKey, option.second, flags, m_config_args, error)) { + if (!CheckValid(key, value, flags, error)) { return false; } + m_settings.ro_config[section][key].push_back(value); } else { if (ignore_invalid_keys) { LogPrintf("Ignoring unknown configuration value %s\n", option.first); @@ -882,7 +767,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) { { LOCK(cs_args); - m_config_args.clear(); + m_settings.ro_config.clear(); m_config_sections.clear(); } @@ -894,58 +779,64 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) { return false; } - // if there is an -includeconf in the override args, but it is empty, that means the user - // passed '-noincludeconf' on the command line, in which case we should not include anything - bool emptyIncludeConf; + // `-includeconf` cannot be included in the command line arguments except + // as `-noincludeconf` (which indicates that no conf file should be used). + bool use_conf_file{true}; { LOCK(cs_args); - emptyIncludeConf = m_override_args.count("-includeconf") == 0; + if (auto* includes = util::FindKey(m_settings.command_line_options, "includeconf")) { + // ParseParameters() fails if a non-negated -includeconf is passed on the command-line + assert(util::SettingsSpan(*includes).last_negated()); + use_conf_file = false; + } } - if (emptyIncludeConf) { + if (use_conf_file) { std::string chain_id = GetChainName(); - std::vector<std::string> includeconf(GetArgs("-includeconf")); - { - // We haven't set m_network yet (that happens in SelectParams()), so manually check - // for network.includeconf args. - std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); - includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); - } + std::vector<std::string> conf_file_names; - // Remove -includeconf from configuration, so we can warn about recursion - // later - { + auto add_includes = [&](const std::string& network, size_t skip = 0) { + size_t num_values = 0; LOCK(cs_args); - m_config_args.erase("-includeconf"); - m_config_args.erase(std::string("-") + chain_id + ".includeconf"); - } - - for (const std::string& to_include : includeconf) { - fsbridge::ifstream include_config(GetConfigFile(to_include)); - if (include_config.good()) { - if (!ReadConfigStream(include_config, to_include, error, ignore_invalid_keys)) { + if (auto* section = util::FindKey(m_settings.ro_config, network)) { + if (auto* values = util::FindKey(*section, "includeconf")) { + for (size_t i = std::max(skip, util::SettingsSpan(*values).negated()); i < values->size(); ++i) { + conf_file_names.push_back((*values)[i].get_str()); + } + num_values = values->size(); + } + } + return num_values; + }; + + // We haven't set m_network yet (that happens in SelectParams()), so manually check + // for network.includeconf args. + const size_t chain_includes = add_includes(chain_id); + const size_t default_includes = add_includes({}); + + for (const std::string& conf_file_name : conf_file_names) { + fsbridge::ifstream conf_file_stream(GetConfigFile(conf_file_name)); + if (conf_file_stream.good()) { + if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { return false; } - LogPrintf("Included configuration file %s\n", to_include); + LogPrintf("Included configuration file %s\n", conf_file_name); } else { - error = "Failed to include configuration file " + to_include; + error = "Failed to include configuration file " + conf_file_name; return false; } } // Warn about recursive -includeconf - includeconf = GetArgs("-includeconf"); - { - std::vector<std::string> includeconf_net(GetArgs(std::string("-") + chain_id + ".includeconf")); - includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); - std::string chain_id_final = GetChainName(); - if (chain_id_final != chain_id) { - // Also warn about recursive includeconf for the chain that was specified in one of the includeconfs - includeconf_net = GetArgs(std::string("-") + chain_id_final + ".includeconf"); - includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); - } + conf_file_names.clear(); + add_includes(chain_id, /* skip= */ chain_includes); + add_includes({}, /* skip= */ default_includes); + std::string chain_id_final = GetChainName(); + if (chain_id_final != chain_id) { + // Also warn about recursive includeconf for the chain that was specified in one of the includeconfs + add_includes(chain_id_final); } - for (const std::string& to_include : includeconf) { - tfm::format(std::cerr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", to_include); + for (const std::string& conf_file_name : conf_file_names) { + tfm::format(std::cerr, "warning: -includeconf cannot be used from included files; ignoring -includeconf=%s\n", conf_file_name); } } } @@ -961,9 +852,16 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys) std::string ArgsManager::GetChainName() const { - LOCK(cs_args); - const bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); - const bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); + auto get_net = [&](const std::string& arg) { + LOCK(cs_args); + util::SettingsValue value = GetSetting(m_settings, /* section= */ "", SettingName(arg), + /* ignore_default_section_config= */ false, + /* get_chain_name= */ true); + return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); + }; + + const bool fRegTest = get_net("-regtest"); + const bool fTestNet = get_net("-testnet"); const bool is_chain_arg_set = IsArgSet("-chain"); if ((int)is_chain_arg_set + (int)fRegTest + (int)fTestNet > 1) { diff --git a/src/util/system.h b/src/util/system.h index 908a3c407d..e0b6371dc9 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -22,6 +22,7 @@ #include <sync.h> #include <tinyformat.h> #include <util/memory.h> +#include <util/settings.h> #include <util/threadnames.h> #include <util/time.h> @@ -157,8 +158,7 @@ protected: }; mutable CCriticalSection cs_args; - std::map<std::string, std::vector<std::string>> m_override_args GUARDED_BY(cs_args); - std::map<std::string, std::vector<std::string>> m_config_args GUARDED_BY(cs_args); + util::Settings m_settings GUARDED_BY(cs_args); std::string m_network GUARDED_BY(cs_args); std::set<std::string> m_network_only_args GUARDED_BY(cs_args); std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); @@ -265,7 +265,7 @@ public: void ForceSetArg(const std::string& strArg, const std::string& strValue); /** - * Looks for -regtest, -testnet and returns the appropriate BIP70 chain name. + * Returns the appropriate chain name from the program arguments. * @return CBaseChainParams::MAIN by default; raises runtime error if an invalid combination is given. */ std::string GetChainName() const; diff --git a/src/validation.cpp b/src/validation.cpp index ca6d2176b3..e744110371 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -18,6 +18,8 @@ #include <flatfile.h> #include <hash.h> #include <index/txindex.h> +#include <logging.h> +#include <logging/timer.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/settings.h> @@ -107,7 +109,7 @@ CBlockIndex *pindexBestHeader = nullptr; Mutex g_best_block_mutex; std::condition_variable g_best_block_cv; uint256 g_best_block; -int nScriptCheckThreads = 0; +bool g_parallel_script_checks{false}; std::atomic_bool fImporting(false); std::atomic_bool fReindex(false); bool fHavePruned = false; @@ -733,7 +735,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // To check these we first check if we meet the RBF criteria, above, and increment the descendant // limits by the direct conflict and its descendants (as these are recalculated in // CalculateMempoolAncestors by assuming the new transaction being added is a new descendant, with no - // removals, of each parent's existing dependant set). The ancestor count limits are unmodified (as + // removals, of each parent's existing dependent set). The ancestor count limits are unmodified (as // the ancestor limits should be the same for both our new transaction and any conflicts). // We don't bother incrementing m_limit_descendants by the full removal count as that limit never comes // into force here (as we're only adding a single transaction). @@ -2069,7 +2071,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockUndo blockundo; - CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : nullptr); + CCheckQueueControl<CScriptCheck> control(fScriptChecks && g_parallel_script_checks ? &scriptcheckqueue : nullptr); std::vector<int> prevheights; CAmount nFees = 0; @@ -2130,7 +2132,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, std::vector<CScriptCheck> vChecks; bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */ TxValidationState tx_state; - if (fScriptChecks && !CheckInputs(tx, tx_state, view, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr)) { + if (fScriptChecks && !CheckInputs(tx, tx_state, view, flags, fCacheResults, fCacheResults, txdata[i], g_parallel_script_checks ? &vChecks : nullptr)) { // Any transaction validation failure in ConnectBlock is a block consensus failure state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); @@ -2199,6 +2201,10 @@ bool CChainState::FlushStateToDisk( static int64_t nLastFlush = 0; std::set<int> setFilesToPrune; bool full_flush_completed = false; + + const size_t coins_count = CoinsTip().GetCacheSize(); + const size_t coins_mem_usage = CoinsTip().DynamicMemoryUsage(); + try { { bool fFlushForPrune = false; @@ -2206,8 +2212,12 @@ bool CChainState::FlushStateToDisk( LOCK(cs_LastBlockFile); if (fPruneMode && (fCheckForPruning || nManualPruneHeight > 0) && !fReindex) { if (nManualPruneHeight > 0) { + LOG_TIME_MILLIS("find files to prune (manual)", BCLog::BENCH); + FindFilesToPruneManual(setFilesToPrune, nManualPruneHeight); } else { + LOG_TIME_MILLIS("find files to prune", BCLog::BENCH); + FindFilesToPrune(setFilesToPrune, chainparams.PruneAfterHeight()); fCheckForPruning = false; } @@ -2246,10 +2256,17 @@ bool CChainState::FlushStateToDisk( if (!CheckDiskSpace(GetBlocksDir())) { return AbortNode(state, "Disk space is too low!", _("Error: Disk space is too low!").translated, CClientUIInterface::MSG_NOPREFIX); } - // First make sure all block and undo data is flushed to disk. - FlushBlockFile(); + { + LOG_TIME_MILLIS("write block and undo data to disk", BCLog::BENCH); + + // First make sure all block and undo data is flushed to disk. + FlushBlockFile(); + } + // Then update all block file information (which may refer to block and undo files). { + LOG_TIME_MILLIS("write block index to disk", BCLog::BENCH); + std::vector<std::pair<int, const CBlockFileInfo*> > vFiles; vFiles.reserve(setDirtyFileInfo.size()); for (std::set<int>::iterator it = setDirtyFileInfo.begin(); it != setDirtyFileInfo.end(); ) { @@ -2267,12 +2284,18 @@ bool CChainState::FlushStateToDisk( } } // Finally remove any pruned files - if (fFlushForPrune) + if (fFlushForPrune) { + LOG_TIME_MILLIS("unlink pruned files", BCLog::BENCH); + UnlinkPrunedFiles(setFilesToPrune); + } nLastWrite = nNow; } // Flush best chain related state. This can only be done if the blocks / block index write was also done. if (fDoFullFlush && !CoinsTip().GetBestBlock().IsNull()) { + LOG_TIME_SECONDS(strprintf("write coins cache to disk (%d coins, %.2fkB)", + coins_count, coins_mem_usage / 1000)); + // Typical Coin structures on disk are around 48 bytes in size. // Pushing a new one to the database can cause it to be written // twice (once in the log, and once in the tables). This is already @@ -2435,7 +2458,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, const CChainParams& UpdateTip(pindexDelete->pprev, chainparams); // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: - GetMainSignals().BlockDisconnected(pblock); + GetMainSignals().BlockDisconnected(pblock, pindexDelete); return true; } @@ -5066,7 +5089,7 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pin fTxTotal = pindex->nChainTx + (nNow - pindex->GetBlockTime()) * data.dTxRate; } - return pindex->nChainTx / fTxTotal; + return std::min<double>(pindex->nChainTx / fTxTotal, 1.0); } class CMainCleanup diff --git a/src/validation.h b/src/validation.h index 7f9582adfd..31721cfbf7 100644 --- a/src/validation.h +++ b/src/validation.h @@ -21,6 +21,7 @@ #include <txmempool.h> // For CTxMemPool::cs #include <txdb.h> #include <versionbits.h> +#include <serialize.h> #include <algorithm> #include <atomic> @@ -76,8 +77,8 @@ static const unsigned int BLOCKFILE_CHUNK_SIZE = 0x1000000; // 16 MiB /** The pre-allocation chunk size for rev?????.dat files (since 0.8) */ static const unsigned int UNDOFILE_CHUNK_SIZE = 0x100000; // 1 MiB -/** Maximum number of script-checking threads allowed */ -static const int MAX_SCRIPTCHECK_THREADS = 16; +/** Maximum number of dedicated script-checking threads allowed */ +static const int MAX_SCRIPTCHECK_THREADS = 15; /** -par default (number of script-checking threads, 0 = auto) */ static const int DEFAULT_SCRIPTCHECK_THREADS = 0; /** Number of blocks that can be requested at any given time from a single peer. */ @@ -146,7 +147,10 @@ extern std::condition_variable g_best_block_cv; extern uint256 g_best_block; extern std::atomic_bool fImporting; extern std::atomic_bool fReindex; -extern int nScriptCheckThreads; +/** Whether there are dedicated script-checking threads running. + * False indicates all script checking is done on the main threadMessageHandler thread. + */ +extern bool g_parallel_script_checks; extern bool fRequireStandard; extern bool fCheckBlockIndex; extern bool fCheckpointsEnabled; diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index a46b4003f1..663308bae9 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -29,7 +29,7 @@ struct MainSignalsInstance { boost::signals2::signal<void (const CBlockIndex *, const CBlockIndex *, bool fInitialDownload)> UpdatedBlockTip; boost::signals2::signal<void (const CTransactionRef &)> TransactionAddedToMempool; boost::signals2::signal<void (const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::vector<CTransactionRef>&)> BlockConnected; - boost::signals2::signal<void (const std::shared_ptr<const CBlock> &)> BlockDisconnected; + boost::signals2::signal<void (const std::shared_ptr<const CBlock>&, const CBlockIndex* pindex)> BlockDisconnected; boost::signals2::signal<void (const CTransactionRef &)> TransactionRemovedFromMempool; boost::signals2::signal<void (const CBlockLocator &)> ChainStateFlushed; boost::signals2::signal<void (const CBlock&, const BlockValidationState&)> BlockChecked; @@ -92,7 +92,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { conns.UpdatedBlockTip = g_signals.m_internals->UpdatedBlockTip.connect(std::bind(&CValidationInterface::UpdatedBlockTip, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); conns.TransactionAddedToMempool = g_signals.m_internals->TransactionAddedToMempool.connect(std::bind(&CValidationInterface::TransactionAddedToMempool, pwalletIn, std::placeholders::_1)); conns.BlockConnected = g_signals.m_internals->BlockConnected.connect(std::bind(&CValidationInterface::BlockConnected, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1)); + conns.BlockDisconnected = g_signals.m_internals->BlockDisconnected.connect(std::bind(&CValidationInterface::BlockDisconnected, pwalletIn, std::placeholders::_1, std::placeholders::_2)); conns.TransactionRemovedFromMempool = g_signals.m_internals->TransactionRemovedFromMempool.connect(std::bind(&CValidationInterface::TransactionRemovedFromMempool, pwalletIn, std::placeholders::_1)); conns.ChainStateFlushed = g_signals.m_internals->ChainStateFlushed.connect(std::bind(&CValidationInterface::ChainStateFlushed, pwalletIn, std::placeholders::_1)); conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); @@ -156,9 +156,10 @@ void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, c }); } -void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock> &pblock) { - m_internals->m_schedulerClient.AddToProcessQueue([pblock, this] { - m_internals->BlockDisconnected(pblock); +void CMainSignals::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex) +{ + m_internals->m_schedulerClient.AddToProcessQueue([pblock, pindex, this] { + m_internals->BlockDisconnected(pblock, pindex); }); } diff --git a/src/validationinterface.h b/src/validationinterface.h index dc8425869b..6a8059a6a0 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -114,7 +114,7 @@ protected: * * Called on a background thread. */ - virtual void BlockDisconnected(const std::shared_ptr<const CBlock> &block) {} + virtual void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) {} /** * Notifies listeners of the new active block chain on-disk. * @@ -178,7 +178,7 @@ public: void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); void TransactionAddedToMempool(const CTransactionRef &); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex, const std::shared_ptr<const std::vector<CTransactionRef>> &); - void BlockDisconnected(const std::shared_ptr<const CBlock> &); + void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex); void ChainStateFlushed(const CBlockLocator &); void BlockChecked(const CBlock&, const BlockValidationState&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr<const CBlock>&); diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index e48eee6c2c..79825847c8 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -166,10 +166,9 @@ BerkeleyEnvironment::~BerkeleyEnvironment() bool BerkeleyEnvironment::Open(bool retry) { - if (fDbEnvInit) + if (fDbEnvInit) { return true; - - boost::this_thread::interruption_point(); + } fs::path pathIn = strPath; TryCreateDirectories(pathIn); @@ -238,14 +237,12 @@ bool BerkeleyEnvironment::Open(bool retry) return true; } -//! Construct an in-memory mock Berkeley environment for testing and as a place-holder for g_dbenvs emplace +//! Construct an in-memory mock Berkeley environment for testing BerkeleyEnvironment::BerkeleyEnvironment() { Reset(); - boost::this_thread::interruption_point(); - - LogPrint(BCLog::DB, "BerkeleyEnvironment::MakeMock\n"); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::MakeMock\n"); dbenv->set_cachesize(1, 0, 1); dbenv->set_lg_bsize(10485760 * 4); @@ -263,8 +260,9 @@ BerkeleyEnvironment::BerkeleyEnvironment() DB_THREAD | DB_PRIVATE, S_IRUSR | S_IWUSR); - if (ret > 0) + if (ret > 0) { throw std::runtime_error(strprintf("BerkeleyEnvironment::MakeMock: Error %d opening database environment.", ret)); + } fDbEnvInit = true; fMockDb = true; @@ -767,7 +765,7 @@ void BerkeleyEnvironment::Flush(bool fShutdown) { int64_t nStart = GetTimeMillis(); // Flush log data to the actual data file on all files that are not in use - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n", strPath, fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started"); if (!fDbEnvInit) return; { @@ -776,21 +774,21 @@ void BerkeleyEnvironment::Flush(bool fShutdown) while (mi != mapFileUseCount.end()) { std::string strFile = (*mi).first; int nRefCount = (*mi).second; - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount); if (nRefCount == 0) { // Move log data to the dat file CloseDb(strFile); - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s checkpoint\n", strFile); dbenv->txn_checkpoint(0, 0, 0); - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s detach\n", strFile); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s detach\n", strFile); if (!fMockDb) dbenv->lsn_reset(strFile.c_str(), 0); - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: %s closed\n", strFile); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: %s closed\n", strFile); mapFileUseCount.erase(mi++); } else mi++; } - LogPrint(BCLog::DB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart); if (fShutdown) { char** listp; if (mapFileUseCount.empty()) { @@ -830,7 +828,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database) std::map<std::string, int>::iterator mi = env->mapFileUseCount.find(strFile); if (mi != env->mapFileUseCount.end()) { - LogPrint(BCLog::DB, "Flushing %s\n", strFile); + LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile); int64_t nStart = GetTimeMillis(); // Flush wallet file so it's self contained @@ -838,7 +836,7 @@ bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase& database) env->CheckpointLSN(strFile); env->mapFileUseCount.erase(mi++); - LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); + LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart); ret = true; } } diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 0a4bb3f396..8f0b495ac4 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -16,7 +16,7 @@ //! Check whether transaction has descendant in wallet or mempool, or has been //! mined, or conflicts with a mined transaction. Return a feebumper::Result. -static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chain, const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWalletTx& wtx, std::vector<std::string>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { if (wallet.HasWalletSpend(wtx.GetHash())) { errors.push_back("Transaction has descendants in the wallet"); @@ -30,7 +30,7 @@ static feebumper::Result PreconditionChecks(interfaces::Chain::Lock& locked_chai } } - if (wtx.GetDepthInMainChain(locked_chain) != 0) { + if (wtx.GetDepthInMainChain() != 0) { errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); return feebumper::Result::WALLET_ERROR; } @@ -146,7 +146,7 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid) if (wtx == nullptr) return false; std::vector<std::string> errors_dummy; - feebumper::Result res = PreconditionChecks(*locked_chain, wallet, *wtx, errors_dummy); + feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); return res == feebumper::Result::OK; } @@ -165,7 +165,7 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(*locked_chain, *wallet, wtx, errors); + Result result = PreconditionChecks(*wallet, wtx, errors); if (result != Result::OK) { return result; } @@ -291,7 +291,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } const CWalletTx& wtx = it->second; - Result result = PreconditionChecks(*locked_chain, wallet, wtx, errors); + Result result = PreconditionChecks(wallet, wtx, errors); if (result != Result::OK) { return result; } @@ -382,7 +382,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti CWalletTx& oldWtx = it->second; // make sure the transaction still has no descendants and hasn't been mined in the meantime - Result result = PreconditionChecks(*locked_chain, wallet, oldWtx, errors); + Result result = PreconditionChecks(wallet, oldWtx, errors); if (result != Result::OK) { return result; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 0eef0502de..d95481500d 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -125,10 +125,7 @@ UniValue importprivkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + EnsureLegacyScriptPubKeyMan(*wallet); WalletRescanReserver reserver(pwallet); bool fRescan = true; @@ -162,7 +159,7 @@ UniValue importprivkey(const JSONRPCRequest& request) if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); + CHECK_NONFATAL(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); @@ -256,10 +253,7 @@ UniValue importaddress(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + EnsureLegacyScriptPubKeyMan(*pwallet); std::string strLabel; if (!request.params[1].isNull()) @@ -322,7 +316,7 @@ UniValue importaddress(const JSONRPCRequest& request) { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(*locked_chain); + pwallet->ReacceptWalletTransactions(); } } @@ -360,28 +354,26 @@ UniValue importprunedfunds(const JSONRPCRequest& request) //Search partial merkle tree in proof for our transaction and index in valid block std::vector<uint256> vMatch; std::vector<unsigned int> vIndex; - unsigned int txnIndex = 0; - if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) == merkleBlock.header.hashMerkleRoot) { - - auto locked_chain = pwallet->chain().lock(); - if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) == nullopt) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); - } - - std::vector<uint256>::const_iterator it; - if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx))==vMatch.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); - } + if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); + } - txnIndex = vIndex[it - vMatch.begin()]; + auto locked_chain = pwallet->chain().lock(); + Optional<int> height = locked_chain->getBlockHeight(merkleBlock.header.GetHash()); + if (height == nullopt) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } - else { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); + + std::vector<uint256>::const_iterator it; + if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); } - wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(), txnIndex); + unsigned int txnIndex = vIndex[it - vMatch.begin()]; + + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *height, merkleBlock.header.GetHash(), txnIndex); + wtx.m_confirm = confirm; - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (pwallet->IsMine(*wtx.tx)) { @@ -462,10 +454,7 @@ UniValue importpubkey(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + EnsureLegacyScriptPubKeyMan(*wallet); std::string strLabel; if (!request.params[1].isNull()) @@ -516,7 +505,7 @@ UniValue importpubkey(const JSONRPCRequest& request) { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(*locked_chain); + pwallet->ReacceptWalletTransactions(); } } @@ -549,10 +538,7 @@ UniValue importwallet(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + EnsureLegacyScriptPubKeyMan(*wallet); if (pwallet->chain().havePruned()) { // Exit early and print an error. @@ -642,7 +628,7 @@ UniValue importwallet(const JSONRPCRequest& request) std::string label = std::get<3>(key_tuple); CPubKey pubkey = key.GetPubKey(); - assert(key.VerifyPubKey(pubkey)); + CHECK_NONFATAL(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid))); @@ -711,10 +697,7 @@ UniValue dumpprivkey(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -726,12 +709,12 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } - auto keyid = GetKeyForDestination(*spk_man, dest); + auto keyid = GetKeyForDestination(spk_man, dest); if (keyid.IsNull()) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; - if (!spk_man->GetKey(keyid, vchSecret)) { + if (!spk_man.GetKey(keyid, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return EncodeSecret(vchSecret); @@ -765,14 +748,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - AssertLockHeld(spk_man->cs_wallet); + AssertLockHeld(spk_man.cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -794,10 +774,10 @@ UniValue dumpwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); std::map<CKeyID, int64_t> mapKeyBirth; - const std::map<CKeyID, int64_t>& mapKeyPool = spk_man->GetAllReserveKeys(); + const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys(); pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); - std::set<CScriptID> scripts = spk_man->GetCScripts(); + std::set<CScriptID> scripts = spk_man.GetCScripts(); // sort time/key pairs std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; @@ -816,11 +796,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) file << "\n"; // add the base58check encoded extended master if the wallet uses HD - CKeyID seed_id = spk_man->GetHDChain().seed_id; + CKeyID seed_id = spk_man.GetHDChain().seed_id; if (!seed_id.IsNull()) { CKey seed; - if (spk_man->GetKey(seed_id, seed)) { + if (spk_man.GetKey(seed_id, seed)) { CExtKey masterKey; masterKey.SetSeed(seed.begin(), seed.size()); @@ -833,20 +813,20 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::string strAddr; std::string strLabel; CKey key; - if (spk_man->GetKey(keyid, key)) { + if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); - if (GetWalletAddressesForKey(spk_man, pwallet, keyid, strAddr, strLabel)) { + if (GetWalletAddressesForKey(&spk_man, pwallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; - } else if (spk_man->mapKeyMetadata[keyid].hdKeypath == "s") { + } else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") { file << "inactivehdseed=1"; } else { file << "change=1"; } - file << strprintf(" # addr=%s%s\n", strAddr, (spk_man->mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(spk_man->mapKeyMetadata[keyid].key_origin.path) : "")); + file << strprintf(" # addr=%s%s\n", strAddr, (spk_man.mapKeyMetadata[keyid].has_key_origin ? " hdkeypath="+WriteHDKeypath(spk_man.mapKeyMetadata[keyid].key_origin.path) : "")); } } file << "\n"; @@ -855,11 +835,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::string create_time = "0"; std::string address = EncodeDestination(ScriptHash(scriptid)); // get birth times for scripts with metadata - auto it = spk_man->m_script_metadata.find(scriptid); - if (it != spk_man->m_script_metadata.end()) { + auto it = spk_man.m_script_metadata.find(scriptid); + if (it != spk_man.m_script_metadata.end()) { create_time = FormatISO8601DateTime(it->second.nCreateTime); } - if(spk_man->GetCScript(scriptid, script)) { + if(spk_man.GetCScript(scriptid, script)) { file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); file << strprintf(" # addr=%s\n", address); } @@ -915,7 +895,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d case TX_SCRIPTHASH: { if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); - assert(script_ctx == ScriptContext::TOP); + CHECK_NONFATAL(script_ctx == ScriptContext::TOP); CScriptID id = CScriptID(uint160(solverdata[0])); auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. if (!subscript) return "missing redeemscript"; @@ -1355,10 +1335,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + EnsureLegacyScriptPubKeyMan(*wallet); const UniValue& requests = mainRequest.params[0]; @@ -1427,7 +1404,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->ReacceptWalletTransactions(*locked_chain); + pwallet->ReacceptWalletTransactions(); } if (pwallet->IsAbortingRescan()) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index bfa4cf2bbe..00742aed7a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -124,19 +124,29 @@ void EnsureWalletIsUnlocked(const CWallet* pwallet) } } +LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet) +{ + LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan(); + if (!spk_man) { + throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); + } + return *spk_man; +} + static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) { - int confirms = wtx.GetDepthInMainChain(locked_chain); + int confirms = wtx.GetDepthInMainChain(); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) entry.pushKV("generated", true); if (confirms > 0) { entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); + entry.pushKV("blockheight", wtx.m_confirm.block_height); entry.pushKV("blockindex", wtx.m_confirm.nIndex); int64_t block_time; bool found_block = chain.findBlock(wtx.m_confirm.hashBlock, nullptr /* block */, &block_time); - assert(found_block); + CHECK_NONFATAL(found_block); entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); @@ -631,7 +641,7 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) - if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) + if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; } @@ -697,7 +707,7 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && pwallet->IsMine(address) && setAddress.count(address)) { - if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) + if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; } } @@ -966,10 +976,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); @@ -987,7 +994,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); } else { - pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); + pubkeys.push_back(AddrToPubKey(&spk_man, keys_or_addrs[i].get_str())); } } @@ -1000,7 +1007,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) // Construct using pay-to-script-hash: CScript inner; - CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *spk_man, inner); + CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner); pwallet->SetAddressBook(dest, label, "send"); UniValue result(UniValue::VOBJ); @@ -1057,7 +1064,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, CWallet * co continue; } - int nDepth = wtx.GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(); if (nDepth < nMinDepth) continue; @@ -1314,8 +1321,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con } // Received - if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) - { + if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { for (const COutputEntry& r : listReceived) { std::string label; @@ -1332,9 +1338,9 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* con MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { - if (wtx.GetDepthInMainChain(locked_chain) < 1) + if (wtx.GetDepthInMainChain() < 1) entry.pushKV("category", "orphan"); - else if (wtx.IsImmatureCoinBase(locked_chain)) + else if (wtx.IsImmatureCoinBase()) entry.pushKV("category", "immature"); else entry.pushKV("category", "generate"); @@ -1362,6 +1368,7 @@ static const std::string TransactionDescriptionString() " \"generated\": xxx, (bool) Only present if transaction only input is a coinbase one.\n" " \"trusted\": xxx, (bool) Only present if we consider transaction to be trusted and so safe to spend from.\n" " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" + " \"blockheight\": n, (numeric) The block height containing the transaction.\n" " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" " \"txid\": \"transactionid\", (string) The transaction id.\n" @@ -1598,7 +1605,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { + if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1715,7 +1722,7 @@ static UniValue gettransaction(const JSONRPCRequest& request) } const CWalletTx& wtx = it->second; - CAmount nCredit = wtx.GetCredit(*locked_chain, filter); + CAmount nCredit = wtx.GetCredit(filter); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); @@ -1779,7 +1786,7 @@ static UniValue abandontransaction(const JSONRPCRequest& request) if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } - if (!pwallet->AbandonTransaction(*locked_chain, hash)) { + if (!pwallet->AbandonTransaction(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } @@ -2210,7 +2217,7 @@ static UniValue lockunspent(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } - if (pwallet->IsSpent(*locked_chain, outpt.hash, outpt.n)) { + if (pwallet->IsSpent(outpt.hash, outpt.n)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } @@ -2943,7 +2950,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CTxDestination witness_destination; if (redeemScript.IsPayToWitnessScriptHash()) { bool extracted = ExtractDestination(redeemScript, witness_destination); - assert(extracted); + CHECK_NONFATAL(extracted); // Also return the witness script const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); CScriptID id; @@ -3756,26 +3763,16 @@ UniValue getaddressinfo(const JSONRPCRequest& request) ret.pushKV("label", pwallet->mapAddressBook[dest].name); } ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); - const CKeyMetadata* meta = nullptr; - CKeyID key_id = GetKeyForDestination(*provider, dest); - if (!key_id.IsNull()) { - auto it = pwallet->mapKeyMetadata.find(key_id); - if (it != pwallet->mapKeyMetadata.end()) { - meta = &it->second; - } - } - if (!meta) { - auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey)); - if (it != pwallet->m_script_metadata.end()) { - meta = &it->second; - } - } - if (meta) { - ret.pushKV("timestamp", meta->nCreateTime); - if (meta->has_key_origin) { - ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path)); - ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); - ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4)); + + ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(); + if (spk_man) { + if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) { + ret.pushKV("timestamp", meta->nCreateTime); + if (meta->has_key_origin) { + ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path)); + ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); + ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4)); + } } } @@ -3833,7 +3830,7 @@ static UniValue getaddressesbylabel(const JSONRPCRequest& request) // address strings, but build a separate set as a precaution just in // case it does. bool unique = addresses.emplace(address).second; - assert(unique); + CHECK_NONFATAL(unique); // UniValue::pushKV checks if the key exists in O(N) // and since duplicate addresses are unexpected (checked with // std::set in O(log(N))), UniValue::__pushKV is used instead, @@ -3935,10 +3932,7 @@ UniValue sethdseed(const JSONRPCRequest& request) }, }.Check(request); - LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); - if (!spk_man) { - throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); - } + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); @@ -3965,22 +3959,22 @@ UniValue sethdseed(const JSONRPCRequest& request) CPubKey master_pub_key; if (request.params[1].isNull()) { - master_pub_key = spk_man->GenerateNewSeed(); + master_pub_key = spk_man.GenerateNewSeed(); } else { CKey key = DecodeSecret(request.params[1].get_str()); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } - if (HaveKey(*spk_man, key)) { + if (HaveKey(spk_man, key)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)"); } - master_pub_key = spk_man->DeriveNewSeed(key); + master_pub_key = spk_man.DeriveNewSeed(key); } - spk_man->SetHDSeed(master_pub_key); - if (flush_key_pool) spk_man->NewKeyPool(); + spk_man.SetHDSeed(master_pub_key); + if (flush_key_pool) spk_man.NewKeyPool(); return NullUniValue; } diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index 31d3f7a5f9..becca455f6 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -12,6 +12,7 @@ class CRPCTable; class CWallet; class JSONRPCRequest; +class LegacyScriptPubKeyMan; class UniValue; struct PartiallySignedTransaction; class CTransaction; @@ -40,6 +41,7 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques std::string HelpRequiringPassphrase(const CWallet*); void EnsureWalletIsUnlocked(const CWallet*); bool EnsureWalletIsAvailable(const CWallet*, bool avoidException); +LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index c13fddfaf3..3eaaf3786c 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -11,11 +11,10 @@ #include <wallet/scriptpubkeyman.h> #include <wallet/wallet.h> -bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) +bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { - LOCK(cs_wallet); error.clear(); - TopUpKeyPool(); + TopUp(); // Generate a new key that is added to wallet CPubKey new_key; @@ -25,8 +24,6 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, const std:: } LearnRelatedScripts(new_key, type); dest = GetDestinationForKey(new_key, type); - - m_wallet.SetAddressBook(dest, label, "receive"); return true; } @@ -265,6 +262,41 @@ bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn) return true; } +bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) +{ + if (!ReserveKeyFromKeyPool(index, keypool, internal)) { + return false; + } + return true; +} + +void LegacyScriptPubKeyMan::KeepDestination(int64_t index) +{ + KeepKey(index); +} + +void LegacyScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) +{ + ReturnKey(index, internal, pubkey); +} + +void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script) +{ + AssertLockHeld(cs_wallet); + // extract addresses and check if they match with an unused keypool key + for (const auto& keyid : GetAffectedKeys(script, *this)) { + std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); + if (mi != m_pool_key_to_index.end()) { + WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); + MarkReserveKeysAsUsed(mi->second); + + if (!TopUp()) { + WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); + } + } + } +} + void LegacyScriptPubKeyMan::UpgradeKeyMetadata() { AssertLockHeld(cs_wallet); @@ -298,8 +330,19 @@ void LegacyScriptPubKeyMan::UpgradeKeyMetadata() } } } - batch.reset(); //write before setting the flag - m_storage.SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); +} + +bool LegacyScriptPubKeyMan::SetupGeneration(bool force) +{ + if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) { + return false; + } + + SetHDSeed(GenerateNewSeed()); + if (!NewKeyPool()) { + return false; + } + return true; } bool LegacyScriptPubKeyMan::IsHDEnabled() const @@ -324,6 +367,58 @@ bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) return keypool_has_keys; } +bool LegacyScriptPubKeyMan::Upgrade(int prev_version, std::string& error) +{ + AssertLockHeld(cs_wallet); + error = ""; + bool hd_upgrade = false; + bool split_upgrade = false; + if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) { + WalletLogPrintf("Upgrading wallet to HD\n"); + m_storage.SetMinVersion(FEATURE_HD); + + // generate a new master key + CPubKey masterPubKey = GenerateNewSeed(); + SetHDSeed(masterPubKey); + hd_upgrade = true; + } + // Upgrade to HD chain split if necessary + if (m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) { + WalletLogPrintf("Upgrading wallet to use HD chain split\n"); + m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); + split_upgrade = FEATURE_HD_SPLIT > prev_version; + } + // Mark all keys currently in the keypool as pre-split + if (split_upgrade) { + MarkPreSplitKeys(); + } + // Regenerate the keypool if upgraded to HD + if (hd_upgrade) { + if (!TopUp()) { + error = _("Unable to generate keys").translated; + return false; + } + } + return true; +} + +bool LegacyScriptPubKeyMan::HavePrivateKeys() const +{ + LOCK(cs_KeyStore); + return !mapKeys.empty() || !mapCryptedKeys.empty(); +} + +void LegacyScriptPubKeyMan::RewriteDB() +{ + AssertLockHeld(cs_wallet); + setInternalKeyPool.clear(); + setExternalKeyPool.clear(); + m_pool_key_to_index.clear(); + // Note: can't top-up keypool here, because wallet is locked. + // User will be prompted to unlock wallet the next operation + // that requires a new key. +} + static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, WalletBatch& batch) { if (setKeyPool.empty()) { return GetTime(); @@ -362,6 +457,39 @@ size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() return setExternalKeyPool.size() + set_pre_split_keypool.size(); } +unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const +{ + AssertLockHeld(cs_wallet); + return setInternalKeyPool.size() + setExternalKeyPool.size(); +} + +int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const +{ + AssertLockHeld(cs_wallet); + return nTimeFirstKey; +} + +const CKeyMetadata* LegacyScriptPubKeyMan::GetMetadata(const CTxDestination& dest) const +{ + AssertLockHeld(cs_wallet); + + CKeyID key_id = GetKeyForDestination(*this, dest); + if (!key_id.IsNull()) { + auto it = mapKeyMetadata.find(key_id); + if (it != mapKeyMetadata.end()) { + return &it->second; + } + } + + CScript scriptPubKey = GetScriptForDestination(dest); + auto it = m_script_metadata.find(CScriptID(scriptPubKey)); + if (it != m_script_metadata.end()) { + return &it->second; + } + + return nullptr; +} + /** * Update wallet first key creation time. This should be called whenever keys * are added to the wallet, with the oldest key creation time. @@ -378,6 +506,11 @@ void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime) } } +bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey) +{ + return AddKeyPubKeyInner(key, pubkey); +} + bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) { WalletBatch batch(m_storage.GetDatabase()); @@ -386,6 +519,8 @@ bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey& secret, const CPubKey &pubk bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& secret, const CPubKey& pubkey) { + AssertLockHeld(cs_wallet); + // Make sure we aren't adding private keys to private key disabled wallets assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); @@ -418,7 +553,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& s secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } - m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + m_storage.UnsetBlankWalletFlag(batch); return true; } @@ -575,7 +710,7 @@ bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch, const CScript UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); if (batch.WriteWatchOnly(dest, meta)) { - m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + m_storage.UnsetBlankWalletFlag(batch); return true; } return false; @@ -853,7 +988,8 @@ void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey& seed) newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); NotifyCanGetAddressesChanged(); - m_wallet.UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + WalletBatch batch(m_storage.GetDatabase()); + m_storage.UnsetBlankWalletFlag(batch); } /** @@ -886,7 +1022,7 @@ bool LegacyScriptPubKeyMan::NewKeyPool() m_pool_key_to_index.clear(); - if (!TopUpKeyPool()) { + if (!TopUp()) { return false; } WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n"); @@ -894,7 +1030,7 @@ bool LegacyScriptPubKeyMan::NewKeyPool() return true; } -bool LegacyScriptPubKeyMan::TopUpKeyPool(unsigned int kpSize) +bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize) { if (!CanGenerateKeys()) { return false; @@ -1011,7 +1147,7 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key { LOCK(cs_wallet); - TopUpKeyPool(); + TopUp(); bool fReturningInternal = fRequestedInternal; fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); @@ -1132,7 +1268,7 @@ bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch& batch, const CScript& if (!FillableSigningProvider::AddCScript(redeemScript)) return false; if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { - m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); + m_storage.UnsetBlankWalletFlag(batch); return true; } return false; @@ -1227,7 +1363,7 @@ bool LegacyScriptPubKeyMan::ImportPubKeys(const std::vector<CKeyID>& ordered_pub return true; } -bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) +bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp) { WalletBatch batch(m_storage.GetDatabase()); for (const CScript& script : script_pub_keys) { @@ -1236,11 +1372,6 @@ bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::string& label, const return false; } } - CTxDestination dest; - ExtractDestination(script, dest); - if (apply_label && IsValidDestination(dest)) { - m_wallet.SetAddressBookWithDB(batch, dest, label, "receive"); - } } return true; } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 55184098b7..4f17156792 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -20,7 +20,7 @@ enum class OutputType; // It provides access to things that are part of the entire wallet and not specific to a ScriptPubKeyMan such as // wallet flags, wallet version, encryption keys, encryption status, and the database itself. This allows a // ScriptPubKeyMan to have callbacks into CWallet without causing a circular dependency. -// WalletStorage should be the same for all ScriptPubKeyMans. +// WalletStorage should be the same for all ScriptPubKeyMans of a wallet. class WalletStorage { public: @@ -28,8 +28,7 @@ public: virtual const std::string GetDisplayName() const = 0; virtual WalletDatabase& GetDatabase() = 0; virtual bool IsWalletFlagSet(uint64_t) const = 0; - virtual void SetWalletFlag(uint64_t) = 0; - virtual void UnsetWalletFlagWithDB(WalletBatch&, uint64_t) = 0; + virtual void UnsetBlankWalletFlag(WalletBatch&) = 0; virtual bool CanSupportFeature(enum WalletFeature) const = 0; virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0; virtual bool IsLocked() const = 0; @@ -38,6 +37,8 @@ public: //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; +std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider); + /** A key from a CWallet's keypool * * The wallet holds one (for pre HD-split wallets) or several keypools. These @@ -145,41 +146,69 @@ protected: public: ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {} + virtual ~ScriptPubKeyMan() {}; + virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; } + virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; } + + virtual bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return false; } + virtual void KeepDestination(int64_t index) {} + virtual void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) {} + + virtual bool TopUp(unsigned int size = 0) { return false; } + + //! Mark unused addresses as being used + virtual void MarkUnusedAddresses(const CScript& script) {} + + /** Sets up the key generation stuff, i.e. generates new HD seeds and sets them as active. + * Returns false if already setup or setup fails, true if setup is successful + * Set force=true to make it re-setup if already setup, used for upgrades + */ + virtual bool SetupGeneration(bool force = false) { return false; } + + /* Returns true if HD is enabled */ + virtual bool IsHDEnabled() const { return false; } + + /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ + virtual bool CanGetAddresses(bool internal = false) { return false; } + + /** Upgrades the wallet to the specified version */ + virtual bool Upgrade(int prev_version, std::string& error) { return false; } + + virtual bool HavePrivateKeys() const { return false; } + + //! The action to do when the DB needs rewrite + virtual void RewriteDB() {} + + virtual int64_t GetOldestKeyPoolTime() { return GetTime(); } + + virtual size_t KeypoolCountExternalKeys() { return 0; } + virtual unsigned int GetKeyPoolSize() const { return 0; } + + virtual int64_t GetTimeFirstKey() const { return 0; } + + //! Return address metadata + virtual const CKeyMetadata* GetMetadata(const CTxDestination& dest) const { return nullptr; } }; class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: - using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; using WatchOnlySet = std::set<CScript>; using WatchKeyMap = std::map<CKeyID, CPubKey>; - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; + + using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>; CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); - bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); - - WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; - - /* the HD chain data model (external chain counters) */ - CHDChain hdChain; - - /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - - std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet); - std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); - std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); - int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; - std::map<CKeyID, int64_t> m_pool_key_to_index; - int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; + bool AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey); + bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); + /** * Private version of AddWatchOnly method which does not accept a * timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if @@ -192,26 +221,91 @@ private: bool AddWatchOnly(const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyInMem(const CScript &dest); - - /** Add a KeyOriginInfo to the wallet */ - bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); + //! Adds a watch-only address to the store, and saves it to disk. + bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKeyWithDB(WalletBatch &batch,const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript& dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch); //! Adds a script to the store and saves it to disk bool AddCScriptWithDB(WalletBatch& batch, const CScript& script); - public: + /** Add a KeyOriginInfo to the wallet */ + bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info); + + /* the HD chain data model (external chain counters) */ + CHDChain hdChain; + + /* HD derive new child key (on internal or external chain) */ + void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet); + std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); + std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); + int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; + std::map<CKeyID, int64_t> m_pool_key_to_index; + //! Fetches a key from the keypool bool GetKeyFromPool(CPubKey &key, bool internal = false); - void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /** + * Reserves a key from the keypool and sets nIndex to its index + * + * @param[out] nIndex the index of the key in keypool + * @param[out] keypool the keypool the key was drawn from, which could be the + * the pre-split pool if present, or the internal or external pool + * @param fRequestedInternal true if the caller would like the key drawn + * from the internal keypool, false if external is preferred + * + * @return true if succeeded, false if failed due to empty keypool + * @throws std::runtime_error if keypool read failed, key was invalid, + * was not found in the wallet, or was misclassified in the internal + * or external keypool + */ + bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); + + void KeepKey(int64_t nIndex); + void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); + +public: + bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override; + isminetype IsMine(const CScript& script) const override; + + //! will encrypt previously unencrypted keys + bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + + bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; + void KeepDestination(int64_t index) override; + void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) override; + + bool TopUp(unsigned int size = 0) override; + + void MarkUnusedAddresses(const CScript& script) override; + + //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo + void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + bool IsHDEnabled() const override; + + bool SetupGeneration(bool force = false) override; + + bool Upgrade(int prev_version, std::string& error) override; + + bool HavePrivateKeys() const override; + + void RewriteDB() override; + + int64_t GetOldestKeyPoolTime() override; + size_t KeypoolCountExternalKeys() override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + unsigned int GetKeyPoolSize() const override; + + int64_t GetTimeFirstKey() const override; + + const CKeyMetadata* GetMetadata(const CTxDestination& dest) const override; + + bool CanGetAddresses(bool internal = false) override; // Map from Key ID to key metadata. std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); @@ -219,94 +313,60 @@ private: // Map from Script ID to key metadata (for watch-only keys). std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); - /** - * keystore implementation - * Generate a new key - */ - CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) - bool LoadKey(const CKey& key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } - //! Load metadata (used by LoadWallet) - void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo - void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - + bool LoadKey(const CKey& key, const CPubKey &pubkey); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); //! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); - bool GetKey(const CKeyID &address, CKey& keyOut) const override; - bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; - bool HaveKey(const CKeyID &address) const override; - std::set<CKeyID> GetKeys() const override; - bool AddCScript(const CScript& redeemScript) override; + void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Adds a CScript to the store bool LoadCScript(const CScript& redeemScript); + //! Load metadata (used by LoadWallet) + void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Generate a new key + CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + /* Set the HD chain model (chain child index counters) */ + void SetHDChain(const CHDChain& chain, bool memonly); + const CHDChain& GetHDChain() const { return hdChain; } - //! Adds a watch-only address to the store, and saves it to disk. - bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet) bool LoadWatchOnly(const CScript &dest); //! Returns whether the watch-only script is in the wallet bool HaveWatchOnly(const CScript &dest) const; //! Returns whether there are any watch-only things in the wallet bool HaveWatchOnly() const; + //! Remove a watch only script from the keystore + bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + //! Fetches a pubkey from mapWatchKeys if it exists there bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; - bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /* SigningProvider overrides */ + bool HaveKey(const CKeyID &address) const override; + bool GetKey(const CKeyID &address, CKey& keyOut) const override; + bool GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const override; + bool AddCScript(const CScript& redeemScript) override; + bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + //! Load a keypool entry + void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool NewKeyPool(); - size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - bool TopUpKeyPool(unsigned int kpSize = 0); - - /** - * Reserves a key from the keypool and sets nIndex to its index - * - * @param[out] nIndex the index of the key in keypool - * @param[out] keypool the keypool the key was drawn from, which could be the - * the pre-split pool if present, or the internal or external pool - * @param fRequestedInternal true if the caller would like the key drawn - * from the internal keypool, false if external is preferred - * - * @return true if succeeded, false if failed due to empty keypool - * @throws std::runtime_error if keypool read failed, key was invalid, - * was not found in the wallet, or was misclassified in the internal - * or external keypool - */ - bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); - void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); - int64_t GetOldestKeyPoolTime(); - /** - * Marks all keys in the keypool up to and including reserve_key as used. - */ - void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } - bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error); - - isminetype IsMine(const CScript& script) const; - - /* Set the HD chain model (chain child index counters) */ - void SetHDChain(const CHDChain& chain, bool memonly); - const CHDChain& GetHDChain() const { return hdChain; } + void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - /* Returns true if HD is enabled */ - bool IsHDEnabled() const; + bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool ImportScriptPubKeys(const std::set<CScript>& script_pub_keys, const bool have_solving_data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Returns true if the wallet can generate new keys */ bool CanGenerateKeys(); - /* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */ - bool CanGetAddresses(bool internal = false); - /* Generates a new HD seed (will not be activated) */ CPubKey GenerateNewSeed(); @@ -333,9 +393,13 @@ private: */ void LearnAllRelatedScripts(const CPubKey& key); - /** Implement lookup of key origin information through wallet key metadata. */ - bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + /** + * Marks all keys in the keypool up to and including reserve_key as used. + */ + void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; } + std::set<CKeyID> GetKeys() const override; // Temporary CWallet accessors and aliases. friend class CWallet; friend class ReserveDestination; diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 397e6ea9d3..bc068f1499 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -9,7 +9,7 @@ #include <amount.h> #include <primitives/transaction.h> #include <random.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index c961456572..f4a4c9fa7c 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -7,7 +7,7 @@ #include <boost/test/unit_test.hpp> #include <fs.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <wallet/db.h> diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h index eb4e72c88b..6ba7d66b7c 100644 --- a/src/wallet/test/init_test_fixture.h +++ b/src/wallet/test/init_test_fixture.h @@ -7,7 +7,7 @@ #include <interfaces/chain.h> #include <node/context.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> struct InitWalletDirTestingSetup: public BasicTestingSetup { diff --git a/src/wallet/test/init_tests.cpp b/src/wallet/test/init_tests.cpp index 279542ffad..c228e06009 100644 --- a/src/wallet/test/init_tests.cpp +++ b/src/wallet/test/init_tests.cpp @@ -5,7 +5,8 @@ #include <boost/test/unit_test.hpp> #include <noui.h> -#include <test/setup_common.h> +#include <test/util/logging.h> +#include <test/util/setup_common.h> #include <util/system.h> #include <wallet/test/init_test_fixture.h> @@ -34,28 +35,31 @@ BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_custom) BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_does_not_exist) { SetWalletDir(m_walletdir_path_cases["nonexistent"]); - noui_suppress(); - bool result = m_chain_client->verify(); - noui_reconnect(); - BOOST_CHECK(result == false); + { + ASSERT_DEBUG_LOG("does not exist"); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); + } } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_directory) { SetWalletDir(m_walletdir_path_cases["file"]); - noui_suppress(); - bool result = m_chain_client->verify(); - noui_reconnect(); - BOOST_CHECK(result == false); + { + ASSERT_DEBUG_LOG("is not a directory"); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); + } } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_is_not_relative) { SetWalletDir(m_walletdir_path_cases["relative"]); - noui_suppress(); - bool result = m_chain_client->verify(); - noui_reconnect(); - BOOST_CHECK(result == false); + { + ASSERT_DEBUG_LOG("is a relative path"); + bool result = m_chain_client->verify(); + BOOST_CHECK(result == false); + } } BOOST_AUTO_TEST_CASE(walletinit_verify_walletdir_no_trailing) diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 24636fd599..76c3639d16 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -6,7 +6,7 @@ #include <node/context.h> #include <script/script.h> #include <script/standard.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <wallet/ismine.h> #include <wallet/wallet.h> diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index 27a64ff12f..d930ca6bea 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -9,7 +9,7 @@ #include <wallet/wallet.h> #include <boost/test/unit_test.hpp> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <wallet/test/wallet_test_fixture.h> BOOST_FIXTURE_TEST_SUITE(psbt_wallet_tests, WalletTestingSetup) diff --git a/src/wallet/test/wallet_crypto_tests.cpp b/src/wallet/test/wallet_crypto_tests.cpp index 2f41813234..97f8c94fa6 100644 --- a/src/wallet/test/wallet_crypto_tests.cpp +++ b/src/wallet/test/wallet_crypto_tests.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <util/strencodings.h> #include <wallet/crypter.h> diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index def6f1934e..4e4129fb2c 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -5,7 +5,7 @@ #ifndef BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H #define BITCOIN_WALLET_TEST_WALLET_TEST_FIXTURE_H -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <interfaces/chain.h> #include <interfaces/wallet.h> diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 72e1b4e83b..2f21b2439b 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -12,7 +12,7 @@ #include <node/context.h> #include <policy/policy.h> #include <rpc/server.h> -#include <test/setup_common.h> +#include <test/util/setup_common.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/test/wallet_test_fixture.h> @@ -50,6 +50,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions accommodates a null start block. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + { + LOCK(wallet.cs_wallet); + wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -65,6 +69,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // and new block files. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + { + LOCK(wallet.cs_wallet); + wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -84,6 +92,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // file. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + { + LOCK(wallet.cs_wallet); + wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -102,6 +114,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions scans no blocks. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); + { + LOCK(wallet.cs_wallet); + wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); @@ -258,18 +274,20 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) LockAssertion lock(::cs_main); LOCK(wallet.cs_wallet); AssertLockHeld(spk_man->cs_wallet); + wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); - wtx.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 0); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0); + wtx.m_confirm = confirm; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 0); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); // Invalidate the cached vanue, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); - BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(*locked_chain), 50*COIN); + BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); } static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) @@ -300,7 +318,8 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 wallet.AddToWallet(wtx); } if (block) { - wtx.SetConf(CWalletTx::Status::CONFIRMED, block->GetBlockHash(), 0); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block->nHeight, block->GetBlockHash(), 0); + wtx.m_confirm = confirm; } wallet.AddToWallet(wtx); return wallet.mapWallet.at(wtx.GetHash()).nTimeSmart; @@ -338,9 +357,10 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests) { CTxDestination dest = PKHash(); LOCK(m_wallet.cs_wallet); - m_wallet.AddDestData(dest, "misc", "val_misc"); - m_wallet.AddDestData(dest, "rr0", "val_rr0"); - m_wallet.AddDestData(dest, "rr1", "val_rr1"); + WalletBatch batch{m_wallet.GetDatabase()}; + m_wallet.AddDestData(batch, dest, "misc", "val_misc"); + m_wallet.AddDestData(batch, dest, "rr0", "val_rr0"); + m_wallet.AddDestData(batch, dest, "rr1", "val_rr1"); auto values = m_wallet.GetDestValues("rr"); BOOST_CHECK_EQUAL(values.size(), 2U); @@ -435,6 +455,10 @@ public: { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + { + LOCK(wallet->cs_wallet); + wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); + } bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); @@ -473,9 +497,11 @@ public: LOCK(cs_main); LOCK(wallet->cs_wallet); + wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); BOOST_CHECK(it != wallet->mapWallet.end()); - it->second.SetConf(CWalletTx::Status::CONFIRMED, ::ChainActive().Tip()->GetBlockHash(), 1); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1); + it->second.m_confirm = confirm; return it->second; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4b1adfb38f..2f2931cef1 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -210,9 +210,14 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& } // Set a seed for the wallet - CPubKey master_pub_key = wallet->m_spk_man->GenerateNewSeed(); - wallet->m_spk_man->SetHDSeed(master_pub_key); - wallet->m_spk_man->NewKeyPool(); + { + if (auto spk_man = wallet->m_spk_man.get()) { + if (!spk_man->SetupGeneration()) { + error = "Unable to generate initial keys"; + return WalletCreationStatus::CREATION_FAILED; + } + } + } // Relock the wallet wallet->Lock(); @@ -236,8 +241,6 @@ std::string COutput::ToString() const return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } -std::vector<CKeyID> GetAffectedKeys(const CScript& spk, const SigningProvider& provider); - const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { LOCK(cs_wallet); @@ -249,10 +252,15 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const void CWallet::UpgradeKeyMetadata() { - AssertLockHeld(m_spk_man->cs_wallet); + if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { + return; + } + if (m_spk_man) { + AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->UpgradeKeyMetadata(); } + SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); } bool CWallet::Unlock(const SecureString& strWalletPassphrase, bool accept_no_keys) @@ -444,7 +452,7 @@ void CWallet::SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator> ran * Outpoint is spent if any non-conflicted transaction * spends it: */ -bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const +bool CWallet::IsSpent(const uint256& hash, unsigned int n) const { const COutPoint outpoint(hash, n); std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; @@ -455,7 +463,7 @@ bool CWallet::IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash const uint256& wtxid = it->second; std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { - int depth = mit->second.GetDepthInMainChain(locked_chain); + int depth = mit->second.GetDepthInMainChain(); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) return true; // Spent } @@ -562,11 +570,13 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) Unlock(strWalletPassphrase); // if we are using HD, replace the HD seed with a new one - if (m_spk_man->IsHDEnabled()) { - m_spk_man->SetHDSeed(m_spk_man->GenerateNewSeed()); + if (auto spk_man = m_spk_man.get()) { + if (spk_man->IsHDEnabled()) { + if (!spk_man->SetupGeneration(true)) { + return false; + } + } } - - m_spk_man->NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might keep @@ -691,19 +701,19 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) return success; } -void CWallet::SetUsedDestinationState(const uint256& hash, unsigned int n, bool used) +void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used) { + AssertLockHeld(cs_wallet); const CWalletTx* srctx = GetWalletTx(hash); if (!srctx) return; CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { if (IsMine(dst)) { - LOCK(cs_wallet); if (used && !GetDestData(dst, "used", nullptr)) { - AddDestData(dst, "used", "p"); // p for "present", opposite of absent (null) + AddDestData(batch, dst, "used", "p"); // p for "present", opposite of absent (null) } else if (!used && GetDestData(dst, "used", nullptr)) { - EraseDestData(dst, "used"); + EraseDestData(batch, dst, "used"); } } } @@ -734,7 +744,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) // Mark used destinations for (const CTxIn& txin : wtxIn.tx->vin) { const COutPoint& op = txin.prevout; - SetUsedDestinationState(op.hash, op.n, true); + SetUsedDestinationState(batch, op.hash, op.n, true); } } @@ -758,10 +768,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) wtx.m_confirm.status = wtxIn.m_confirm.status; wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; + wtx.m_confirm.block_height = wtxIn.m_confirm.block_height; fUpdated = true; } else { assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex); assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock); + assert(wtx.m_confirm.block_height == wtxIn.m_confirm.block_height); } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { @@ -812,12 +824,22 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn) { // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. auto locked_chain = LockChain(); - // If tx hasn't been reorged out of chain while wallet being shutdown - // change tx status to UNCONFIRMED and reset hashBlock/nIndex. - if (!wtxIn.m_confirm.hashBlock.IsNull()) { - if (locked_chain && !locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) { + if (locked_chain) { + Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock); + if (block_height) { + // Update cached block height variable since it not stored in the + // serialized transaction. + wtxIn.m_confirm.block_height = *block_height; + } else if (wtxIn.isConflicted() || wtxIn.isConfirmed()) { + // If tx block (or conflicting block) was reorged out of chain + // while the wallet was shutdown, change tx status to UNCONFIRMED + // and reset block height, hash, and index. ABANDONED tx don't have + // associated blocks and don't need to be updated. The case where a + // transaction was reorged out while online and then reconfirmed + // while offline is covered by the rescan logic. wtxIn.setUnconfirmed(); wtxIn.m_confirm.hashBlock = uint256(); + wtxIn.m_confirm.block_height = 0; wtxIn.m_confirm.nIndex = 0; } } @@ -834,25 +856,25 @@ void CWallet::LoadToWallet(CWalletTx& wtxIn) if (it != mapWallet.end()) { CWalletTx& prevtx = it->second; if (prevtx.isConflicted()) { - MarkConflicted(prevtx.m_confirm.hashBlock, wtx.GetHash()); + MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetHash()); } } } } -bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) +bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool fUpdate) { const CTransaction& tx = *ptx; { AssertLockHeld(cs_wallet); - if (!block_hash.IsNull()) { + if (!confirm.hashBlock.IsNull()) { for (const CTxIn& txin : tx.vin) { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetHash()) { - WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); - MarkConflicted(block_hash, range.first->second); + WalletLogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n); + MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second); } range.first++; } @@ -871,17 +893,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St // loop though all outputs for (const CTxOut& txout: tx.vout) { - // extract addresses and check if they match with an unused keypool key - for (const auto& keyid : GetAffectedKeys(txout.scriptPubKey, *m_spk_man)) { - std::map<CKeyID, int64_t>::const_iterator mi = m_spk_man->m_pool_key_to_index.find(keyid); - if (mi != m_spk_man->m_pool_key_to_index.end()) { - WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__); - MarkReserveKeysAsUsed(mi->second); - - if (!m_spk_man->TopUpKeyPool()) { - WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__); - } - } + if (auto spk_man = m_spk_man.get()) { + spk_man->MarkUnusedAddresses(txout.scriptPubKey); } } @@ -889,7 +902,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::St // Block disconnection override an abandoned tx as unconfirmed // which means user may have to call abandontransaction again - wtx.SetConf(status, block_hash, posInBlock); + wtx.m_confirm = confirm; return AddToWallet(wtx, false); } @@ -902,7 +915,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const auto locked_chain = chain().lock(); LOCK(cs_wallet); const CWalletTx* wtx = GetWalletTx(hashTx); - return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); + return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef& tx) @@ -915,9 +928,9 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx) } } -bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx) +bool CWallet::AbandonTransaction(const uint256& hashTx) { - auto locked_chain_recursive = chain().lock(); // Temporary. Removed in upcoming lock cleanup + auto locked_chain = chain().lock(); // Temporary. Removed in upcoming lock cleanup LOCK(cs_wallet); WalletBatch batch(*database, "r+"); @@ -929,7 +942,7 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui auto it = mapWallet.find(hashTx); assert(it != mapWallet.end()); CWalletTx& origtx = it->second; - if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { + if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { return false; } @@ -942,14 +955,13 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(locked_chain); + int currentconfirm = wtx.GetDepthInMainChain(); // If the orig tx was not in block, none of its spends can be assert(currentconfirm <= 0); // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can be in mempool assert(!wtx.InMempool()); - wtx.m_confirm.nIndex = 0; wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); @@ -971,12 +983,12 @@ bool CWallet::AbandonTransaction(interfaces::Chain::Lock& locked_chain, const ui return true; } -void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) +void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); + int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1; // If number of conflict confirms cannot be determined, this means // that the block is still unknown or not yet part of the main chain, // for example when loading the wallet during a reindex. Do nothing in that @@ -999,12 +1011,13 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx& wtx = it->second; - int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); + int currentconfirm = wtx.GetDepthInMainChain(); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. wtx.m_confirm.nIndex = 0; wtx.m_confirm.hashBlock = hashBlock; + wtx.m_confirm.block_height = conflicting_height; wtx.setConflicted(); wtx.MarkDirty(); batch.WriteTx(wtx); @@ -1023,9 +1036,9 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) } } -void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool update_tx) +void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmation confirm, bool update_tx) { - if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock, update_tx)) + if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx)) return; // Not one of ours // If a transaction changes 'conflicted' state, that changes the balance @@ -1037,7 +1050,8 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Status stat void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); - SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); + CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0); + SyncTransaction(ptx, confirm); auto it = mapWallet.find(ptx->GetHash()); if (it != mapWallet.end()) { @@ -1053,23 +1067,26 @@ void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { } } -void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) { +void CWallet::BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height) +{ const uint256& block_hash = block.GetHash(); auto locked_chain = chain().lock(); LOCK(cs_wallet); - for (size_t i = 0; i < block.vtx.size(); i++) { - SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash, i); - TransactionRemovedFromMempool(block.vtx[i]); + m_last_block_processed_height = height; + m_last_block_processed = block_hash; + for (size_t index = 0; index < block.vtx.size(); index++) { + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index); + SyncTransaction(block.vtx[index], confirm); + TransactionRemovedFromMempool(block.vtx[index]); } for (const CTransactionRef& ptx : vtxConflicted) { TransactionRemovedFromMempool(ptx); } - - m_last_block_processed = block_hash; } -void CWallet::BlockDisconnected(const CBlock& block) { +void CWallet::BlockDisconnected(const CBlock& block, int height) +{ auto locked_chain = chain().lock(); LOCK(cs_wallet); @@ -1077,8 +1094,11 @@ void CWallet::BlockDisconnected(const CBlock& block) { // be unconfirmed, whether or not the transaction is added back to the mempool. // User may have to call abandontransaction again. It may be addressed in the // future with a stickier abandoned state or even removing abandontransaction call. + m_last_block_processed_height = height - 1; + m_last_block_processed = block.hashPrevBlock; for (const CTransactionRef& ptx : block.vtx) { - SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED, {} /* block hash */, 0 /* position in block */); + CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0); + SyncTransaction(ptx, confirm); } } @@ -1095,7 +1115,7 @@ void CWallet::BlockUntilSyncedToCurrentChain() { // for the queue to drain enough to execute it (indicating we are caught up // at least with the time we entered this function). uint256 last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); - chain().waitForNotificationsIfNewBlocksConnected(last_block_hash); + chain().waitForNotificationsIfTipChanged(last_block_hash); } @@ -1304,6 +1324,11 @@ void CWallet::UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag) throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } +void CWallet::UnsetBlankWalletFlag(WalletBatch& batch) +{ + UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); +} + bool CWallet::IsWalletFlagSet(uint64_t flag) const { return (m_wallet_flags & flag); @@ -1400,9 +1425,19 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri return false; } AssertLockHeld(spk_man->cs_wallet); - if (!spk_man->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, apply_label, timestamp)) { + if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data, timestamp)) { return false; } + if (apply_label) { + WalletBatch batch(*database); + for (const CScript& script : script_pub_keys) { + CTxDestination dest; + ExtractDestination(script, dest); + if (IsValidDestination(dest)) { + SetAddressBookWithDB(batch, dest, label, "receive"); + } + } + } return true; } @@ -1609,7 +1644,8 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { - SyncTransaction(block.vtx[posInBlock], CWalletTx::Status::CONFIRMED, block_hash, posInBlock, fUpdate); + CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *block_height, block_hash, posInBlock); + SyncTransaction(block.vtx[posInBlock], confirm, fUpdate); } // scan succeeded, record block as most recent successfully scanned result.last_scanned_block = block_hash; @@ -1657,7 +1693,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc return result; } -void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) +void CWallet::ReacceptWalletTransactions() { // If transactions aren't being broadcasted, don't let them into local mempool either if (!fBroadcastTransactions) @@ -1670,7 +1706,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) CWalletTx& wtx = item.second; assert(wtx.GetHash() == wtxid); - int nDepth = wtx.GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); @@ -1681,11 +1717,11 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { CWalletTx& wtx = *(item.second); std::string unused_err_string; - wtx.SubmitMemoryPoolAndRelay(unused_err_string, false, locked_chain); + wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); } } -bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain) +bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) return false; @@ -1695,7 +1731,7 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, in // cause log spam. if (IsCoinBase()) return false; // Don't try to submit conflicted or confirmed transactions. - if (GetDepthInMainChain(locked_chain) != 0) return false; + if (GetDepthInMainChain() != 0) return false; // Submit transaction to mempool for relay pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); @@ -1749,10 +1785,10 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const return debit; } -CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const +CAmount CWalletTx::GetCredit(const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase(locked_chain)) + if (IsImmatureCoinBase()) return 0; CAmount credit = 0; @@ -1766,16 +1802,16 @@ CAmount CWalletTx::GetCredit(interfaces::Chain::Lock& locked_chain, const ismine return credit; } -CAmount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache) const +CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const { - if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { + if (IsImmatureCoinBase() && IsInMainChain()) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return 0; } -CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache, const isminefilter& filter) const +CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const { if (pwallet == nullptr) return 0; @@ -1784,7 +1820,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase(locked_chain)) + if (IsImmatureCoinBase()) return 0; if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { @@ -1796,7 +1832,7 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo uint256 hashTx = GetHash(); for (unsigned int i = 0; i < tx->vout.size(); i++) { - if (!pwallet->IsSpent(locked_chain, hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) { + if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsUsedDestination(hashTx, i))) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) @@ -1811,9 +1847,9 @@ CAmount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock& locked_chain, boo return nCredit; } -CAmount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache) const +CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const { - if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { + if (IsImmatureCoinBase() && IsInMainChain()) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } @@ -1836,32 +1872,37 @@ bool CWalletTx::InMempool() const bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const { + std::set<uint256> s; + return IsTrusted(locked_chain, s); +} + +bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const +{ // Quick answer in most cases - if (!locked_chain.checkFinalTx(*tx)) { - return false; - } - int nDepth = GetDepthInMainChain(locked_chain); - if (nDepth >= 1) - return true; - if (nDepth < 0) - return false; - if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit - return false; + if (!locked_chain.checkFinalTx(*tx)) return false; + int nDepth = GetDepthInMainChain(); + if (nDepth >= 1) return true; + if (nDepth < 0) return false; + // using wtx's cached debit + if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) return false; // Don't trust unconfirmed transactions from us unless they are in the mempool. - if (!InMempool()) - return false; + if (!InMempool()) return false; // Trusted if all inputs are from us and are in the mempool: for (const CTxIn& txin : tx->vin) { // Transactions not sent by us: not trusted const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash); - if (parent == nullptr) - return false; + if (parent == nullptr) return false; const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; - if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) - return false; + // Check that this specific input being spent is trusted + if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) return false; + // If we've already trusted this parent, continue + if (trusted_parents.count(parent->GetHash())) continue; + // Recurse to check that the parent is also trusted + if (!parent->IsTrusted(locked_chain, trusted_parents)) return false; + trusted_parents.insert(parent->GetHash()); } return true; } @@ -1915,7 +1956,7 @@ void CWallet::ResendWalletTransactions() // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; std::string unused_err_string; - if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true, *locked_chain)) ++submitted_tx_count; + if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; } } // locked_chain and cs_wallet @@ -1947,13 +1988,14 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons { auto locked_chain = chain().lock(); LOCK(cs_wallet); + std::set<uint256> trusted_parents; for (const auto& entry : mapWallet) { const CWalletTx& wtx = entry.second; - const bool is_trusted{wtx.IsTrusted(*locked_chain)}; - const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; - const CAmount tx_credit_mine{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; - const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; + const bool is_trusted{wtx.IsTrusted(*locked_chain, trusted_parents)}; + const int tx_depth{wtx.GetDepthInMainChain()}; + const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; + const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; @@ -1962,8 +2004,8 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } - ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); - ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); + ret.m_mine_immature += wtx.GetImmatureCredit(); + ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); } } return ret; @@ -1997,6 +2039,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; + std::set<uint256> trusted_parents; for (const auto& entry : mapWallet) { const uint256& wtxid = entry.first; @@ -2006,10 +2049,10 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - if (wtx.IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase()) continue; - int nDepth = wtx.GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(); if (nDepth < 0) continue; @@ -2018,7 +2061,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = wtx.IsTrusted(locked_chain); + bool safeTx = wtx.IsTrusted(locked_chain, trusted_parents); // We should not consider coins from transactions that are replacing // other transactions. @@ -2069,7 +2112,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (IsLockedCoin(entry.first, i)) continue; - if (IsSpent(locked_chain, wtxid, i)) + if (IsSpent(wtxid, i)) continue; isminetype mine = IsMine(wtx.tx->vout[i]); @@ -2128,7 +2171,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch for (const COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(locked_chain); + int depth = it->second.GetDepthInMainChain(); if (depth >= 0 && output.n < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { CTxDestination address; @@ -2783,11 +2826,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std SignatureData sigdata; const SigningProvider* provider = GetSigningProvider(); - if (!provider) { - return false; - } - - if (!ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) + if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; return false; @@ -2872,7 +2911,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve } std::string err_string; - if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) { + if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. } @@ -2893,12 +2932,9 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { if (database->Rewrite("\x04pool")) { - setInternalKeyPool.clear(); - setExternalKeyPool.clear(); - m_spk_man->m_pool_key_to_index.clear(); - // Note: can't top-up keypool here, because wallet is locked. - // User will be prompted to unlock wallet the next operation - // that requires a new key. + if (auto spk_man = m_spk_man.get()) { + spk_man->RewriteDB(); + } } } @@ -2930,12 +2966,9 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256 { if (database->Rewrite("\x04pool")) { - setInternalKeyPool.clear(); - setExternalKeyPool.clear(); - m_spk_man->m_pool_key_to_index.clear(); - // Note: can't top-up keypool here, because wallet is locked. - // User will be prompted to unlock wallet the next operation - // that requires a new key. + if (auto spk_man = m_spk_man.get()) { + spk_man->RewriteDB(); + } } } @@ -2954,13 +2987,9 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx) { if (database->Rewrite("\x04pool")) { - LOCK(cs_wallet); - setInternalKeyPool.clear(); - setExternalKeyPool.clear(); - m_spk_man->m_pool_key_to_index.clear(); - // Note: can't top-up keypool here, because wallet is locked. - // User will be prompted to unlock wallet the next operation - // that requires a new key. + if (auto spk_man = m_spk_man.get()) { + spk_man->RewriteDB(); + } } } @@ -3027,23 +3056,39 @@ size_t CWallet::KeypoolCountExternalKeys() return count; } +unsigned int CWallet::GetKeyPoolSize() const +{ + AssertLockHeld(cs_wallet); + + unsigned int count = 0; + if (auto spk_man = m_spk_man.get()) { + count += spk_man->GetKeyPoolSize(); + } + return count; +} + bool CWallet::TopUpKeyPool(unsigned int kpSize) { bool res = true; if (auto spk_man = m_spk_man.get()) { - res &= spk_man->TopUpKeyPool(kpSize); + res &= spk_man->TopUp(kpSize); } return res; } bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, std::string& error) { + LOCK(cs_wallet); error.clear(); bool result = false; auto spk_man = m_spk_man.get(); if (spk_man) { - result = spk_man->GetNewDestination(type, label, dest, error); + result = spk_man->GetNewDestination(type, dest, error); } + if (result) { + SetAddressBook(dest, label, "receive"); + } + return result; } @@ -3051,7 +3096,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des { error.clear(); - m_spk_man->TopUpKeyPool(); + m_spk_man->TopUp(); ReserveDestination reservedest(this); if (!reservedest.GetReservedDestination(type, dest, true)) { @@ -3078,17 +3123,18 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: { LOCK(cs_wallet); + std::set<uint256> trusted_parents; for (const auto& walletEntry : mapWallet) { const CWalletTx& wtx = walletEntry.second; - if (!wtx.IsTrusted(locked_chain)) + if (!wtx.IsTrusted(locked_chain, trusted_parents)) continue; - if (wtx.IsImmatureCoinBase(locked_chain)) + if (wtx.IsImmatureCoinBase()) continue; - int nDepth = wtx.GetDepthInMainChain(locked_chain); + int nDepth = wtx.GetDepthInMainChain(); if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) continue; @@ -3100,7 +3146,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(locked_chain, walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; + CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; if (!balances.count(addr)) balances[addr] = 0; @@ -3233,7 +3279,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin if (nIndex == -1) { CKeyPool keypool; - if (!m_spk_man->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { + if (!m_spk_man->GetReservedDestination(type, internal, nIndex, keypool)) { return false; } vchPubKey = keypool.vchPubKey; @@ -3249,7 +3295,7 @@ bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestin void ReserveDestination::KeepDestination() { if (nIndex != -1) - m_spk_man->KeepKey(nIndex); + m_spk_man->KeepDestination(nIndex); nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); @@ -3258,7 +3304,7 @@ void ReserveDestination::KeepDestination() void ReserveDestination::ReturnDestination() { if (nIndex != -1) { - m_spk_man->ReturnKey(nIndex, fInternal, vchPubKey); + m_spk_man->ReturnDestination(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); @@ -3414,20 +3460,20 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const return nTimeSmart; } -bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) +bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value) { if (boost::get<CNoDestination>(&dest)) return false; mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); - return WalletBatch(*database).WriteDestData(EncodeDestination(dest), key, value); + return batch.WriteDestData(EncodeDestination(dest), key, value); } -bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) +bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key) { if (!mapAddressBook[dest].destdata.erase(key)) return false; - return WalletBatch(*database).EraseDestData(EncodeDestination(dest), key); + return batch.EraseDestData(EncodeDestination(dest), key); } void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) @@ -3604,31 +3650,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, return nullptr; } - bool hd_upgrade = false; - bool split_upgrade = false; - if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->m_spk_man->IsHDEnabled()) { - walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); - walletInstance->SetMinVersion(FEATURE_HD); - - // generate a new master key - CPubKey masterPubKey = walletInstance->m_spk_man->GenerateNewSeed(); - walletInstance->m_spk_man->SetHDSeed(masterPubKey); - hd_upgrade = true; - } - // Upgrade to HD chain split if necessary - if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { - walletInstance->WalletLogPrintf("Upgrading wallet to use HD chain split\n"); - walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); - split_upgrade = FEATURE_HD_SPLIT > prev_version; - } - // Mark all keys currently in the keypool as pre-split - if (split_upgrade) { - walletInstance->MarkPreSplitKeys(); - } - // Regenerate the keypool if upgraded to HD - if (hd_upgrade) { - if (!walletInstance->m_spk_man->TopUpKeyPool()) { - error = _("Unable to generate keys").translated; + if (auto spk_man = walletInstance->m_spk_man.get()) { + std::string error; + if (!spk_man->Upgrade(prev_version, error)) { + chain.initError(error); return nullptr; } } @@ -3641,15 +3666,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, walletInstance->SetWalletFlags(wallet_creation_flags, false); if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { - // generate a new seed - CPubKey seed = walletInstance->m_spk_man->GenerateNewSeed(); - walletInstance->m_spk_man->SetHDSeed(seed); - } - - // Top up the keypool - if (walletInstance->m_spk_man->CanGenerateKeys() && !walletInstance->m_spk_man->TopUpKeyPool()) { - error = _("Unable to generate initial keys").translated; - return nullptr; + if (auto spk_man = walletInstance->m_spk_man.get()) { + if (!spk_man->SetupGeneration()) { + error = _("Unable to generate initial keys").translated; + return nullptr; + } + } } auto locked_chain = chain.lock(); @@ -3659,9 +3681,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, error = strprintf(_("Error loading %s: Private keys can only be disabled during creation").translated, walletFile); return NULL; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - LOCK(walletInstance->cs_KeyStore); - if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { - warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile)); + if (walletInstance->m_spk_man) { + if (walletInstance->m_spk_man->HavePrivateKeys()) { + warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile)); + } } } @@ -3782,8 +3805,10 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const Optional<int> tip_height = locked_chain->getHeight(); if (tip_height) { walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); + walletInstance->m_last_block_processed_height = *tip_height; } else { walletInstance->m_last_block_processed.SetNull(); + walletInstance->m_last_block_processed_height = -1; } if (tip_height && *tip_height != rescan_height) @@ -3811,8 +3836,13 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // No need to read and scan block if block was created before // our wallet birthday (as adjusted for block time variability) - if (walletInstance->nTimeFirstKey) { - if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height, nullptr)) { + Optional<int64_t> time_first_key; + if (auto spk_man = walletInstance->m_spk_man.get()) { + int64_t time = spk_man->GetTimeFirstKey(); + if (!time_first_key || time < *time_first_key) time_first_key = time; + } + if (time_first_key) { + if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) { rescan_height = *first_block; } } @@ -3880,7 +3910,7 @@ void CWallet::postInitProcess() // Add wallet transactions that aren't already in a block to mempool // Do this here as mempool requires genesis block to be loaded - ReacceptWalletTransactions(*locked_chain); + ReacceptWalletTransactions(); // Update wallet transactions with current mempool transactions. chain().requestMempoolTransactions(*this); @@ -3906,38 +3936,28 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn) m_pre_split = false; } -void CWalletTx::SetConf(Status status, const uint256& block_hash, int posInBlock) -{ - // Update tx status - m_confirm.status = status; - - // Update the tx's hashBlock - m_confirm.hashBlock = block_hash; - - // set the position of the transaction in the block - m_confirm.nIndex = posInBlock; -} - -int CWalletTx::GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetDepthInMainChain() const { + assert(pwallet != nullptr); + AssertLockHeld(pwallet->cs_wallet); if (isUnconfirmed() || isAbandoned()) return 0; - return locked_chain.getBlockDepth(m_confirm.hashBlock) * (isConflicted() ? -1 : 1); + return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); } -int CWalletTx::GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const +int CWalletTx::GetBlocksToMaturity() const { if (!IsCoinBase()) return 0; - int chain_depth = GetDepthInMainChain(locked_chain); + int chain_depth = GetDepthInMainChain(); assert(chain_depth >= 0); // coinbase tx should not be conflicted return std::max(0, (COINBASE_MATURITY+1) - chain_depth); } -bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const +bool CWalletTx::IsImmatureCoinBase() const { // note GetBlocksToMaturity is 0 for non-coinbase tx - return GetBlocksToMaturity(locked_chain) > 0; + return GetBlocksToMaturity() > 0; } std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f3b791441c..a6b9c0131a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -356,14 +356,17 @@ public: ABANDONED }; - /* Confirmation includes tx status and a pair of {block hash/tx index in block} at which tx has been confirmed. - * This pair is both 0 if tx hasn't confirmed yet. Meaning of these fields changes with CONFLICTED state - * where they instead point to block hash and index of the deepest conflicting tx. + /* Confirmation includes tx status and a triplet of {block height/block hash/tx index in block} + * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned. + * Meaning of these fields changes with CONFLICTED state where they instead point to block hash + * and block height of the deepest conflicting tx. */ struct Confirmation { - Status status = UNCONFIRMED; - uint256 hashBlock = uint256(); - int nIndex = 0; + Status status; + int block_height; + uint256 hashBlock; + int nIndex; + Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {} }; Confirmation m_confirm; @@ -406,7 +409,6 @@ public: * compatibility (pre-commit 9ac63d6). */ if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { - m_confirm.hashBlock = uint256(); setAbandoned(); } else if (serializedIndex == -1) { setConflicted(); @@ -447,14 +449,14 @@ public: //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(interfaces::Chain::Lock& locked_chain, const isminefilter& filter) const; - CAmount GetImmatureCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true) const; + CAmount GetCredit(const isminefilter& filter) const; + CAmount GetImmatureCredit(bool fUseCache = true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(interfaces::Chain::Lock& locked_chain, bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock& locked_chain, const bool fUseCache=true) const; + CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; + CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; CAmount GetChange() const; // Get the marginal bytes if spending the specified output from this transaction @@ -476,11 +478,12 @@ public: bool InMempool() const; bool IsTrusted(interfaces::Chain::Lock& locked_chain) const; + bool IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const; int64_t GetTxTime() const; // Pass this transaction to node for mempool insertion and relay to peers if flag set to true - bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay, interfaces::Chain::Lock& locked_chain); + bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation @@ -490,38 +493,44 @@ public: // in place. std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; - void SetConf(Status status, const uint256& block_hash, int posInBlock); - /** * Return depth of transaction in blockchain: * <0 : conflicts with a transaction this deep in the blockchain * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ - int GetDepthInMainChain(interfaces::Chain::Lock& locked_chain) const; - bool IsInMainChain(interfaces::Chain::Lock& locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; + bool IsInMainChain() const { return GetDepthInMainChain() > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ - int GetBlocksToMaturity(interfaces::Chain::Lock& locked_chain) const; + int GetBlocksToMaturity() const; bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } void setAbandoned() { m_confirm.status = CWalletTx::ABANDONED; m_confirm.hashBlock = uint256(); + m_confirm.block_height = 0; m_confirm.nIndex = 0; } bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } + bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; } void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } const uint256& GetHash() const { return tx->GetHash(); } bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const; + bool IsImmatureCoinBase() const; }; class COutput @@ -641,10 +650,10 @@ private: * Abandoned state should probably be more carefully tracked via different * posInBlock signals or by checking mempool presence when necessary. */ - bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ - void MarkConflicted(const uint256& hashBlock, const uint256& hashTx); + void MarkConflicted(const uint256& hashBlock, int conflicting_height, const uint256& hashTx); /* Mark a transaction's inputs dirty, thus forcing the outputs to be recomputed */ void MarkInputsDirty(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); @@ -653,14 +662,17 @@ private: /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with non-zero block_hash and posInBlock if this is for a transaction that is included in a block. */ - void SyncTransaction(const CTransactionRef& tx, CWalletTx::Status status, const uint256& block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void SyncTransaction(const CTransactionRef& tx, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::atomic<uint64_t> m_wallet_flags{0}; bool SetAddressBookWithDB(WalletBatch& batch, const CTxDestination& address, const std::string& strName, const std::string& strPurpose); //! Unsets a wallet flag and saves it to disk - void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag) override; + void UnsetWalletFlagWithDB(WalletBatch& batch, uint64_t flag); + + //! Unset the blank wallet flag and saves it to disk + void UnsetBlankWalletFlag(WalletBatch& batch) override; /** Interface for accessing chain state. */ interfaces::Chain* m_chain; @@ -675,12 +687,18 @@ private: * The following is used to keep track of how far behind the wallet is * from the chain sync, and to allow clients to block on us being caught up. * - * Note that this is *not* how far we've processed, we may need some rescan - * to have seen all transactions in the chain, but is only used to track - * live BlockConnected callbacks. + * Processed hash is a pointer on node's tip and doesn't imply that the wallet + * has scanned sequentially all blocks up to this one. */ uint256 m_last_block_processed GUARDED_BY(cs_wallet); + /* Height of last block processed is used by wallet to know depth of transactions + * without relying on Chain interface beyond asynchronous updates. For safety, we + * initialize it to -1. Height is a pointer on node's tip and doesn't imply + * that the wallet has scanned sequentially all blocks up to this one. + */ + int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1; + public: /* * Main wallet lock. @@ -790,12 +808,12 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params, bool& bnb_used) const; - bool IsSpent(interfaces::Chain::Lock& locked_chain, const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Whether this or any UTXO with the same CTxDestination has been spent. bool IsUsedDestination(const CTxDestination& dst) const; bool IsUsedDestination(const uint256& hash, unsigned int n) const; - void SetUsedDestinationState(const uint256& hash, unsigned int n, bool used); + void SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; @@ -820,9 +838,9 @@ public: bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } //! Adds a destination data tuple to the store, and saves it to disk - bool AddDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool AddDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Erases a destination data tuple in the store and on disk - bool EraseDestData(const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool EraseDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a destination data tuple to the store, without saving it to disk void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Look up a destination data tuple in the store, return true if found false otherwise @@ -851,8 +869,8 @@ public: bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); void LoadToWallet(CWalletTx& wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef& tx) override; - void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted) override; - void BlockDisconnected(const CBlock& block) override; + void BlockConnected(const CBlock& block, const std::vector<CTransactionRef>& vtxConflicted, int height) override; + void BlockDisconnected(const CBlock& block, int height) override; void UpdatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update); @@ -873,7 +891,7 @@ public: }; ScanResult ScanForWalletTransactions(const uint256& first_block, const uint256& last_block, const WalletRescanReserver& reserver, bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; - void ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); struct Balance { CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more @@ -989,11 +1007,7 @@ public: bool DelAddressBook(const CTxDestination& address); - unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) - { - AssertLockHeld(cs_wallet); - return setInternalKeyPool.size() + setExternalKeyPool.size(); - } + unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override; @@ -1056,7 +1070,7 @@ public: bool TransactionCanBeAbandoned(const uint256& hashTx) const; /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */ - bool AbandonTransaction(interfaces::Chain::Lock& locked_chain, const uint256& hashTx); + bool AbandonTransaction(const uint256& hashTx); /** Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256& originalHash, const uint256& newHash); @@ -1090,7 +1104,7 @@ public: void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_main, cs_wallet); /** set a single wallet flag */ - void SetWalletFlag(uint64_t flags) override; + void SetWalletFlag(uint64_t flags); /** Unsets a single wallet flag */ void UnsetWalletFlag(uint64_t flag); @@ -1128,14 +1142,22 @@ public: LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly; LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; - std::set<int64_t>& setInternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setInternalKeyPool; - std::set<int64_t>& setExternalKeyPool GUARDED_BY(cs_wallet) = m_spk_man->setExternalKeyPool; - int64_t& nTimeFirstKey GUARDED_BY(cs_wallet) = m_spk_man->nTimeFirstKey; - std::map<CKeyID, CKeyMetadata>& mapKeyMetadata GUARDED_BY(cs_wallet) = m_spk_man->mapKeyMetadata; - std::map<CScriptID, CKeyMetadata>& m_script_metadata GUARDED_BY(cs_wallet) = m_spk_man->m_script_metadata; - void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkPreSplitKeys(); } - void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(m_spk_man->cs_wallet); m_spk_man->MarkReserveKeysAsUsed(keypool_id); } using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; + + /** Get last block processed height */ + int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + { + AssertLockHeld(cs_wallet); + assert(m_last_block_processed_height >= 0); + return m_last_block_processed_height; + }; + /** Set last block processed height, currently only use in unit test */ + void SetLastBlockProcessed(int block_height, uint256 block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) + { + AssertLockHeld(cs_wallet); + m_last_block_processed_height = block_height; + m_last_block_processed = block_hash; + }; }; /** diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 2ba7cdac36..7d04b04764 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -629,7 +629,7 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u } else if ((*it) == hash) { if(!EraseTx(hash)) { - LogPrint(BCLog::DB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex()); + LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex()); delerror = true; } vTxHashOut.push_back(hash); diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index ebbaf8683d..b3b97b6a2a 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -185,7 +185,7 @@ void CZMQNotificationInterface::BlockConnected(const std::shared_ptr<const CBloc } } -void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) +void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) { for (const CTransactionRef& ptx : pblock->vtx) { // Do a normal notify for each transaction removed in block disconnection diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 6be0554a65..8bf9b0ba47 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -27,7 +27,7 @@ protected: // CValidationInterface void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override; - void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override; + void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; private: |