aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/test/util/logging.cpp8
-rw-r--r--src/test/util/logging.h14
-rw-r--r--src/wallet/test/wallet_tests.cpp114
3 files changed, 131 insertions, 5 deletions
diff --git a/src/test/util/logging.cpp b/src/test/util/logging.cpp
index fe2e69104b..65a64f2384 100644
--- a/src/test/util/logging.cpp
+++ b/src/test/util/logging.cpp
@@ -11,13 +11,13 @@
#include <stdexcept>
-DebugLogHelper::DebugLogHelper(std::string message)
- : m_message{std::move(message)}
+DebugLogHelper::DebugLogHelper(std::string message, MatchFn match)
+ : m_message{std::move(message)}, m_match(std::move(match))
{
m_print_connection = LogInstance().PushBackCallback(
[this](const std::string& s) {
if (m_found) return;
- m_found = s.find(m_message) != std::string::npos;
+ m_found = s.find(m_message) != std::string::npos && m_match(&s);
});
noui_test_redirect();
}
@@ -26,7 +26,7 @@ void DebugLogHelper::check_found()
{
noui_reconnect();
LogInstance().DeleteCallback(m_print_connection);
- if (!m_found) {
+ if (!m_found && m_match(nullptr)) {
throw std::runtime_error(strprintf("'%s' not found in debug log\n", m_message));
}
}
diff --git a/src/test/util/logging.h b/src/test/util/logging.h
index 45ec44173c..1fcf7ca305 100644
--- a/src/test/util/logging.h
+++ b/src/test/util/logging.h
@@ -17,10 +17,22 @@ class DebugLogHelper
bool m_found{false};
std::list<std::function<void(const std::string&)>>::iterator m_print_connection;
+ //! Custom match checking function.
+ //!
+ //! Invoked with pointers to lines containing matching strings, and with
+ //! null if check_found() is called without any successful match.
+ //!
+ //! Can return true to enable default DebugLogHelper behavior of:
+ //! (1) ending search after first successful match, and
+ //! (2) raising an error in check_found if no match was found
+ //! Can return false to do the opposite in either case.
+ using MatchFn = std::function<bool(const std::string* line)>;
+ MatchFn m_match;
+
void check_found();
public:
- DebugLogHelper(std::string message);
+ DebugLogHelper(std::string message, MatchFn match = [](const std::string*){ return true; });
~DebugLogHelper() { check_found(); }
};
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 2bdeb89f3c..1215956bb4 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -4,6 +4,7 @@
#include <wallet/wallet.h>
+#include <future>
#include <memory>
#include <stdint.h>
#include <vector>
@@ -12,6 +13,7 @@
#include <node/context.h>
#include <policy/policy.h>
#include <rpc/server.h>
+#include <test/util/logging.h>
#include <test/util/setup_common.h>
#include <validation.h>
#include <wallet/coincontrol.h>
@@ -26,6 +28,36 @@ extern UniValue importwallet(const JSONRPCRequest& request);
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
+static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
+{
+ std::string error;
+ std::vector<std::string> warnings;
+ auto wallet = CWallet::CreateWalletFromFile(chain, WalletLocation(""), error, warnings);
+ wallet->postInitProcess();
+ return wallet;
+}
+
+static void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
+{
+ SyncWithValidationInterfaceQueue();
+ wallet->m_chain_notifications_handler.reset();
+ UnloadWallet(std::move(wallet));
+}
+
+static CMutableTransaction TestSimpleSpend(const CTransaction& from, uint32_t index, const CKey& key, const CScript& pubkey)
+{
+ CMutableTransaction mtx;
+ mtx.vout.push_back({from.vout[index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey});
+ mtx.vin.push_back({CTxIn{from.GetHash(), index}});
+ FillableSigningProvider keystore;
+ keystore.AddKey(key);
+ std::map<COutPoint, Coin> coins;
+ coins[mtx.vin[0].prevout].out = from.vout[index];
+ std::map<int, std::string> input_errors;
+ BOOST_CHECK(SignTransaction(mtx, &keystore, coins, SIGHASH_ALL, input_errors));
+ return mtx;
+}
+
static void AddKey(CWallet& wallet, const CKey& key)
{
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
@@ -658,4 +690,86 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure, malformed_descriptor);
}
+//! Test CreateWalletFromFile function and its behavior handling potential race
+//! 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.
+BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup)
+{
+ // Create new wallet with known key and unload it.
+ auto chain = interfaces::MakeChain(m_node);
+ auto wallet = TestLoadWallet(*chain);
+ CKey key;
+ key.MakeNewKey(true);
+ AddKey(*wallet, key);
+ TestUnloadWallet(std::move(wallet));
+
+ // Add log hook to detect AddToWallet events from rescans, blockConnected,
+ // and transactionAddedToMempool notifications
+ int addtx_count = 0;
+ DebugLogHelper addtx_counter("[default wallet] AddToWallet", [&](const std::string* s) {
+ if (s) ++addtx_count;
+ 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;
+ }
+ return false;
+ });
+
+ // Block the queue to prevent the wallet receiving blockConnected and
+ // transactionAddedToMempool notifications, and create block and mempool
+ // transactions paying to the wallet
+ std::promise<void> promise;
+ CallFunctionInValidationInterfaceQueue([&promise] {
+ promise.get_future().wait();
+ });
+ std::string error;
+ m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
+ m_coinbase_txns.push_back(CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
+ 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;
+ }
+
+ // Unblock notification queue and make sure stale blockConnected and
+ // transactionAddedToMempool events are processed
+ promise.set_value();
+ SyncWithValidationInterfaceQueue();
+ 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);
+ }
+
+ TestUnloadWallet(std::move(wallet));
+}
+
BOOST_AUTO_TEST_SUITE_END()