/ src / wallet / test / db_tests.cpp
db_tests.cpp
  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_SUITE_END()
301  } // namespace wallet