4
4
5
5
#include < wallet/wallet.h>
6
6
7
+ #include < future>
7
8
#include < memory>
8
9
#include < stdint.h>
9
10
#include < vector>
12
13
#include < node/context.h>
13
14
#include < policy/policy.h>
14
15
#include < rpc/server.h>
16
+ #include < test/util/logging.h>
15
17
#include < test/util/setup_common.h>
16
18
#include < validation.h>
17
19
#include < wallet/coincontrol.h>
@@ -26,6 +28,36 @@ extern UniValue importwallet(const JSONRPCRequest& request);
26
28
27
29
BOOST_FIXTURE_TEST_SUITE (wallet_tests, WalletTestingSetup)
28
30
31
+ static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain& chain)
32
+ {
33
+ std::string error;
34
+ std::vector<std::string> warnings;
35
+ auto wallet = CWallet::CreateWalletFromFile (chain, WalletLocation (" " ), error, warnings);
36
+ wallet->postInitProcess ();
37
+ return wallet;
38
+ }
39
+
40
+ static void TestUnloadWallet (std::shared_ptr<CWallet>&& wallet)
41
+ {
42
+ SyncWithValidationInterfaceQueue ();
43
+ wallet->m_chain_notifications_handler .reset ();
44
+ UnloadWallet (std::move (wallet));
45
+ }
46
+
47
+ static CMutableTransaction TestSimpleSpend (const CTransaction& from, uint32_t index, const CKey& key, const CScript& pubkey)
48
+ {
49
+ CMutableTransaction mtx;
50
+ mtx.vout .push_back ({from.vout [index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey});
51
+ mtx.vin .push_back ({CTxIn{from.GetHash (), index}});
52
+ FillableSigningProvider keystore;
53
+ keystore.AddKey (key);
54
+ std::map<COutPoint, Coin> coins;
55
+ coins[mtx.vin [0 ].prevout ].out = from.vout [index];
56
+ std::map<int , std::string> input_errors;
57
+ BOOST_CHECK (SignTransaction (mtx, &keystore, coins, SIGHASH_ALL, input_errors));
58
+ return mtx;
59
+ }
60
+
29
61
static void AddKey (CWallet& wallet, const CKey& key)
30
62
{
31
63
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan ();
@@ -658,4 +690,86 @@ BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup)
658
690
BOOST_CHECK_EXCEPTION (vr >> w_desc, std::ios_base::failure, malformed_descriptor);
659
691
}
660
692
693
+ // ! Test CreateWalletFromFile function and its behavior handling potential race
694
+ // ! conditions if it's called the same time an incoming transaction shows up in
695
+ // ! the mempool or a new block.
696
+ // !
697
+ // ! It isn't possible for a unit test to totally verify there aren't race
698
+ // ! conditions without hooking into the implementation more, so this test just
699
+ // ! verifies that new transactions are detected during loading without any
700
+ // ! notifications at all, to infer that timing of notifications shouldn't
701
+ // ! matter. The test could be extended to cover other scenarios in the future.
702
+ BOOST_FIXTURE_TEST_CASE (CreateWalletFromFile, TestChain100Setup)
703
+ {
704
+ // Create new wallet with known key and unload it.
705
+ auto chain = interfaces::MakeChain (m_node);
706
+ auto wallet = TestLoadWallet (*chain);
707
+ CKey key;
708
+ key.MakeNewKey (true );
709
+ AddKey (*wallet, key);
710
+ TestUnloadWallet (std::move (wallet));
711
+
712
+ // Add log hook to detect AddToWallet events from rescans, blockConnected,
713
+ // and transactionAddedToMempool notifications
714
+ int addtx_count = 0 ;
715
+ DebugLogHelper addtx_counter (" [default wallet] AddToWallet" , [&](const std::string* s) {
716
+ if (s) ++addtx_count;
717
+ return false ;
718
+ });
719
+
720
+ bool rescan_completed = false ;
721
+ DebugLogHelper rescan_check (" [default wallet] Rescan completed" , [&](const std::string* s) {
722
+ if (s) {
723
+ // For now, just assert that cs_main is being held during the
724
+ // rescan, ensuring that a new block couldn't be connected
725
+ // that the wallet would miss. After
726
+ // https://github.com/bitcoin/bitcoin/pull/16426 when cs_main is no
727
+ // longer held, the test can be extended to append a new block here
728
+ // and check it's handled correctly.
729
+ AssertLockHeld (::cs_main);
730
+ rescan_completed = true ;
731
+ }
732
+ return false ;
733
+ });
734
+
735
+ // Block the queue to prevent the wallet receiving blockConnected and
736
+ // transactionAddedToMempool notifications, and create block and mempool
737
+ // transactions paying to the wallet
738
+ std::promise<void > promise;
739
+ CallFunctionInValidationInterfaceQueue ([&promise] {
740
+ promise.get_future ().wait ();
741
+ });
742
+ std::string error;
743
+ m_coinbase_txns.push_back (CreateAndProcessBlock ({}, GetScriptForRawPubKey (coinbaseKey.GetPubKey ())).vtx [0 ]);
744
+ auto block_tx = TestSimpleSpend (*m_coinbase_txns[0 ], 0 , coinbaseKey, GetScriptForRawPubKey (key.GetPubKey ()));
745
+ m_coinbase_txns.push_back (CreateAndProcessBlock ({block_tx}, GetScriptForRawPubKey (coinbaseKey.GetPubKey ())).vtx [0 ]);
746
+ auto mempool_tx = TestSimpleSpend (*m_coinbase_txns[1 ], 0 , coinbaseKey, GetScriptForRawPubKey (key.GetPubKey ()));
747
+ BOOST_CHECK (chain->broadcastTransaction (MakeTransactionRef (mempool_tx), DEFAULT_TRANSACTION_MAXFEE, false , error));
748
+
749
+ // Reload wallet and make sure new transactions are detected despite events
750
+ // being blocked
751
+ wallet = TestLoadWallet (*chain);
752
+ BOOST_CHECK (rescan_completed);
753
+ BOOST_CHECK_EQUAL (addtx_count, 2 );
754
+ unsigned int block_tx_time, mempool_tx_time;
755
+ {
756
+ LOCK (wallet->cs_wallet );
757
+ block_tx_time = wallet->mapWallet .at (block_tx.GetHash ()).nTimeReceived ;
758
+ mempool_tx_time = wallet->mapWallet .at (mempool_tx.GetHash ()).nTimeReceived ;
759
+ }
760
+
761
+ // Unblock notification queue and make sure stale blockConnected and
762
+ // transactionAddedToMempool events are processed
763
+ promise.set_value ();
764
+ SyncWithValidationInterfaceQueue ();
765
+ BOOST_CHECK_EQUAL (addtx_count, 4 );
766
+ {
767
+ LOCK (wallet->cs_wallet );
768
+ BOOST_CHECK_EQUAL (block_tx_time, wallet->mapWallet .at (block_tx.GetHash ()).nTimeReceived );
769
+ BOOST_CHECK_EQUAL (mempool_tx_time, wallet->mapWallet .at (mempool_tx.GetHash ()).nTimeReceived );
770
+ }
771
+
772
+ TestUnloadWallet (std::move (wallet));
773
+ }
774
+
661
775
BOOST_AUTO_TEST_SUITE_END ()
0 commit comments