diff options
Diffstat (limited to 'src/wallet/test')
-rw-r--r-- | src/wallet/test/db_tests.cpp | 40 | ||||
-rw-r--r-- | src/wallet/test/fuzz/wallet_bdb_parser.cpp | 133 | ||||
-rw-r--r-- | src/wallet/test/util.cpp | 5 |
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; |