diff options
80 files changed, 979 insertions, 790 deletions
diff --git a/.travis.yml b/.travis.yml index 1e49815a4c..63e502cc3e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -128,7 +128,7 @@ jobs: FILE_ENV="./ci/test/00_setup_env_i686_centos.sh" - stage: test - name: 'x86_64 Linux [GOAL: install] [bionic] [previous releases, uses qt5 dev package and some depends packages] [unsigned char]' + name: 'x86_64 Linux [GOAL: install] [bionic] [C++17, previous releases, uses qt5 dev package and some depends packages] [unsigned char]' env: >- FILE_ENV="./ci/test/00_setup_env_native_qt5.sh" @@ -149,11 +149,6 @@ jobs: FILE_ENV="./ci/test/00_setup_env_native_valgrind.sh" - stage: test - name: 'x86_64 Linux [GOAL: install] [bionic] [no depends, only system libs, c++17]' - env: >- - FILE_ENV="./ci/test/00_setup_env_native_cxx17.sh" - - - stage: test name: 'x86_64 Linux [GOAL: install] [focal] [no depends, only system libs, sanitizers: fuzzer,address,undefined]' env: >- FILE_ENV="./ci/test/00_setup_env_native_fuzz.sh" diff --git a/ci/test/00_setup_env_native_cxx17.sh b/ci/test/00_setup_env_native_cxx17.sh deleted file mode 100644 index 9a75276420..0000000000 --- a/ci/test/00_setup_env_native_cxx17.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2020 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -export LC_ALL=C.UTF-8 - -export CONTAINER_NAME=ci_native_cxx17 -export PACKAGES="python3-zmq libevent-dev bsdmainutils libboost-system-dev libboost-filesystem-dev libboost-test-dev libboost-thread-dev libdb5.3++-dev libminiupnpc-dev libzmq3-dev" -export NO_DEPENDS=1 -export GOAL="install" -export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --enable-c++17" diff --git a/ci/test/00_setup_env_native_qt5.sh b/ci/test/00_setup_env_native_qt5.sh index 87c8b70998..4dedea23bc 100644 --- a/ci/test/00_setup_env_native_qt5.sh +++ b/ci/test/00_setup_env_native_qt5.sh @@ -14,4 +14,4 @@ export RUN_UNIT_TESTS_SEQUENTIAL="true" export RUN_UNIT_TESTS="false" export GOAL="install" export TEST_PREVIOUS_RELEASES=true -export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-debug CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\"" +export BITCOIN_CONFIG="--enable-zmq --with-gui=qt5 --enable-glibc-back-compat --enable-reduce-exports --enable-c++17 --enable-debug CFLAGS=\"-g0 -O2 -funsigned-char\" CXXFLAGS=\"-g0 -O2 -funsigned-char\"" diff --git a/ci/test/00_setup_env_native_valgrind.sh b/ci/test/00_setup_env_native_valgrind.sh index d5b38a1d88..153a781b0a 100644 --- a/ci/test/00_setup_env_native_valgrind.sh +++ b/ci/test/00_setup_env_native_valgrind.sh @@ -13,7 +13,7 @@ export NO_DEPENDS=1 if [[ "${TRAVIS}" == "true" && "${TRAVIS_REPO_SLUG}" != "bitcoin/bitcoin" ]]; then export TEST_RUNNER_EXTRA="wallet_disable" # Only run wallet_disable as a smoke test to not hit the 50 min travis time limit else - export TEST_RUNNER_EXTRA="--exclude rpc_bind" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 + export TEST_RUNNER_EXTRA="--exclude rpc_bind --factor=2" # Excluded for now, see https://github.com/bitcoin/bitcoin/issues/17765#issuecomment-602068547 fi export GOAL="install" export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=no CC=clang CXX=clang++" # TODO enable GUI diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 48db60f086..68ba1bc68e 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -53,6 +53,7 @@ FUZZ_TARGETS = \ test/fuzz/locale \ test/fuzz/merkle_block_deserialize \ test/fuzz/merkleblock \ + test/fuzz/message \ test/fuzz/messageheader_deserialize \ test/fuzz/multiplication_overflow \ test/fuzz/net_permissions \ @@ -67,6 +68,7 @@ FUZZ_TARGETS = \ test/fuzz/parse_univalue \ test/fuzz/partial_merkle_tree_deserialize \ test/fuzz/partially_signed_transaction_deserialize \ + test/fuzz/policy_estimator \ test/fuzz/pow \ test/fuzz/prefilled_transaction_deserialize \ test/fuzz/prevector \ @@ -103,6 +105,7 @@ FUZZ_TARGETS = \ test/fuzz/psbt_output_deserialize \ test/fuzz/pub_key_deserialize \ test/fuzz/random \ + test/fuzz/rbf \ test/fuzz/rolling_bloom_filter \ test/fuzz/script \ test/fuzz/script_deserialize \ @@ -593,6 +596,12 @@ test_fuzz_merkleblock_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_merkleblock_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_merkleblock_SOURCES = test/fuzz/merkleblock.cpp +test_fuzz_message_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_message_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_message_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_message_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_message_SOURCES = test/fuzz/message.cpp + test_fuzz_messageheader_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DMESSAGEHEADER_DESERIALIZE=1 test_fuzz_messageheader_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_messageheader_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) @@ -683,6 +692,12 @@ test_fuzz_partially_signed_transaction_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMO test_fuzz_partially_signed_transaction_deserialize_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_partially_signed_transaction_deserialize_SOURCES = test/fuzz/deserialize.cpp +test_fuzz_policy_estimator_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_policy_estimator_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_policy_estimator_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_policy_estimator_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_policy_estimator_SOURCES = test/fuzz/policy_estimator.cpp + test_fuzz_pow_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) test_fuzz_pow_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_pow_LDADD = $(FUZZ_SUITE_LD_COMMON) @@ -893,6 +908,12 @@ test_fuzz_random_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_random_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) test_fuzz_random_SOURCES = test/fuzz/random.cpp +test_fuzz_rbf_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_rbf_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_rbf_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_rbf_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_rbf_SOURCES = test/fuzz/rbf.cpp + test_fuzz_rolling_bloom_filter_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) test_fuzz_rolling_bloom_filter_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_rolling_bloom_filter_LDADD = $(FUZZ_SUITE_LD_COMMON) diff --git a/src/bench/ccoins_caching.cpp b/src/bench/ccoins_caching.cpp index d658976c3c..86f9a0bf67 100644 --- a/src/bench/ccoins_caching.cpp +++ b/src/bench/ccoins_caching.cpp @@ -47,8 +47,6 @@ static void CCoinsCaching(benchmark::State& state) while (state.KeepRunning()) { bool success = AreInputsStandard(tx_1, coins); assert(success); - CAmount value = coins.GetValueIn(tx_1); - assert(value == (50 + 21 + 22) * COIN); } ECC_Stop(); } diff --git a/src/coins.cpp b/src/coins.cpp index 6b4cb2aec7..7b76c13f98 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -233,18 +233,6 @@ unsigned int CCoinsViewCache::GetCacheSize() const { return cacheCoins.size(); } -CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const -{ - if (tx.IsCoinBase()) - return 0; - - CAmount nResult = 0; - for (unsigned int i = 0; i < tx.vin.size(); i++) - nResult += AccessCoin(tx.vin[i].prevout).out.nValue; - - return nResult; -} - bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { if (!tx.IsCoinBase()) { diff --git a/src/coins.h b/src/coins.h index 2aed56c2bd..dcec741998 100644 --- a/src/coins.h +++ b/src/coins.h @@ -315,16 +315,6 @@ public: //! Calculate the size of the cache (in bytes) size_t DynamicMemoryUsage() const; - /** - * Amount of bitcoins coming in to a transaction - * Note that lightweight clients may not know anything besides the hash of previous transactions, - * so may not be able to calculate this. - * - * @param[in] tx transaction for which we are checking input total - * @return Sum of value of all inputs (scriptSigs) - */ - CAmount GetValueIn(const CTransaction& tx) const; - //! Check whether all prevouts of the transaction are present in the UTXO set represented by this view bool HaveInputs(const CTransaction& tx) const; diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index a5582e3b2c..0f7848bae1 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -8,6 +8,7 @@ class CWallet; enum class WalletCreationStatus; +struct bilingual_str; namespace interfaces { class Chain; @@ -72,12 +73,12 @@ std::vector<std::shared_ptr<CWallet>> GetWallets() throw std::logic_error("Wallet function called in non-wallet build."); } -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::vector<std::string>& warnings) +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) { throw std::logic_error("Wallet function called in non-wallet build."); } -WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result) +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result) { throw std::logic_error("Wallet function called in non-wallet build."); } diff --git a/src/init.cpp b/src/init.cpp index a213dacbe0..3e2ee4d425 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -708,6 +708,10 @@ static void ThreadImport(std::vector<fs::path> vImportFiles) break; // This error is logged in OpenBlockFile LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); LoadExternalBlockFile(chainparams, file, &pos); + if (ShutdownRequested()) { + LogPrintf("Shutdown requested. Exit %s\n", __func__); + return; + } nFile++; } pblocktree->WriteReindexing(false); @@ -723,6 +727,10 @@ static void ThreadImport(std::vector<fs::path> vImportFiles) if (file) { LogPrintf("Importing blocks file %s...\n", path.string()); LoadExternalBlockFile(chainparams, file); + if (ShutdownRequested()) { + LogPrintf("Shutdown requested. Exit %s\n", __func__); + return; + } } else { LogPrintf("Warning: Could not open blocks file %s\n", path.string()); } diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 0e7641ae32..c5262e4bc0 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -53,88 +53,6 @@ bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<Rec return true; } -class LockImpl : public Chain::Lock, public UniqueLock<RecursiveMutex> -{ - Optional<int> getHeight() override - { - LockAssertion lock(::cs_main); - int height = ::ChainActive().Height(); - if (height >= 0) { - return height; - } - return nullopt; - } - Optional<int> getBlockHeight(const uint256& hash) override - { - LockAssertion lock(::cs_main); - CBlockIndex* block = LookupBlockIndex(hash); - if (block && ::ChainActive().Contains(block)) { - return block->nHeight; - } - return nullopt; - } - uint256 getBlockHash(int height) override - { - LockAssertion lock(::cs_main); - CBlockIndex* block = ::ChainActive()[height]; - assert(block != nullptr); - return block->GetBlockHash(); - } - bool haveBlockOnDisk(int height) override - { - LockAssertion lock(::cs_main); - CBlockIndex* block = ::ChainActive()[height]; - return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0; - } - Optional<int> findFirstBlockWithTimeAndHeight(int64_t time, int height, uint256* hash) override - { - LockAssertion lock(::cs_main); - CBlockIndex* block = ::ChainActive().FindEarliestAtLeast(time, height); - if (block) { - if (hash) *hash = block->GetBlockHash(); - return block->nHeight; - } - return nullopt; - } - Optional<int> findFork(const uint256& hash, Optional<int>* height) override - { - LockAssertion lock(::cs_main); - const CBlockIndex* block = LookupBlockIndex(hash); - const CBlockIndex* fork = block ? ::ChainActive().FindFork(block) : nullptr; - if (height) { - if (block) { - *height = block->nHeight; - } else { - height->reset(); - } - } - if (fork) { - return fork->nHeight; - } - return nullopt; - } - CBlockLocator getTipLocator() override - { - LockAssertion lock(::cs_main); - return ::ChainActive().GetLocator(); - } - Optional<int> findLocatorFork(const CBlockLocator& locator) override - { - LockAssertion lock(::cs_main); - if (CBlockIndex* fork = FindForkInGlobalIndex(::ChainActive(), locator)) { - return fork->nHeight; - } - return nullopt; - } - bool checkFinalTx(const CTransaction& tx) override - { - LockAssertion lock(::cs_main); - return CheckFinalTx(tx); - } - - using UniqueLock::UniqueLock; -}; - class NotificationsProxy : public CValidationInterface { public: @@ -227,12 +145,64 @@ class ChainImpl : public Chain { public: explicit ChainImpl(NodeContext& node) : m_node(node) {} - std::unique_ptr<Chain::Lock> lock(bool try_lock) override + Optional<int> getHeight() override + { + LOCK(::cs_main); + int height = ::ChainActive().Height(); + if (height >= 0) { + return height; + } + return nullopt; + } + Optional<int> getBlockHeight(const uint256& hash) override + { + LOCK(::cs_main); + CBlockIndex* block = LookupBlockIndex(hash); + if (block && ::ChainActive().Contains(block)) { + return block->nHeight; + } + return nullopt; + } + uint256 getBlockHash(int height) override + { + LOCK(::cs_main); + CBlockIndex* block = ::ChainActive()[height]; + assert(block); + return block->GetBlockHash(); + } + bool haveBlockOnDisk(int height) override + { + LOCK(cs_main); + CBlockIndex* block = ::ChainActive()[height]; + return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0; + } + Optional<int> findFirstBlockWithTimeAndHeight(int64_t time, int height, uint256* hash) override { - auto lock = MakeUnique<LockImpl>(::cs_main, "cs_main", __FILE__, __LINE__, try_lock); - if (try_lock && lock && !*lock) return {}; - std::unique_ptr<Chain::Lock> result = std::move(lock); // Temporary to avoid CWG 1579 - return result; + LOCK(cs_main); + CBlockIndex* block = ::ChainActive().FindEarliestAtLeast(time, height); + if (block) { + if (hash) *hash = block->GetBlockHash(); + return block->nHeight; + } + return nullopt; + } + CBlockLocator getTipLocator() override + { + LOCK(cs_main); + return ::ChainActive().GetLocator(); + } + bool checkFinalTx(const CTransaction& tx) override + { + LOCK(cs_main); + return CheckFinalTx(tx); + } + Optional<int> findLocatorFork(const CBlockLocator& locator) override + { + LOCK(cs_main); + if (CBlockIndex* fork = FindForkInGlobalIndex(::ChainActive(), locator)) { + return fork->nHeight; + } + return nullopt; } bool findBlock(const uint256& hash, const FoundBlock& block) override { diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index fb77c81cdc..e33fe54ac8 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -59,12 +59,7 @@ public: //! internal workings of the bitcoin node, and not being very convenient to use. //! Chain methods should be cleaned up and simplified over time. Examples: //! -//! * The Chain::lock() method, which lets clients delay chain tip updates -//! should be removed when clients are able to respond to updates -//! asynchronously -//! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269). -//! -//! * The initMessage() and showProgress() methods which the wallet uses to send +//! * The initMessages() and showProgress() methods which the wallet uses to send //! notifications to the GUI should go away when GUI and wallet can directly //! communicate with each other without going through the node //! (https://github.com/bitcoin/bitcoin/pull/15288#discussion_r253321096). @@ -72,68 +67,53 @@ public: //! * The handleRpc, registerRpcs, rpcEnableDeprecated methods and other RPC //! methods can go away if wallets listen for HTTP requests on their own //! ports instead of registering to handle requests on the node HTTP port. +//! +//! * Move fee estimation queries to an asynchronous interface and let the +//! wallet cache it, fee estimation being driven by node mempool, wallet +//! should be the consumer. +//! +//! * The `guessVerificationProgress`, `getBlockHeight`, `getBlockHash`, etc +//! methods can go away if rescan logic is moved on the node side, and wallet +//! only register rescan request. class Chain { public: virtual ~Chain() {} - //! Interface for querying locked chain state, used by legacy code that - //! assumes state won't change between calls. New code should avoid using - //! the Lock interface and instead call higher-level Chain methods - //! that return more information so the chain doesn't need to stay locked - //! between calls. - class Lock - { - public: - virtual ~Lock() {} - - //! Get current chain height, not including genesis block (returns 0 if - //! chain only contains genesis block, nullopt if chain does not contain - //! any blocks). - virtual Optional<int> getHeight() = 0; - - //! Get block height above genesis block. Returns 0 for genesis block, - //! 1 for following block, and so on. Returns nullopt for a block not - //! included in the current chain. - virtual Optional<int> getBlockHeight(const uint256& hash) = 0; - - //! Get block hash. Height must be valid or this function will abort. - virtual uint256 getBlockHash(int height) = 0; - - //! Check that the block is available on disk (i.e. has not been - //! pruned), and contains transactions. - virtual bool haveBlockOnDisk(int height) = 0; - - //! Return height of the first block in the chain with timestamp equal - //! or greater than the given time and height equal or greater than the - //! given height, or nullopt if there is no block with a high enough - //! timestamp and height. Also return the block hash as an optional output parameter - //! (to avoid the cost of a second lookup in case this information is needed.) - virtual Optional<int> findFirstBlockWithTimeAndHeight(int64_t time, int height, uint256* hash) = 0; - - //! Return height of the specified block if it is on the chain, otherwise - //! return the height of the highest block on chain that's an ancestor - //! of the specified block, or nullopt if there is no common ancestor. - //! Also return the height of the specified block as an optional output - //! parameter (to avoid the cost of a second hash lookup in case this - //! information is desired). - virtual Optional<int> findFork(const uint256& hash, Optional<int>* height) = 0; - - //! Get locator for the current chain tip. - virtual CBlockLocator getTipLocator() = 0; - - //! Return height of the highest block on chain in common with the locator, - //! which will either be the original block used to create the locator, - //! or one of its ancestors. - virtual Optional<int> findLocatorFork(const CBlockLocator& locator) = 0; - - //! Check if transaction will be final given chain height current time. - virtual bool checkFinalTx(const CTransaction& tx) = 0; - }; + //! Get current chain height, not including genesis block (returns 0 if + //! chain only contains genesis block, nullopt if chain does not contain + //! any blocks) + virtual Optional<int> getHeight() = 0; + + //! Get block height above genesis block. Returns 0 for genesis block, + //! 1 for following block, and so on. Returns nullopt for a block not + //! included in the current chain. + virtual Optional<int> getBlockHeight(const uint256& hash) = 0; + + //! Get block hash. Height must be valid or this function will abort. + virtual uint256 getBlockHash(int height) = 0; + + //! Check that the block is available on disk (i.e. has not been + //! pruned), and contains transactions. + virtual bool haveBlockOnDisk(int height) = 0; + + //! Return height of the first block in the chain with timestamp equal + //! or greater than the given time and height equal or greater than the + //! given height, or nullopt if there is no block with a high enough + //! timestamp and height. Also return the block hash as an optional output parameter + //! (to avoid the cost of a second lookup in case this information is needed.) + virtual Optional<int> findFirstBlockWithTimeAndHeight(int64_t time, int height, uint256* hash) = 0; + + //! Get locator for the current chain tip. + virtual CBlockLocator getTipLocator() = 0; + + //! Return height of the highest block on chain in common with the locator, + //! which will either be the original block used to create the locator, + //! or one of its ancestors. + virtual Optional<int> findLocatorFork(const CBlockLocator& locator) = 0; - //! Return Lock interface. Chain is locked when this is called, and - //! unlocked when the returned interface is freed. - virtual std::unique_ptr<Lock> lock(bool try_lock = false) = 0; + //! Check if transaction will be final given chain height current time. + virtual bool checkFinalTx(const CTransaction& tx) = 0; //! Return whether node has the block and optionally return block metadata //! or contents. diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 5ebbd61584..8564819e6a 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -43,8 +43,8 @@ class CWallet; fs::path GetWalletDir(); std::vector<fs::path> ListWalletDir(); std::vector<std::shared_ptr<CWallet>> GetWallets(); -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::vector<std::string>& warnings); -WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result); +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result); std::unique_ptr<interfaces::Handler> HandleLoadWallet(interfaces::Node::LoadWalletFn load_wallet); namespace interfaces { @@ -259,11 +259,11 @@ public: } return wallets; } - std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::vector<std::string>& warnings) override + std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) override { return MakeWallet(LoadWallet(*m_context.chain, name, error, warnings)); } - std::unique_ptr<Wallet> createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, WalletCreationStatus& status) override + std::unique_ptr<Wallet> createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, WalletCreationStatus& status) override { std::shared_ptr<CWallet> wallet; status = CreateWallet(*m_context.chain, passphrase, wallet_creation_flags, name, error, warnings, wallet); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 53a20886cd..db9b42b293 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -27,9 +27,10 @@ class Coin; class RPCTimerInterface; class UniValue; class proxyType; +enum class WalletCreationStatus; struct CNodeStateStats; struct NodeContext; -enum class WalletCreationStatus; +struct bilingual_str; namespace interfaces { class Handler; @@ -201,10 +202,10 @@ public: //! Attempts to load a wallet from file or directory. //! The loaded wallet is also notified to handlers previously registered //! with handleLoadWallet. - virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, std::string& error, std::vector<std::string>& warnings) = 0; + virtual std::unique_ptr<Wallet> loadWallet(const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) = 0; //! Create a wallet from file - virtual std::unique_ptr<Wallet> createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, WalletCreationStatus& status) = 0; + virtual std::unique_ptr<Wallet> createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, WalletCreationStatus& status) = 0; //! Register handler for init messages. using InitMessageFn = std::function<void(const std::string& message)>; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index 152917ac60..13b034936b 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -60,7 +60,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) } //! Construct wallet tx status struct. -WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx) +WalletTxStatus MakeWalletTxStatus(CWallet& wallet, const CWalletTx& wtx) { WalletTxStatus result; result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max(); @@ -68,8 +68,8 @@ WalletTxStatus MakeWalletTxStatus(interfaces::Chain::Lock& locked_chain, const C 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_final = wallet.chain().checkFinalTx(*wtx.tx); + result.is_trusted = wtx.IsTrusted(); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); result.is_in_main_chain = wtx.IsInMainChain(); @@ -196,25 +196,21 @@ public: } void lockCoin(const COutPoint& output) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->LockCoin(output); } void unlockCoin(const COutPoint& output) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->UnlockCoin(output); } bool isLockedCoin(const COutPoint& output) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->IsLockedCoin(output.hash, output.n); } void listLockedCoins(std::vector<COutPoint>& outputs) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->ListLockedCoins(outputs); } @@ -223,12 +219,11 @@ public: bool sign, int& change_pos, CAmount& fee, - std::string& fail_reason) override + bilingual_str& fail_reason) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); CTransactionRef tx; - if (!m_wallet->CreateTransaction(*locked_chain, recipients, tx, fee, change_pos, + if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos, fail_reason, coin_control, sign)) { return {}; } @@ -238,14 +233,12 @@ public: WalletValueMap value_map, WalletOrderForm order_form) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form)); } bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->AbandonTransaction(txid); } @@ -255,7 +248,7 @@ public: } bool createBumpTransaction(const uint256& txid, const CCoinControl& coin_control, - std::vector<std::string>& errors, + std::vector<bilingual_str>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) override @@ -265,7 +258,7 @@ public: bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); } bool commitBumpTransaction(const uint256& txid, CMutableTransaction&& mtx, - std::vector<std::string>& errors, + std::vector<bilingual_str>& errors, uint256& bumped_txid) override { return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) == @@ -273,7 +266,6 @@ public: } CTransactionRef getTx(const uint256& txid) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { @@ -283,7 +275,6 @@ public: } WalletTx getWalletTx(const uint256& txid) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { @@ -293,7 +284,6 @@ public: } std::vector<WalletTx> getWalletTxs() override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); std::vector<WalletTx> result; result.reserve(m_wallet->mapWallet.size()); @@ -307,10 +297,6 @@ public: int& num_blocks, int64_t& block_time) override { - auto locked_chain = m_wallet->chain().lock(true /* try_lock */); - if (!locked_chain) { - return false; - } TRY_LOCK(m_wallet->cs_wallet, locked_wallet); if (!locked_wallet) { return false; @@ -322,7 +308,7 @@ public: num_blocks = m_wallet->GetLastBlockHeight(); block_time = -1; CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(block_time))); - tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + tx_status = MakeWalletTxStatus(*m_wallet, mi->second); return true; } WalletTx getWalletTxDetails(const uint256& txid, @@ -331,14 +317,13 @@ public: bool& in_mempool, int& num_blocks) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { - num_blocks = locked_chain->getHeight().get_value_or(-1); + num_blocks = m_wallet->GetLastBlockHeight(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; - tx_status = MakeWalletTxStatus(*locked_chain, mi->second); + tx_status = MakeWalletTxStatus(*m_wallet, mi->second); return MakeWalletTx(*m_wallet, mi->second); } return {}; @@ -368,8 +353,6 @@ public: } bool tryGetBalances(WalletBalances& balances, int& num_blocks, bool force, int cached_num_blocks) override { - auto locked_chain = m_wallet->chain().lock(true /* try_lock */); - if (!locked_chain) return false; TRY_LOCK(m_wallet->cs_wallet, locked_wallet); if (!locked_wallet) { return false; @@ -386,34 +369,29 @@ public: } isminetype txinIsMine(const CTxIn& txin) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->IsMine(txin); } isminetype txoutIsMine(const CTxOut& txout) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->IsMine(txout); } CAmount getDebit(const CTxIn& txin, isminefilter filter) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->GetDebit(txin, filter); } CAmount getCredit(const CTxOut& txout, isminefilter filter) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); return m_wallet->GetCredit(txout, filter); } CoinsList listCoins() override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); CoinsList result; - for (const auto& entry : m_wallet->ListCoins(*locked_chain)) { + for (const auto& entry : m_wallet->ListCoins()) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), @@ -424,7 +402,6 @@ public: } std::vector<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override { - auto locked_chain = m_wallet->chain().lock(); LOCK(m_wallet->cs_wallet); std::vector<WalletTxOut> result; result.reserve(outputs.size()); @@ -496,6 +473,7 @@ public: { return MakeHandler(m_wallet->NotifyCanGetAddressesChanged.connect(fn)); } + CWallet* wallet() override { return m_wallet.get(); } std::shared_ptr<CWallet> m_wallet; }; diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 5d870c5e3d..f35335c69f 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -31,6 +31,7 @@ enum class TransactionError; enum isminetype : unsigned int; struct CRecipient; struct PartiallySignedTransaction; +struct bilingual_str; typedef uint8_t isminefilter; namespace interfaces { @@ -136,7 +137,7 @@ public: bool sign, int& change_pos, CAmount& fee, - std::string& fail_reason) = 0; + bilingual_str& fail_reason) = 0; //! Commit transaction. virtual void commitTransaction(CTransactionRef tx, @@ -155,7 +156,7 @@ public: //! Create bump transaction. virtual bool createBumpTransaction(const uint256& txid, const CCoinControl& coin_control, - std::vector<std::string>& errors, + std::vector<bilingual_str>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) = 0; @@ -166,7 +167,7 @@ public: //! Commit bump transaction. virtual bool commitBumpTransaction(const uint256& txid, CMutableTransaction&& mtx, - std::vector<std::string>& errors, + std::vector<bilingual_str>& errors, uint256& bumped_txid) = 0; //! Get a transaction. @@ -300,6 +301,9 @@ public: //! Register handler for keypool changed messages. using CanGetAddressesChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) = 0; + + //! Return pointer to internal wallet class, useful for testing. + virtual CWallet* wallet() { return nullptr; } }; //! Information about one wallet address. diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 00ccbc32f9..58b3e8aedc 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -324,8 +324,6 @@ public: // Return sum of txouts. CAmount GetValueOut() const; - // GetValueIn() is a method on CCoinsViewCache, because - // inputs must be known to compute value in. /** * Get the total transaction size in bytes, including witness data. diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 1aaf33c6a4..f48f28d03a 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -136,6 +136,8 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept); + + GUIUtil::handleCloseWindowShortcut(this); } AddressBookPage::~AddressBookPage() diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 67e7704551..3d1963b6e6 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -10,6 +10,7 @@ #include <qt/forms/ui_askpassphrasedialog.h> #include <qt/guiconstants.h> +#include <qt/guiutil.h> #include <qt/walletmodel.h> #include <support/allocators/secure.h> @@ -75,6 +76,8 @@ AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureStri connect(ui->passEdit1, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); connect(ui->passEdit2, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); connect(ui->passEdit3, &QLineEdit::textChanged, this, &AskPassphraseDialog::textChanged); + + GUIUtil::handleCloseWindowShortcut(this); } AskPassphraseDialog::~AskPassphraseDialog() diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 71e4d59ea7..cf2de1a417 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -211,6 +211,8 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformSty #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; #endif + + GUIUtil::handleCloseWindowShortcut(this); } BitcoinGUI::~BitcoinGUI() diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 1889f5e056..b46f416b6c 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -134,6 +134,8 @@ CoinControlDialog::CoinControlDialog(const PlatformStyle *_platformStyle, QWidge ui->radioTreeMode->click(); if (settings.contains("nCoinControlSortColumn") && settings.contains("nCoinControlSortOrder")) sortView(settings.value("nCoinControlSortColumn").toInt(), (static_cast<Qt::SortOrder>(settings.value("nCoinControlSortOrder").toInt()))); + + GUIUtil::handleCloseWindowShortcut(this); } CoinControlDialog::~CoinControlDialog() diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index 4711412ac4..e0af9a20a8 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -43,6 +43,8 @@ EditAddressDialog::EditAddressDialog(Mode _mode, QWidget *parent) : GUIUtil::ItemDelegate* delegate = new GUIUtil::ItemDelegate(mapper); connect(delegate, &GUIUtil::ItemDelegate::keyEscapePressed, this, &EditAddressDialog::reject); mapper->setItemDelegate(delegate); + + GUIUtil::handleCloseWindowShortcut(this); } EditAddressDialog::~EditAddressDialog() diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a54b65961c..af86fe5d27 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -54,6 +54,7 @@ #include <QSettings> #include <QSize> #include <QString> +#include <QShortcut> #include <QTextDocument> // for Qt::mightBeRichText #include <QThread> #include <QUrlQuery> @@ -378,6 +379,11 @@ void bringToFront(QWidget* w) } } +void handleCloseWindowShortcut(QWidget* w) +{ + QObject::connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), w), &QShortcut::activated, w, &QWidget::close); +} + void openDebugLogfile() { fs::path pathDebug = GetDataDir() / "debug.log"; diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index e571262443..0d9293fbad 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -124,6 +124,9 @@ namespace GUIUtil // Activate, show and raise the widget void bringToFront(QWidget* w); + // Set shortcut to close window + void handleCloseWindowShortcut(QWidget* w); + // Open debug.log void openDebugLogfile(); diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index b9dea2f8bf..9a3d43c2a6 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -15,6 +15,8 @@ OpenURIDialog::OpenURIDialog(QWidget *parent) : ui(new Ui::OpenURIDialog) { ui->setupUi(this); + + GUIUtil::handleCloseWindowShortcut(this); } OpenURIDialog::~OpenURIDialog() diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 8ee6c947e6..ae6aeb7709 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -135,6 +135,8 @@ OptionsDialog::OptionsDialog(QWidget *parent, bool enableWallet) : ui->minimizeToTray->setChecked(false); ui->minimizeToTray->setEnabled(false); } + + GUIUtil::handleCloseWindowShortcut(this); } OptionsDialog::~OptionsDialog() diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index b4fae7d78d..30bd5c6a5a 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -30,6 +30,8 @@ ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : #endif connect(ui->btnSaveAs, &QPushButton::clicked, ui->lblQRCode, &QRImageWidget::saveImage); + + GUIUtil::handleCloseWindowShortcut(this); } ReceiveRequestDialog::~ReceiveRequestDialog() diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 18a7e9b021..bfe316bdcd 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -498,6 +498,8 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty consoleFontSize = settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize()).toInt(); clear(); + + GUIUtil::handleCloseWindowShortcut(this); } RPCConsole::~RPCConsole() diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 67c2c71e9c..5cfbb67449 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -47,6 +47,8 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformS ui->signatureOut_SM->setFont(GUIUtil::fixedPitchFont()); ui->signatureIn_VM->setFont(GUIUtil::fixedPitchFont()); + + GUIUtil::handleCloseWindowShortcut(this); } SignVerifyMessageDialog::~SignVerifyMessageDialog() diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index d8fe4a9238..ced6a299d5 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -127,6 +127,8 @@ SplashScreen::SplashScreen(interfaces::Node& node, Qt::WindowFlags f, const Netw subscribeToCoreSignals(); installEventFilter(this); + + GUIUtil::handleCloseWindowShortcut(this); } SplashScreen::~SplashScreen() diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 94a1e0a63d..2ee9ae0d86 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -145,7 +145,6 @@ void TestGUI(interfaces::Node& node) wallet->LoadWallet(firstRun); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); - auto locked_chain = wallet->chain().lock(); LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); diff --git a/src/qt/transactiondescdialog.cpp b/src/qt/transactiondescdialog.cpp index ca72720e00..715e312b19 100644 --- a/src/qt/transactiondescdialog.cpp +++ b/src/qt/transactiondescdialog.cpp @@ -5,6 +5,7 @@ #include <qt/transactiondescdialog.h> #include <qt/forms/ui_transactiondescdialog.h> +#include <qt/guiutil.h> #include <qt/transactiontablemodel.h> #include <QModelIndex> @@ -17,6 +18,8 @@ TransactionDescDialog::TransactionDescDialog(const QModelIndex &idx, QWidget *pa setWindowTitle(tr("Details for %1").arg(idx.data(TransactionTableModel::TxHashRole).toString())); QString desc = idx.data(TransactionTableModel::LongDescriptionRole).toString(); ui->detailText->setHtml(desc); + + GUIUtil::handleCloseWindowShortcut(this); } TransactionDescDialog::~TransactionDescDialog() diff --git a/src/qt/utilitydialog.cpp b/src/qt/utilitydialog.cpp index d16feba1bb..01922cf996 100644 --- a/src/qt/utilitydialog.cpp +++ b/src/qt/utilitydialog.cpp @@ -10,6 +10,8 @@ #include <qt/forms/ui_helpmessagedialog.h> +#include <qt/guiutil.h> + #include <clientversion.h> #include <init.h> #include <util/system.h> @@ -102,6 +104,8 @@ HelpMessageDialog::HelpMessageDialog(interfaces::Node& node, QWidget *parent, bo ui->scrollArea->setVisible(false); ui->aboutLogo->setVisible(false); } + + GUIUtil::handleCloseWindowShortcut(this); } HelpMessageDialog::~HelpMessageDialog() @@ -141,6 +145,8 @@ ShutdownWindow::ShutdownWindow(QWidget *parent, Qt::WindowFlags f): tr("%1 is shutting down...").arg(PACKAGE_NAME) + "<br /><br />" + tr("Do not shut down the computer until this window disappears."))); setLayout(layout); + + GUIUtil::handleCloseWindowShortcut(this); } QWidget* ShutdownWindow::showShutdownWindow(QMainWindow* window) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index 9c1488fb0e..7cde3ca30b 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -14,6 +14,7 @@ #include <interfaces/handler.h> #include <interfaces/node.h> #include <util/string.h> +#include <util/translation.h> #include <wallet/wallet.h> #include <algorithm> @@ -244,10 +245,10 @@ void CreateWalletActivity::finish() { destroyProgressDialog(); - if (!m_error_message.empty()) { - QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message)); + if (!m_error_message.original.empty()) { + QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated)); } else if (!m_warning_message.empty()) { - QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, "\n"))); + QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, "\n", OpTranslated))); } if (m_wallet_model) Q_EMIT created(m_wallet_model); @@ -285,10 +286,10 @@ void OpenWalletActivity::finish() { destroyProgressDialog(); - if (!m_error_message.empty()) { - QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message)); + if (!m_error_message.original.empty()) { + QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated)); } else if (!m_warning_message.empty()) { - QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, "\n"))); + QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, "\n", OpTranslated))); } if (m_wallet_model) Q_EMIT opened(m_wallet_model); diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 3808b7d8bf..24dd83adf7 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -8,6 +8,7 @@ #include <qt/sendcoinsrecipient.h> #include <support/allocators/secure.h> #include <sync.h> +#include <util/translation.h> #include <map> #include <memory> @@ -104,8 +105,8 @@ protected: QWidget* const m_parent_widget; QProgressDialog* m_progress_dialog{nullptr}; WalletModel* m_wallet_model{nullptr}; - std::string m_error_message; - std::vector<std::string> m_warning_message; + bilingual_str m_error_message; + std::vector<bilingual_str> m_warning_message; }; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e7581cd64e..70ee7f4917 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -24,6 +24,7 @@ #include <psbt.h> #include <ui_interface.h> #include <util/system.h> // for GetBoolArg +#include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/wallet.h> // for CRecipient @@ -185,10 +186,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { CAmount nFeeRequired = 0; int nChangePosRet = -1; - std::string strFailReason; + bilingual_str error; auto& newTx = transaction.getWtx(); - newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, strFailReason); + newTx = m_wallet->createTransaction(vecSend, coinControl, !wallet().privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) transaction.reassignAmounts(nChangePosRet); @@ -199,8 +200,8 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { return SendCoinsReturn(AmountWithFeeExceedsBalance); } - Q_EMIT message(tr("Send Coins"), QString::fromStdString(strFailReason), - CClientUIInterface::MSG_ERROR); + Q_EMIT message(tr("Send Coins"), QString::fromStdString(error.translated), + CClientUIInterface::MSG_ERROR); return TransactionCreationFailed; } @@ -482,14 +483,14 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) { CCoinControl coin_control; coin_control.m_signal_bip125_rbf = true; - std::vector<std::string> errors; + std::vector<bilingual_str> errors; CAmount old_fee; CAmount new_fee; CMutableTransaction mtx; if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) { QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" + - (errors.size() ? QString::fromStdString(errors[0]) : "") +")"); - return false; + (errors.size() ? QString::fromStdString(errors[0].translated) : "") +")"); + return false; } const bool create_psbt = m_wallet->privateKeysDisabled(); @@ -551,8 +552,8 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash) // commit the bumped transaction if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) { QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" + - QString::fromStdString(errors[0])+")"); - return false; + QString::fromStdString(errors[0].translated)+")"); + return false; } return true; } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 827b83d67e..f7ccbae706 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2242,8 +2242,7 @@ UniValue dumptxoutset(const JSONRPCRequest& request) { RPCHelpMan{ "dumptxoutset", - "\nWrite the serialized UTXO set to disk.\n" - "Incidentally flushes the latest coinsdb (leveldb) to disk.\n", + "\nWrite the serialized UTXO set to disk.\n", { {"path", RPCArg::Type::STR, diff --git a/src/script/script.h b/src/script/script.h index daf4224530..773ffbb985 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -329,7 +329,7 @@ public: std::vector<unsigned char> result; const bool neg = value < 0; - uint64_t absvalue = neg ? -value : value; + uint64_t absvalue = neg ? ~static_cast<uint64_t>(value) + 1 : static_cast<uint64_t>(value); while(absvalue) { diff --git a/src/support/lockedpool.cpp b/src/support/lockedpool.cpp index 9b14637b95..f17b539e09 100644 --- a/src/support/lockedpool.cpp +++ b/src/support/lockedpool.cpp @@ -253,8 +253,10 @@ void *PosixLockedPageAllocator::AllocateLocked(size_t len, bool *lockingSuccess) } if (addr) { *lockingSuccess = mlock(addr, len) == 0; -#ifdef MADV_DONTDUMP +#if defined(MADV_DONTDUMP) // Linux madvise(addr, len, MADV_DONTDUMP); +#elif defined(MADV_NOCORE) // FreeBSD + madvise(addr, len, MADV_NOCORE); #endif } return addr; diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index d5b5ec3c9a..35d6804d4f 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -148,11 +148,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) const CScriptNum script_num{i64}; (void)script_num.getint(); - // Avoid negation failure: - // script/script.h:332:35: runtime error: negation of -9223372036854775808 cannot be represented in type 'int64_t' (aka 'long'); cast to an unsigned type to negate this value to itself - if (script_num != CScriptNum{std::numeric_limits<int64_t>::min()}) { - (void)script_num.getvch(); - } + (void)script_num.getvch(); const arith_uint256 au256 = UintToArith256(u256); assert(ArithToUint256(au256) == u256); diff --git a/src/test/fuzz/message.cpp b/src/test/fuzz/message.cpp new file mode 100644 index 0000000000..dfa98a812b --- /dev/null +++ b/src/test/fuzz/message.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2020 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 <chainparams.h> +#include <key_io.h> +#include <optional.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <util/message.h> +#include <util/strencodings.h> + +#include <cassert> +#include <cstdint> +#include <iostream> +#include <string> +#include <vector> + +void initialize() +{ + static const ECCVerifyHandle ecc_verify_handle; + ECC_Start(); + SelectParams(CBaseChainParams::REGTEST); +} + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + const std::string random_message = fuzzed_data_provider.ConsumeRandomLengthString(1024); + { + const std::vector<uint8_t> random_bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider); + CKey private_key; + private_key.Set(random_bytes.begin(), random_bytes.end(), fuzzed_data_provider.ConsumeBool()); + std::string signature; + const bool message_signed = MessageSign(private_key, random_message, signature); + if (private_key.IsValid()) { + assert(message_signed); + const MessageVerificationResult verification_result = MessageVerify(EncodeDestination(PKHash(private_key.GetPubKey().GetID())), signature, random_message); + assert(verification_result == MessageVerificationResult::OK); + } + } + { + (void)MessageHash(random_message); + (void)MessageVerify(fuzzed_data_provider.ConsumeRandomLengthString(1024), fuzzed_data_provider.ConsumeRandomLengthString(1024), random_message); + (void)SigningResultString(fuzzed_data_provider.PickValueInArray({SigningResult::OK, SigningResult::PRIVATE_KEY_NOT_AVAILABLE, SigningResult::SIGNING_FAILED})); + } +} diff --git a/src/test/fuzz/policy_estimator.cpp b/src/test/fuzz/policy_estimator.cpp new file mode 100644 index 0000000000..201f49c87b --- /dev/null +++ b/src/test/fuzz/policy_estimator.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2020 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 <optional.h> +#include <policy/fees.h> +#include <primitives/transaction.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <txmempool.h> + +#include <cstdint> +#include <string> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + CBlockPolicyEstimator block_policy_estimator; + while (fuzzed_data_provider.ConsumeBool()) { + switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 3)) { + case 0: { + const Optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mtx) { + break; + } + const CTransaction tx{*mtx}; + block_policy_estimator.processTransaction(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx), fuzzed_data_provider.ConsumeBool()); + if (fuzzed_data_provider.ConsumeBool()) { + (void)block_policy_estimator.removeTx(tx.GetHash(), /* inBlock */ fuzzed_data_provider.ConsumeBool()); + } + break; + } + case 1: { + std::vector<CTxMemPoolEntry> mempool_entries; + while (fuzzed_data_provider.ConsumeBool()) { + const Optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mtx) { + break; + } + const CTransaction tx{*mtx}; + mempool_entries.push_back(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx)); + } + std::vector<const CTxMemPoolEntry*> ptrs; + ptrs.reserve(mempool_entries.size()); + for (const CTxMemPoolEntry& mempool_entry : mempool_entries) { + ptrs.push_back(&mempool_entry); + } + block_policy_estimator.processBlock(fuzzed_data_provider.ConsumeIntegral<unsigned int>(), ptrs); + break; + } + case 2: { + (void)block_policy_estimator.removeTx(ConsumeUInt256(fuzzed_data_provider), /* inBlock */ fuzzed_data_provider.ConsumeBool()); + break; + } + case 3: { + block_policy_estimator.FlushUnconfirmed(); + break; + } + } + (void)block_policy_estimator.estimateFee(fuzzed_data_provider.ConsumeIntegral<int>()); + EstimationResult result; + (void)block_policy_estimator.estimateRawFee(fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeFloatingPoint<double>(), fuzzed_data_provider.PickValueInArray({FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE}), fuzzed_data_provider.ConsumeBool() ? &result : nullptr); + FeeCalculation fee_calculation; + (void)block_policy_estimator.estimateSmartFee(fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeBool() ? &fee_calculation : nullptr, fuzzed_data_provider.ConsumeBool()); + (void)block_policy_estimator.HighestTargetTracked(fuzzed_data_provider.PickValueInArray({FeeEstimateHorizon::SHORT_HALFLIFE, FeeEstimateHorizon::MED_HALFLIFE, FeeEstimateHorizon::LONG_HALFLIFE})); + } +} diff --git a/src/test/fuzz/rbf.cpp b/src/test/fuzz/rbf.cpp new file mode 100644 index 0000000000..eb54b05df9 --- /dev/null +++ b/src/test/fuzz/rbf.cpp @@ -0,0 +1,47 @@ +// Copyright (c) 2020 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 <optional.h> +#include <policy/rbf.h> +#include <primitives/transaction.h> +#include <sync.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> +#include <txmempool.h> + +#include <cstdint> +#include <string> +#include <vector> + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + Optional<CMutableTransaction> mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!mtx) { + return; + } + CTxMemPool pool; + while (fuzzed_data_provider.ConsumeBool()) { + const Optional<CMutableTransaction> another_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider); + if (!another_mtx) { + break; + } + const CTransaction another_tx{*another_mtx}; + if (fuzzed_data_provider.ConsumeBool() && !mtx->vin.empty()) { + mtx->vin[0].prevout = COutPoint{another_tx.GetHash(), 0}; + } + LOCK2(cs_main, pool.cs); + pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, another_tx)); + } + const CTransaction tx{*mtx}; + if (fuzzed_data_provider.ConsumeBool()) { + LOCK2(cs_main, pool.cs); + pool.addUnchecked(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx)); + } + { + LOCK(pool.cs); + (void)IsRBFOptIn(tx, pool); + } +} diff --git a/src/test/fuzz/scriptnum_ops.cpp b/src/test/fuzz/scriptnum_ops.cpp index 42b1432f13..f4e079fb89 100644 --- a/src/test/fuzz/scriptnum_ops.cpp +++ b/src/test/fuzz/scriptnum_ops.cpp @@ -129,10 +129,6 @@ void test_one_input(const std::vector<uint8_t>& buffer) break; } (void)script_num.getint(); - // Avoid negation failure: - // script/script.h:332:35: runtime error: negation of -9223372036854775808 cannot be represented in type 'int64_t' (aka 'long'); cast to an unsigned type to negate this value to itself - if (script_num != CScriptNum{std::numeric_limits<int64_t>::min()}) { - (void)script_num.getvch(); - } + (void)script_num.getvch(); } } diff --git a/src/test/fuzz/util.h b/src/test/fuzz/util.h index 9c7b0d47a2..501bb1de5a 100644 --- a/src/test/fuzz/util.h +++ b/src/test/fuzz/util.h @@ -8,12 +8,15 @@ #include <amount.h> #include <arith_uint256.h> #include <attributes.h> +#include <consensus/consensus.h> #include <optional.h> +#include <primitives/transaction.h> #include <script/script.h> #include <serialize.h> #include <streams.h> #include <test/fuzz/FuzzedDataProvider.h> #include <test/fuzz/fuzz.h> +#include <txmempool.h> #include <uint256.h> #include <version.h> @@ -97,6 +100,21 @@ NODISCARD inline arith_uint256 ConsumeArithUInt256(FuzzedDataProvider& fuzzed_da return UintToArith256(ConsumeUInt256(fuzzed_data_provider)); } +NODISCARD inline CTxMemPoolEntry ConsumeTxMemPoolEntry(FuzzedDataProvider& fuzzed_data_provider, const CTransaction& tx) noexcept +{ + // Avoid: + // policy/feerate.cpp:28:34: runtime error: signed integer overflow: 34873208148477500 * 1000 cannot be represented in type 'long' + // + // Reproduce using CFeeRate(348732081484775, 10).GetFeePerK() + const CAmount fee = std::min<CAmount>(ConsumeMoney(fuzzed_data_provider), std::numeric_limits<CAmount>::max() / static_cast<CAmount>(100000)); + assert(MoneyRange(fee)); + const int64_t time = fuzzed_data_provider.ConsumeIntegral<int64_t>(); + const unsigned int entry_height = fuzzed_data_provider.ConsumeIntegral<unsigned int>(); + const bool spends_coinbase = fuzzed_data_provider.ConsumeBool(); + const unsigned int sig_op_cost = fuzzed_data_provider.ConsumeIntegralInRange<unsigned int>(0, MAX_BLOCK_SIGOPS_COST); + return CTxMemPoolEntry{MakeTransactionRef(tx), fee, time, entry_height, spends_coinbase, sig_op_cost, {}}; +} + template <typename T> NODISCARD bool MultiplicationOverflow(const T i, const T j) noexcept { diff --git a/src/test/sanity_tests.cpp b/src/test/sanity_tests.cpp index 4d50845256..9a490aaf6b 100644 --- a/src/test/sanity_tests.cpp +++ b/src/test/sanity_tests.cpp @@ -14,7 +14,7 @@ BOOST_AUTO_TEST_CASE(basic_sanity) { BOOST_CHECK_MESSAGE(glibc_sanity_test() == true, "libc sanity test"); BOOST_CHECK_MESSAGE(glibcxx_sanity_test() == true, "stdlib sanity test"); - BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "openssl ECC test"); + BOOST_CHECK_MESSAGE(ECC_InitSanityCheck() == true, "secp256k1 sanity test"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 9d0ae56c3f..ddbc68f8e2 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -305,7 +305,6 @@ BOOST_AUTO_TEST_CASE(test_Get) t1.vout[0].scriptPubKey << OP_1; BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins)); - BOOST_CHECK_EQUAL(coins.GetValueIn(CTransaction(t1)), (50+21+22)*CENT); } static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index b8af869b8e..c345f1eafb 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -340,4 +340,38 @@ BOOST_AUTO_TEST_CASE(mempool_locks_reorg) rpc_thread.join(); } } + +BOOST_AUTO_TEST_CASE(witness_commitment_index) +{ + CScript pubKey; + pubKey << 1 << OP_TRUE; + auto ptemplate = BlockAssembler(*m_node.mempool, Params()).CreateNewBlock(pubKey); + CBlock pblock = ptemplate->block; + + CTxOut witness; + witness.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); + witness.scriptPubKey[0] = OP_RETURN; + witness.scriptPubKey[1] = 0x24; + witness.scriptPubKey[2] = 0xaa; + witness.scriptPubKey[3] = 0x21; + witness.scriptPubKey[4] = 0xa9; + witness.scriptPubKey[5] = 0xed; + + // A witness larger than the minimum size is still valid + CTxOut min_plus_one = witness; + min_plus_one.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT + 1); + + CTxOut invalid = witness; + invalid.scriptPubKey[0] = OP_VERIFY; + + CMutableTransaction txCoinbase(*pblock.vtx[0]); + txCoinbase.vout.resize(4); + txCoinbase.vout[0] = witness; + txCoinbase.vout[1] = witness; + txCoinbase.vout[2] = min_plus_one; + txCoinbase.vout[3] = invalid; + pblock.vtx[0] = MakeTransactionRef(std::move(txCoinbase)); + + BOOST_CHECK_EQUAL(GetWitnessCommitmentIndex(pblock), 2); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/translation.h b/src/util/translation.h index fc45da440a..b2d964c977 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -18,6 +18,20 @@ struct bilingual_str { std::string translated; }; +inline bilingual_str operator+(const bilingual_str& lhs, const bilingual_str& rhs) +{ + return bilingual_str{ + lhs.original + rhs.original, + lhs.translated + rhs.translated}; +} + +/** Mark a bilingual_str as untranslated */ +inline static bilingual_str Untranslated(std::string original) { return {original, original}; } +/** Unary operator to return the original */ +inline static std::string OpOriginal(const bilingual_str& b) { return b.original; } +/** Unary operator to return the translation */ +inline static std::string OpTranslated(const bilingual_str& b) { return b.translated; } + namespace tinyformat { template <typename... Args> bilingual_str format(const bilingual_str& fmt, const Args&... args) diff --git a/src/validation.cpp b/src/validation.cpp index 8632636637..a924af90af 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3386,7 +3386,14 @@ int GetWitnessCommitmentIndex(const CBlock& block) int commitpos = -1; if (!block.vtx.empty()) { for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) { - if (block.vtx[0]->vout[o].scriptPubKey.size() >= 38 && block.vtx[0]->vout[o].scriptPubKey[0] == OP_RETURN && block.vtx[0]->vout[o].scriptPubKey[1] == 0x24 && block.vtx[0]->vout[o].scriptPubKey[2] == 0xaa && block.vtx[0]->vout[o].scriptPubKey[3] == 0x21 && block.vtx[0]->vout[o].scriptPubKey[4] == 0xa9 && block.vtx[0]->vout[o].scriptPubKey[5] == 0xed) { + const CTxOut& vout = block.vtx[0]->vout[o]; + if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT && + vout.scriptPubKey[0] == OP_RETURN && + vout.scriptPubKey[1] == 0x24 && + vout.scriptPubKey[2] == 0xaa && + vout.scriptPubKey[3] == 0x21 && + vout.scriptPubKey[4] == 0xa9 && + vout.scriptPubKey[5] == 0xed) { commitpos = o; } } @@ -3417,7 +3424,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc CHash256().Write(witnessroot.begin(), 32).Write(ret.data(), 32).Finalize(witnessroot.begin()); CTxOut out; out.nValue = 0; - out.scriptPubKey.resize(38); + out.scriptPubKey.resize(MINIMUM_WITNESS_COMMITMENT); out.scriptPubKey[0] = OP_RETURN; out.scriptPubKey[1] = 0x24; out.scriptPubKey[2] = 0xaa; @@ -4637,7 +4644,7 @@ bool LoadGenesisBlock(const CChainParams& chainparams) return ::ChainstateActive().LoadGenesisBlock(chainparams); } -bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos *dbp) +void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp) { // Map of disk positions for blocks with unknown parent (only used for reindex) static std::multimap<uint256, FlatFilePos> mapBlocksUnknownParent; @@ -4649,7 +4656,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi CBufferedFile blkdat(fileIn, 2*MAX_BLOCK_SERIALIZED_SIZE, MAX_BLOCK_SERIALIZED_SIZE+8, SER_DISK, CLIENT_VERSION); uint64_t nRewind = blkdat.GetPos(); while (!blkdat.eof()) { - boost::this_thread::interruption_point(); + if (ShutdownRequested()) return; blkdat.SetPos(nRewind); nRewind++; // start one byte further next time, in case of failure @@ -4754,9 +4761,7 @@ bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFi } catch (const std::runtime_error& e) { AbortNode(std::string("System error: ") + e.what()); } - if (nLoaded > 0) - LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart); - return nLoaded > 0; + LogPrintf("Loaded %i blocks from external file in %dms\n", nLoaded, GetTimeMillis() - nStart); } void CChainState::CheckBlockIndex(const Consensus::Params& consensusParams) diff --git a/src/validation.h b/src/validation.h index 91b1ba6497..c4a5cc4593 100644 --- a/src/validation.h +++ b/src/validation.h @@ -92,6 +92,8 @@ static const unsigned int DEFAULT_CHECKLEVEL = 3; // one 128MB block file + added 15% undo data = 147MB greater for a total of 545MB // Setting the target to >= 550 MiB will make it likely we can respect the target. static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; +/** Minimum size of a witness commitment structure. Defined in BIP 141. **/ +static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38}; struct BlockHasher { @@ -180,7 +182,7 @@ FILE* OpenBlockFile(const FlatFilePos &pos, bool fReadOnly = false); /** Translation to a filesystem path */ fs::path GetBlockPosFilename(const FlatFilePos &pos); /** Import blocks from an external file */ -bool LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos *dbp = nullptr); +void LoadExternalBlockFile(const CChainParams& chainparams, FILE* fileIn, FlatFilePos* dbp = nullptr); /** Ensures we have a genesis block in the block tree, possibly writing one to disk. */ bool LoadGenesisBlock(const CChainParams& chainparams); /** Load the block tree and coins database from disk, diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index 67be4d85d2..3f7c2d09cc 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -393,7 +393,7 @@ bool BerkeleyBatch::Recover(const fs::path& file_path, void *callbackDataIn, boo return fSuccess; } -bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& errorStr) +bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr) { std::string walletFile; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile); @@ -403,14 +403,14 @@ bool BerkeleyBatch::VerifyEnvironment(const fs::path& file_path, std::string& er LogPrintf("Using wallet %s\n", file_path.string()); if (!env->Open(true /* retry */)) { - errorStr = strprintf(_("Error initializing wallet database environment %s!").translated, walletDir); + errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir); return false; } return true; } -bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::vector<std::string>& warnings, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc) +bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc) { std::string walletFile; std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, walletFile); @@ -425,12 +425,12 @@ bool BerkeleyBatch::VerifyDatabaseFile(const fs::path& file_path, std::vector<st warnings.push_back(strprintf(_("Warning: Wallet file corrupt, data salvaged!" " Original %s saved as %s in %s; if" " your balance or transactions are incorrect you should" - " restore from a backup.").translated, + " restore from a backup."), walletFile, backup_filename, walletDir)); } if (r == BerkeleyEnvironment::VerifyResult::RECOVER_FAIL) { - errorStr = strprintf(_("%s corrupt, salvage failed").translated, walletFile); + errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile); return false; } } diff --git a/src/wallet/db.h b/src/wallet/db.h index 1c5f42abca..1bf3375475 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -21,6 +21,8 @@ #include <db_cxx.h> +struct bilingual_str; + static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100; static const bool DEFAULT_WALLET_PRIVDB = true; @@ -242,9 +244,9 @@ public: ideal to be called periodically */ static bool PeriodicFlush(BerkeleyDatabase& database); /* verifies the database environment */ - static bool VerifyEnvironment(const fs::path& file_path, std::string& errorStr); + static bool VerifyEnvironment(const fs::path& file_path, bilingual_str& errorStr); /* verifies the database file */ - static bool VerifyDatabaseFile(const fs::path& file_path, std::vector<std::string>& warnings, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc); + static bool VerifyDatabaseFile(const fs::path& file_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc); template <typename K, typename T> bool Read(const K& key, T& value) diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index dafa022ded..cacf306891 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -3,44 +3,45 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <interfaces/chain.h> -#include <wallet/coincontrol.h> -#include <wallet/feebumper.h> -#include <wallet/fees.h> -#include <wallet/wallet.h> #include <policy/fees.h> #include <policy/policy.h> #include <util/moneystr.h> #include <util/rbf.h> #include <util/system.h> +#include <util/translation.h> +#include <wallet/coincontrol.h> +#include <wallet/feebumper.h> +#include <wallet/fees.h> +#include <wallet/wallet.h> //! 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(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<bilingual_str>& errors) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { if (wallet.HasWalletSpend(wtx.GetHash())) { - errors.push_back("Transaction has descendants in the wallet"); + errors.push_back(Untranslated("Transaction has descendants in the wallet")); return feebumper::Result::INVALID_PARAMETER; } { if (wallet.chain().hasDescendantsInMempool(wtx.GetHash())) { - errors.push_back("Transaction has descendants in the mempool"); + errors.push_back(Untranslated("Transaction has descendants in the mempool")); return feebumper::Result::INVALID_PARAMETER; } } if (wtx.GetDepthInMainChain() != 0) { - errors.push_back("Transaction has been mined, or is conflicted with a mined transaction"); + errors.push_back(Untranslated("Transaction has been mined, or is conflicted with a mined transaction")); return feebumper::Result::WALLET_ERROR; } if (!SignalsOptInRBF(*wtx.tx)) { - errors.push_back("Transaction is not BIP 125 replaceable"); + errors.push_back(Untranslated("Transaction is not BIP 125 replaceable")); return feebumper::Result::WALLET_ERROR; } if (wtx.mapValue.count("replaced_by_txid")) { - errors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid"))); + errors.push_back(strprintf(Untranslated("Cannot bump transaction %s which was already bumped by transaction %s"), wtx.GetHash().ToString(), wtx.mapValue.at("replaced_by_txid"))); return feebumper::Result::WALLET_ERROR; } @@ -48,7 +49,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; if (!wallet.IsAllFromMe(*wtx.tx, filter)) { - errors.push_back("Transaction contains inputs that don't belong to this wallet"); + errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); return feebumper::Result::WALLET_ERROR; } @@ -57,7 +58,8 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet } //! Check if the user provided a valid feeRate -static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<std::string>& errors) { +static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CFeeRate& newFeerate, const int64_t maxTxSize, std::vector<bilingual_str>& errors) +{ // check that fee rate is higher than mempool's minimum fee // (no point in bumping fee if we know that the new tx won't be accepted to the mempool) // This may occur if the user set fee_rate or paytxfee too low, if fallbackfee is too low, or, perhaps, @@ -67,7 +69,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt if (newFeerate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { errors.push_back(strprintf( - "New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- ", + Untranslated("New fee rate (%s) is lower than the minimum fee rate (%s) to get into the mempool -- "), FormatMoney(newFeerate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()))); return feebumper::Result::WALLET_ERROR; @@ -86,14 +88,14 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt CAmount minTotalFee = nOldFeeRate.GetFee(maxTxSize) + incrementalRelayFee.GetFee(maxTxSize); if (new_total_fee < minTotalFee) { - errors.push_back(strprintf("Insufficient total fee %s, must be at least %s (oldFee %s + incrementalFee %s)", + errors.push_back(strprintf(Untranslated("Insufficient total fee %s, must be at least %s (oldFee %s + incrementalFee %s)"), FormatMoney(new_total_fee), FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxTxSize)), FormatMoney(incrementalRelayFee.GetFee(maxTxSize)))); return feebumper::Result::INVALID_PARAMETER; } CAmount requiredFee = GetRequiredFee(wallet, maxTxSize); if (new_total_fee < requiredFee) { - errors.push_back(strprintf("Insufficient total fee (cannot be less than required fee %s)", + errors.push_back(strprintf(Untranslated("Insufficient total fee (cannot be less than required fee %s)"), FormatMoney(requiredFee))); return feebumper::Result::INVALID_PARAMETER; } @@ -101,8 +103,8 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt // Check that in all cases the new fee doesn't violate maxTxFee const CAmount max_tx_fee = wallet.m_default_max_tx_fee; if (new_total_fee > max_tx_fee) { - errors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than -maxtxfee %s)", - FormatMoney(new_total_fee), FormatMoney(max_tx_fee))); + errors.push_back(strprintf(Untranslated("Specified or calculated fee %s is too high (cannot be higher than -maxtxfee %s)"), + FormatMoney(new_total_fee), FormatMoney(max_tx_fee))); return feebumper::Result::WALLET_ERROR; } @@ -140,28 +142,26 @@ namespace feebumper { bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid) { - auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); const CWalletTx* wtx = wallet.GetWalletTx(txid); if (wtx == nullptr) return false; - std::vector<std::string> errors_dummy; + std::vector<bilingual_str> errors_dummy; feebumper::Result res = PreconditionChecks(wallet, *wtx, errors_dummy); return res == feebumper::Result::OK; } -Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<std::string>& errors, +Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCoinControl& coin_control, std::vector<bilingual_str>& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) { // We are going to modify coin control later, copy to re-use CCoinControl new_coin_control(coin_control); - auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); errors.clear(); auto it = wallet.mapWallet.find(txid); if (it == wallet.mapWallet.end()) { - errors.push_back("Invalid or non-wallet transaction id"); + errors.push_back(Untranslated("Invalid or non-wallet transaction id")); return Result::INVALID_ADDRESS_OR_KEY; } const CWalletTx& wtx = it->second; @@ -218,9 +218,9 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo CTransactionRef tx_new = MakeTransactionRef(); CAmount fee_ret; int change_pos_in_out = -1; // No requested location for change - std::string fail_reason; - if (!wallet.CreateTransaction(*locked_chain, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { - errors.push_back("Unable to create transaction: " + fail_reason); + bilingual_str fail_reason; + if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, false)) { + errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); return Result::WALLET_ERROR; } @@ -240,21 +240,19 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx) { - auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); return wallet.SignTransaction(mtx); } -Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<std::string>& errors, uint256& bumped_txid) +Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransaction&& mtx, std::vector<bilingual_str>& errors, uint256& bumped_txid) { - auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); if (!errors.empty()) { return Result::MISC_ERROR; } auto it = txid.IsNull() ? wallet.mapWallet.end() : wallet.mapWallet.find(txid); if (it == wallet.mapWallet.end()) { - errors.push_back("Invalid or non-wallet transaction id"); + errors.push_back(Untranslated("Invalid or non-wallet transaction id")); return Result::MISC_ERROR; } CWalletTx& oldWtx = it->second; @@ -279,7 +277,7 @@ Result CommitTransaction(CWallet& wallet, const uint256& txid, CMutableTransacti // along with an exception. It would be good to return information about // wtxBumped to the caller even if marking the original transaction // replaced does not succeed for some reason. - errors.push_back("Created new bumpfee transaction but could not mark the original transaction as replaced"); + errors.push_back(Untranslated("Created new bumpfee transaction but could not mark the original transaction as replaced")); } return Result::OK; } diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h index fc038ae731..50577c9d3e 100644 --- a/src/wallet/feebumper.h +++ b/src/wallet/feebumper.h @@ -12,6 +12,7 @@ class CWalletTx; class uint256; class CCoinControl; enum class FeeEstimateMode; +struct bilingual_str; namespace feebumper { @@ -30,12 +31,12 @@ bool TransactionCanBeBumped(const CWallet& wallet, const uint256& txid); //! Create bumpfee transaction based on feerate estimates. Result CreateRateBumpTransaction(CWallet& wallet, - const uint256& txid, - const CCoinControl& coin_control, - std::vector<std::string>& errors, - CAmount& old_fee, - CAmount& new_fee, - CMutableTransaction& mtx); + const uint256& txid, + const CCoinControl& coin_control, + std::vector<bilingual_str>& errors, + CAmount& old_fee, + CAmount& new_fee, + CMutableTransaction& mtx); //! Sign the new transaction, //! @return false if the tx couldn't be found or if it was @@ -47,10 +48,10 @@ bool SignTransaction(CWallet& wallet, CMutableTransaction& mtx); //! but sets errors if the tx could not be added to the mempool (will try later) //! or if the old transaction could not be marked as replaced. Result CommitTransaction(CWallet& wallet, - const uint256& txid, - CMutableTransaction&& mtx, - std::vector<std::string>& errors, - uint256& bumped_txid); + const uint256& txid, + CMutableTransaction&& mtx, + std::vector<bilingual_str>& errors, + uint256& bumped_txid); } // namespace feebumper diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index 0afd2dfcf0..217a950457 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -53,12 +53,14 @@ bool VerifyWallets(interfaces::Chain& chain, const std::vector<std::string>& wal return false; } - std::string error_string; - std::vector<std::string> warnings; + bilingual_str error_string; + std::vector<bilingual_str> warnings; bool verify_success = CWallet::Verify(chain, location, salvage_wallet, error_string, warnings); - if (!error_string.empty()) chain.initError(error_string); - if (!warnings.empty()) chain.initWarning(Join(warnings, "\n")); - if (!verify_success) return false; + if (!warnings.empty()) chain.initWarning(Join(warnings, "\n", OpTranslated)); + if (!verify_success) { + chain.initError(error_string.translated); + return false; + } } return true; @@ -68,12 +70,12 @@ bool LoadWallets(interfaces::Chain& chain, const std::vector<std::string>& walle { try { for (const std::string& walletFile : wallet_files) { - std::string error; - std::vector<std::string> warnings; + bilingual_str error; + std::vector<bilingual_str> warnings; std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(chain, WalletLocation(walletFile), error, warnings); - if (!warnings.empty()) chain.initWarning(Join(warnings, "\n")); + if (!warnings.empty()) chain.initWarning(Join(warnings, "\n", OpTranslated)); if (!pwallet) { - chain.initError(error); + chain.initError(error.translated); return false; } AddWallet(pwallet); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 9417e2bd58..c863d22530 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -133,7 +133,6 @@ UniValue importprivkey(const JSONRPCRequest& request) WalletRescanReserver reserver(*pwallet); bool fRescan = true; { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -285,7 +284,6 @@ UniValue importaddress(const JSONRPCRequest& request) fP2SH = request.params[3].get_bool(); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); @@ -317,7 +315,6 @@ UniValue importaddress(const JSONRPCRequest& request) { RescanWallet(*pwallet, reserver); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } @@ -361,7 +358,6 @@ UniValue importprunedfunds(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); int height; if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { @@ -407,7 +403,6 @@ UniValue removeprunedfunds(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -487,7 +482,6 @@ UniValue importpubkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::set<CScript> script_pub_keys; @@ -505,7 +499,6 @@ UniValue importpubkey(const JSONRPCRequest& request) { RescanWallet(*pwallet, reserver); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } @@ -557,7 +550,6 @@ UniValue importwallet(const JSONRPCRequest& request) int64_t nTimeBegin = 0; bool fGood = true; { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -700,7 +692,6 @@ UniValue dumpprivkey(const JSONRPCRequest& request) LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet); - auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); EnsureWalletIsUnlocked(pwallet); @@ -756,7 +747,6 @@ UniValue dumpwallet(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); EnsureWalletIsUnlocked(&wallet); @@ -780,7 +770,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::map<CKeyID, int64_t> mapKeyBirth; const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys(); - pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth); + pwallet->GetKeyBirthTimes(mapKeyBirth); std::set<CScriptID> scripts = spk_man.GetCScripts(); @@ -1379,7 +1369,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) int64_t nLowestTimestamp = 0; UniValue response(UniValue::VARR); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -1414,7 +1403,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) if (fRescan && fRunScan && requests.size()) { int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } @@ -1676,7 +1664,6 @@ UniValue importdescriptors(const JSONRPCRequest& main_request) { bool rescan = false; UniValue response(UniValue::VARR); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -1705,7 +1692,6 @@ UniValue importdescriptors(const JSONRPCRequest& main_request) { if (rescan) { int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 49c583c422..e666d55e11 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -23,6 +23,7 @@ #include <util/moneystr.h> #include <util/string.h> #include <util/system.h> +#include <util/translation.h> #include <util/url.h> #include <util/vector.h> #include <wallet/coincontrol.h> @@ -133,7 +134,7 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_cr return *spk_man; } -static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& locked_chain, const CWalletTx& wtx, UniValue& entry) +static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniValue& entry) { int confirms = wtx.GetDepthInMainChain(); entry.pushKV("confirmations", confirms); @@ -148,7 +149,7 @@ static void WalletTxToJSON(interfaces::Chain& chain, interfaces::Chain::Lock& lo CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); entry.pushKV("blocktime", block_time); } else { - entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); + entry.pushKV("trusted", wtx.IsTrusted()); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); @@ -322,7 +323,7 @@ static UniValue setlabel(const JSONRPCRequest& request) } -static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) +static CTransactionRef SendMoney(CWallet* const pwallet, const CTxDestination& address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue) { CAmount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse).m_mine_trusted; @@ -338,16 +339,16 @@ static CTransactionRef SendMoney(interfaces::Chain::Lock& locked_chain, CWallet // Create and send the transaction CAmount nFeeRequired = 0; - std::string strError; + bilingual_str error; std::vector<CRecipient> vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CTransactionRef tx; - if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strError, coin_control)) { + if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) - strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired)); - throw JSONRPCError(RPC_WALLET_ERROR, strError); + error = strprintf(Untranslated("Error: This transaction requires a transaction fee of at least %s"), FormatMoney(nFeeRequired)); + throw JSONRPCError(RPC_WALLET_ERROR, error.original); } pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */); return tx; @@ -399,7 +400,6 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); @@ -445,7 +445,7 @@ static UniValue sendtoaddress(const JSONRPCRequest& request) EnsureWalletIsUnlocked(pwallet); - CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); + CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); return tx->GetHash().GetHex(); } @@ -487,11 +487,10 @@ static UniValue listaddressgroupings(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(*locked_chain); + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) @@ -543,7 +542,6 @@ static UniValue signmessage(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -572,7 +570,7 @@ static UniValue signmessage(const JSONRPCRequest& request) return signature; } -static CAmount GetReceived(interfaces::Chain::Lock& locked_chain, const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { std::set<CTxDestination> address_set; @@ -602,7 +600,7 @@ static CAmount GetReceived(interfaces::Chain::Lock& locked_chain, const CWallet& CAmount amount = 0; for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { const CWalletTx& wtx = wtx_pair.second; - if (wtx.IsCoinBase() || !locked_chain.checkFinalTx(*wtx.tx) || wtx.GetDepthInMainChain() < min_depth) { + if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx) || wtx.GetDepthInMainChain() < min_depth) { continue; } @@ -652,10 +650,9 @@ static UniValue getreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ValueFromAmount(GetReceived(*locked_chain, *pwallet, request.params, /* by_label */ false)); + return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ false)); } @@ -693,10 +690,9 @@ static UniValue getreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ValueFromAmount(GetReceived(*locked_chain, *pwallet, request.params, /* by_label */ true)); + return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ true)); } @@ -736,7 +732,6 @@ static UniValue getbalance(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); const UniValue& dummy_value = request.params[0]; @@ -778,7 +773,6 @@ static UniValue getunconfirmedbalance(const JSONRPCRequest &request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); @@ -841,7 +835,6 @@ static UniValue sendmany(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { @@ -911,11 +904,11 @@ static UniValue sendmany(const JSONRPCRequest& request) // Send CAmount nFeeRequired = 0; int nChangePosRet = -1; - std::string strFailReason; + bilingual_str error; CTransactionRef tx; - bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strFailReason, coin_control); + bool fCreated = pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control); if (!fCreated) - throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */); return tx->GetHash().GetHex(); } @@ -963,7 +956,6 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet); - auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); std::string label; @@ -1016,7 +1008,7 @@ struct tallyitem } }; -static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, const CWallet* const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static UniValue ListReceived(const CWallet* const pwallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { // Minimum confirmations int nMinDepth = 1; @@ -1049,7 +1041,7 @@ static UniValue ListReceived(interfaces::Chain::Lock& locked_chain, const CWalle for (const std::pair<const uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { const CWalletTx& wtx = pairWtx.second; - if (wtx.IsCoinBase() || !locked_chain.checkFinalTx(*wtx.tx)) { + if (wtx.IsCoinBase() || !pwallet->chain().checkFinalTx(*wtx.tx)) { continue; } @@ -1209,10 +1201,9 @@ static UniValue listreceivedbyaddress(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ListReceived(*locked_chain, pwallet, request.params, false); + return ListReceived(pwallet, request.params, false); } static UniValue listreceivedbylabel(const JSONRPCRequest& request) @@ -1254,10 +1245,9 @@ static UniValue listreceivedbylabel(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - return ListReceived(*locked_chain, pwallet, request.params, true); + return ListReceived(pwallet, request.params, true); } static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) @@ -1278,7 +1268,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ -static void ListTransactions(interfaces::Chain::Lock& locked_chain, const CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) +static void ListTransactions(const CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { CAmount nFee; std::list<COutputEntry> listReceived; @@ -1307,7 +1297,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, const CWalle entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-nFee)); if (fLong) - WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); + WalletTxToJSON(pwallet->chain(), wtx, entry); entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } @@ -1349,7 +1339,7 @@ static void ListTransactions(interfaces::Chain::Lock& locked_chain, const CWalle } entry.pushKV("vout", r.vout); if (fLong) - WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); + WalletTxToJSON(pwallet->chain(), wtx, entry); ret.push_back(entry); } } @@ -1464,7 +1454,6 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); const CWallet::TxItems & txOrdered = pwallet->wtxOrdered; @@ -1473,7 +1462,7 @@ UniValue listtransactions(const JSONRPCRequest& request) for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; - ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter, filter_label); + ListTransactions(pwallet, *pwtx, 0, true, ret, filter, filter_label); if ((int)ret.size() >= (nCount+nFrom)) break; } } @@ -1557,7 +1546,6 @@ static UniValue listsinceblock(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // The way the 'height' is initialized is just a workaround for the gcc bug #47679 since version 4.6.0. @@ -1598,7 +1586,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) CWalletTx tx = pairWtx.second; if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { - ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); + ListTransactions(pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } @@ -1615,7 +1603,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request) if (it != pwallet->mapWallet.end()) { // We want all transactions regardless of confirmation count to appear here, // even negative confirmation ones, hence the big negative. - ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); + ListTransactions(pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } blockId = block.hashPrevBlock; @@ -1700,7 +1688,6 @@ static UniValue gettransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -1729,10 +1716,10 @@ static UniValue gettransaction(const JSONRPCRequest& request) if (wtx.IsFromMe(filter)) entry.pushKV("fee", ValueFromAmount(nFee)); - WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); + WalletTxToJSON(pwallet->chain(), wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); + ListTransactions(pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); @@ -1776,7 +1763,6 @@ static UniValue abandontransaction(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); uint256 hash(ParseHashV(request.params[0], "txid")); @@ -1817,7 +1803,6 @@ static UniValue backupwallet(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::string strDest = request.params[0].get_str(); @@ -1855,7 +1840,6 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool @@ -1909,7 +1893,6 @@ static UniValue walletpassphrase(const JSONRPCRequest& request) int64_t nSleepTime; { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { @@ -1991,7 +1974,6 @@ static UniValue walletpassphrasechange(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { @@ -2047,7 +2029,6 @@ static UniValue walletlock(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { @@ -2094,7 +2075,6 @@ static UniValue encryptwallet(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { @@ -2173,7 +2153,6 @@ static UniValue lockunspent(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); @@ -2286,7 +2265,6 @@ static UniValue listlockunspent(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::vector<COutPoint> vOutpts; @@ -2329,7 +2307,6 @@ static UniValue settxfee(const JSONRPCRequest& request) }, }.Check(request); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); CAmount nAmount = AmountFromValue(request.params[0]); @@ -2388,7 +2365,6 @@ static UniValue getbalances(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); - auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); const auto bal = wallet.GetBalance(); @@ -2465,7 +2441,6 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); @@ -2615,14 +2590,14 @@ static UniValue loadwallet(const JSONRPCRequest& request) } } - std::string error; - std::vector<std::string> warning; - std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_chain, location, error, warning); - if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error); + bilingual_str error; + std::vector<bilingual_str> warnings; + std::shared_ptr<CWallet> const wallet = LoadWallet(*g_rpc_chain, location, error, warnings); + if (!wallet) throw JSONRPCError(RPC_WALLET_ERROR, error.original); UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); - obj.pushKV("warning", Join(warning, "\n")); + obj.pushKV("warning", Join(warnings, "\n", OpOriginal)); return obj; } @@ -2731,12 +2706,12 @@ static UniValue createwallet(const JSONRPCRequest& request) } SecureString passphrase; passphrase.reserve(100); - std::vector<std::string> warnings; + std::vector<bilingual_str> warnings; if (!request.params[3].isNull()) { passphrase = request.params[3].get_str().c_str(); if (passphrase.empty()) { // Empty string means unencrypted - warnings.emplace_back("Empty string given as passphrase, wallet will not be encrypted."); + warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted.")); } } @@ -2747,14 +2722,14 @@ static UniValue createwallet(const JSONRPCRequest& request) flags |= WALLET_FLAG_DESCRIPTORS; } - std::string error; + bilingual_str error; std::shared_ptr<CWallet> wallet; WalletCreationStatus status = CreateWallet(*g_rpc_chain, passphrase, flags, request.params[0].get_str(), error, warnings, wallet); switch (status) { case WalletCreationStatus::CREATION_FAILED: - throw JSONRPCError(RPC_WALLET_ERROR, error); + throw JSONRPCError(RPC_WALLET_ERROR, error.original); case WalletCreationStatus::ENCRYPTION_FAILED: - throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error.original); case WalletCreationStatus::SUCCESS: break; // no default case, so the compiler can warn about missing cases @@ -2762,7 +2737,7 @@ static UniValue createwallet(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); - obj.pushKV("warning", Join(warnings, "\n")); + obj.pushKV("warning", Join(warnings, "\n", OpOriginal)); return obj; } @@ -2940,9 +2915,8 @@ static UniValue listunspent(const JSONRPCRequest& request) cctl.m_avoid_address_reuse = false; cctl.m_min_depth = nMinDepth; cctl.m_max_depth = nMaxDepth; - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); - pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); + pwallet->AvailableCoins(vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); } LOCK(pwallet->cs_wallet); @@ -3135,10 +3109,10 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f setSubtractFeeFromOutputs.insert(pos); } - std::string strFailReason; + bilingual_str error; - if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { - throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + if (!pwallet->FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + throw JSONRPCError(RPC_WALLET_ERROR, error.original); } } @@ -3312,7 +3286,6 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) } // Sign the transaction - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); @@ -3441,12 +3414,11 @@ static UniValue bumpfee(const JSONRPCRequest& request) // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); - std::vector<std::string> errors; + std::vector<bilingual_str> errors; CAmount old_fee; CAmount new_fee; CMutableTransaction mtx; @@ -3456,19 +3428,19 @@ static UniValue bumpfee(const JSONRPCRequest& request) if (res != feebumper::Result::OK) { switch(res) { case feebumper::Result::INVALID_ADDRESS_OR_KEY: - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errors[0]); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errors[0].original); break; case feebumper::Result::INVALID_REQUEST: - throw JSONRPCError(RPC_INVALID_REQUEST, errors[0]); + throw JSONRPCError(RPC_INVALID_REQUEST, errors[0].original); break; case feebumper::Result::INVALID_PARAMETER: - throw JSONRPCError(RPC_INVALID_PARAMETER, errors[0]); + throw JSONRPCError(RPC_INVALID_PARAMETER, errors[0].original); break; case feebumper::Result::WALLET_ERROR: - throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); + throw JSONRPCError(RPC_WALLET_ERROR, errors[0].original); break; default: - throw JSONRPCError(RPC_MISC_ERROR, errors[0]); + throw JSONRPCError(RPC_MISC_ERROR, errors[0].original); break; } } @@ -3484,7 +3456,7 @@ static UniValue bumpfee(const JSONRPCRequest& request) uint256 txid; if (feebumper::CommitTransaction(*pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) { - throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); + throw JSONRPCError(RPC_WALLET_ERROR, errors[0].original); } result.pushKV("txid", txid.GetHex()); @@ -3502,8 +3474,8 @@ static UniValue bumpfee(const JSONRPCRequest& request) result.pushKV("origfee", ValueFromAmount(old_fee)); result.pushKV("fee", ValueFromAmount(new_fee)); UniValue result_errors(UniValue::VARR); - for (const std::string& error : errors) { - result_errors.push_back(error); + for (const bilingual_str& error : errors) { + result_errors.push_back(error.original); } result.pushKV("errors", result_errors); @@ -3548,7 +3520,6 @@ UniValue rescanblockchain(const JSONRPCRequest& request) Optional<int> stop_height; uint256 start_block; { - auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); int tip_height = pwallet->GetLastBlockHeight(); @@ -3563,8 +3534,7 @@ UniValue rescanblockchain(const JSONRPCRequest& request) stop_height = request.params[1].get_int(); if (*stop_height < 0 || *stop_height > tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); - } - else if (*stop_height < start_height) { + } else if (*stop_height < start_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height"); } } @@ -4011,7 +3981,6 @@ UniValue sethdseed(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled"); } - auto locked_chain = pwallet->chain().lock(); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); // Do not do anything to non-HD wallets @@ -4271,12 +4240,12 @@ static UniValue upgradewallet(const JSONRPCRequest& request) version = request.params[0].get_int(); } - std::string error; - std::vector<std::string> warnings; + bilingual_str error; + std::vector<bilingual_str> warnings; if (!pwallet->UpgradeWallet(version, error, warnings)) { - throw JSONRPCError(RPC_WALLET_ERROR, error); + throw JSONRPCError(RPC_WALLET_ERROR, error.original); } - return error; + return error.original; } UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index ecb95d599d..e4be5045e1 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -377,10 +377,9 @@ bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const return keypool_has_keys; } -bool LegacyScriptPubKeyMan::Upgrade(int prev_version, std::string& error) +bool LegacyScriptPubKeyMan::Upgrade(int prev_version, bilingual_str& error) { LOCK(cs_KeyStore); - error = ""; bool hd_upgrade = false; bool split_upgrade = false; if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) { @@ -405,7 +404,7 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, std::string& error) // Regenerate the keypool if upgraded to HD if (hd_upgrade) { if (!TopUp()) { - error = _("Unable to generate keys").translated; + error = _("Unable to generate keys"); return false; } } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 3117b13d35..5414fc5923 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -19,6 +19,7 @@ #include <boost/signals2/signal.hpp> enum class OutputType; +struct bilingual_str; // Wallet storage things that ScriptPubKeyMans need in order to be able to store things to the wallet database. // It provides access to things that are part of the entire wallet and not specific to a ScriptPubKeyMan such as @@ -191,7 +192,7 @@ public: virtual bool CanGetAddresses(bool internal = false) const { return false; } /** Upgrades the wallet to the specified version */ - virtual bool Upgrade(int prev_version, std::string& error) { return false; } + virtual bool Upgrade(int prev_version, bilingual_str& error) { return false; } virtual bool HavePrivateKeys() const { return false; } @@ -343,7 +344,7 @@ public: bool SetupGeneration(bool force = false) override; - bool Upgrade(int prev_version, std::string& error) override; + bool Upgrade(int prev_version, bilingual_str& error) override; bool HavePrivateKeys() const override; diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 1215956bb4..0826b88f0a 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -15,6 +15,7 @@ #include <rpc/server.h> #include <test/util/logging.h> #include <test/util/setup_common.h> +#include <util/translation.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/test/wallet_test_fixture.h> @@ -30,8 +31,8 @@ BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain) { - std::string error; - std::vector<std::string> warnings; + bilingual_str error; + std::vector<bilingual_str> warnings; auto wallet = CWallet::CreateWalletFromFile(chain, WalletLocation(""), error, warnings); wallet->postInitProcess(); return wallet; @@ -75,8 +76,6 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) NodeContext node; auto chain = interfaces::MakeChain(node); - auto locked_chain = chain->lock(); - LockAssertion lock(::cs_main); // Verify ScanForWalletTransactions fails to read an unknown start block. { @@ -116,7 +115,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) } // Prune the older block file. - PruneOneBlockFile(oldTip->GetBlockPos().nFile); + { + LOCK(cs_main); + PruneOneBlockFile(oldTip->GetBlockPos().nFile); + } UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block @@ -139,7 +141,10 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) } // Prune the remaining block file. - PruneOneBlockFile(newTip->GetBlockPos().nFile); + { + LOCK(cs_main); + PruneOneBlockFile(newTip->GetBlockPos().nFile); + } UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. @@ -171,11 +176,12 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) NodeContext node; auto chain = interfaces::MakeChain(node); - auto locked_chain = chain->lock(); - LockAssertion lock(::cs_main); // Prune the older block file. - PruneOneBlockFile(oldTip->GetBlockPos().nFile); + { + LOCK(cs_main); + PruneOneBlockFile(oldTip->GetBlockPos().nFile); + } UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify importmulti RPC returns failure for a key whose creation time is @@ -241,8 +247,6 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) NodeContext node; auto chain = interfaces::MakeChain(node); - auto locked_chain = chain->lock(); - LockAssertion lock(::cs_main); std::string backup_file = (GetDataDir() / "wallet.backup").string(); @@ -308,8 +312,6 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); CWalletTx wtx(&wallet, m_coinbase_txns.back()); - auto locked_chain = chain->lock(); - LockAssertion lock(::cs_main); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); @@ -334,8 +336,6 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 SetMockTime(mockTime); CBlockIndex* block = nullptr; if (blockTime > 0) { - auto locked_chain = wallet.chain().lock(); - LockAssertion lock(::cs_main); auto inserted = ::BlockIndex().emplace(GetRandHash(), new CBlockIndex); assert(inserted.second); const uint256& hash = inserted.first->first; @@ -345,7 +345,6 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64 } CWalletTx wtx(&wallet, MakeTransactionRef(tx)); - LOCK(cs_main); LOCK(wallet.cs_wallet); // If transaction is already in map, to avoid inconsistencies, unconfirmation // is needed before confirm again with different block. @@ -492,7 +491,7 @@ public: CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); { - LOCK2(::cs_main, wallet->cs_wallet); + LOCK2(wallet->cs_wallet, ::cs_main); wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } bool firstRun; @@ -517,11 +516,10 @@ public: CTransactionRef tx; CAmount fee; int changePos = -1; - std::string error; + bilingual_str error; CCoinControl dummy; { - auto locked_chain = m_chain->lock(); - BOOST_CHECK(wallet->CreateTransaction(*locked_chain, {recipient}, tx, fee, changePos, error, dummy)); + BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy)); } wallet->CommitTransaction(tx, {}, {}); CMutableTransaction blocktx; @@ -531,7 +529,6 @@ public: } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); - LOCK(cs_main); LOCK(wallet->cs_wallet); wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetHash()); @@ -554,9 +551,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) // address. std::map<CTxDestination, std::vector<COutput>> list; { - auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); - list = wallet->ListCoins(*locked_chain); + list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); @@ -571,9 +567,8 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) // pubkey. AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { - auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); - list = wallet->ListCoins(*locked_chain); + list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); @@ -581,10 +576,9 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) // Lock both coins. Confirm number of available coins drops to 0. { - auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(*locked_chain, available); + wallet->AvailableCoins(available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto& group : list) { @@ -594,18 +588,16 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) } } { - auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); std::vector<COutput> available; - wallet->AvailableCoins(*locked_chain, available); + wallet->AvailableCoins(available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { - auto locked_chain = m_chain->lock(); LOCK(wallet->cs_wallet); - list = wallet->ListCoins(*locked_chain); + list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); @@ -694,11 +686,20 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup) //! conditions if it's called the same time an incoming transaction shows up in //! the mempool or a new block. //! -//! It isn't possible for a unit test to totally verify there aren't race -//! conditions without hooking into the implementation more, so this test just -//! verifies that new transactions are detected during loading without any -//! notifications at all, to infer that timing of notifications shouldn't -//! matter. The test could be extended to cover other scenarios in the future. +//! It isn't possible to verify there aren't race condition in every case, so +//! this test just checks two specific cases and ensures that timing of +//! notifications in these cases doesn't prevent the wallet from detecting +//! transactions. +//! +//! In the first case, block and mempool transactions are created before the +//! wallet is loaded, but notifications about these transactions are delayed +//! until after it is loaded. The notifications are superfluous in this case, so +//! the test verifies the transactions are detected before they arrive. +//! +//! In the second case, block and mempool transactions are created after the +//! wallet rescan and notifications are immediately synced, to verify the wallet +//! must already have a handler in place for them, and there's no gap after +//! rescanning where new transactions in new blocks could be lost. BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) { // Create new wallet with known key and unload it. @@ -709,6 +710,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) AddKey(*wallet, key); TestUnloadWallet(std::move(wallet)); + // Add log hook to detect AddToWallet events from rescans, blockConnected, // and transactionAddedToMempool notifications int addtx_count = 0; @@ -717,21 +719,14 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) return false; }); + bool rescan_completed = false; DebugLogHelper rescan_check("[default wallet] Rescan completed", [&](const std::string* s) { - if (s) { - // For now, just assert that cs_main is being held during the - // rescan, ensuring that a new block couldn't be connected - // that the wallet would miss. After - // https://github.com/bitcoin/bitcoin/pull/16426 when cs_main is no - // longer held, the test can be extended to append a new block here - // and check it's handled correctly. - AssertLockHeld(::cs_main); - rescan_completed = true; - } + if (s) rescan_completed = true; return false; }); + // Block the queue to prevent the wallet receiving blockConnected and // transactionAddedToMempool notifications, and create block and mempool // transactions paying to the wallet @@ -746,29 +741,56 @@ BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey())); BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error)); + // Reload wallet and make sure new transactions are detected despite events // being blocked wallet = TestLoadWallet(*chain); BOOST_CHECK(rescan_completed); BOOST_CHECK_EQUAL(addtx_count, 2); - unsigned int block_tx_time, mempool_tx_time; { LOCK(wallet->cs_wallet); - block_tx_time = wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived; - mempool_tx_time = wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived; + BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1); + BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1); } + // Unblock notification queue and make sure stale blockConnected and // transactionAddedToMempool events are processed promise.set_value(); SyncWithValidationInterfaceQueue(); BOOST_CHECK_EQUAL(addtx_count, 4); + + + TestUnloadWallet(std::move(wallet)); + + + // Load wallet again, this time creating new block and mempool transactions + // paying to the wallet as the wallet finishes loading and syncing the + // queue so the events have to be handled immediately. Releasing the wallet + // lock during the sync is a little artificial but is needed to avoid a + // deadlock during the sync and simulates a new block notification happening + // as soon as possible. + addtx_count = 0; + auto handler = HandleLoadWallet([&](std::unique_ptr<interfaces::Wallet> wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet->wallet()->cs_wallet) { + BOOST_CHECK(rescan_completed); + m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); + block_tx = TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey())); + m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]); + mempool_tx = TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey())); + BOOST_CHECK(chain->broadcastTransaction(MakeTransactionRef(mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false, error)); + LEAVE_CRITICAL_SECTION(wallet->wallet()->cs_wallet); + SyncWithValidationInterfaceQueue(); + ENTER_CRITICAL_SECTION(wallet->wallet()->cs_wallet); + }); + wallet = TestLoadWallet(*chain); + BOOST_CHECK_EQUAL(addtx_count, 4); { LOCK(wallet->cs_wallet); - BOOST_CHECK_EQUAL(block_tx_time, wallet->mapWallet.at(block_tx.GetHash()).nTimeReceived); - BOOST_CHECK_EQUAL(mempool_tx_time, wallet->mapWallet.at(mempool_tx.GetHash()).nTimeReceived); + BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1); + BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetHash()), 1); } + TestUnloadWallet(std::move(wallet)); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6d46841cee..a893548971 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -27,6 +27,7 @@ #include <util/fees.h> #include <util/moneystr.h> #include <util/rbf.h> +#include <util/string.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> @@ -149,34 +150,34 @@ void UnloadWallet(std::shared_ptr<CWallet>&& wallet) } } -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings) +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings) { try { if (!CWallet::Verify(chain, location, false, error, warnings)) { - error = "Wallet file verification failed: " + error; + error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; return nullptr; } std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings); if (!wallet) { - error = "Wallet loading failed: " + error; + error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error; return nullptr; } AddWallet(wallet); wallet->postInitProcess(); return wallet; } catch (const std::runtime_error& e) { - error = e.what(); + error = Untranslated(e.what()); return nullptr; } } -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::vector<std::string>& warnings) +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings) { return LoadWallet(chain, WalletLocation(name), error, warnings); } -WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result) +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result) { // Indicate that the wallet is actually supposed to be blank and not just blank to make it encrypted bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); @@ -189,39 +190,39 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& // Check the wallet file location WalletLocation location(name); if (location.Exists()) { - error = "Wallet " + location.GetName() + " already exists."; + error = strprintf(Untranslated("Wallet %s already exists."), location.GetName()); return WalletCreationStatus::CREATION_FAILED; } // Wallet::Verify will check if we're trying to create a wallet with a duplicate name. if (!CWallet::Verify(chain, location, false, error, warnings)) { - error = "Wallet file verification failed: " + error; + error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; return WalletCreationStatus::CREATION_FAILED; } // Do not allow a passphrase when private keys are disabled if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - error = "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."; + error = Untranslated("Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled."); return WalletCreationStatus::CREATION_FAILED; } // Make the wallet std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chain, location, error, warnings, wallet_creation_flags); if (!wallet) { - error = "Wallet creation failed: " + error; + error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error; return WalletCreationStatus::CREATION_FAILED; } // Encrypt the wallet if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!wallet->EncryptWallet(passphrase)) { - error = "Error: Wallet created but failed to encrypt."; + error = Untranslated("Error: Wallet created but failed to encrypt."); return WalletCreationStatus::ENCRYPTION_FAILED; } if (!create_blank) { // Unlock the wallet if (!wallet->Unlock(passphrase)) { - error = "Error: Wallet was encrypted but could not be unlocked"; + error = Untranslated("Error: Wallet was encrypted but could not be unlocked"); return WalletCreationStatus::ENCRYPTION_FAILED; } @@ -233,7 +234,7 @@ WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& } else { for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { - error = "Unable to generate initial keys"; + error = Untranslated("Unable to generate initial keys"); return WalletCreationStatus::CREATION_FAILED; } } @@ -885,10 +886,9 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) void CWallet::LoadToWallet(CWalletTx& wtxIn) { - // If wallet doesn't have a chain (e.g bitcoin-wallet), lock can't be taken. - auto locked_chain = LockChain(); - if (locked_chain) { - Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock); + // If wallet doesn't have a chain (e.g wallet-tool), don't bother to update txn. + if (HaveChain()) { + Optional<int> block_height = chain().getBlockHeight(wtxIn.m_confirm.hashBlock); if (block_height) { // Update cached block height variable since it not stored in the // serialized transaction. @@ -975,7 +975,6 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, CWalletTx::Co 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() == 0 && !wtx->InMempool(); @@ -993,7 +992,6 @@ void CWallet::MarkInputsDirty(const CTransactionRef& tx) bool CWallet::AbandonTransaction(const uint256& hashTx) { - auto locked_chain = chain().lock(); // Temporary. Removed in upcoming lock cleanup LOCK(cs_wallet); WalletBatch batch(*database, "r+"); @@ -1048,7 +1046,6 @@ bool CWallet::AbandonTransaction(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 = (m_last_block_processed_height - conflicting_height + 1) * -1; @@ -1111,7 +1108,6 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio } void CWallet::transactionAddedToMempool(const CTransactionRef& ptx) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, {}, /* nIndex */ 0); SyncTransaction(ptx, confirm); @@ -1133,7 +1129,6 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef &ptx) { void CWallet::blockConnected(const CBlock& block, int height) { const uint256& block_hash = block.GetHash(); - auto locked_chain = chain().lock(); LOCK(cs_wallet); m_last_block_processed_height = height; @@ -1147,7 +1142,6 @@ void CWallet::blockConnected(const CBlock& block, int height) void CWallet::blockDisconnected(const CBlock& block, int height) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); // At block disconnection, this will change an abandoned transaction to @@ -1686,7 +1680,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc uint256 next_block_hash; bool reorg = false; if (chain().findBlock(block_hash, FoundBlock().data(block)) && !block.IsNull()) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); next_block = chain().findNextBlock(block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg); if (reorg) { @@ -1715,7 +1708,6 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc break; } { - auto locked_chain = chain().lock(); if (!next_block || reorg) { // break successfully when rescan has reached the tip, or // previous block is no longer on the chain due to a reorg @@ -1928,16 +1920,16 @@ bool CWalletTx::InMempool() const return fInMempool; } -bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain) const +bool CWalletTx::IsTrusted() const { std::set<uint256> s; - return IsTrusted(locked_chain, s); + return IsTrusted(s); } -bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const +bool CWalletTx::IsTrusted(std::set<uint256>& trusted_parents) const { // Quick answer in most cases - if (!locked_chain.checkFinalTx(*tx)) return false; + if (!pwallet->chain().checkFinalTx(*tx)) return false; int nDepth = GetDepthInMainChain(); if (nDepth >= 1) return true; if (nDepth < 0) return false; @@ -1959,7 +1951,7 @@ bool CWalletTx::IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint25 // 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; + if (!parent->IsTrusted(trusted_parents)) return false; trusted_parents.insert(parent->GetHash()); } return true; @@ -2003,8 +1995,7 @@ void CWallet::ResendWalletTransactions() int submitted_tx_count = 0; - { // locked_chain and cs_wallet scope - auto locked_chain = chain().lock(); + { // cs_wallet scope LOCK(cs_wallet); // Relay transactions @@ -2017,7 +2008,7 @@ void CWallet::ResendWalletTransactions() std::string unused_err_string; if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; } - } // locked_chain and cs_wallet + } // cs_wallet if (submitted_tx_count > 0) { WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count); @@ -2045,13 +2036,12 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; { - 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, trusted_parents)}; + const bool is_trusted{wtx.IsTrusted(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)}; @@ -2072,12 +2062,11 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const { - auto locked_chain = chain().lock(); LOCK(cs_wallet); CAmount balance = 0; std::vector<COutput> vCoins; - AvailableCoins(*locked_chain, vCoins, true, coinControl); + AvailableCoins(vCoins, true, coinControl); for (const COutput& out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; @@ -2086,7 +2075,7 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const return balance; } -void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const +void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const { AssertLockHeld(cs_wallet); @@ -2104,7 +2093,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< const uint256& wtxid = entry.first; const CWalletTx& wtx = entry.second; - if (!locked_chain.checkFinalTx(*wtx.tx)) { + if (!chain().checkFinalTx(*wtx.tx)) { continue; } @@ -2120,7 +2109,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< if (nDepth == 0 && !wtx.InMempool()) continue; - bool safeTx = wtx.IsTrusted(locked_chain, trusted_parents); + bool safeTx = wtx.IsTrusted(trusted_parents); // We should not consider coins from transactions that are replacing // other transactions. @@ -2208,14 +2197,14 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< } } -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock& locked_chain) const +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const { AssertLockHeld(cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; - AvailableCoins(locked_chain, availableCoins); + AvailableCoins(availableCoins); for (const COutput& coin : availableCoins) { CTxDestination address; @@ -2532,7 +2521,7 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh return SigningResult::PRIVATE_KEY_NOT_AVAILABLE; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; @@ -2551,11 +2540,10 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). - auto locked_chain = chain().lock(); LOCK(cs_wallet); CTransactionRef tx_new; - if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { + if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, false)) { return false; } @@ -2671,8 +2659,7 @@ OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vec return m_default_address_type; } -bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) +bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); @@ -2683,7 +2670,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std { if (nValue < 0 || recipient.nAmount < 0) { - strFailReason = _("Transaction amounts must not be negative").translated; + error = _("Transaction amounts must not be negative"); return false; } nValue += recipient.nAmount; @@ -2693,7 +2680,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } if (vecSend.empty()) { - strFailReason = _("Transaction must have at least one recipient").translated; + error = _("Transaction must have at least one recipient"); return false; } @@ -2703,12 +2690,11 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std int nBytes; { std::set<CInputCoin> setCoins; - auto locked_chain = chain().lock(); LOCK(cs_wallet); txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); { std::vector<COutput> vAvailableCoins; - AvailableCoins(*locked_chain, vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + AvailableCoins(vAvailableCoins, true, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy // Create change script that will be used if we need change @@ -2731,7 +2717,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // destination in case we don't need change. CTxDestination dest; if (!reservedest.GetReservedDestination(dest, true)) { - strFailReason = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.").translated; + error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first."); } scriptChange = GetScriptForDestination(dest); assert(!dest.empty() || scriptChange.empty()); @@ -2793,12 +2779,12 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { if (txout.nValue < 0) - strFailReason = _("The transaction amount is too small to pay the fee").translated; + error = _("The transaction amount is too small to pay the fee"); else - strFailReason = _("The transaction amount is too small to send after the fee has been deducted").translated; + error = _("The transaction amount is too small to send after the fee has been deducted"); } else - strFailReason = _("Transaction amount too small").translated; + error = _("Transaction amount too small"); return false; } txNew.vout.push_back(txout); @@ -2826,7 +2812,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std continue; } else { - strFailReason = _("Insufficient funds").translated; + error = _("Insufficient funds"); return false; } } @@ -2857,7 +2843,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { - strFailReason = _("Change index out of range").translated; + error = _("Change index out of range"); return false; } @@ -2876,14 +2862,14 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); if (nBytes < 0) { - strFailReason = _("Signing transaction failed").translated; + error = _("Signing transaction failed"); return false; } nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control, &feeCalc); if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { // eventually allow a fallback fee - strFailReason = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.").translated; + error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); return false; } @@ -2923,7 +2909,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // fee to pay for the new output and still meet nFeeNeeded // Or we should have just subtracted fee from recipients and // nFeeNeeded should not have changed - strFailReason = _("Transaction fee and change calculation failed").translated; + error = _("Transaction fee and change calculation failed"); return false; } @@ -2976,7 +2962,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } if (sign && !SignTransaction(txNew)) { - strFailReason = _("Signing transaction failed").translated; + error = _("Signing transaction failed"); return false; } @@ -2986,20 +2972,20 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // Limit size if (GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) { - strFailReason = _("Transaction too large").translated; + error = _("Transaction too large"); return false; } } if (nFeeRet > m_default_max_tx_fee) { - strFailReason = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); + error = Untranslated(TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED)); return false; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!chain().checkChainLimits(tx)) { - strFailReason = _("Transaction has too long of a mempool chain").translated; + error = _("Transaction has too long of a mempool chain"); return false; } } @@ -3021,7 +3007,6 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm) { - auto locked_chain = chain().lock(); LOCK(cs_wallet); CWalletTx wtxNew(this, std::move(tx)); @@ -3061,11 +3046,6 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { - // Even if we don't use this lock in this function, we want to preserve - // lock order in LoadToWallet if query of chain state is needed to know - // tx status. If lock can't be taken (e.g bitcoin-wallet), tx confirmation - // status may be not reliable. - auto locked_chain = LockChain(); LOCK(cs_wallet); fFirstRunRet = false; @@ -3284,7 +3264,7 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations } } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain::Lock& locked_chain) const +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const { std::map<CTxDestination, CAmount> balances; @@ -3295,7 +3275,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances(interfaces::Chain: { const CWalletTx& wtx = walletEntry.second; - if (!wtx.IsTrusted(locked_chain, trusted_parents)) + if (!wtx.IsTrusted(trusted_parents)) continue; if (wtx.IsImmatureCoinBase()) @@ -3511,7 +3491,7 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const /** @} */ // end of Actions -void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CKeyID, int64_t>& mapKeyBirth) const { +void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t>& mapKeyBirth) const { AssertLockHeld(cs_wallet); mapKeyBirth.clear(); @@ -3677,7 +3657,7 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const return values; } -bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::vector<std::string>& warnings) +bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, bilingual_str& error_string, std::vector<bilingual_str>& warnings) { // Do some checking on wallet path. It should be either a: // @@ -3691,17 +3671,17 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b if (!(path_type == fs::file_not_found || path_type == fs::directory_file || (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || (path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) { - error_string = strprintf( + error_string = Untranslated(strprintf( "Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and " "database/log.?????????? files can be stored, a location where such a directory could be created, " "or (for backwards compatibility) the name of an existing data file in -walletdir (%s)", - location.GetName(), GetWalletDir()); + location.GetName(), GetWalletDir())); return false; } // Make sure that the wallet path doesn't clash with an existing wallet path if (IsWalletLoaded(wallet_path)) { - error_string = strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName()); + error_string = Untranslated(strprintf("Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName())); return false; } @@ -3713,7 +3693,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b return false; } } catch (const fs::filesystem_error& e) { - error_string = strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e)); + error_string = Untranslated(strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e))); return false; } @@ -3721,11 +3701,6 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b // Recover readable keypairs: CWallet dummyWallet(&chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; - // Even if we don't use this lock in this function, we want to preserve - // lock order in LoadToWallet if query of chain state is needed to know - // tx status. If lock can't be taken, tx confirmation status may be not - // reliable. - auto locked_chain = dummyWallet.LockChain(); if (!WalletBatch::Recover(wallet_path, (void *)&dummyWallet, WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; } @@ -3734,7 +3709,7 @@ bool CWallet::Verify(interfaces::Chain& chain, const WalletLocation& location, b return WalletBatch::VerifyDatabaseFile(wallet_path, warnings, error_string); } -std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings, uint64_t wallet_creation_flags) +std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags) { const std::string walletFile = WalletDataFilePath(location.GetPath()).string(); @@ -3747,7 +3722,7 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(&chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { - error = strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile); + error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile); return nullptr; } } @@ -3762,26 +3737,26 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { - error = strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile); + error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile); return nullptr; } else if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { warnings.push_back(strprintf(_("Error reading %s! All keys read correctly, but transaction data" - " or address book entries might be missing or incorrect.").translated, + " or address book entries might be missing or incorrect."), walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { - error = strprintf(_("Error loading %s: Wallet requires newer version of %s").translated, walletFile, PACKAGE_NAME); + error = strprintf(_("Error loading %s: Wallet requires newer version of %s"), walletFile, PACKAGE_NAME); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { - error = strprintf(_("Wallet needed to be rewritten: restart %s to complete").translated, PACKAGE_NAME); + error = strprintf(_("Wallet needed to be rewritten: restart %s to complete"), PACKAGE_NAME); return nullptr; } else { - error = strprintf(_("Error loading %s").translated, walletFile); + error = strprintf(_("Error loading %s"), walletFile); return nullptr; } } @@ -3807,47 +3782,46 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Legacy wallets need SetupGeneration here. for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { - error = _("Unable to generate initial keys").translated; + error = _("Unable to generate initial keys"); return nullptr; } } } } - auto locked_chain = chain.lock(); - walletInstance->chainStateFlushed(locked_chain->getTipLocator()); + walletInstance->chainStateFlushed(chain.getTipLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation - error = strprintf(_("Error loading %s: Private keys can only be disabled during creation").translated, walletFile); + error = strprintf(_("Error loading %s: Private keys can only be disabled during creation"), walletFile); return NULL; } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (spk_man->HavePrivateKeys()) { - warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys").translated, walletFile)); + warnings.push_back(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); break; } } } if (!gArgs.GetArg("-addresstype", "").empty() && !ParseOutputType(gArgs.GetArg("-addresstype", ""), walletInstance->m_default_address_type)) { - error = strprintf(_("Unknown address type '%s'").translated, gArgs.GetArg("-addresstype", "")); + error = strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", "")); return nullptr; } if (!gArgs.GetArg("-changetype", "").empty() && !ParseOutputType(gArgs.GetArg("-changetype", ""), walletInstance->m_default_change_type)) { - error = strprintf(_("Unknown change type '%s'").translated, gArgs.GetArg("-changetype", "")); + error = strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", "")); return nullptr; } if (gArgs.IsArgSet("-mintxfee")) { CAmount n = 0; if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || 0 == n) { - error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")).translated; + error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { - warnings.push_back(AmountHighWarn("-mintxfee").translated + " " + - _("This is the minimum transaction fee you pay on every transaction.").translated); + warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") + + _("This is the minimum transaction fee you pay on every transaction.")); } walletInstance->m_min_fee = CFeeRate(n); } @@ -3855,12 +3829,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-fallbackfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { - error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'").translated, gArgs.GetArg("-fallbackfee", "")); + error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", "")); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - warnings.push_back(AmountHighWarn("-fallbackfee").translated + " " + - _("This is the transaction fee you may pay when fee estimates are not available.").translated); + warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") + + _("This is the transaction fee you may pay when fee estimates are not available.")); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); } @@ -3870,28 +3844,28 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-discardfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-discardfee", ""), nFeePerK)) { - error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'").translated, gArgs.GetArg("-discardfee", "")); + error = strprintf(_("Invalid amount for -discardfee=<amount>: '%s'"), gArgs.GetArg("-discardfee", "")); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - warnings.push_back(AmountHighWarn("-discardfee").translated + " " + - _("This is the transaction fee you may discard if change is smaller than dust at this level").translated); + warnings.push_back(AmountHighWarn("-discardfee") + Untranslated(" ") + + _("This is the transaction fee you may discard if change is smaller than dust at this level")); } walletInstance->m_discard_rate = CFeeRate(nFeePerK); } if (gArgs.IsArgSet("-paytxfee")) { CAmount nFeePerK = 0; if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { - error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")).translated; + error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { - warnings.push_back(AmountHighWarn("-paytxfee").translated + " " + - _("This is the transaction fee you will pay if you send a transaction.").translated); + warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") + + _("This is the transaction fee you will pay if you send a transaction.")); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) { - error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)").translated, + error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' (must be at least %s)"), gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString()); return nullptr; } @@ -3900,23 +3874,23 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, if (gArgs.IsArgSet("-maxtxfee")) { CAmount nMaxFee = 0; if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { - error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")).translated; + error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")); return nullptr; } if (nMaxFee > HIGH_MAX_TX_FEE) { - warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.").translated); + warnings.push_back(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction.")); } if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) { - error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)").translated, - gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString()); + error = strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"), + gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString()); return nullptr; } walletInstance->m_default_max_tx_fee = nMaxFee; } if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) { - warnings.push_back(AmountHighWarn("-minrelaytxfee").translated + " " + - _("The wallet will avoid paying less than the minimum relay fee.").translated); + warnings.push_back(AmountHighWarn("-minrelaytxfee") + Untranslated(" ") + + _("The wallet will avoid paying less than the minimum relay fee.")); } walletInstance->m_confirm_target = gArgs.GetArg("-txconfirmtarget", DEFAULT_TX_CONFIRM_TARGET); @@ -3928,24 +3902,33 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); - auto locked_chain = chain.lock(); LOCK(walletInstance->cs_wallet); + // Register wallet with validationinterface. It's done before rescan to avoid + // missing block connections between end of rescan and validation subscribing. + // Because of wallet lock being hold, block connection notifications are going to + // be pending on the validation-side until lock release. It's likely to have + // block processing duplicata (if rescan block range overlaps with notification one) + // but we guarantee at least than wallet state is correct after notifications delivery. + // This is temporary until rescan and notifications delivery are unified under same + // interface. + walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); + int rescan_height = 0; if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; if (batch.ReadBestBlock(locator)) { - if (const Optional<int> fork_height = locked_chain->findLocatorFork(locator)) { + if (const Optional<int> fork_height = chain.findLocatorFork(locator)) { rescan_height = *fork_height; } } } - const Optional<int> tip_height = locked_chain->getHeight(); + const Optional<int> tip_height = chain.getHeight(); if (tip_height) { - walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); + walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height); walletInstance->m_last_block_processed_height = *tip_height; } else { walletInstance->m_last_block_processed.SetNull(); @@ -3962,12 +3945,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, // If a block is pruned after this check, we will load the wallet, // but fail the rescan with a generic error. int block_height = *tip_height; - while (block_height > 0 && locked_chain->haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { + while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { --block_height; } if (rescan_height != block_height) { - error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)").translated; + error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)"); return nullptr; } } @@ -3984,19 +3967,19 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, 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)) { + if (Optional<int> first_block = chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) { rescan_height = *first_block; } } { WalletRescanReserver reserver(*walletInstance); - if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(locked_chain->getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) { - error = _("Failed to rescan the wallet during initialization").translated; + if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) { + error = _("Failed to rescan the wallet during initialization"); return nullptr; } } - walletInstance->chainStateFlushed(locked_chain->getTipLocator()); + walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 @@ -4031,9 +4014,6 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } } - // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. - walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); - walletInstance->SetBroadcastTransactions(gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); { @@ -4055,7 +4035,7 @@ const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest return &address_book_it->second; } -bool CWallet::UpgradeWallet(int version, std::string& error, std::vector<std::string>& warnings) +bool CWallet::UpgradeWallet(int version, bilingual_str& error, std::vector<bilingual_str>& warnings) { int prev_version = GetVersion(); int nMaxVersion = version; @@ -4064,12 +4044,12 @@ bool CWallet::UpgradeWallet(int version, std::string& error, std::vector<std::st WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = FEATURE_LATEST; SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately - } - else + } else { WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); + } if (nMaxVersion < GetVersion()) { - error = _("Cannot downgrade wallet").translated; + error = _("Cannot downgrade wallet"); return false; } SetMaxVersion(nMaxVersion); @@ -4079,7 +4059,7 @@ bool CWallet::UpgradeWallet(int version, std::string& error, std::vector<std::st // Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT int max_version = GetVersion(); if (!CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { - error = _("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use version 169900 or no version specified.").translated; + error = _("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use version 169900 or no version specified."); return false; } @@ -4093,7 +4073,6 @@ bool CWallet::UpgradeWallet(int version, std::string& error, std::vector<std::st void CWallet::postInitProcess() { - auto locked_chain = chain().lock(); LOCK(cs_wallet); // Add wallet transactions that aren't already in a block to mempool diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 24c78fceb3..3be3435596 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -40,6 +40,8 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>; +struct bilingual_str; + //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. @@ -52,7 +54,7 @@ bool RemoveWallet(const std::shared_ptr<CWallet>& wallet); bool HasWallets(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); -std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings); +std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings); std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet); enum class WalletCreationStatus { @@ -61,7 +63,7 @@ enum class WalletCreationStatus { ENCRYPTION_FAILED }; -WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::vector<std::string>& warnings, std::shared_ptr<CWallet>& result); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, bilingual_str& error, std::vector<bilingual_str>& warnings, std::shared_ptr<CWallet>& result); //! -paytxfee default constexpr CAmount DEFAULT_PAY_TX_FEE = 0; @@ -499,8 +501,8 @@ public: bool IsEquivalentTo(const CWalletTx& tx) const; bool InMempool() const; - bool IsTrusted(interfaces::Chain::Lock& locked_chain) const; - bool IsTrusted(interfaces::Chain::Lock& locked_chain, std::set<uint256>& trusted_parents) const; + bool IsTrusted() const; + bool IsTrusted(std::set<uint256>& trusted_parents) const; int64_t GetTxTime() const; @@ -775,8 +777,8 @@ public: bool IsLocked() const override; bool Lock(); - /** Interface to assert chain access and if successful lock it */ - std::unique_ptr<interfaces::Chain::Lock> LockChain() { return m_chain ? m_chain->lock() : nullptr; } + /** Interface to assert chain access */ + bool HaveChain() const { return m_chain ? true : false; } std::map<uint256, CWalletTx> mapWallet GUARDED_BY(cs_wallet); @@ -805,12 +807,12 @@ public: /** * populate vCoins with vector of available COutputs. */ - void AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector<COutput>& vCoins, bool fOnlySafe = true, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe = true, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change output address. */ - std::map<CTxDestination, std::vector<COutput>> ListCoins(interfaces::Chain::Lock& locked_chain) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. @@ -875,7 +877,7 @@ public: bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); - void GetKeyBirthTimes(interfaces::Chain::Lock& locked_chain, std::map<CKeyID, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; /** @@ -930,7 +932,7 @@ public: * Insert additional inputs into the transaction by * calling CreateTransaction(); */ - bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); + bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl); // Fetch the inputs and sign with SIGHASH_ALL. bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Sign the tx given the input coins and sighash. @@ -961,8 +963,7 @@ public: * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random position */ - bool CreateTransaction(interfaces::Chain::Lock& locked_chain, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl& coin_control, bool sign = true); + bool CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, bool sign = true); /** * Submit the transaction to the node's mempool and then relay to peers. * Should be called after CreateTransaction unless you want to abort @@ -1012,7 +1013,7 @@ public: int64_t GetOldestKeyPoolTime() const; std::set<std::set<CTxDestination>> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - std::map<CTxDestination, CAmount> GetAddressBalances(interfaces::Chain::Lock& locked_chain) const; + std::map<CTxDestination, CAmount> GetAddressBalances() const; std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; @@ -1125,10 +1126,10 @@ public: bool MarkReplaced(const uint256& originalHash, const uint256& newHash); //! Verify wallet naming and perform salvage on the wallet if required - static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, std::string& error_string, std::vector<std::string>& warnings); + static bool Verify(interfaces::Chain& chain, const WalletLocation& location, bool salvage_wallet, bilingual_str& error_string, std::vector<bilingual_str>& warnings); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings, uint64_t wallet_creation_flags = 0); + static std::shared_ptr<CWallet> CreateWalletFromFile(interfaces::Chain& chain, const WalletLocation& location, bilingual_str& error, std::vector<bilingual_str>& warnings, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup @@ -1181,7 +1182,7 @@ public: }; /** Upgrade the wallet */ - bool UpgradeWallet(int version, std::string& error, std::vector<std::string>& warnings); + bool UpgradeWallet(int version, bilingual_str& error, std::vector<bilingual_str>& warnings); //! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 79316ca3e7..45033f9bac 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -913,12 +913,12 @@ bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, C return true; } -bool WalletBatch::VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr) +bool WalletBatch::VerifyEnvironment(const fs::path& wallet_path, bilingual_str& errorStr) { return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr); } -bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::vector<std::string>& warnings, std::string& errorStr) +bool WalletBatch::VerifyDatabaseFile(const fs::path& wallet_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr) { return BerkeleyBatch::VerifyDatabaseFile(wallet_path, warnings, errorStr, WalletBatch::Recover); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 2701481c58..ae72a5b265 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -272,9 +272,9 @@ public: /* Function to determine if a certain KV/key-type is a key (cryptographical key) type */ static bool IsKeyType(const std::string& strType); /* verifies the database environment */ - static bool VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr); + static bool VerifyEnvironment(const fs::path& wallet_path, bilingual_str& errorStr); /* verifies the database file */ - static bool VerifyDatabaseFile(const fs::path& wallet_path, std::vector<std::string>& warnings, std::string& errorStr); + static bool VerifyDatabaseFile(const fs::path& wallet_path, std::vector<bilingual_str>& warnings, bilingual_str& errorStr); //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp index 65643669c2..522efaa884 100644 --- a/src/wallet/wallettool.cpp +++ b/src/wallet/wallettool.cpp @@ -4,6 +4,7 @@ #include <fs.h> #include <util/system.h> +#include <util/translation.h> #include <wallet/wallet.h> #include <wallet/walletutil.h> @@ -117,9 +118,9 @@ bool ExecuteWalletToolFunc(const std::string& command, const std::string& name) tfm::format(std::cerr, "Error: no wallet file at %s\n", name); return false; } - std::string error; + bilingual_str error; if (!WalletBatch::VerifyEnvironment(path, error)) { - tfm::format(std::cerr, "Error loading %s. Is wallet being used by other process?\n", name); + tfm::format(std::cerr, "%s\nError loading %s. Is wallet being used by other process?\n", error.original, name); return false; } std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path); diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index 166c28d376..9cff79a42c 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -18,15 +18,16 @@ needs an older patch version. import os import shutil -from test_framework.test_framework import BitcoinTestFramework, SkipTest +from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import descsum_create from test_framework.util import ( assert_equal, sync_blocks, - sync_mempools + sync_mempools, ) + class BackwardsCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -42,35 +43,15 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + self.skip_if_no_previous_releases() def setup_nodes(self): - if os.getenv("TEST_PREVIOUS_RELEASES") == "false": - raise SkipTest("backwards compatibility tests") - - releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" - if not os.path.isdir(releases_path): - if os.getenv("TEST_PREVIOUS_RELEASES") == "true": - raise AssertionError("TEST_PREVIOUS_RELEASES=1 but releases missing: " + releases_path) - raise SkipTest("This test requires binaries for previous releases") - self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, - 190000, + 190001, 180100, - 170100 - ], binary=[ - self.options.bitcoind, - self.options.bitcoind, - releases_path + "/v0.19.0.1/bin/bitcoind", - releases_path + "/v0.18.1/bin/bitcoind", - releases_path + "/v0.17.1/bin/bitcoind" - ], binary_cli=[ - self.options.bitcoincli, - self.options.bitcoincli, - releases_path + "/v0.19.0.1/bin/bitcoin-cli", - releases_path + "/v0.18.1/bin/bitcoin-cli", - releases_path + "/v0.17.1/bin/bitcoin-cli" + 170100, ]) self.start_nodes() @@ -321,9 +302,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): node_v17.rpc.createwallet(wallet_name="u1_v17") wallet = node_v17.get_wallet_rpc("u1_v17") address = wallet.getnewaddress("bech32") - info = wallet.getaddressinfo(address) - hdkeypath = info["hdkeypath"] - pubkey = info["pubkey"] + v17_info = wallet.getaddressinfo(address) + hdkeypath = v17_info["hdkeypath"] + pubkey = v17_info["pubkey"] # Copy the 0.17 wallet to the last Bitcoin Core version and open it: node_v17.unloadwallet("u1_v17") @@ -337,6 +318,18 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): descriptor = "wpkh([" + info["hdmasterfingerprint"] + hdkeypath[1:] + "]" + pubkey + ")" assert_equal(info["desc"], descsum_create(descriptor)) + # Now copy that same wallet back to 0.17 to make sure no automatic upgrade breaks it + node_master.unloadwallet("u1_v17") + shutil.rmtree(os.path.join(node_v17_wallets_dir, "u1_v17")) + shutil.copytree( + os.path.join(node_master_wallets_dir, "u1_v17"), + os.path.join(node_v17_wallets_dir, "u1_v17") + ) + node_v17.loadwallet("u1_v17") + wallet = node_v17.get_wallet_rpc("u1_v17") + info = wallet.getaddressinfo(address) + assert_equal(info, v17_info) + # Copy the 0.19 wallet to the last Bitcoin Core version and open it: shutil.copytree( os.path.join(node_v19_wallets_dir, "w1_v19"), @@ -346,5 +339,16 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): wallet = node_master.get_wallet_rpc("w1_v19") assert wallet.getaddressinfo(address_18075)["solvable"] + # Now copy that same wallet back to 0.19 to make sure no automatic upgrade breaks it + node_master.unloadwallet("w1_v19") + shutil.rmtree(os.path.join(node_v19_wallets_dir, "w1_v19")) + shutil.copytree( + os.path.join(node_master_wallets_dir, "w1_v19"), + os.path.join(node_v19_wallets_dir, "w1_v19") + ) + node_v19.loadwallet("w1_v19") + wallet = node_v19.get_wallet_rpc("w1_v19") + assert wallet.getaddressinfo(address_18075)["solvable"] + if __name__ == '__main__': BackwardsCompatibilityTest().main() diff --git a/test/functional/framework_test_script.py b/test/functional/framework_test_script.py deleted file mode 100755 index 9d916c0022..0000000000 --- a/test/functional/framework_test_script.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2020 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Tests for test_framework.script.""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.script import bn2vch -from test_framework.util import assert_equal - -def test_bn2vch(): - assert_equal(bn2vch(0), bytes([])) - assert_equal(bn2vch(1), bytes([0x01])) - assert_equal(bn2vch(-1), bytes([0x81])) - assert_equal(bn2vch(0x7F), bytes([0x7F])) - assert_equal(bn2vch(-0x7F), bytes([0xFF])) - assert_equal(bn2vch(0x80), bytes([0x80, 0x00])) - assert_equal(bn2vch(-0x80), bytes([0x80, 0x80])) - assert_equal(bn2vch(0xFF), bytes([0xFF, 0x00])) - assert_equal(bn2vch(-0xFF), bytes([0xFF, 0x80])) - assert_equal(bn2vch(0x100), bytes([0x00, 0x01])) - assert_equal(bn2vch(-0x100), bytes([0x00, 0x81])) - assert_equal(bn2vch(0x7FFF), bytes([0xFF, 0x7F])) - assert_equal(bn2vch(-0x8000), bytes([0x00, 0x80, 0x80])) - assert_equal(bn2vch(-0x7FFFFF), bytes([0xFF, 0xFF, 0xFF])) - assert_equal(bn2vch(0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x00])) - assert_equal(bn2vch(-0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x80])) - assert_equal(bn2vch(0xFFFFFFFF), bytes([0xFF, 0xFF, 0xFF, 0xFF, 0x00])) - - assert_equal(bn2vch(123456789), bytes([0x15, 0xCD, 0x5B, 0x07])) - assert_equal(bn2vch(-54321), bytes([0x31, 0xD4, 0x80])) - -class FrameworkTestScript(BitcoinTestFramework): - def setup_network(self): - pass - - def set_test_params(self): - self.num_nodes = 0 - - def run_test(self): - test_bn2vch() - -if __name__ == '__main__': - FrameworkTestScript().main() diff --git a/test/functional/p2p_leak.py b/test/functional/p2p_leak.py index 7f7430d04e..157af68203 100755 --- a/test/functional/p2p_leak.py +++ b/test/functional/p2p_leak.py @@ -141,12 +141,11 @@ class P2PLeakTest(BitcoinTestFramework): assert no_verack_idlenode.unexpected_msg == False self.log.info('Check that the version message does not leak the local address of the node') - time_begin = int(time.time()) p2p_version_store = self.nodes[0].add_p2p_connection(P2PVersionStore()) - time_end = time.time() ver = p2p_version_store.version_received - assert_greater_than_or_equal(ver.nTime, time_begin) - assert_greater_than_or_equal(time_end, ver.nTime) + # Check that received time is within one hour of now + assert_greater_than_or_equal(ver.nTime, time.time() - 3600) + assert_greater_than_or_equal(time.time() + 3600, ver.nTime) assert_equal(ver.addrFrom.port, 0) assert_equal(ver.addrFrom.ip, '0.0.0.0') assert_equal(ver.nStartingHeight, 201) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index dbdce6552a..6fb0fec32b 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -1896,12 +1896,12 @@ class SegWitTest(BitcoinTestFramework): def test_upgrade_after_activation(self): """Test the behavior of starting up a segwit-aware node after the softfork has activated.""" - # Restart with the new binary self.stop_node(2) self.start_node(2, extra_args=["-segwitheight={}".format(SEGWIT_HEIGHT)]) connect_nodes(self.nodes[0], 2) - self.sync_blocks() + # We reconnect more than 100 blocks, give it plenty of time + self.sync_blocks(timeout=240) # Make sure that this peer thinks segwit has activated. assert softfork_active(self.nodes[2], 'segwit') diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 257499fcb9..31cec66ee7 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -120,8 +120,9 @@ class P2PConnection(asyncio.Protocol): def is_connected(self): return self._transport is not None - def peer_connect(self, dstaddr, dstport, *, net): + def peer_connect(self, dstaddr, dstport, *, net, factor): assert not self.is_connected + self.factor = factor self.dstaddr = dstaddr self.dstport = dstport # The initial message to send after the connection was made: @@ -367,9 +368,12 @@ class P2PInterface(P2PConnection): # Connection helper methods + def wait_until(self, test_function, timeout): + wait_until(test_function, timeout=timeout, lock=mininode_lock, factor=self.factor) + def wait_for_disconnect(self, timeout=60): test_function = lambda: not self.is_connected - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) # Message receiving helper methods @@ -380,14 +384,14 @@ class P2PInterface(P2PConnection): return False return self.last_message['tx'].tx.rehash() == txid - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_block(self, blockhash, timeout=60): def test_function(): assert self.is_connected return self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_header(self, blockhash, timeout=60): def test_function(): @@ -397,7 +401,7 @@ class P2PInterface(P2PConnection): return False return last_headers.headers[0].rehash() == int(blockhash, 16) - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_merkleblock(self, blockhash, timeout=60): def test_function(): @@ -407,7 +411,7 @@ class P2PInterface(P2PConnection): return False return last_filtered_block.merkleblock.header.rehash() == int(blockhash, 16) - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_getdata(self, hash_list, timeout=60): """Waits for a getdata message. @@ -421,7 +425,7 @@ class P2PInterface(P2PConnection): return False return [x.hash for x in last_data.inv] == hash_list - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_getheaders(self, timeout=60): """Waits for a getheaders message. @@ -435,7 +439,7 @@ class P2PInterface(P2PConnection): assert self.is_connected return self.last_message.get("getheaders") - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_inv(self, expected_inv, timeout=60): """Waits for an INV message and checks that the first inv object in the message was as expected.""" @@ -448,13 +452,13 @@ class P2PInterface(P2PConnection): self.last_message["inv"].inv[0].type == expected_inv[0].type and \ self.last_message["inv"].inv[0].hash == expected_inv[0].hash - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) def wait_for_verack(self, timeout=60): def test_function(): return self.message_count["verack"] - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) # Message sending helper functions @@ -470,7 +474,7 @@ class P2PInterface(P2PConnection): assert self.is_connected return self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter - wait_until(test_function, timeout=timeout, lock=mininode_lock) + self.wait_until(test_function, timeout=timeout) self.ping_counter += 1 @@ -586,7 +590,7 @@ class P2PDataStore(P2PInterface): self.send_message(msg_block(block=b)) else: self.send_message(msg_headers([CBlockHeader(block) for block in blocks])) - wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) + self.wait_until(lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout) if expect_disconnect: self.wait_for_disconnect(timeout=timeout) @@ -594,7 +598,7 @@ class P2PDataStore(P2PInterface): self.sync_with_ping(timeout=timeout) if success: - wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout) + self.wait_until(lambda: node.getbestblockhash() == blocks[-1].hash, timeout=timeout) else: assert node.getbestblockhash() != blocks[-1].hash diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index e475ed8596..9102266456 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -8,6 +8,7 @@ This file is modified from python-bitcoinlib. """ import hashlib import struct +import unittest from .messages import ( CTransaction, @@ -708,3 +709,25 @@ def SegwitV0SignatureHash(script, txTo, inIdx, hashtype, amount): ss += struct.pack("<I", hashtype) return hash256(ss) + +class TestFrameworkScript(unittest.TestCase): + def test_bn2vch(self): + self.assertEqual(bn2vch(0), bytes([])) + self.assertEqual(bn2vch(1), bytes([0x01])) + self.assertEqual(bn2vch(-1), bytes([0x81])) + self.assertEqual(bn2vch(0x7F), bytes([0x7F])) + self.assertEqual(bn2vch(-0x7F), bytes([0xFF])) + self.assertEqual(bn2vch(0x80), bytes([0x80, 0x00])) + self.assertEqual(bn2vch(-0x80), bytes([0x80, 0x80])) + self.assertEqual(bn2vch(0xFF), bytes([0xFF, 0x00])) + self.assertEqual(bn2vch(-0xFF), bytes([0xFF, 0x80])) + self.assertEqual(bn2vch(0x100), bytes([0x00, 0x01])) + self.assertEqual(bn2vch(-0x100), bytes([0x00, 0x81])) + self.assertEqual(bn2vch(0x7FFF), bytes([0xFF, 0x7F])) + self.assertEqual(bn2vch(-0x8000), bytes([0x00, 0x80, 0x80])) + self.assertEqual(bn2vch(-0x7FFFFF), bytes([0xFF, 0xFF, 0xFF])) + self.assertEqual(bn2vch(0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x00])) + self.assertEqual(bn2vch(-0x80000000), bytes([0x00, 0x00, 0x00, 0x80, 0x80])) + self.assertEqual(bn2vch(0xFFFFFFFF), bytes([0xFF, 0xFF, 0xFF, 0xFF, 0x00])) + self.assertEqual(bn2vch(123456789), bytes([0x15, 0xCD, 0x5B, 0x07])) + self.assertEqual(bn2vch(-54321), bytes([0x31, 0xD4, 0x80])) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 8719bd0d39..11c96deefb 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -6,11 +6,12 @@ import configparser from enum import Enum -import logging import argparse +import logging import os import pdb import random +import re import shutil import subprocess import sys @@ -101,6 +102,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.bind_to_localhost_only = True self.set_test_params() self.parse_args() + self.rpc_timeout = int(self.rpc_timeout * self.options.factor) # optionally, increase timeout by a factor def main(self): """Main function. This should not be overridden by the subclass test scripts.""" @@ -167,6 +169,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): help="set a random seed for deterministically reproducing a previous test run") parser.add_argument("--descriptors", default=False, action="store_true", help="Run test using a descriptor wallet") + parser.add_argument('--factor', type=float, default=1.0, help='adjust test timeouts by a factor') self.add_options(parser) self.options = parser.parse_args() @@ -185,10 +188,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"]) self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"]) + self.options.previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" + os.environ['PATH'] = os.pathsep.join([ os.path.join(config['environment']['BUILDDIR'], 'src'), - os.path.join(config['environment']['BUILDDIR'], 'src', 'qt'), - os.environ['PATH'] + os.path.join(config['environment']['BUILDDIR'], 'src', 'qt'), os.environ['PATH'] ]) # Set up temp directory and start logging @@ -388,6 +392,25 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): Should only be called once after the nodes have been specified in set_test_params().""" + def get_bin_from_version(version, bin_name, bin_default): + if not version: + return bin_default + return os.path.join( + self.options.previous_releases_path, + re.sub( + r'\.0$', + '', # remove trailing .0 for point releases + 'v{}.{}.{}.{}'.format( + (version % 100000000) // 1000000, + (version % 1000000) // 10000, + (version % 10000) // 100, + (version % 100) // 1, + ), + ), + 'bin', + bin_name, + ) + if self.bind_to_localhost_only: extra_confs = [["bind=127.0.0.1"]] * num_nodes else: @@ -397,9 +420,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if versions is None: versions = [None] * num_nodes if binary is None: - binary = [self.options.bitcoind] * num_nodes + binary = [get_bin_from_version(v, 'bitcoind', self.options.bitcoind) for v in versions] if binary_cli is None: - binary_cli = [self.options.bitcoincli] * num_nodes + binary_cli = [get_bin_from_version(v, 'bitcoin-cli', self.options.bitcoincli) for v in versions] assert_equal(len(extra_confs), num_nodes) assert_equal(len(extra_args), num_nodes) assert_equal(len(versions), num_nodes) @@ -412,6 +435,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): chain=self.chain, rpchost=rpchost, timewait=self.rpc_timeout, + factor=self.options.factor, bitcoind=binary[i], bitcoin_cli=binary_cli[i], version=versions[i], @@ -558,6 +582,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): extra_args=['-disablewallet'], rpchost=None, timewait=self.rpc_timeout, + factor=self.options.factor, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, coverage_dir=None, @@ -640,6 +665,25 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): if not self.is_cli_compiled(): raise SkipTest("bitcoin-cli has not been compiled.") + def skip_if_no_previous_releases(self): + """Skip the running test if previous releases are not available.""" + if not self.has_previous_releases(): + raise SkipTest("previous releases not available or disabled") + + def has_previous_releases(self): + """Checks whether previous releases are present and enabled.""" + if os.getenv("TEST_PREVIOUS_RELEASES") == "false": + # disabled + return False + + if not os.path.isdir(self.options.previous_releases_path): + if os.getenv("TEST_PREVIOUS_RELEASES") == "true": + raise AssertionError("TEST_PREVIOUS_RELEASES=true but releases missing: {}".format( + self.options.previous_releases_path)) + # missing + return False + return True + def is_cli_compiled(self): """Checks whether bitcoin-cli was compiled.""" return self.config["components"].getboolean("ENABLE_CLI") diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index c0075fd8ec..e6ec3c1b2d 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -62,7 +62,7 @@ class TestNode(): To make things easier for the test writer, any unrecognised messages will be dispatched to the RPC connection.""" - def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False): + def __init__(self, i, datadir, *, chain, rpchost, timewait, factor, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False): """ Kwargs: start_perf (bool): If True, begin profiling the node with `perf` as soon as @@ -128,6 +128,7 @@ class TestNode(): self.perf_subprocesses = {} self.p2ps = [] + self.factor = factor AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key']) PRIV_KEYS = [ @@ -324,13 +325,13 @@ class TestNode(): return True def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): - wait_until(self.is_node_stopped, timeout=timeout) + wait_until(self.is_node_stopped, timeout=timeout, factor=self.factor) @contextlib.contextmanager def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): if unexpected_msgs is None: unexpected_msgs = [] - time_end = time.time() + timeout + time_end = time.time() + timeout * self.factor debug_log = os.path.join(self.datadir, self.chain, 'debug.log') with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) @@ -487,7 +488,7 @@ class TestNode(): if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' - p2p_conn.peer_connect(**kwargs, net=self.chain)() + p2p_conn.peer_connect(**kwargs, net=self.chain, factor=self.factor)() self.p2ps.append(p2p_conn) if wait_for_verack: # Wait for the node to send us the version and verack diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index eb9f6528b3..20ab9ee464 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -208,9 +208,10 @@ def str_to_b64str(string): def satoshi_round(amount): return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) -def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None): +def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, factor=1.0): if attempts == float('inf') and timeout == float('inf'): timeout = 60 + timeout = timeout * factor attempt = 0 time_end = time.time() + timeout @@ -265,7 +266,7 @@ def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None): """ proxy_kwargs = {} if timeout is not None: - proxy_kwargs['timeout'] = timeout + proxy_kwargs['timeout'] = int(timeout) proxy = AuthServiceProxy(url, **proxy_kwargs) proxy.url = url # store URL on proxy for info diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 6f5f61de2c..45f591ba4a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -24,6 +24,7 @@ import sys import tempfile import re import logging +import unittest # Formatting. Default colors to empty strings. BOLD, GREEN, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "") @@ -65,6 +66,10 @@ if os.name != 'nt' or sys.getwindowsversion() >= (10, 0, 14393): TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 +TEST_FRAMEWORK_MODULES = [ + "script", +] + EXTENDED_SCRIPTS = [ # These tests are not run by default. # Longest test should go first, to favor running tests in parallel @@ -237,7 +242,6 @@ BASE_SCRIPTS = [ 'rpc_help.py', 'feature_help.py', 'feature_shutdown.py', - 'framework_test_script.py', # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time ] @@ -400,6 +404,16 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= if os.path.isdir(cache_dir): print("%sWARNING!%s There is a cache directory here: %s. If tests fail unexpectedly, try deleting the cache directory." % (BOLD[1], BOLD[0], cache_dir)) + # Test Framework Tests + print("Running Unit Tests for Test Framework Modules") + test_framework_tests = unittest.TestSuite() + for module in TEST_FRAMEWORK_MODULES: + test_framework_tests.addTest(unittest.TestLoader().loadTestsFromName("test_framework.{}".format(module))) + result = unittest.TextTestRunner(verbosity=1, failfast=True).run(test_framework_tests) + if not result.wasSuccessful(): + logging.debug("Early exiting after failure in TestFramework unit tests") + sys.exit(False) + tests_dir = src_dir + '/test/functional/' flags = ['--cachedir={}'.format(cache_dir)] + args @@ -623,7 +637,7 @@ class TestResult(): def check_script_prefixes(): """Check that test scripts start with one of the allowed name prefixes.""" - good_prefixes_re = re.compile("^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool|framework_test)_") + good_prefixes_re = re.compile("^(example|feature|interface|mempool|mining|p2p|rpc|wallet|tool)_") bad_script_names = [script for script in ALL_SCRIPTS if good_prefixes_re.match(script) is None] if bad_script_names: diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index b3d496dd51..039ce7daee 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -15,6 +15,7 @@ from test_framework.util import assert_equal BUFFER_SIZE = 16 * 1024 + class ToolWalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 @@ -48,7 +49,7 @@ class ToolWalletTest(BitcoinTestFramework): h = hashlib.sha1() mv = memoryview(bytearray(BUFFER_SIZE)) with open(self.wallet_path, 'rb', buffering=0) as f: - for n in iter(lambda : f.readinto(mv), 0): + for n in iter(lambda: f.readinto(mv), 0): h.update(mv[:n]) return h.hexdigest() @@ -69,7 +70,12 @@ class ToolWalletTest(BitcoinTestFramework): self.assert_raises_tool_error('Invalid command: help', 'help') self.assert_raises_tool_error('Error: two methods provided (info and create). Only one method should be provided.', 'info', 'create') self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') - self.assert_raises_tool_error('Error loading wallet.dat. Is wallet being used by other process?', '-wallet=wallet.dat', 'info') + self.assert_raises_tool_error( + 'Error initializing wallet database environment "{}"!\nError loading wallet.dat. Is wallet being used by other process?' + .format(os.path.join(self.nodes[0].datadir, self.chain, 'wallets')), + '-wallet=wallet.dat', + 'info', + ) self.assert_raises_tool_error('Error: no wallet file at nonexistent.dat', '-wallet=nonexistent.dat', 'info') def test_tool_wallet_info(self): @@ -84,7 +90,7 @@ class ToolWalletTest(BitcoinTestFramework): # # self.log.debug('Setting wallet file permissions to 400 (read-only)') # os.chmod(self.wallet_path, stat.S_IRUSR) - # assert(self.wallet_permissions() in ['400', '666']) # Sanity check. 666 because Appveyor. + # assert self.wallet_permissions() in ['400', '666'] # Sanity check. 666 because Appveyor. # shasum_before = self.wallet_shasum() timestamp_before = self.wallet_timestamp() self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before)) @@ -103,7 +109,7 @@ class ToolWalletTest(BitcoinTestFramework): self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after) self.log.debug('Setting wallet file permissions back to 600 (read/write)') os.chmod(self.wallet_path, stat.S_IRUSR | stat.S_IWUSR) - assert(self.wallet_permissions() in ['600', '666']) # Sanity check. 666 because Appveyor. + assert self.wallet_permissions() in ['600', '666'] # Sanity check. 666 because Appveyor. # # TODO: Wallet tool info should not write to the wallet file. # The following lines should be uncommented and the tests still succeed: diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 9ba23db42d..27197e3b6d 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -300,7 +300,7 @@ def test_maxtxfee_fails(self, rbf_node, dest_address): self.restart_node(1, ['-maxtxfee=0.000025'] + self.extra_args[1]) rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) rbfid = spend_one_input(rbf_node, dest_address) - assert_raises_rpc_error(-4, "Unable to create transaction: Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) + assert_raises_rpc_error(-4, "Unable to create transaction. Fee exceeds maximum configured by -maxtxfee", rbf_node.bumpfee, rbfid) self.restart_node(1, self.extra_args[1]) rbf_node.walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT) @@ -517,7 +517,7 @@ def test_no_more_inputs_fails(self, rbf_node, dest_address): rbf_node.generatetoaddress(1, dest_address) # spend all funds, no change output rbfid = rbf_node.sendtoaddress(rbf_node.getnewaddress(), rbf_node.getbalance(), "", "", True) - assert_raises_rpc_error(-4, "Unable to create transaction: Insufficient funds", rbf_node.bumpfee, rbfid) + assert_raises_rpc_error(-4, "Unable to create transaction. Insufficient funds", rbf_node.bumpfee, rbfid) if __name__ == "__main__": diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index c569416292..580a61f9f3 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -227,10 +227,10 @@ class MultiWalletTest(BitcoinTestFramework): assert_raises_rpc_error(-18, 'Wallet wallets not found.', self.nodes[0].loadwallet, 'wallets') # Fail to load duplicate wallets - assert_raises_rpc_error(-4, 'Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0]) + assert_raises_rpc_error(-4, 'Wallet file verification failed. Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0]) # Fail to load duplicate wallets by different ways (directory and filepath) - assert_raises_rpc_error(-4, "Wallet file verification failed: Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat') + assert_raises_rpc_error(-4, "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat') # Fail to load if one wallet is a copy of another assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') @@ -240,7 +240,7 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load if wallet file is a symlink - assert_raises_rpc_error(-4, "Wallet file verification failed: Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') + assert_raises_rpc_error(-4, "Wallet file verification failed. Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') # Fail to load if a directory is specified that doesn't contain a wallet os.mkdir(wallet_dir('empty_wallet_dir')) diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index d04bc4ce44..e7e71bf3f6 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -12,14 +12,15 @@ contrib/devtools/previous_release.sh -b v0.15.2 v0.16.3 import os import shutil -from test_framework.test_framework import BitcoinTestFramework, SkipTest +from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( adjust_bitcoin_conf_for_pre_17, assert_equal, assert_greater_than, - assert_is_hex_string + assert_is_hex_string, ) + class UpgradeWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True @@ -32,32 +33,16 @@ class UpgradeWalletTest(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + self.skip_if_no_previous_releases() def setup_network(self): self.setup_nodes() def setup_nodes(self): - if os.getenv("TEST_PREVIOUS_RELEASES") == "false": - raise SkipTest("upgradewallet RPC tests") - - releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases" - if not os.path.isdir(releases_path): - if os.getenv("TEST_PREVIOUS_RELEASES") == "true": - raise AssertionError("TEST_PREVIOUS_RELEASES=1 but releases missing: " + releases_path) - raise SkipTest("This test requires binaries for previous releases") - self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, 160300, - 150200 - ], binary=[ - self.options.bitcoind, - releases_path + "/v0.16.3/bin/bitcoind", - releases_path + "/v0.15.2/bin/bitcoind", - ], binary_cli=[ - self.options.bitcoincli, - releases_path + "/v0.16.3/bin/bitcoin-cli", - releases_path + "/v0.15.2/bin/bitcoin-cli", + 150200, ]) # adapt bitcoin.conf, because older bitcoind's don't recognize config sections adjust_bitcoin_conf_for_pre_17(self.nodes[1].bitcoinconf) |