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