aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/test')
-rw-r--r--src/wallet/test/db_tests.cpp40
-rw-r--r--src/wallet/test/fuzz/wallet_bdb_parser.cpp133
-rw-r--r--src/wallet/test/util.cpp5
3 files changed, 167 insertions, 11 deletions
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp
index 438dfceb7f..2fac356263 100644
--- a/src/wallet/test/db_tests.cpp
+++ b/src/wallet/test/db_tests.cpp
@@ -16,6 +16,7 @@
#ifdef USE_SQLITE
#include <wallet/sqlite.h>
#endif
+#include <wallet/migrate.h>
#include <wallet/test/util.h>
#include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
@@ -132,6 +133,8 @@ static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path
bilingual_str error;
#ifdef USE_BDB
dbs.emplace_back(MakeBerkeleyDatabase(path_root / "bdb", options, status, error));
+ // Needs BDB to make the DB to read
+ dbs.emplace_back(std::make_unique<BerkeleyRODatabase>(BDBDataFile(path_root / "bdb"), /*open=*/false));
#endif
#ifdef USE_SQLITE
dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
@@ -146,11 +149,16 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
for (const auto& database : TestDatabases(m_path_root)) {
std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
- // Write elements to it
std::unique_ptr<DatabaseBatch> handler = Assert(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));
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
+ database->Open();
+ } else {
+ // Write elements to it if not berkeleyro
+ for (unsigned int i = 0; i < 10; i++) {
+ for (const auto& prefix : prefixes) {
+ BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
+ }
}
}
@@ -178,6 +186,8 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
// Let's now read it once more, it should return DONE
BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
}
+ handler.reset();
+ database->Close();
}
}
@@ -197,13 +207,23 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
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(Span{k}, Span{v});
+
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
+ database->Open();
+ } else {
+ // Write elements to it if not berkeleyro
+ for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
+ batch->Write(Span{k}, Span{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});
+ batch.reset();
+ database->Close();
}
}
@@ -213,6 +233,10 @@ BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
// To simulate the behavior, record overwrites are disallowed, and the test verifies
// that the database remains active after failing to store an existing record.
for (const auto& database : TestDatabases(m_path_root)) {
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // Skip this test if BerkeleyRO
+ continue;
+ }
// Write original record
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
std::string key = "key";
@@ -241,6 +265,10 @@ BOOST_AUTO_TEST_CASE(erase_prefix)
auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
for (const auto& database : TestDatabases(m_path_root)) {
+ if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
+ // Skip this test if BerkeleyRO
+ continue;
+ }
std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
// Write two entries with the same key type prefix, a third one with a different prefix
diff --git a/src/wallet/test/fuzz/wallet_bdb_parser.cpp b/src/wallet/test/fuzz/wallet_bdb_parser.cpp
new file mode 100644
index 0000000000..24ef75f791
--- /dev/null
+++ b/src/wallet/test/fuzz/wallet_bdb_parser.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2023 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include <config/bitcoin-config.h> // IWYU pragma: keep
+#include <test/fuzz/FuzzedDataProvider.h>
+#include <test/fuzz/fuzz.h>
+#include <test/fuzz/util.h>
+#include <test/util/setup_common.h>
+#include <util/fs.h>
+#include <util/time.h>
+#include <util/translation.h>
+#include <wallet/bdb.h>
+#include <wallet/db.h>
+#include <wallet/dump.h>
+#include <wallet/migrate.h>
+
+#include <fstream>
+#include <iostream>
+
+using wallet::DatabaseOptions;
+using wallet::DatabaseStatus;
+
+namespace {
+TestingSetup* g_setup;
+} // namespace
+
+void initialize_wallet_bdb_parser()
+{
+ static auto testing_setup = MakeNoLogFileContext<TestingSetup>();
+ g_setup = testing_setup.get();
+}
+
+FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
+{
+ const auto wallet_path = g_setup->m_args.GetDataDirNet() / "fuzzed_wallet.dat";
+
+ {
+ AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
+ outfile << Span{buffer};
+ }
+
+ const DatabaseOptions options{};
+ DatabaseStatus status;
+ bilingual_str error;
+
+ fs::path bdb_ro_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb_ro.dump"};
+ if (fs::exists(bdb_ro_dumpfile)) { // Writing into an existing dump file will throw an exception
+ remove(bdb_ro_dumpfile);
+ }
+ g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_ro_dumpfile));
+
+#ifdef USE_BDB
+ bool bdb_ro_err = false;
+ bool bdb_ro_pgno_err = false;
+#endif
+ auto db{MakeBerkeleyRODatabase(wallet_path, options, status, error)};
+ if (db) {
+ assert(DumpWallet(g_setup->m_args, *db, error));
+ } else {
+#ifdef USE_BDB
+ bdb_ro_err = true;
+#endif
+ if (error.original == "AutoFile::ignore: end of file: iostream error" ||
+ error.original == "AutoFile::read: end of file: iostream error" ||
+ error.original == "Not a BDB file" ||
+ error.original == "Unsupported BDB data file version number" ||
+ error.original == "Unexpected page type, should be 9 (BTree Metadata)" ||
+ error.original == "Unexpected database flags, should only be 0x20 (subdatabases)" ||
+ error.original == "Unexpected outer database root page type" ||
+ error.original == "Unexpected number of entries in outer database root page" ||
+ error.original == "Subdatabase has an unexpected name" ||
+ error.original == "Subdatabase page number has unexpected length" ||
+ error.original == "Unexpected inner database page type" ||
+ error.original == "Unknown record type in records page" ||
+ error.original == "Unknown record type in internal page" ||
+ error.original == "Unexpected page size" ||
+ error.original == "Unexpected page type" ||
+ error.original == "Page number mismatch" ||
+ error.original == "Bad btree level" ||
+ error.original == "Bad page size" ||
+ error.original == "File size is not a multiple of page size" ||
+ error.original == "Meta page number mismatch") {
+ // Do nothing
+ } else if (error.original == "Subdatabase last page is greater than database last page" ||
+ error.original == "Page number is greater than database last page" ||
+ error.original == "Page number is greater than subdatabase last page" ||
+ error.original == "Last page number could not fit in file") {
+#ifdef USE_BDB
+ bdb_ro_pgno_err = true;
+#endif
+ } else {
+ throw std::runtime_error(error.original);
+ }
+ }
+
+#ifdef USE_BDB
+ // Try opening with BDB
+ fs::path bdb_dumpfile{g_setup->m_args.GetDataDirNet() / "fuzzed_dumpfile_bdb.dump"};
+ if (fs::exists(bdb_dumpfile)) { // Writing into an existing dump file will throw an exception
+ remove(bdb_dumpfile);
+ }
+ g_setup->m_args.ForceSetArg("-dumpfile", fs::PathToString(bdb_dumpfile));
+
+ try {
+ auto db{MakeBerkeleyDatabase(wallet_path, options, status, error)};
+ if (bdb_ro_err && !db) {
+ return;
+ }
+ assert(db);
+ if (bdb_ro_pgno_err) {
+ // BerkeleyRO will throw on opening for errors involving bad page numbers, but BDB does not.
+ // Ignore those.
+ return;
+ }
+ assert(!bdb_ro_err);
+ assert(DumpWallet(g_setup->m_args, *db, error));
+ } catch (const std::runtime_error& e) {
+ if (bdb_ro_err) return;
+ throw e;
+ }
+
+ // Make sure the dumpfiles match
+ if (fs::exists(bdb_ro_dumpfile) && fs::exists(bdb_dumpfile)) {
+ std::ifstream bdb_ro_dump(bdb_ro_dumpfile, std::ios_base::binary | std::ios_base::in);
+ std::ifstream bdb_dump(bdb_dumpfile, std::ios_base::binary | std::ios_base::in);
+ assert(std::equal(
+ std::istreambuf_iterator<char>(bdb_ro_dump.rdbuf()),
+ std::istreambuf_iterator<char>(),
+ std::istreambuf_iterator<char>(bdb_dump.rdbuf())));
+ }
+#endif
+}
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index 49d206f409..b21a9a601d 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -93,11 +93,6 @@ 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;