aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/test')
-rw-r--r--src/wallet/test/coinselector_tests.cpp5
-rw-r--r--src/wallet/test/db_tests.cpp128
-rw-r--r--src/wallet/test/fuzz/notifications.cpp4
-rw-r--r--src/wallet/test/group_outputs_tests.cpp3
-rw-r--r--src/wallet/test/init_test_fixture.cpp3
-rw-r--r--src/wallet/test/init_test_fixture.h3
-rw-r--r--src/wallet/test/ismine_tests.cpp77
-rw-r--r--src/wallet/test/scriptpubkeyman_tests.cpp3
-rw-r--r--src/wallet/test/util.cpp154
-rw-r--r--src/wallet/test/util.h99
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp8
-rw-r--r--src/wallet/test/wallet_test_fixture.h3
-rw-r--r--src/wallet/test/wallet_tests.cpp125
-rw-r--r--src/wallet/test/walletdb_tests.cpp28
-rw-r--r--src/wallet/test/walletload_tests.cpp97
15 files changed, 517 insertions, 223 deletions
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 7f66179517..b1d67c1432 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -12,6 +12,7 @@
#include <wallet/coincontrol.h>
#include <wallet/coinselection.h>
#include <wallet/spend.h>
+#include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h>
#include <wallet/wallet.h>
@@ -176,7 +177,7 @@ inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinsResult& availab
static std::unique_ptr<CWallet> NewWallet(const node::NodeContext& m_node, const std::string& wallet_name = "")
{
- std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), wallet_name, CreateMockWalletDatabase());
+ std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), wallet_name, CreateMockableWalletDatabase());
BOOST_CHECK(wallet->LoadWallet() == DBErrors::LOAD_OK);
LOCK(wallet->cs_wallet);
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -431,7 +432,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
CAmount selection_target = 16 * CENT;
const auto& no_res = SelectCoinsBnB(GroupCoins(available_coins.All(), /*subtract_fee_outputs*/true),
selection_target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT);
- BOOST_ASSERT(!no_res);
+ BOOST_REQUIRE(!no_res);
BOOST_CHECK(util::ErrorString(no_res).original.find("The inputs size exceeds the maximum weight") != std::string::npos);
// Now add same coin value with a good size and check that it gets selected
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 7761308bbc..4cda35ed8d 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -6,13 +6,56 @@
#include <test/util/setup_common.h>
#include <util/fs.h>
+#include <util/translation.h>
+#ifdef USE_BDB
#include <wallet/bdb.h>
+#endif
+#ifdef USE_SQLITE
+#include <wallet/sqlite.h>
+#endif
+#include <wallet/test/util.h>
+#include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
#include <fstream>
#include <memory>
#include <string>
+inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
+{
+ Span key{kv.first}, value{kv.second};
+ os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \""
+ << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\")";
+ return os;
+}
+
namespace wallet {
+
+static Span<const std::byte> StringBytes(std::string_view str)
+{
+ return AsBytes<const char>({str.data(), str.size()});
+}
+
+static SerializeData StringData(std::string_view str)
+{
+ auto bytes = StringBytes(str);
+ return SerializeData{bytes.begin(), bytes.end()};
+}
+
+static void CheckPrefix(DatabaseBatch& batch, Span<const std::byte> prefix, MockableData expected)
+{
+ std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
+ MockableData actual;
+ while (true) {
+ DataStream key, value;
+ DatabaseCursor::Status status = cursor->Next(key, value);
+ if (status == DatabaseCursor::Status::DONE) break;
+ BOOST_CHECK(status == DatabaseCursor::Status::MORE);
+ BOOST_CHECK(
+ actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
+ }
+ BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
+}
+
BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, fs::path& database_filename)
@@ -78,5 +121,90 @@ BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance)
BOOST_CHECK(env_2_a == env_2_b);
}
+static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
+{
+ std::vector<std::unique_ptr<WalletDatabase>> dbs;
+ DatabaseOptions options;
+ DatabaseStatus status;
+ bilingual_str error;
+#ifdef USE_BDB
+ dbs.emplace_back(MakeBerkeleyDatabase(path_root / "bdb", options, status, error));
+#endif
+#ifdef USE_SQLITE
+ dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
+#endif
+ dbs.emplace_back(CreateMockableWalletDatabase());
+ return dbs;
+}
+
+BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
+{
+ // Test each supported db
+ for (const auto& database : TestDatabases(m_path_root)) {
+ BOOST_ASSERT(database);
+
+ std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
+
+ // Write elements to it
+ std::unique_ptr<DatabaseBatch> handler = database->MakeBatch();
+ for (unsigned int i = 0; i < 10; i++) {
+ for (const auto& prefix : prefixes) {
+ BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
+ }
+ }
+
+ // Now read all the items by prefix and verify that each element gets parsed correctly
+ for (const auto& prefix : prefixes) {
+ DataStream s_prefix;
+ s_prefix << prefix;
+ std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix);
+ DataStream key;
+ DataStream value;
+ for (int i = 0; i < 10; i++) {
+ DatabaseCursor::Status status = cursor->Next(key, value);
+ BOOST_ASSERT(status == DatabaseCursor::Status::MORE);
+
+ std::string key_back;
+ unsigned int i_back;
+ key >> key_back >> i_back;
+ BOOST_CHECK_EQUAL(key_back, prefix);
+
+ unsigned int value_back;
+ value >> value_back;
+ BOOST_CHECK_EQUAL(value_back, i_back);
+ }
+
+ // Let's now read it once more, it should return DONE
+ BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
+ }
+ }
+}
+
+// Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't
+// covered in the higher level test above. The higher level test uses
+// serialized strings which are prefixed with string length, so it doesn't test
+// truly empty prefixes or prefixes that begin with \xff
+BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
+{
+ const MockableData::value_type
+ e{StringData(""), StringData("e")},
+ p{StringData("prefix"), StringData("p")},
+ ps{StringData("prefixsuffix"), StringData("ps")},
+ f{StringData("\xff"), StringData("f")},
+ fs{StringData("\xffsuffix"), StringData("fs")},
+ ff{StringData("\xff\xff"), StringData("ff")},
+ ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
+ for (const auto& database : TestDatabases(m_path_root)) {
+ std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
+ for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
+ batch->Write(MakeUCharSpan(k), MakeUCharSpan(v));
+ }
+ CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
+ CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
+ CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
+ CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp
index de381a5ec9..f4b69f7403 100644
--- a/src/wallet/test/fuzz/notifications.cpp
+++ b/src/wallet/test/fuzz/notifications.cpp
@@ -141,6 +141,10 @@ FUZZ_TARGET_INIT(wallet_notifications, initialize_setup)
info.prev_hash = &block.hashPrevBlock;
info.height = chain.size();
info.data = &block;
+ // Ensure that no blocks are skipped by the wallet by setting the chain's accumulated
+ // time to the maximum value. This ensures that the wallet's birth time is always
+ // earlier than this maximum time.
+ info.chain_time_max = std::numeric_limits<unsigned int>::max();
a.wallet->blockConnected(info);
b.wallet->blockConnected(info);
// Store the coins for the next block
diff --git a/src/wallet/test/group_outputs_tests.cpp b/src/wallet/test/group_outputs_tests.cpp
index 283e87989c..e6b25cc216 100644
--- a/src/wallet/test/group_outputs_tests.cpp
+++ b/src/wallet/test/group_outputs_tests.cpp
@@ -6,6 +6,7 @@
#include <wallet/coinselection.h>
#include <wallet/spend.h>
+#include <wallet/test/util.h>
#include <wallet/wallet.h>
#include <boost/test/unit_test.hpp>
@@ -17,7 +18,7 @@ static int nextLockTime = 0;
static std::shared_ptr<CWallet> NewWallet(const node::NodeContext& m_node)
{
- std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockWalletDatabase());
+ std::unique_ptr<CWallet> wallet = std::make_unique<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
wallet->LoadWallet();
LOCK(wallet->cs_wallet);
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index 0adc63876c..5bdf36ec19 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -4,6 +4,7 @@
#include <common/args.h>
#include <univalue.h>
+#include <util/chaintype.h>
#include <util/check.h>
#include <util/fs.h>
@@ -13,7 +14,7 @@
#include <wallet/test/init_test_fixture.h>
namespace wallet {
-InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainName) : BasicTestingSetup(chainName)
+InitWalletDirTestingSetup::InitWalletDirTestingSetup(const ChainType chainType) : BasicTestingSetup(chainType)
{
m_wallet_loader = MakeWalletLoader(*m_node.chain, m_args);
diff --git a/src/wallet/test/init_test_fixture.h b/src/wallet/test/init_test_fixture.h
index df5819fd1d..ac7bb8997c 100644
--- a/src/wallet/test/init_test_fixture.h
+++ b/src/wallet/test/init_test_fixture.h
@@ -9,11 +9,12 @@
#include <interfaces/wallet.h>
#include <node/context.h>
#include <test/util/setup_common.h>
+#include <util/chaintype.h>
namespace wallet {
struct InitWalletDirTestingSetup: public BasicTestingSetup {
- explicit InitWalletDirTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
+ explicit InitWalletDirTestingSetup(const ChainType chain_type = ChainType::MAIN);
~InitWalletDirTestingSetup();
void SetWalletDir(const fs::path& walletdir_path);
diff --git a/src/wallet/test/ismine_tests.cpp b/src/wallet/test/ismine_tests.cpp
index f6f2d423e4..fd0718fbb9 100644
--- a/src/wallet/test/ismine_tests.cpp
+++ b/src/wallet/test/ismine_tests.cpp
@@ -10,6 +10,7 @@
#include <test/util/setup_common.h>
#include <wallet/types.h>
#include <wallet/wallet.h>
+#include <wallet/test/util.h>
#include <boost/test/unit_test.hpp>
@@ -55,7 +56,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK compressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(pubkeys[0]);
@@ -74,7 +75,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK compressed - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "pk(" + EncodeSecret(keys[0]) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -86,7 +87,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK uncompressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForRawPubKey(uncompressedPubkey);
@@ -105,7 +106,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PK uncompressed - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "pk(" + EncodeSecret(uncompressedKey) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -117,7 +118,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH compressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(pubkeys[0]));
@@ -136,7 +137,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH compressed - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "pkh(" + EncodeSecret(keys[0]) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -148,7 +149,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH uncompressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
scriptPubKey = GetScriptForDestination(PKHash(uncompressedPubkey));
@@ -167,7 +168,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2PKH uncompressed - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "pkh(" + EncodeSecret(uncompressedKey) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -179,7 +180,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -206,7 +207,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "sh(pkh(" + EncodeSecret(keys[0]) + "))";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -219,7 +220,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2SH (invalid) - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -238,7 +239,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2SH (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "sh(sh(" + EncodeSecret(keys[0]) + "))";
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
@@ -247,7 +248,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2WSH (invalid) - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -266,7 +267,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2SH inside P2WSH (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wsh(sh(" + EncodeSecret(keys[0]) + "))";
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
@@ -275,7 +276,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH inside P2WSH (invalid) - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -292,7 +293,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH inside P2WSH (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wsh(wpkh(" + EncodeSecret(keys[0]) + "))";
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
@@ -301,7 +302,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2WSH inside P2WSH (invalid) - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -320,7 +321,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// (P2PKH inside) P2WSH inside P2WSH (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wsh(wsh(" + EncodeSecret(keys[0]) + "))";
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
@@ -329,7 +330,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH compressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@@ -345,7 +346,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH compressed - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wpkh(" + EncodeSecret(keys[0]) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -357,7 +358,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH uncompressed - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@@ -378,7 +379,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WPKH uncompressed (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wpkh(" + EncodeSecret(uncompressedKey) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, false);
@@ -387,7 +388,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// scriptPubKey multisig - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -422,7 +423,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// scriptPubKey multisig - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + ")";
auto spk_manager = CreateDescriptor(keystore, desc_str, true);
@@ -434,7 +435,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH multisig - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@@ -457,7 +458,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2SH multisig - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "sh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))";
@@ -471,7 +472,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with compressed keys - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@@ -500,7 +501,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with compressed keys - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + "))";
@@ -514,7 +515,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with uncompressed key - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(uncompressedKey));
@@ -543,7 +544,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig with uncompressed key (invalid) - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "wsh(multi(2, " + EncodeSecret(uncompressedKey) + ", " + EncodeSecret(keys[1]) + "))";
@@ -553,7 +554,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig wrapped in P2SH - Legacy
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
@@ -583,7 +584,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// P2WSH multisig wrapped in P2SH - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "sh(wsh(multi(2, " + EncodeSecret(keys[0]) + ", " + EncodeSecret(keys[1]) + ")))";
@@ -598,7 +599,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Combo - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "combo(" + EncodeSecret(keys[0]) + ")";
@@ -642,7 +643,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Taproot - Descriptor
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
std::string desc_str = "tr(" + EncodeSecret(keys[0]) + ")";
@@ -660,7 +661,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// OP_RETURN
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@@ -675,7 +676,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// witness unspendable
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@@ -690,7 +691,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// witness unknown
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", CreateMockableWalletDatabase());
keystore.SetupLegacyScriptPubKeyMan();
LOCK(keystore.GetLegacyScriptPubKeyMan()->cs_KeyStore);
BOOST_CHECK(keystore.GetLegacyScriptPubKeyMan()->AddKey(keys[0]));
@@ -705,7 +706,7 @@ BOOST_AUTO_TEST_CASE(ismine_standard)
// Nonstandard
{
- CWallet keystore(chain.get(), "", CreateDummyWalletDatabase());
+ CWallet keystore(chain.get(), "", 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 90042f5252..d4997c418a 100644
--- a/src/wallet/test/scriptpubkeyman_tests.cpp
+++ b/src/wallet/test/scriptpubkeyman_tests.cpp
@@ -7,6 +7,7 @@
#include <test/util/setup_common.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/wallet.h>
+#include <wallet/test/util.h>
#include <boost/test/unit_test.hpp>
@@ -18,7 +19,7 @@ BOOST_FIXTURE_TEST_SUITE(scriptpubkeyman_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(CanProvide)
{
// Set up wallet and keyman variables.
- CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", 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 b7bf312edf..069ab25f26 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -7,7 +7,9 @@
#include <chain.h>
#include <key.h>
#include <key_io.h>
+#include <streams.h>
#include <test/util/setup_common.h>
+#include <wallet/context.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
@@ -16,7 +18,7 @@
namespace wallet {
std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key)
{
- auto wallet = std::make_unique<CWallet>(&chain, "", CreateMockWalletDatabase());
+ auto wallet = std::make_unique<CWallet>(&chain, "", CreateMockableWalletDatabase());
{
LOCK2(wallet->cs_wallet, ::cs_main);
wallet->SetLastBlockProcessed(cchain.Height(), cchain.Tip()->GetBlockHash());
@@ -44,28 +46,39 @@ std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cc
return wallet;
}
-std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options)
+std::shared_ptr<CWallet> TestLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, uint64_t create_flags)
{
- auto new_database = CreateMockWalletDatabase(options);
-
- // Get a cursor to the original database
- auto batch = database.MakeBatch();
- std::unique_ptr<wallet::DatabaseCursor> cursor = batch->GetNewCursor();
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+ auto wallet = CWallet::Create(context, "", std::move(database), create_flags, error, warnings);
+ NotifyWalletLoaded(context, wallet);
+ if (context.chain) {
+ wallet->postInitProcess();
+ }
+ return wallet;
+}
- // Get a batch for the new database
- auto new_batch = new_database->MakeBatch();
+std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
+{
+ DatabaseOptions options;
+ options.create_flags = WALLET_FLAG_DESCRIPTORS;
+ DatabaseStatus status;
+ bilingual_str error;
+ std::vector<bilingual_str> warnings;
+ auto database = MakeWalletDatabase("", options, status, error);
+ return TestLoadWallet(std::move(database), context, options.create_flags);
+}
- // Read all records from the original database and write them to the new one
- while (true) {
- DataStream key{};
- DataStream value{};
- DatabaseCursor::Status status = cursor->Next(key, value);
- assert(status != DatabaseCursor::Status::FAIL);
- if (status == DatabaseCursor::Status::DONE) break;
- new_batch->Write(key, value);
- }
+void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet)
+{
+ SyncWithValidationInterfaceQueue();
+ wallet->m_chain_notifications_handler.reset();
+ UnloadWallet(std::move(wallet));
+}
- return new_database;
+std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database)
+{
+ return std::make_unique<MockableDatabase>(dynamic_cast<MockableDatabase&>(database).m_records);
}
std::string getnewaddress(CWallet& w)
@@ -79,4 +92,107 @@ CTxDestination getNewDestination(CWallet& w, OutputType output_type)
return *Assert(w.GetNewDestination(output_type, ""));
}
+// BytePrefix compares equality with other byte spans that begin with the same prefix.
+struct BytePrefix { Span<const std::byte> prefix; };
+bool operator<(BytePrefix a, Span<const std::byte> b) { return a.prefix < b.subspan(0, std::min(a.prefix.size(), b.size())); }
+bool operator<(Span<const std::byte> a, BytePrefix b) { return a.subspan(0, std::min(a.size(), b.prefix.size())) < b.prefix; }
+
+MockableCursor::MockableCursor(const MockableData& records, bool pass, Span<const std::byte> prefix)
+{
+ m_pass = pass;
+ std::tie(m_cursor, m_cursor_end) = records.equal_range(BytePrefix{prefix});
+}
+
+DatabaseCursor::Status MockableCursor::Next(DataStream& key, DataStream& value)
+{
+ if (!m_pass) {
+ return Status::FAIL;
+ }
+ if (m_cursor == m_cursor_end) {
+ return Status::DONE;
+ }
+ key.clear();
+ value.clear();
+ const auto& [key_data, value_data] = *m_cursor;
+ key.write(key_data);
+ value.write(value_data);
+ m_cursor++;
+ return Status::MORE;
+}
+
+bool MockableBatch::ReadKey(DataStream&& key, DataStream& value)
+{
+ if (!m_pass) {
+ return false;
+ }
+ SerializeData key_data{key.begin(), key.end()};
+ const auto& it = m_records.find(key_data);
+ if (it == m_records.end()) {
+ return false;
+ }
+ value.clear();
+ value.write(it->second);
+ return true;
+}
+
+bool MockableBatch::WriteKey(DataStream&& key, DataStream&& value, bool overwrite)
+{
+ if (!m_pass) {
+ return false;
+ }
+ SerializeData key_data{key.begin(), key.end()};
+ SerializeData value_data{value.begin(), value.end()};
+ auto [it, inserted] = m_records.emplace(key_data, value_data);
+ if (!inserted && overwrite) { // Overwrite if requested
+ it->second = value_data;
+ inserted = true;
+ }
+ return inserted;
+}
+
+bool MockableBatch::EraseKey(DataStream&& key)
+{
+ if (!m_pass) {
+ return false;
+ }
+ SerializeData key_data{key.begin(), key.end()};
+ m_records.erase(key_data);
+ return true;
+}
+
+bool MockableBatch::HasKey(DataStream&& key)
+{
+ if (!m_pass) {
+ return false;
+ }
+ SerializeData key_data{key.begin(), key.end()};
+ return m_records.count(key_data) > 0;
+}
+
+bool MockableBatch::ErasePrefix(Span<const std::byte> prefix)
+{
+ if (!m_pass) {
+ return false;
+ }
+ auto it = m_records.begin();
+ while (it != m_records.end()) {
+ auto& key = it->first;
+ if (key.size() < prefix.size() || std::search(key.begin(), key.end(), prefix.begin(), prefix.end()) != key.begin()) {
+ it++;
+ continue;
+ }
+ it = m_records.erase(it);
+ }
+ return true;
+}
+
+std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(MockableData records)
+{
+ return std::make_unique<MockableDatabase>(records);
+}
+
+MockableDatabase& GetMockableDatabase(CWallet& wallet)
+{
+ return dynamic_cast<MockableDatabase&>(wallet.GetDatabase());
+}
} // namespace wallet
diff --git a/src/wallet/test/util.h b/src/wallet/test/util.h
index d726517e21..2a1fe639de 100644
--- a/src/wallet/test/util.h
+++ b/src/wallet/test/util.h
@@ -6,6 +6,8 @@
#define BITCOIN_WALLET_TEST_UTIL_H
#include <script/standard.h>
+#include <wallet/db.h>
+
#include <memory>
class ArgsManager;
@@ -18,19 +20,112 @@ class Chain;
namespace wallet {
class CWallet;
-struct DatabaseOptions;
class WalletDatabase;
+struct WalletContext;
+
+static const DatabaseFormat DATABASE_FORMATS[] = {
+#ifdef USE_SQLITE
+ DatabaseFormat::SQLITE,
+#endif
+#ifdef USE_BDB
+ DatabaseFormat::BERKELEY,
+#endif
+};
+
+const std::string ADDRESS_BCRT1_UNSPENDABLE = "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq3xueyj";
std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cchain, const CKey& key);
+std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context);
+std::shared_ptr<CWallet> TestLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, uint64_t create_flags);
+void TestUnloadWallet(std::shared_ptr<CWallet>&& wallet);
+
// Creates a copy of the provided database
-std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database, DatabaseOptions& options);
+std::unique_ptr<WalletDatabase> DuplicateMockDatabase(WalletDatabase& database);
/** Returns a new encoded destination from the wallet (hardcoded to BECH32) */
std::string getnewaddress(CWallet& w);
/** Returns a new destination, of an specific type, from the wallet */
CTxDestination getNewDestination(CWallet& w, OutputType output_type);
+using MockableData = std::map<SerializeData, SerializeData, std::less<>>;
+
+class MockableCursor: public DatabaseCursor
+{
+public:
+ MockableData::const_iterator m_cursor;
+ MockableData::const_iterator m_cursor_end;
+ bool m_pass;
+
+ explicit MockableCursor(const MockableData& records, bool pass) : m_cursor(records.begin()), m_cursor_end(records.end()), m_pass(pass) {}
+ MockableCursor(const MockableData& records, bool pass, Span<const std::byte> prefix);
+ ~MockableCursor() {}
+
+ Status Next(DataStream& key, DataStream& value) override;
+};
+
+class MockableBatch : public DatabaseBatch
+{
+private:
+ MockableData& m_records;
+ bool m_pass;
+
+ bool ReadKey(DataStream&& key, DataStream& value) override;
+ bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite=true) override;
+ bool EraseKey(DataStream&& key) override;
+ bool HasKey(DataStream&& key) override;
+ bool ErasePrefix(Span<const std::byte> prefix) override;
+
+public:
+ explicit MockableBatch(MockableData& records, bool pass) : m_records(records), m_pass(pass) {}
+ ~MockableBatch() {}
+
+ void Flush() override {}
+ void Close() override {}
+
+ std::unique_ptr<DatabaseCursor> GetNewCursor() override
+ {
+ return std::make_unique<MockableCursor>(m_records, m_pass);
+ }
+ std::unique_ptr<DatabaseCursor> GetNewPrefixCursor(Span<const std::byte> prefix) override {
+ return std::make_unique<MockableCursor>(m_records, m_pass, prefix);
+ }
+ bool TxnBegin() override { return m_pass; }
+ bool TxnCommit() override { return m_pass; }
+ bool TxnAbort() override { return m_pass; }
+};
+
+/** A WalletDatabase whose contents and return values can be modified as needed for testing
+ **/
+class MockableDatabase : public WalletDatabase
+{
+public:
+ MockableData m_records;
+ bool m_pass{true};
+
+ MockableDatabase(MockableData 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<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<MockableBatch>(m_records, m_pass); }
+};
+
+std::unique_ptr<WalletDatabase> CreateMockableWalletDatabase(MockableData 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 2dd8f9ad33..57c538e4ef 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -2,15 +2,17 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h>
#include <scheduler.h>
+#include <util/chaintype.h>
namespace wallet {
-WalletTestingSetup::WalletTestingSetup(const std::string& chainName)
- : TestingSetup(chainName),
+WalletTestingSetup::WalletTestingSetup(const ChainType chainType)
+ : TestingSetup(chainType),
m_wallet_loader{interfaces::MakeWalletLoader(*m_node.chain, *Assert(m_node.args))},
- m_wallet(m_node.chain.get(), "", CreateMockWalletDatabase())
+ m_wallet(m_node.chain.get(), "", CreateMockableWalletDatabase())
{
m_wallet.LoadWallet();
m_chain_notifications_handler = m_node.chain->handleNotifications({ &m_wallet, [](CWallet*) {} });
diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h
index f1ef15a282..4aae02e075 100644
--- a/src/wallet/test/wallet_test_fixture.h
+++ b/src/wallet/test/wallet_test_fixture.h
@@ -10,6 +10,7 @@
#include <interfaces/chain.h>
#include <interfaces/wallet.h>
#include <node/context.h>
+#include <util/chaintype.h>
#include <util/check.h>
#include <wallet/wallet.h>
@@ -19,7 +20,7 @@ namespace wallet {
/** Testing setup and teardown for wallet.
*/
struct WalletTestingSetup : public TestingSetup {
- explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
+ explicit WalletTestingSetup(const ChainType chainType = ChainType::MAIN);
~WalletTestingSetup();
std::unique_ptr<interfaces::WalletLoader> m_wallet_loader;
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index da4b553394..65b02c267d 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -29,7 +29,6 @@
#include <univalue.h>
using node::MAX_BLOCKFILE_SIZE;
-using node::UnlinkPrunedFiles;
namespace wallet {
RPCHelpMan importmulti();
@@ -43,26 +42,6 @@ static_assert(WALLET_INCREMENTAL_RELAY_FEE >= DEFAULT_INCREMENTAL_RELAY_FEE, "wa
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
-static std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
-{
- DatabaseOptions options;
- options.create_flags = WALLET_FLAG_DESCRIPTORS;
- DatabaseStatus status;
- bilingual_str error;
- std::vector<bilingual_str> warnings;
- auto database = MakeWalletDatabase("", options, status, error);
- auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
- NotifyWalletLoaded(context, wallet);
- 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;
@@ -98,7 +77,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(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
LOCK(wallet.cs_wallet);
LOCK(Assert(m_node.chainman)->GetMutex());
@@ -119,7 +98,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(), "", CreateMockWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
LOCK(wallet.cs_wallet);
LOCK(Assert(m_node.chainman)->GetMutex());
@@ -159,12 +138,12 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
file_number = oldTip->GetBlockPos().nFile;
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
}
- UnlinkPrunedFiles({file_number});
+ m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
- CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
LOCK(wallet.cs_wallet);
LOCK(Assert(m_node.chainman)->GetMutex());
@@ -188,11 +167,11 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
file_number = newTip->GetBlockPos().nFile;
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
}
- UnlinkPrunedFiles({file_number});
+ m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
// Verify ScanForWalletTransactions scans no blocks.
{
- CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
LOCK(wallet.cs_wallet);
LOCK(Assert(m_node.chainman)->GetMutex());
@@ -226,13 +205,13 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
file_number = oldTip->GetBlockPos().nFile;
Assert(m_node.chainman)->m_blockman.PruneOneBlockFile(file_number);
}
- UnlinkPrunedFiles({file_number});
+ m_node.chainman->m_blockman.UnlinkPrunedFiles({file_number});
// Verify importmulti RPC returns failure for a key whose creation time is
// before the missing block, and success for a key whose creation time is
// after.
{
- const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash()));
WalletContext context;
@@ -298,7 +277,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{
WalletContext context;
context.args = &m_args;
- const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
@@ -321,7 +300,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<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
@@ -355,7 +334,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(), "", CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
LOCK(wallet.cs_wallet);
LOCK(Assert(m_node.chainman)->GetMutex());
@@ -427,15 +406,6 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 5, 50, 600), 300);
}
-static const DatabaseFormat DATABASE_FORMATS[] = {
-#ifdef USE_SQLITE
- DatabaseFormat::SQLITE,
-#endif
-#ifdef USE_BDB
- DatabaseFormat::BERKELEY,
-#endif
-};
-
void TestLoadWallet(const std::string& name, DatabaseFormat format, std::function<void(std::shared_ptr<CWallet>)> f)
{
node::NodeContext node;
@@ -709,7 +679,7 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, ListCoinsTest)
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
{
- const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
@@ -717,7 +687,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
}
{
- const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateDummyWalletDatabase());
+ const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", CreateMockableWalletDatabase());
LOCK(wallet->cs_wallet);
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetMinVersion(FEATURE_LATEST);
@@ -855,10 +825,11 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// Reload wallet and make sure new transactions are detected despite events
// being blocked
+ // Loading will also ask for current mempool transactions
wallet = TestLoadWallet(context);
BOOST_CHECK(rescan_completed);
- // AddToWallet events for block_tx and mempool_tx
- BOOST_CHECK_EQUAL(addtx_count, 2);
+ // AddToWallet events for block_tx and mempool_tx (x2)
+ BOOST_CHECK_EQUAL(addtx_count, 3);
{
LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);
@@ -872,7 +843,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
SyncWithValidationInterfaceQueue();
// AddToWallet events for block_tx and mempool_tx events are counted a
// second time as the notification queue is processed
- BOOST_CHECK_EQUAL(addtx_count, 4);
+ BOOST_CHECK_EQUAL(addtx_count, 5);
TestUnloadWallet(std::move(wallet));
@@ -895,7 +866,9 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
SyncWithValidationInterfaceQueue();
});
wallet = TestLoadWallet(context);
- BOOST_CHECK_EQUAL(addtx_count, 2);
+ // Since mempool transactions are requested at the end of loading, there will
+ // be 2 additional AddToWallet calls, one from the previous test, and a duplicate for mempool_tx
+ BOOST_CHECK_EQUAL(addtx_count, 2 + 2);
{
LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);
@@ -951,62 +924,13 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
TestUnloadWallet(std::move(wallet));
}
-class FailCursor : public DatabaseCursor
-{
-public:
- Status Next(DataStream& key, DataStream& value) override { return Status::FAIL; }
-};
-
-/** RAII class that provides access to a FailDatabase. Which fails if needed. */
-class FailBatch : public DatabaseBatch
-{
-private:
- bool m_pass{true};
- bool ReadKey(DataStream&& key, DataStream& value) override { return m_pass; }
- bool WriteKey(DataStream&& key, DataStream&& value, bool overwrite = true) override { return m_pass; }
- bool EraseKey(DataStream&& key) override { return m_pass; }
- bool HasKey(DataStream&& key) override { return m_pass; }
- bool ErasePrefix(Span<const std::byte> prefix) override { return m_pass; }
-
-public:
- explicit FailBatch(bool pass) : m_pass(pass) {}
- void Flush() override {}
- void Close() override {}
-
- std::unique_ptr<DatabaseCursor> GetNewCursor() override { return std::make_unique<FailCursor>(); }
- 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<DatabaseBatch> MakeBatch(bool flush_on_close = true) override { return std::make_unique<FailBatch>(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, TestingSetup)
{
- CWallet wallet(m_node.chain.get(), "", std::make_unique<FailDatabase>());
+ CWallet wallet(m_node.chain.get(), "", CreateMockableWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -1014,11 +938,10 @@ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
}
// Add tx to wallet
- const auto& op_dest = wallet.GetNewDestination(OutputType::BECH32M, "");
- BOOST_ASSERT(op_dest);
+ const auto op_dest{*Assert(wallet.GetNewDestination(OutputType::BECH32M, ""))};
CMutableTransaction mtx;
- mtx.vout.push_back({COIN, GetScriptForDestination(*op_dest)});
+ mtx.vout.push_back({COIN, GetScriptForDestination(op_dest)});
mtx.vin.push_back(CTxIn(g_insecure_rand_ctx.rand256(), 0));
const auto& tx_id_to_spend = wallet.AddToWallet(MakeTransactionRef(mtx), TxStateInMempool{})->GetHash();
@@ -1053,7 +976,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_sync_tx_invalid_state_test, TestingSetup)
// 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<FailDatabase&>(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)),
diff --git a/src/wallet/test/walletdb_tests.cpp b/src/wallet/test/walletdb_tests.cpp
index 21842fe780..17b6c4f7ed 100644
--- a/src/wallet/test/walletdb_tests.cpp
+++ b/src/wallet/test/walletdb_tests.cpp
@@ -6,6 +6,8 @@
#include <clientversion.h>
#include <streams.h>
#include <uint256.h>
+#include <wallet/test/util.h>
+#include <wallet/wallet.h>
#include <boost/test/unit_test.hpp>
@@ -27,5 +29,31 @@ BOOST_AUTO_TEST_CASE(walletdb_readkeyvalue)
BOOST_CHECK_THROW(ssValue >> dummy, std::ios_base::failure);
}
+BOOST_AUTO_TEST_CASE(walletdb_read_write_deadlock)
+{
+ // Exercises a db read write operation that shouldn't deadlock.
+ for (const DatabaseFormat& db_format : DATABASE_FORMATS) {
+ // Context setup
+ DatabaseOptions options;
+ options.require_format = db_format;
+ DatabaseStatus status;
+ bilingual_str error_string;
+ std::unique_ptr<WalletDatabase> db = MakeDatabase(m_path_root / strprintf("wallet_%d_.dat", db_format).c_str(), options, status, error_string);
+ BOOST_CHECK_EQUAL(status, DatabaseStatus::SUCCESS);
+
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db)));
+ wallet->m_keypool_size = 4;
+
+ // Create legacy spkm
+ LOCK(wallet->cs_wallet);
+ auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
+ BOOST_CHECK(legacy_spkm->SetupGeneration(true));
+ wallet->Flush();
+
+ // Now delete all records, which performs a read write operation.
+ BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
+ }
+}
+
BOOST_AUTO_TEST_SUITE_END()
} // namespace wallet
diff --git a/src/wallet/test/walletload_tests.cpp b/src/wallet/test/walletload_tests.cpp
index 9f5a4b14d3..73a4b77188 100644
--- a/src/wallet/test/walletload_tests.cpp
+++ b/src/wallet/test/walletload_tests.cpp
@@ -34,7 +34,7 @@ public:
BOOST_FIXTURE_TEST_CASE(wallet_load_unknown_descriptor, TestingSetup)
{
- std::unique_ptr<WalletDatabase> database = CreateMockWalletDatabase();
+ std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
{
// Write unknown active descriptor
WalletBatch batch(*database, false);
@@ -70,38 +70,45 @@ bool HasAnyRecordOfType(WalletDatabase& db, const std::string& key)
return false;
}
-BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup)
+template<typename... Args>
+SerializeData MakeSerializeData(const Args&... args)
{
- // The test duplicates the db so each case has its own db instance.
- int NUMBER_OF_TESTS = 4;
- std::vector<std::unique_ptr<WalletDatabase>> dbs;
- CKey first_key;
- auto get_db = [](std::vector<std::unique_ptr<WalletDatabase>>& dbs) {
- std::unique_ptr<WalletDatabase> db = std::move(dbs.back());
- dbs.pop_back();
- return db;
- };
-
- { // Context setup.
+ CDataStream s(0, 0);
+ SerializeMany(s, args...);
+ return {s.begin(), s.end()};
+}
+
+
+BOOST_FIXTURE_TEST_CASE(wallet_load_ckey, TestingSetup)
+{
+ SerializeData ckey_record_key;
+ SerializeData ckey_record_value;
+ MockableData records;
+
+ {
+ // Context setup.
// Create and encrypt legacy wallet
- std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockWalletDatabase()));
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase()));
LOCK(wallet->cs_wallet);
auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
BOOST_CHECK(legacy_spkm->SetupGeneration(true));
- // Get the first key in the wallet
+ // Retrieve a key
CTxDestination dest = *Assert(legacy_spkm->GetNewDestination(OutputType::LEGACY));
CKeyID key_id = GetKeyForDestination(*legacy_spkm, dest);
+ CKey first_key;
BOOST_CHECK(legacy_spkm->GetKey(key_id, first_key));
- // Encrypt the wallet and duplicate database
+ // Encrypt the wallet
BOOST_CHECK(wallet->EncryptWallet("encrypt"));
wallet->Flush();
- DatabaseOptions options;
- for (int i=0; i < NUMBER_OF_TESTS; i++) {
- dbs.emplace_back(DuplicateMockDatabase(wallet->GetDatabase(), options));
- }
+ // Store a copy of all the records
+ records = GetMockableDatabase(*wallet).m_records;
+
+ // Get the record for the retrieved key
+ ckey_record_key = MakeSerializeData(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
+ ckey_record_value = records.at(ckey_record_key);
}
{
@@ -112,7 +119,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup)
// the records every time that 'CWallet::Unlock' gets called, which is not good.
// Load the wallet and check that is encrypted
- std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", get_db(dbs)));
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
BOOST_CHECK(wallet->IsCrypted());
BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
@@ -127,18 +134,12 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup)
{
// Second test case:
// Verify that loading up a 'ckey' with no checksum triggers a complete re-write of the crypted keys.
- std::unique_ptr<WalletDatabase> db = get_db(dbs);
- {
- std::unique_ptr<DatabaseBatch> batch = db->MakeBatch(false);
- std::pair<std::vector<unsigned char>, uint256> value;
- BOOST_CHECK(batch->Read(std::make_pair(DBKeys::CRYPTED_KEY, first_key.GetPubKey()), value));
- const auto key = std::make_pair(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
- BOOST_CHECK(batch->Write(key, value.first, /*fOverwrite=*/true));
- }
+ // Cut off the 32 byte checksum from a ckey record
+ records[ckey_record_key].resize(ckey_record_value.size() - 32);
// Load the wallet and check that is encrypted
- std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db)));
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
BOOST_CHECK(wallet->IsCrypted());
BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
@@ -154,35 +155,25 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_key_checksum, TestingSetup)
{
// Third test case:
// Verify that loading up a 'ckey' with an invalid checksum throws an error.
- std::unique_ptr<WalletDatabase> db = get_db(dbs);
- {
- std::unique_ptr<DatabaseBatch> batch = db->MakeBatch(false);
- std::vector<unsigned char> crypted_data;
- BOOST_CHECK(batch->Read(std::make_pair(DBKeys::CRYPTED_KEY, first_key.GetPubKey()), crypted_data));
-
- // Write an invalid checksum
- std::pair<std::vector<unsigned char>, uint256> value = std::make_pair(crypted_data, uint256::ONE);
- const auto key = std::make_pair(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
- BOOST_CHECK(batch->Write(key, value, /*fOverwrite=*/true));
- }
-
- std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db)));
+
+ // Cut off the 32 byte checksum from a ckey record
+ records[ckey_record_key].resize(ckey_record_value.size() - 32);
+ // Fill in the checksum space with 0s
+ records[ckey_record_key].resize(ckey_record_value.size());
+
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
}
{
// Fourth test case:
// Verify that loading up a 'ckey' with an invalid pubkey throws an error
- std::unique_ptr<WalletDatabase> db = get_db(dbs);
- {
- CPubKey invalid_key;
- BOOST_ASSERT(!invalid_key.IsValid());
- const auto key = std::make_pair(DBKeys::CRYPTED_KEY, invalid_key);
- std::pair<std::vector<unsigned char>, uint256> value;
- BOOST_CHECK(db->MakeBatch(false)->Write(key, value, /*fOverwrite=*/true));
- }
-
- std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(db)));
+ CPubKey invalid_key;
+ BOOST_CHECK(!invalid_key.IsValid());
+ SerializeData key = MakeSerializeData(DBKeys::CRYPTED_KEY, invalid_key);
+ records[key] = ckey_record_value;
+
+ std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
}
}