diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 7b44f5425bdc..c82520eb5471 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -16,7 +17,7 @@ using wallet::CHANGE_LOWER; using wallet::CoinEligibilityFilter; using wallet::CoinSelectionParams; using wallet::COutput; -using wallet::CreateDummyWalletDatabase; +using wallet::CreateMockableWalletDatabase; using wallet::CWallet; using wallet::CWalletTx; using wallet::OutputGroup; @@ -43,7 +44,7 @@ static void CoinSelection(benchmark::Bench& bench) { NodeContext node; auto chain = interfaces::MakeChain(node); - CWallet wallet(chain.get(), /*coinjoin_loader=*/nullptr, "", gArgs, CreateDummyWalletDatabase()); + CWallet wallet(chain.get(), /*coinjoin_loader=*/nullptr, "", gArgs, CreateMockableWalletDatabase()); std::vector> wtxs; LOCK(wallet.cs_wallet); diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp index 7c8db6187d55..82a05ac8c079 100644 --- a/src/bench/wallet_balance.cpp +++ b/src/bench/wallet_balance.cpp @@ -14,7 +14,7 @@ #include -using wallet::CreateMockWalletDatabase; +using wallet::CreateMockableWalletDatabase; using wallet::CWallet; using wallet::DBErrors; using wallet::WALLET_FLAG_DESCRIPTORS; @@ -24,7 +24,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b const auto test_setup = MakeNoLogFileContext(); const auto& ADDRESS_WATCHONLY = ADDRESS_B58T_UNSPENDABLE; - CWallet wallet{test_setup->m_node.chain.get(), test_setup->m_node.coinjoin_loader.get(), "", gArgs, CreateMockWalletDatabase()}; + CWallet wallet{test_setup->m_node.chain.get(), test_setup->m_node.coinjoin_loader.get(), "", gArgs, CreateMockableWalletDatabase()}; { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/bench/wallet_loading.cpp b/src/bench/wallet_loading.cpp index 1a2bd7eceb32..3e4f078414f4 100644 --- a/src/bench/wallet_loading.cpp +++ b/src/bench/wallet_loading.cpp @@ -17,18 +17,17 @@ #include using wallet::CWallet; -using wallet::DatabaseFormat; -using wallet::DatabaseOptions; +using wallet::CreateMockableWalletDatabase; using wallet::TxStateInactive; using wallet::WALLET_FLAG_DESCRIPTORS; using wallet::WalletContext; using wallet::WalletDatabase; -static std::shared_ptr BenchLoadWallet(std::unique_ptr database, WalletContext& context, DatabaseOptions& options) +static std::shared_ptr BenchLoadWallet(std::unique_ptr database, WalletContext& context, uint64_t create_flags) { bilingual_str error; std::vector warnings; - auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings); + auto wallet = CWallet::Create(context, "", std::move(database), create_flags, error, warnings); NotifyWalletLoaded(context, wallet); if (context.chain) { wallet->postInitProcess(); @@ -52,34 +51,14 @@ static void AddTx(CWallet& wallet) wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInactive{}); } -static std::unique_ptr DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options) +static std::unique_ptr DuplicateMockDatabase(WalletDatabase& database) { - auto new_database = CreateMockWalletDatabase(options); - - // Get a cursor to the original database - auto batch = database.MakeBatch(); - batch->StartCursor(); - - // Get a batch for the new database - auto new_batch = new_database->MakeBatch(); - - // Read all records from the original database and write them to the new one - while (true) { - CDataStream key(SER_DISK, CLIENT_VERSION); - CDataStream value(SER_DISK, CLIENT_VERSION); - bool complete; - batch->ReadAtCursor(key, value, complete); - if (complete) break; - new_batch->Write(key, value); - } - - return new_database; + return std::make_unique(dynamic_cast(database).m_records); } static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) { const auto test_setup = MakeNoLogFileContext(); - test_setup->m_args.ForceSetArg("-unsafesqlitesync", "1"); WalletContext context; context.args = &test_setup->m_args; @@ -87,31 +66,28 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet) // Setup the wallet // Loading the wallet will also create it - DatabaseOptions options; - if (legacy_wallet) { - options.require_format = DatabaseFormat::BERKELEY; - } else { - options.create_flags = WALLET_FLAG_DESCRIPTORS; - options.require_format = DatabaseFormat::SQLITE; + uint64_t create_flags = 0; + if (!legacy_wallet) { + create_flags = WALLET_FLAG_DESCRIPTORS; } - auto database = CreateMockWalletDatabase(options); - auto wallet = BenchLoadWallet(std::move(database), context, options); + auto database = CreateMockableWalletDatabase(); + auto wallet = BenchLoadWallet(std::move(database), context, create_flags); // Generate a bunch of transactions and addresses to put into the wallet for (int i = 0; i < 1000; ++i) { AddTx(*wallet); } - database = DuplicateMockDatabase(wallet->GetDatabase(), options); + database = DuplicateMockDatabase(wallet->GetDatabase()); // reload the wallet for the actual benchmark BenchUnloadWallet(std::move(wallet)); bench.epochs(5).run([&] { - wallet = BenchLoadWallet(std::move(database), context, options); + wallet = BenchLoadWallet(std::move(database), context, create_flags); // Cleanup - database = DuplicateMockDatabase(wallet->GetDatabase(), options); + database = DuplicateMockDatabase(wallet->GetDatabase()); BenchUnloadWallet(std::move(wallet)); }); } diff --git a/src/qt/test/addressbooktests.cpp b/src/qt/test/addressbooktests.cpp index 3aa570ce793f..a7d564c14a1c 100644 --- a/src/qt/test/addressbooktests.cpp +++ b/src/qt/test/addressbooktests.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,7 @@ #include using wallet::AddWallet; -using wallet::CreateMockWalletDatabase; +using wallet::CreateMockableWalletDatabase; using wallet::CWallet; using wallet::RemoveWallet; using wallet::WALLET_FLAG_DESCRIPTORS; @@ -72,7 +73,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node) { TestChain100Setup test; node.setContext(&test.m_node); - const std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), node.context()->coinjoin_loader.get(), "", gArgs, CreateMockWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), node.context()->coinjoin_loader.get(), "", gArgs, CreateMockableWalletDatabase()); wallet->LoadWallet(); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); { diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 9cd8d1ceb885..f1a95758adbb 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,7 @@ using wallet::AddWallet; using wallet::CWallet; -using wallet::CreateMockWalletDatabase; +using wallet::CreateMockableWalletDatabase; using wallet::RemoveWallet; using wallet::WALLET_FLAG_DESCRIPTORS; using wallet::WalletContext; @@ -121,7 +122,7 @@ void TestGUI(interfaces::Node& node) } node.setContext(&test.m_node); WalletContext& context = *node.walletLoader().context(); - const std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), node.context()->coinjoin_loader.get(), "", gArgs, CreateMockWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), node.context()->coinjoin_loader.get(), "", gArgs, CreateMockableWalletDatabase()); AddWallet(context, wallet); wallet->LoadWallet(); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/test/util/wallet.cpp b/src/test/util/wallet.cpp index e1d59b3bc76e..801ae747e553 100644 --- a/src/test/util/wallet.cpp +++ b/src/test/util/wallet.cpp @@ -28,6 +28,15 @@ std::string getnewaddress(CWallet& w) return EncodeDestination(*Assert(w.GetNewDestination(""))); } +std::unique_ptr CreateMockableWalletDatabase(std::map records) +{ + return std::make_unique(records); +} + +MockableDatabase& GetMockableDatabase(CWallet& wallet) +{ + return dynamic_cast(wallet.GetDatabase()); +} // void importaddress(CWallet& wallet, const std::string& address) // { // auto spk_man = wallet.GetLegacyScriptPubKeyMan(); diff --git a/src/wallet/db.h b/src/wallet/db.h index dba86c8a90bd..48829803d5ff 100644 --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -158,47 +158,6 @@ class WalletDatabase virtual bool SupportsAutoBackup() { return false; } }; -/** RAII class that provides access to a DummyDatabase. Never fails. */ -class DummyBatch : public DatabaseBatch -{ -private: - bool ReadKey(CDataStream&& key, CDataStream& value) override { return true; } - bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return true; } - bool EraseKey(CDataStream&& key) override { return true; } - bool HasKey(CDataStream&& key) override { return true; } - -public: - void Flush() override {} - void Close() override {} - - bool StartCursor() override { return true; } - bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return true; } - void CloseCursor() override {} - bool TxnBegin() override { return true; } - bool TxnCommit() override { return true; } - bool TxnAbort() override { return true; } -}; - -/** A dummy WalletDatabase that does nothing and never fails. Only used by unit tests. - **/ -class DummyDatabase : public WalletDatabase -{ -public: - void Open() override {}; - void AddRef() override {} - void RemoveRef() override {} - bool Rewrite(const char* pszSkip=nullptr) override { return true; } - bool Backup(const std::string& strDest) const override { return true; } - void Close() override {} - void Flush() override {} - bool PeriodicFlush() override { return true; } - void IncrementUpdateCounter() override { ++nUpdateCounter; } - void ReloadDbEnv() override {} - std::string Filename() override { return "dummy"; } - std::string Format() override { return "dummy"; } - std::unique_ptr MakeBatch(bool flush_on_close = true) override { return std::make_unique(); } -}; - enum class DatabaseFormat { BERKELEY, SQLITE, diff --git a/src/wallet/salvage.cpp b/src/wallet/salvage.cpp index 1ee0edd00e8f..b8b3d5d429ca 100644 --- a/src/wallet/salvage.cpp +++ b/src/wallet/salvage.cpp @@ -23,6 +23,48 @@ static bool KeyFilter(const std::string& type) return WalletBatch::IsKeyType(type) || type == DBKeys::HDCHAIN; } +/** RAII class that provides access to a DummyDatabase. Never fails. */ +class DummyBatch : public DatabaseBatch +{ +private: + bool ReadKey(CDataStream&& key, CDataStream& value) override { return true; } + bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return true; } + bool EraseKey(CDataStream&& key) override { return true; } + bool HasKey(CDataStream&& key) override { return true; } + +public: + void Flush() override {} + void Close() override {} + + bool StartCursor() override { return true; } + bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return true; } + void CloseCursor() override {} + bool TxnBegin() override { return true; } + bool TxnCommit() override { return true; } + bool TxnAbort() override { return true; } +}; + +/** A dummy WalletDatabase that does nothing and never fails. Only used by unit tests. + **/ +class DummyDatabase : public WalletDatabase +{ +public: + void Open() override {}; + void AddRef() override {} + void RemoveRef() override {} + bool Rewrite(const char* pszSkip=nullptr) override { return true; } + bool Backup(const std::string& strDest) const override { return true; } + void Close() override {} + void Flush() override {} + bool PeriodicFlush() override { return true; } + void IncrementUpdateCounter() override { ++nUpdateCounter; } + void ReloadDbEnv() override {} + std::string Filename() override { return "dummy"; } + std::string Format() override { return "dummy"; } + std::unique_ptr MakeBatch(bool flush_on_close = true) override { return std::make_unique(); } +}; + + bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bilingual_str& error, std::vector& warnings) { DatabaseOptions options; @@ -135,7 +177,7 @@ bool RecoverDatabaseFile(const ArgsManager& args, const fs::path& file_path, bil } DbTxn* ptxn = env->TxnBegin(); - CWallet dummyWallet(/*chain=*/nullptr, /*coinjoin_loader=*/nullptr, "", gArgs, CreateDummyWalletDatabase()); + CWallet dummyWallet(/*chain=*/nullptr, /*coinjoin_loader=*/nullptr, "", gArgs, std::make_unique()); dummyWallet.SetupLegacyScriptPubKeyMan(); for (KeyValPair& row : salvagedData) { diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index 2d567548412f..b51fbe67df99 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -297,7 +298,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) coin_selection_params_bnb.m_subtract_fee_outputs = true; { - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -319,7 +320,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) } { - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -338,7 +339,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) BOOST_CHECK(result10); } { - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -400,7 +401,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) FastRandomContext rand{}; const auto temp1{[&rand](std::vector& g, const CAmount& v, CAmount c) { return KnapsackSolver(g, v, c, rand); }}; const auto KnapsackSolver{temp1}; - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -710,7 +711,7 @@ BOOST_AUTO_TEST_CASE(knapsack_solver_test) BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { FastRandomContext rand{}; - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -732,7 +733,7 @@ BOOST_AUTO_TEST_CASE(ApproximateBestSubset) // Tests that with the ideal conditions, the coin selector will always be able to find a solution that can pay the target value BOOST_AUTO_TEST_CASE(SelectCoins_test) { - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -918,7 +919,7 @@ BOOST_AUTO_TEST_CASE(effective_value_test) /* --------------------------- Dash-specific tests start here --------------------------- */ BOOST_AUTO_TEST_CASE(minimum_inputs_test) { - std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockWalletDatabase()); + std::unique_ptr wallet = std::make_unique(m_node.chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); wallet->LoadWallet(); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp index 28bfbf3bbb8c..f715d0209d04 100644 --- a/src/wallet/test/ismine_tests.cpp +++ b/src/wallet/test/ismine_tests.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -37,7 +38,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK compressed { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForRawPubKey(pubkeys[0]); @@ -54,7 +55,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PK uncompressed { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey); @@ -71,7 +72,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH compressed { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0])); @@ -88,7 +89,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2PKH uncompressed { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey)); @@ -105,7 +106,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -129,7 +130,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // (P2PKH inside) P2SH inside P2SH (invalid) { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -147,7 +148,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // scriptPubKey multisig { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); @@ -178,7 +179,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // P2SH multisig { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey)); @@ -199,7 +200,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // OP_RETURN { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); @@ -213,7 +214,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard) // Nonstandard { - CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet keystore(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); keystore.SetupLegacyScriptPubKeyMan(); LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore); BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0])); diff --git a/src/wallet/test/scriptpubkeyman_tests.cpp b/src/wallet/test/scriptpubkeyman_tests.cpp index 6ce39aa0c8c3..e86482b7f625 100644 --- a/src/wallet/test/scriptpubkeyman_tests.cpp +++ b/src/wallet/test/scriptpubkeyman_tests.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -22,7 +23,7 @@ BOOST_AUTO_TEST_CASE(CanProvide) // Set up wallet and keyman variables. NodeContext node; std::unique_ptr chain = interfaces::MakeChain(node); - CWallet wallet(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(chain.get(), /*coinjoin_loader=*/nullptr, "", m_args, CreateMockableWalletDatabase()); LegacyScriptPubKeyMan& keyman = *wallet.GetOrCreateLegacyScriptPubKeyMan(); // Make a 1 of 2 multisig script diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp index 3c9f6b58be74..3ad39a250605 100644 --- a/src/wallet/test/util.cpp +++ b/src/wallet/test/util.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,9 +17,18 @@ #include namespace wallet { +std::unique_ptr CreateMockableWalletDatabase(std::map records) +{ + return std::make_unique(records); +} + +MockableDatabase& GetMockableDatabase(CWallet& wallet) +{ + return dynamic_cast(wallet.GetDatabase()); +} std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, interfaces::CoinJoin::Loader& coinjoin_loader, CChain& cchain, ArgsManager& args, const CKey& key) { - auto wallet = std::make_unique(&chain, &coinjoin_loader, "", args, CreateMockWalletDatabase()); + auto wallet = std::make_unique(&chain, &coinjoin_loader, "", args, CreateMockableWalletDatabase()); { LOCK(wallet->cs_wallet); wallet->SetLastBlockProcessed(cchain.Height(), cchain.Tip()->GetBlockHash()); @@ -45,4 +55,85 @@ std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, interfaces BOOST_CHECK(result.last_failed_block.IsNull()); return wallet; } +MockableBatch::MockableBatch(Record& records, bool pass) + : m_records(records), m_pass(pass), m_cursor_active(false) +{ +} + +MockableBatch::~MockableBatch() {} + +bool MockableBatch::DeserializeKey(CDataStream&& key, SerializeData& out) +{ + try { key >> out; return true; } + catch (...) { return false; } +} + +bool MockableBatch::ReadKey(CDataStream&& key, CDataStream& value) +{ + SerializeData skey; + if (!DeserializeKey(std::move(key), skey)) return false; + auto it = m_records.find(skey); + if (it == m_records.end()) return false; + value << it->second; + return true; +} + +bool MockableBatch::WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite) +{ + SerializeData skey, svalue; + if (!DeserializeKey(std::move(key), skey)) return false; + try { value >> svalue; } catch (...) { return false; } + if (!overwrite && m_records.count(skey)) return false; + m_records[skey] = svalue; + return true; +} + +bool MockableBatch::EraseKey(CDataStream&& key) +{ + SerializeData skey; + if (!DeserializeKey(std::move(key), skey)) return false; + return m_records.erase(skey) > 0; +} + +bool MockableBatch::HasKey(CDataStream&& key) +{ + SerializeData skey; + if (!DeserializeKey(std::move(key), skey)) return false; + return m_records.count(skey) != 0; +} + +bool MockableBatch::ErasePrefix(Span) +{ + return true; +} + +void MockableBatch::Flush() {} +void MockableBatch::Close() {} + +bool MockableBatch::StartCursor() +{ + m_cursor_it = m_records.begin(); + m_cursor_active = true; + return true; +} + +bool MockableBatch::ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) +{ + if (!m_cursor_active) { complete = true; return false; } + if (m_cursor_it == m_records.end()) { complete = true; return true; } + ssKey << m_cursor_it->first; + ssValue << m_cursor_it->second; + ++m_cursor_it; + complete = (m_cursor_it == m_records.end()); + return true; +} + +void MockableBatch::CloseCursor() +{ + m_cursor_active = false; +} + +bool MockableBatch::TxnBegin() { return m_pass; } +bool MockableBatch::TxnCommit() { return m_pass; } +bool MockableBatch::TxnAbort() { return m_pass; } } // namespace wallet diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h index b6293072374d..3a0ce6952861 100644 --- a/src/wallet/test/util.h +++ b/src/wallet/test/util.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_WALLET_TEST_UTIL_H #define BITCOIN_WALLET_TEST_UTIL_H +#include + #include class ArgsManager; @@ -21,6 +23,73 @@ namespace wallet { class CWallet; std::unique_ptr CreateSyncedWallet(interfaces::Chain& chain, interfaces::CoinJoin::Loader& coinjoin_loader, CChain& cchain, ArgsManager& args, const CKey& key); + +class MockableBatch : public DatabaseBatch +{ +private: + using Record = std::map; + + Record& m_records; + bool m_pass; + Record::iterator m_cursor_it; + bool m_cursor_active; + + static bool DeserializeKey(CDataStream&& key, SerializeData& out); + +public: + MockableBatch(Record& records, bool pass); + ~MockableBatch(); + + bool ReadKey(CDataStream&& key, CDataStream& value) override; + bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override; + bool EraseKey(CDataStream&& key) override; + bool HasKey(CDataStream&& key) override; + bool ErasePrefix(Span prefix) override; + + void Flush() override; + void Close() override; + + bool StartCursor() override; + bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override; + void CloseCursor() override; + + bool TxnBegin() override; + bool TxnCommit() override; + bool TxnAbort() override; +}; + + +/** A WalletDatabase whose contents and return values can be modified as needed for testing + **/ +class MockableDatabase : public WalletDatabase +{ +public: + std::map m_records; + bool m_pass{true}; + + MockableDatabase(std::map records = {}) : WalletDatabase(), m_records(records) {} + ~MockableDatabase() {}; + + void Open() override {} + void AddRef() override {} + void RemoveRef() override {} + + bool Rewrite(const char* pszSkip=nullptr) override { return m_pass; } + bool Backup(const std::string& strDest) const override { return m_pass; } + void Flush() override {} + void Close() override {} + bool PeriodicFlush() override { return m_pass; } + void IncrementUpdateCounter() override {} + void ReloadDbEnv() override {} + + std::string Filename() override { return "mockable"; } + std::string Format() override { return "mock"; } + std::unique_ptr MakeBatch(bool flush_on_close = true) override { return std::make_unique(m_records, m_pass); } +}; + +std::unique_ptr CreateMockableWalletDatabase(std::map records = {}); + +MockableDatabase& GetMockableDatabase(CWallet& wallet); } // namespace wallet #endif // BITCOIN_WALLET_TEST_UTIL_H diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 44df749ff299..94bfba65145a 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include @@ -11,7 +12,7 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName) : TestingSetup(chainName), m_coinjoin_loader{interfaces::MakeCoinJoinLoader(m_node)}, m_wallet_loader{interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args), m_node, *Assert(m_coinjoin_loader))}, - m_wallet(m_node.chain.get(), m_coinjoin_loader.get(), "", m_args, CreateMockWalletDatabase()) + m_wallet(m_node.chain.get(), m_coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()) { m_wallet.LoadWallet(); m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} }); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index f219766a87bb..cfd3405349fd 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -112,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions fails to read an unknown start block. { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); wallet.SetupLegacyScriptPubKeyMan(); { LOCK(wallet.cs_wallet); @@ -133,7 +133,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockWalletDatabase()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -177,7 +177,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions only picks transactions in the new block // file. { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -204,7 +204,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) // Verify ScanForWalletTransactions scans no blocks. { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -243,7 +243,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) // before the missing block, and success for a key whose creation time is // after. { - const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); wallet->SetupLegacyScriptPubKeyMan(); WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash())); WalletContext context; @@ -309,7 +309,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { WalletContext context; context.args = &m_args; - const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); @@ -331,7 +331,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { - const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); LOCK(wallet->cs_wallet); wallet->SetupLegacyScriptPubKeyMan(); @@ -364,7 +364,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); CWalletTx wtx{m_coinbase_txns.back(), TxStateConfirmed{m_node.chainman->ActiveChain().Tip()->GetBlockHash(), m_node.chainman->ActiveChain().Height(), /*index=*/0}}; LOCK(wallet.cs_wallet); @@ -633,7 +633,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { { - const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); wallet->SetupLegacyScriptPubKeyMan(); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); @@ -641,7 +641,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) BOOST_CHECK(!wallet->GetNewDestination("")); } { - const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateDummyWalletDatabase()); + const std::shared_ptr wallet = std::make_shared(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); LOCK(wallet->cs_wallet); wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS); wallet->SetMinVersion(FEATURE_LATEST); @@ -1477,57 +1477,13 @@ BOOST_FIXTURE_TEST_CASE(select_coins_grouped_by_addresses, ListCoinsTestingSetup BOOST_CHECK_EQUAL(GetAvailableBalance(*wallet), (500 + 499) * COIN); } -/** RAII class that provides access to a FailDatabase. Which fails if needed. */ -class FailBatch : public DatabaseBatch -{ -private: - bool m_pass{true}; - bool ReadKey(CDataStream&& key, CDataStream& value) override { return m_pass; } - bool WriteKey(CDataStream&& key, CDataStream&& value, bool overwrite=true) override { return m_pass; } - bool EraseKey(CDataStream&& key) override { return m_pass; } - bool HasKey(CDataStream&& key) override { return m_pass; } - -public: - explicit FailBatch(bool pass) : m_pass(pass) {} - void Flush() override {} - void Close() override {} - - bool StartCursor() override { return true; } - bool ReadAtCursor(CDataStream& ssKey, CDataStream& ssValue, bool& complete) override { return false; } - void CloseCursor() override {} - bool TxnBegin() override { return false; } - bool TxnCommit() override { return false; } - bool TxnAbort() override { return false; } -}; - -/** A dummy WalletDatabase that does nothing, only fails if needed.**/ -class FailDatabase : public WalletDatabase -{ -public: - bool m_pass{true}; // false when this db should fail - - void Open() override {}; - void AddRef() override {} - void RemoveRef() override {} - bool Rewrite(const char* pszSkip=nullptr) override { return true; } - bool Backup(const std::string& strDest) const override { return true; } - void Close() override {} - void Flush() override {} - bool PeriodicFlush() override { return true; } - void IncrementUpdateCounter() override { ++nUpdateCounter; } - void ReloadDbEnv() override {} - std::string Filename() override { return "faildb"; } - std::string Format() override { return "faildb"; } - std::unique_ptr MakeBatch(bool flush_on_close = true) override { return std::make_unique(m_pass); } -}; - /** * Checks a wallet invalid state where the inputs (prev-txs) of a new arriving transaction are not marked dirty, * while the transaction that spends them exist inside the in-memory wallet tx map (not stored on db due a db write failure). */ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestChain100Setup) { - CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, std::make_unique()); + CWallet wallet(m_node.chain.get(), m_node.coinjoin_loader.get(), "", m_args, CreateMockableWalletDatabase()); { LOCK(wallet.cs_wallet); wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS); @@ -1574,7 +1530,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestChain100Setup) // 1) Make db always fail // 2) Try to add a transaction that spends the previously created transaction and // verify that we are not moving forward if the wallet cannot store it - static_cast(wallet.GetDatabase()).m_pass = false; + GetMockableDatabase(wallet).m_pass = false; mtx.vin.clear(); mtx.vin.push_back(CTxIn(good_tx_id, 0)); BOOST_CHECK_EXCEPTION(wallet.transactionAddedToMempool(MakeTransactionRef(mtx), 0, 0), diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index f408f9815b6e..2db9eefb5c79 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1248,44 +1248,4 @@ std::unique_ptr MakeDatabase(const fs::path& path, const Databas return nullptr; } } - -/** Return object for accessing dummy database with no read/write capabilities. */ -std::unique_ptr CreateDummyWalletDatabase() -{ - return std::make_unique(); -} - -/** Return object for accessing temporary in-memory database. */ -std::unique_ptr CreateMockWalletDatabase(DatabaseOptions& options) -{ - - std::optional format; - if (options.require_format) format = options.require_format; - if (!format) { -#ifdef USE_BDB - format = DatabaseFormat::BERKELEY; -#endif -#ifdef USE_SQLITE - format = DatabaseFormat::SQLITE; -#endif - } - - if (format == DatabaseFormat::SQLITE) { -#ifdef USE_SQLITE - return std::make_unique(":memory:", "", options, true); -#endif - assert(false); - } - -#ifdef USE_BDB - return std::make_unique(std::make_shared(), "", options); -#endif - assert(false); -} - -std::unique_ptr CreateMockWalletDatabase() -{ - DatabaseOptions options; - return CreateMockWalletDatabase(options); -} } // namespace wallet diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 44aaa0145a30..18d42fb29985 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -264,12 +264,6 @@ using KeyFilterFn = std::function; //! Unserialize a given Key-Value pair and load it into the wallet bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr); -/** Return object for accessing dummy database with no read/write capabilities. */ -std::unique_ptr CreateDummyWalletDatabase(); - -/** Return object for accessing temporary in-memory database. */ -std::unique_ptr CreateMockWalletDatabase(DatabaseOptions& options); -std::unique_ptr CreateMockWalletDatabase(); } // namespace wallet #endif // BITCOIN_WALLET_WALLETDB_H