db_tests.cpp raw

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