LCOV - code coverage report
Current view: top level - src/wallet/test - db_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 95.8 % 166 159
Test Date: 2025-05-10 04:59:59 Functions: 94.7 % 19 18
Branches: 50.5 % 994 502

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2018-present The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <boost/test/unit_test.hpp>
       6                 :             : 
       7                 :             : #include <test/util/setup_common.h>
       8                 :             : #include <util/check.h>
       9                 :             : #include <util/fs.h>
      10                 :             : #include <util/translation.h>
      11                 :             : #include <wallet/sqlite.h>
      12                 :             : #include <wallet/migrate.h>
      13                 :             : #include <wallet/test/util.h>
      14                 :             : #include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
      15                 :             : 
      16                 :             : #include <fstream>
      17                 :             : #include <memory>
      18                 :             : #include <string>
      19                 :             : 
      20                 :           0 : inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
      21                 :             : {
      22                 :           0 :     std::span key{kv.first}, value{kv.second};
      23                 :           0 :     os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \""
      24                 :           0 :        << std::string_view{reinterpret_cast<const char*>(value.data()), value.size()} << "\")";
      25                 :           0 :     return os;
      26                 :             : }
      27                 :             : 
      28                 :             : namespace wallet {
      29                 :             : 
      30                 :          22 : inline std::span<const std::byte> StringBytes(std::string_view str)
      31                 :             : {
      32                 :          22 :     return std::as_bytes(std::span{str});
      33                 :             : }
      34                 :             : 
      35                 :          14 : static SerializeData StringData(std::string_view str)
      36                 :             : {
      37                 :          14 :     auto bytes = StringBytes(str);
      38                 :          14 :     return SerializeData{bytes.begin(), bytes.end()};
      39                 :             : }
      40                 :             : 
      41                 :           8 : static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected)
      42                 :             : {
      43                 :           8 :     std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
      44                 :           8 :     MockableData actual;
      45                 :          68 :     while (true) {
      46                 :          38 :         DataStream key, value;
      47         [ +  - ]:          38 :         DatabaseCursor::Status status = cursor->Next(key, value);
      48         [ +  + ]:          38 :         if (status == DatabaseCursor::Status::DONE) break;
      49   [ +  -  +  -  :          60 :         BOOST_CHECK(status == DatabaseCursor::Status::MORE);
                   +  - ]
      50   [ +  -  +  -  :          60 :         BOOST_CHECK(
          +  -  +  -  +  
                      - ]
      51                 :             :             actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
      52                 :          38 :     }
      53   [ +  -  +  -  :          16 :     BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
                   +  - ]
      54                 :           8 : }
      55                 :             : 
      56                 :             : BOOST_FIXTURE_TEST_SUITE(db_tests, BasicTestingSetup)
      57                 :             : 
      58                 :           4 : static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
      59                 :             : {
      60                 :           4 :     std::vector<std::unique_ptr<WalletDatabase>> dbs;
      61         [ +  - ]:           4 :     DatabaseOptions options;
      62                 :           4 :     DatabaseStatus status;
      63         [ +  - ]:           4 :     bilingual_str error;
      64                 :             :     // Unable to test BerkeleyRO since we cannot create a new BDB database to open
      65   [ +  -  +  -  :          12 :     dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
             +  -  +  - ]
      66   [ +  -  +  - ]:           4 :     dbs.emplace_back(CreateMockableWalletDatabase());
      67                 :           4 :     return dbs;
      68                 :           4 : }
      69                 :             : 
      70   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
      71                 :             : {
      72                 :             :     // Test each supported db
      73         [ +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
      74         [ +  - ]:           2 :         std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
      75                 :             : 
      76   [ +  -  +  - ]:           2 :         std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
      77                 :             :         // Write elements to it
      78         [ +  + ]:          22 :         for (unsigned int i = 0; i < 10; i++) {
      79         [ +  + ]:         140 :             for (const auto& prefix : prefixes) {
      80   [ +  -  +  -  :         240 :                 BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
             +  -  +  - ]
      81                 :             :             }
      82                 :             :         }
      83                 :             : 
      84                 :             :         // Now read all the items by prefix and verify that each element gets parsed correctly
      85         [ +  + ]:          14 :         for (const auto& prefix : prefixes) {
      86                 :          12 :             DataStream s_prefix;
      87         [ +  - ]:          12 :             s_prefix << prefix;
      88         [ +  - ]:          12 :             std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix);
      89                 :          12 :             DataStream key;
      90                 :          12 :             DataStream value;
      91         [ +  + ]:         132 :             for (int i = 0; i < 10; i++) {
      92         [ +  - ]:         120 :                 DatabaseCursor::Status status = cursor->Next(key, value);
      93   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(status, DatabaseCursor::Status::MORE);
      94                 :             : 
      95         [ +  - ]:         120 :                 std::string key_back;
      96                 :         120 :                 unsigned int i_back;
      97   [ +  -  +  - ]:         120 :                 key >> key_back >> i_back;
      98   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(key_back, prefix);
      99                 :             : 
     100                 :         120 :                 unsigned int value_back;
     101         [ +  - ]:         120 :                 value >> value_back;
     102   [ +  -  +  - ]:         120 :                 BOOST_CHECK_EQUAL(value_back, i_back);
     103                 :         120 :             }
     104                 :             : 
     105                 :             :             // Let's now read it once more, it should return DONE
     106   [ +  -  +  -  :          24 :             BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
                   +  - ]
     107                 :          12 :         }
     108         [ +  - ]:           2 :         handler.reset();
     109         [ +  - ]:           2 :         database->Close();
     110                 :           3 :     }
     111                 :           1 : }
     112                 :             : 
     113                 :             : // Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't
     114                 :             : // covered in the higher level test above. The higher level test uses
     115                 :             : // serialized strings which are prefixed with string length, so it doesn't test
     116                 :             : // truly empty prefixes or prefixes that begin with \xff
     117   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     118                 :             : {
     119                 :           1 :     const MockableData::value_type
     120   [ +  -  +  - ]:           1 :         e{StringData(""), StringData("e")},
     121   [ +  -  +  -  :           1 :         p{StringData("prefix"), StringData("p")},
                   +  - ]
     122   [ +  -  +  -  :           1 :         ps{StringData("prefixsuffix"), StringData("ps")},
                   +  - ]
     123   [ +  -  +  -  :           1 :         f{StringData("\xff"), StringData("f")},
                   +  - ]
     124   [ +  -  +  -  :           1 :         fs{StringData("\xffsuffix"), StringData("fs")},
                   +  - ]
     125   [ +  -  +  -  :           1 :         ff{StringData("\xff\xff"), StringData("ff")},
                   +  - ]
     126   [ +  -  +  - ]:           1 :         ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
     127   [ +  -  +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     128         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     129                 :             : 
     130                 :             :         // Write elements to it if not berkeleyro
     131   [ +  -  +  -  :          44 :         for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
          +  -  +  -  +  
          -  +  -  +  -  
          -  +  +  +  -  
                      - ]
     132         [ -  + ]:          14 :             batch->Write(std::span{k}, std::span{v});
     133   [ +  +  -  - ]:          16 :         }
     134                 :             : 
     135   [ +  -  +  -  :          18 :         CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
             +  +  -  - ]
     136   [ +  -  +  -  :           8 :         CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
             +  +  -  - ]
     137   [ +  -  +  -  :          12 :         CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
             +  +  -  - ]
     138   [ +  -  +  -  :           8 :         CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
             +  +  -  - ]
     139         [ +  - ]:           2 :         batch.reset();
     140         [ +  - ]:           2 :         database->Close();
     141                 :           3 :     }
     142   [ +  -  +  -  :          15 : }
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  -  -  -  -  
             -  -  -  - ]
     143                 :             : 
     144   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     145                 :             : {
     146                 :             :     // Ensures the database remains accessible without deadlocking after a write error.
     147                 :             :     // To simulate the behavior, record overwrites are disallowed, and the test verifies
     148                 :             :     // that the database remains active after failing to store an existing record.
     149         [ +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     150                 :             :         // Write original record
     151         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     152         [ +  - ]:           2 :         std::string key = "key";
     153         [ +  - ]:           2 :         std::string value = "value";
     154         [ +  - ]:           2 :         std::string value2 = "value_2";
     155   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Write(key, value));
             +  -  +  - ]
     156                 :             :         // Attempt to overwrite the record (expect failure)
     157   [ +  -  +  -  :           4 :         BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false));
             +  -  +  - ]
     158                 :             :         // Successfully overwrite the record
     159   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true));
             +  -  +  - ]
     160                 :             :         // Sanity-check; read and verify the overwritten value
     161         [ +  - ]:           2 :         std::string read_value;
     162   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->Read(key, read_value));
             +  -  +  - ]
     163   [ +  -  +  - ]:           2 :         BOOST_CHECK_EQUAL(read_value, value2);
     164                 :           3 :     }
     165                 :           1 : }
     166                 :             : 
     167                 :             : // Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
     168                 :             : // Keys are in the form of std::pair<TYPE, ENTRY_ID>
     169   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(erase_prefix)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     170                 :             : {
     171                 :           1 :     const std::string key = "key";
     172         [ +  - ]:           1 :     const std::string key2 = "key2";
     173         [ +  - ]:           1 :     const std::string value = "value";
     174         [ +  - ]:           1 :     const std::string value2 = "value_2";
     175   [ +  -  +  -  :          16 :     auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
          +  -  +  -  +  
          -  +  -  +  -  
                   +  - ]
     176                 :             : 
     177   [ +  -  +  + ]:           3 :     for (const auto& database : TestDatabases(m_path_root)) {
     178   [ +  -  -  + ]:           2 :         if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
     179                 :             :             // Skip this test if BerkeleyRO
     180                 :           0 :             continue;
     181                 :             :         }
     182         [ +  - ]:           2 :         std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
     183                 :             : 
     184                 :             :         // Write two entries with the same key type prefix, a third one with a different prefix
     185                 :             :         // and a fourth one with the type-id values inverted
     186   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key, value), value));
          +  -  +  -  +  
                -  +  - ]
     187   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key, value2), value2));
          +  -  +  -  +  
                -  +  - ]
     188   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(key2, value), value));
          +  -  +  -  +  
                -  +  - ]
     189   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Write(make_key(value, key), value));
          +  -  +  -  +  
                -  +  - ]
     190                 :             : 
     191                 :             :         // Erase the ones with the same prefix and verify result
     192   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->TxnBegin());
             +  -  +  - ]
     193   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
          +  -  +  -  +  
                      - ]
     194   [ +  -  +  -  :           4 :         BOOST_CHECK(batch->TxnCommit());
             +  -  +  - ]
     195                 :             : 
     196   [ +  -  +  -  :           8 :         BOOST_CHECK(!batch->Exists(make_key(key, value)));
          +  -  +  -  +  
                -  +  - ]
     197   [ +  -  +  -  :           8 :         BOOST_CHECK(!batch->Exists(make_key(key, value2)));
          +  -  +  -  +  
                -  +  - ]
     198                 :             :         // Also verify that entries with a different prefix were not erased
     199   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Exists(make_key(key2, value)));
          +  -  +  -  +  
                -  +  - ]
     200   [ +  -  +  -  :           8 :         BOOST_CHECK(batch->Exists(make_key(value, key)));
          +  -  +  -  +  
                      - ]
     201                 :           3 :     }
     202                 :           1 : }
     203                 :             : 
     204                 :             : // Test-only statement execution error
     205                 :             : constexpr int TEST_SQLITE_ERROR = -999;
     206                 :             : 
     207                 :             : class DbExecBlocker : public SQliteExecHandler
     208                 :             : {
     209                 :             : private:
     210                 :             :     SQliteExecHandler m_base_exec;
     211                 :             :     std::set<std::string> m_blocked_statements;
     212                 :             : public:
     213         [ +  - ]:           1 :     DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {}
     214                 :           1 :     int Exec(SQLiteDatabase& database, const std::string& statement) override {
     215         [ -  + ]:           1 :         if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR;
     216                 :           0 :         return m_base_exec.Exec(database, statement);
     217                 :             :     }
     218                 :             : };
     219                 :             : 
     220   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     221                 :             : {
     222                 :             :     // Verifies that there is no active dangling, to-be-reversed db txn
     223                 :             :     // after the batch object that initiated it is destroyed.
     224         [ +  - ]:           1 :     DatabaseOptions options;
     225                 :           1 :     DatabaseStatus status;
     226         [ +  - ]:           1 :     bilingual_str error;
     227   [ +  -  +  -  :           3 :     std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
                   +  - ]
     228                 :             : 
     229         [ +  - ]:           1 :     std::string key = "key";
     230         [ +  - ]:           1 :     std::string value = "value";
     231                 :             : 
     232         [ +  - ]:           1 :     std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database);
     233   [ +  -  +  -  :           2 :     BOOST_CHECK(batch->TxnBegin());
             +  -  +  - ]
     234   [ +  -  +  -  :           2 :     BOOST_CHECK(batch->Write(key, value));
             +  -  +  - ]
     235                 :             :     // Set a handler to prevent txn abortion during destruction.
     236                 :             :     // Mimicking a db statement execution failure.
     237   [ +  -  +  -  :           2 :     batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"}));
          +  -  +  +  -  
                      - ]
     238                 :             :     // Destroy batch
     239         [ +  - ]:           1 :     batch.reset();
     240                 :             : 
     241                 :             :     // Ensure there is no dangling, to-be-reversed db txn
     242   [ +  -  +  -  :           2 :     BOOST_CHECK(!database->HasActiveTxn());
             +  -  +  - ]
     243                 :             : 
     244                 :             :     // And, just as a sanity check; verify that new batchs only write what they suppose to write
     245                 :             :     // and nothing else.
     246         [ +  - ]:           1 :     std::string key2 = "key2";
     247         [ +  - ]:           1 :     std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database);
     248   [ +  -  +  -  :           2 :     BOOST_CHECK(batch2->Write(key2, value));
             +  -  +  - ]
     249                 :             :     // The first key must not exist
     250   [ +  -  +  -  :           2 :     BOOST_CHECK(!batch2->Exists(key));
                   +  - ]
     251         [ +  - ]:           3 : }
     252                 :             : 
     253   [ +  -  +  -  :           7 : BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     254                 :             : {
     255                 :           1 :     std::string key = "key";
     256         [ +  - ]:           1 :     std::string value = "value";
     257         [ +  - ]:           1 :     std::string value2 = "value_2";
     258                 :             : 
     259         [ +  - ]:           1 :     DatabaseOptions options;
     260                 :           1 :     DatabaseStatus status;
     261         [ +  - ]:           1 :     bilingual_str error;
     262   [ +  -  +  -  :           3 :     const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
                   +  - ]
     263                 :             : 
     264   [ +  -  +  - ]:           1 :     std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
     265                 :             : 
     266                 :             :     // Verify concurrent db transactions does not interfere between each other.
     267                 :             :     // Start db txn, write key and check the key does exist within the db txn.
     268   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->TxnBegin());
             +  -  +  - ]
     269   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Write(key, value));
             +  -  +  - ]
     270   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Exists(key));
             +  -  +  - ]
     271                 :             : 
     272                 :             :     // But, the same key, does not exist in another handler
     273   [ +  -  +  - ]:           1 :     std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch();
     274   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Exists(key));
             +  -  +  - ]
     275                 :             : 
     276                 :             :     // Attempt to commit the handler txn calling the handler2 methods.
     277                 :             :     // Which, must not be possible.
     278   [ +  -  +  -  :           2 :     BOOST_CHECK(!handler2->TxnCommit());
             +  -  +  - ]
     279   [ +  -  +  -  :           2 :     BOOST_CHECK(!handler2->TxnAbort());
             +  -  +  - ]
     280                 :             : 
     281                 :             :     // Only the first handler can commit the changes.
     282   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->TxnCommit());
             +  -  +  - ]
     283                 :             :     // And, once commit is completed, handler2 can read the record
     284         [ +  - ]:           1 :     std::string read_value;
     285   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Read(key, read_value));
             +  -  +  - ]
     286   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(read_value, value);
     287                 :             : 
     288                 :             :     // Also, once txn is committed, single write statements are re-enabled.
     289                 :             :     // Which means that handler2 can read the record changes directly.
     290   [ +  -  +  -  :           2 :     BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true));
             +  -  +  - ]
     291   [ +  -  +  -  :           2 :     BOOST_CHECK(handler2->Read(key, read_value));
             +  -  +  - ]
     292   [ +  -  +  - ]:           1 :     BOOST_CHECK_EQUAL(read_value, value2);
     293                 :           2 : }
     294                 :             : 
     295                 :             : BOOST_AUTO_TEST_SUITE_END()
     296                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1