aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test/wallet_tests.cpp
diff options
context:
space:
mode:
authorRussell Yanofsky <russ@yanofsky.org>2020-04-21 12:41:35 -0400
committerRussell Yanofsky <russ@yanofsky.org>2020-04-26 20:23:05 -0400
commit7918c1b019a36a8f9aa55daae422c6b6723b2a39 (patch)
treeb79c090383a2f68f615cca184550608c4df1d89b /src/wallet/test/wallet_tests.cpp
parenteef90c14ed0f559e3f6e187341009270b84f45cb (diff)
downloadbitcoin-7918c1b019a36a8f9aa55daae422c6b6723b2a39.tar.xz
test: Add CreateWalletFromFile test
Add unit test calling CreateWalletFromFile, which isn't currently called from other unit tests, with some basic checks to make sure it rescans and registers for notifications correctly. Motivation for this change was to try to write a test that would fail without the early `handleNotifications` call in ef8c6ca60767cac589d98ca57ee33179608ccda8 from https://github.com/bitcoin/bitcoin/pull/16426, but succeed with it: https://github.com/bitcoin/bitcoin/blob/ef8c6ca60767cac589d98ca57ee33179608ccda8/src/wallet/wallet.cpp#L3978-L3986 However, writing a full test for the race condition that call prevents isn't possible without the locking changes from #16426. So this PR just adds as much test coverage as is possible now. This new test is also useful for https://github.com/bitcoin/bitcoin/pull/15719, since it detects the stale notifications.transactionAddedToMempool notifications that PR eliminates.
Diffstat (limited to 'src/wallet/test/wallet_tests.cpp')
-rw-r--r--src/wallet/test/wallet_tests.cpp114
1 files changed, 114 insertions, 0 deletions
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()