diff options
Diffstat (limited to 'src/wallet/test/db_tests.cpp')
-rw-r--r-- | src/wallet/test/db_tests.cpp | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/src/wallet/test/db_tests.cpp b/src/wallet/test/db_tests.cpp index d341e84d9b..f783424df8 100644 --- a/src/wallet/test/db_tests.cpp +++ b/src/wallet/test/db_tests.cpp @@ -2,6 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + #include <boost/test/unit_test.hpp> #include <test/util/setup_common.h> @@ -205,5 +209,155 @@ BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test) } } +BOOST_AUTO_TEST_CASE(db_availability_after_write_error) +{ + // Ensures the database remains accessible without deadlocking after a 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)) { + // Write original record + std::unique_ptr<DatabaseBatch> batch = database->MakeBatch(); + std::string key = "key"; + std::string value = "value"; + std::string value2 = "value_2"; + BOOST_CHECK(batch->Write(key, value)); + // Attempt to overwrite the record (expect failure) + BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false)); + // Successfully overwrite the record + BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true)); + // Sanity-check; read and verify the overwritten value + std::string read_value; + BOOST_CHECK(batch->Read(key, read_value)); + BOOST_CHECK_EQUAL(read_value, value2); + } +} + +// Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet. +// Keys are in the form of std::pair<TYPE, ENTRY_ID> +BOOST_AUTO_TEST_CASE(erase_prefix) +{ + const std::string key = "key"; + const std::string key2 = "key2"; + const std::string value = "value"; + const std::string value2 = "value_2"; + auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); }; + + for (const auto& database : TestDatabases(m_path_root)) { + std::unique_ptr<DatabaseBatch> batch = database->MakeBatch(); + + // Write two entries with the same key type prefix, a third one with a different prefix + // and a fourth one with the type-id values inverted + BOOST_CHECK(batch->Write(make_key(key, value), value)); + BOOST_CHECK(batch->Write(make_key(key, value2), value2)); + BOOST_CHECK(batch->Write(make_key(key2, value), value)); + BOOST_CHECK(batch->Write(make_key(value, key), value)); + + // Erase the ones with the same prefix and verify result + BOOST_CHECK(batch->TxnBegin()); + BOOST_CHECK(batch->ErasePrefix(DataStream() << key)); + BOOST_CHECK(batch->TxnCommit()); + + BOOST_CHECK(!batch->Exists(make_key(key, value))); + BOOST_CHECK(!batch->Exists(make_key(key, value2))); + // Also verify that entries with a different prefix were not erased + BOOST_CHECK(batch->Exists(make_key(key2, value))); + BOOST_CHECK(batch->Exists(make_key(value, key))); + } +} + +#ifdef USE_SQLITE + +// Test-only statement execution error +constexpr int TEST_SQLITE_ERROR = -999; + +class DbExecBlocker : public SQliteExecHandler +{ +private: + SQliteExecHandler m_base_exec; + std::set<std::string> m_blocked_statements; +public: + DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {} + int Exec(SQLiteDatabase& database, const std::string& statement) override { + if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR; + return m_base_exec.Exec(database, statement); + } +}; + +BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn) +{ + // Verifies that there is no active dangling, to-be-reversed db txn + // after the batch object that initiated it is destroyed. + DatabaseOptions options; + DatabaseStatus status; + bilingual_str error; + std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error); + + std::string key = "key"; + std::string value = "value"; + + std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database); + BOOST_CHECK(batch->TxnBegin()); + BOOST_CHECK(batch->Write(key, value)); + // Set a handler to prevent txn abortion during destruction. + // Mimicking a db statement execution failure. + batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"})); + // Destroy batch + batch.reset(); + + // Ensure there is no dangling, to-be-reversed db txn + BOOST_CHECK(!database->HasActiveTxn()); + + // And, just as a sanity check; verify that new batchs only write what they suppose to write + // and nothing else. + std::string key2 = "key2"; + std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database); + BOOST_CHECK(batch2->Write(key2, value)); + // The first key must not exist + BOOST_CHECK(!batch2->Exists(key)); +} + +BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere) +{ + std::string key = "key"; + std::string value = "value"; + std::string value2 = "value_2"; + + DatabaseOptions options; + DatabaseStatus status; + bilingual_str error; + const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error); + + std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch(); + + // Verify concurrent db transactions does not interfere between each other. + // Start db txn, write key and check the key does exist within the db txn. + BOOST_CHECK(handler->TxnBegin()); + BOOST_CHECK(handler->Write(key, value)); + BOOST_CHECK(handler->Exists(key)); + + // But, the same key, does not exist in another handler + std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch(); + BOOST_CHECK(handler2->Exists(key)); + + // Attempt to commit the handler txn calling the handler2 methods. + // Which, must not be possible. + BOOST_CHECK(!handler2->TxnCommit()); + BOOST_CHECK(!handler2->TxnAbort()); + + // Only the first handler can commit the changes. + BOOST_CHECK(handler->TxnCommit()); + // And, once commit is completed, handler2 can read the record + std::string read_value; + BOOST_CHECK(handler2->Read(key, read_value)); + BOOST_CHECK_EQUAL(read_value, value); + + // Also, once txn is committed, single write statements are re-enabled. + // Which means that handler2 can read the record changes directly. + BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true)); + BOOST_CHECK(handler2->Read(key, read_value)); + BOOST_CHECK_EQUAL(read_value, value2); +} +#endif // USE_SQLITE + BOOST_AUTO_TEST_SUITE_END() } // namespace wallet |