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