dbwrapper_tests.cpp
1 // Copyright (c) 2012-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 <dbwrapper.h> 6 #include <test/util/common.h> 7 #include <test/util/random.h> 8 #include <test/util/setup_common.h> 9 #include <uint256.h> 10 #include <util/byte_units.h> 11 #include <util/string.h> 12 13 #include <memory> 14 #include <ranges> 15 16 #include <boost/test/unit_test.hpp> 17 18 using util::ToString; 19 20 BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup) 21 22 BOOST_AUTO_TEST_CASE(dbwrapper) 23 { 24 // Perform tests both obfuscated and non-obfuscated. 25 for (const bool obfuscate : {false, true}) { 26 constexpr size_t CACHE_SIZE{1_MiB}; 27 const fs::path path{m_args.GetDataDirBase() / "dbwrapper"}; 28 29 Obfuscation obfuscation; 30 std::vector<std::pair<uint8_t, uint256>> key_values{}; 31 32 // Write values 33 { 34 CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .wipe_data = true, .obfuscate = obfuscate}}; 35 BOOST_CHECK_EQUAL(obfuscate, !dbw.IsEmpty()); 36 37 // Ensure that we're doing real obfuscation when obfuscate=true 38 obfuscation = dbwrapper_private::GetObfuscation(dbw); 39 BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); 40 41 for (uint8_t k{0}; k < 10; ++k) { 42 uint8_t key{k}; 43 uint256 value{m_rng.rand256()}; 44 dbw.Write(key, value); 45 key_values.emplace_back(key, value); 46 } 47 } 48 49 // Verify that the obfuscation key is never obfuscated 50 { 51 CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = false}}; 52 BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw)); 53 } 54 55 // Read back the values 56 { 57 CDBWrapper dbw{{.path = path, .cache_bytes = CACHE_SIZE, .obfuscate = obfuscate}}; 58 59 // Ensure obfuscation is read back correctly 60 BOOST_CHECK_EQUAL(obfuscation, dbwrapper_private::GetObfuscation(dbw)); 61 BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); 62 63 // Verify all written values 64 for (const auto& [key, expected_value] : key_values) { 65 uint256 read_value{}; 66 BOOST_CHECK(dbw.Read(key, read_value)); 67 BOOST_CHECK_EQUAL(read_value, expected_value); 68 } 69 } 70 } 71 } 72 73 BOOST_AUTO_TEST_CASE(dbwrapper_basic_data) 74 { 75 // Perform tests both obfuscated and non-obfuscated. 76 for (bool obfuscate : {false, true}) { 77 fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_1_obfuscate_true" : "dbwrapper_1_obfuscate_false"); 78 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB, .memory_only = false, .wipe_data = true, .obfuscate = obfuscate}); 79 80 uint256 res; 81 uint32_t res_uint_32; 82 bool res_bool; 83 84 // Ensure that we're doing real obfuscation when obfuscate=true 85 BOOST_CHECK_EQUAL(obfuscate, dbwrapper_private::GetObfuscation(dbw)); 86 87 //Simulate block raw data - "b + block hash" 88 std::string key_block = "b" + m_rng.rand256().ToString(); 89 90 uint256 in_block = m_rng.rand256(); 91 dbw.Write(key_block, in_block); 92 BOOST_CHECK(dbw.Read(key_block, res)); 93 BOOST_CHECK_EQUAL(res.ToString(), in_block.ToString()); 94 95 //Simulate file raw data - "f + file_number" 96 std::string key_file = strprintf("f%04x", m_rng.rand32()); 97 98 uint256 in_file_info = m_rng.rand256(); 99 dbw.Write(key_file, in_file_info); 100 BOOST_CHECK(dbw.Read(key_file, res)); 101 BOOST_CHECK_EQUAL(res.ToString(), in_file_info.ToString()); 102 103 //Simulate transaction raw data - "t + transaction hash" 104 std::string key_transaction = "t" + m_rng.rand256().ToString(); 105 106 uint256 in_transaction = m_rng.rand256(); 107 dbw.Write(key_transaction, in_transaction); 108 BOOST_CHECK(dbw.Read(key_transaction, res)); 109 BOOST_CHECK_EQUAL(res.ToString(), in_transaction.ToString()); 110 111 //Simulate UTXO raw data - "c + transaction hash" 112 std::string key_utxo = "c" + m_rng.rand256().ToString(); 113 114 uint256 in_utxo = m_rng.rand256(); 115 dbw.Write(key_utxo, in_utxo); 116 BOOST_CHECK(dbw.Read(key_utxo, res)); 117 BOOST_CHECK_EQUAL(res.ToString(), in_utxo.ToString()); 118 119 //Simulate last block file number - "l" 120 uint8_t key_last_blockfile_number{'l'}; 121 uint32_t lastblockfilenumber = m_rng.rand32(); 122 dbw.Write(key_last_blockfile_number, lastblockfilenumber); 123 BOOST_CHECK(dbw.Read(key_last_blockfile_number, res_uint_32)); 124 BOOST_CHECK_EQUAL(lastblockfilenumber, res_uint_32); 125 126 //Simulate Is Reindexing - "R" 127 uint8_t key_IsReindexing{'R'}; 128 bool isInReindexing = m_rng.randbool(); 129 dbw.Write(key_IsReindexing, isInReindexing); 130 BOOST_CHECK(dbw.Read(key_IsReindexing, res_bool)); 131 BOOST_CHECK_EQUAL(isInReindexing, res_bool); 132 133 //Simulate last block hash up to which UXTO covers - 'B' 134 uint8_t key_lastblockhash_uxto{'B'}; 135 uint256 lastblock_hash = m_rng.rand256(); 136 dbw.Write(key_lastblockhash_uxto, lastblock_hash); 137 BOOST_CHECK(dbw.Read(key_lastblockhash_uxto, res)); 138 BOOST_CHECK_EQUAL(lastblock_hash, res); 139 140 //Simulate file raw data - "F + filename_number + filename" 141 std::string file_option_tag = "F"; 142 uint8_t filename_length = m_rng.randbits(8); 143 std::string filename = "randomfilename"; 144 std::string key_file_option = strprintf("%s%01x%s", file_option_tag, filename_length, filename); 145 146 bool in_file_bool = m_rng.randbool(); 147 dbw.Write(key_file_option, in_file_bool); 148 BOOST_CHECK(dbw.Read(key_file_option, res_bool)); 149 BOOST_CHECK_EQUAL(res_bool, in_file_bool); 150 } 151 } 152 153 // Test batch operations 154 BOOST_AUTO_TEST_CASE(dbwrapper_batch) 155 { 156 // Perform tests both obfuscated and non-obfuscated. 157 for (const bool obfuscate : {false, true}) { 158 fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_batch_obfuscate_true" : "dbwrapper_batch_obfuscate_false"); 159 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate}); 160 161 uint8_t key{'i'}; 162 uint256 in = m_rng.rand256(); 163 uint8_t key2{'j'}; 164 uint256 in2 = m_rng.rand256(); 165 uint8_t key3{'k'}; 166 uint256 in3 = m_rng.rand256(); 167 168 uint256 res; 169 CDBBatch batch(dbw); 170 171 batch.Write(key, in); 172 batch.Write(key2, in2); 173 batch.Write(key3, in3); 174 175 // Remove key3 before it's even been written 176 batch.Erase(key3); 177 178 dbw.WriteBatch(batch); 179 180 BOOST_CHECK(dbw.Read(key, res)); 181 BOOST_CHECK_EQUAL(res.ToString(), in.ToString()); 182 BOOST_CHECK(dbw.Read(key2, res)); 183 BOOST_CHECK_EQUAL(res.ToString(), in2.ToString()); 184 185 // key3 should've never been written 186 BOOST_CHECK(dbw.Read(key3, res) == false); 187 } 188 } 189 190 BOOST_AUTO_TEST_CASE(dbwrapper_iterator) 191 { 192 // Perform tests both obfuscated and non-obfuscated. 193 for (const bool obfuscate : {false, true}) { 194 fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_iterator_obfuscate_true" : "dbwrapper_iterator_obfuscate_false"); 195 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate}); 196 197 // The two keys are intentionally chosen for ordering 198 uint8_t key{'j'}; 199 uint256 in = m_rng.rand256(); 200 dbw.Write(key, in); 201 uint8_t key2{'k'}; 202 uint256 in2 = m_rng.rand256(); 203 dbw.Write(key2, in2); 204 205 std::unique_ptr<CDBIterator> it(dbw.NewIterator()); 206 207 // Be sure to seek past the obfuscation key (if it exists) 208 it->Seek(key); 209 210 // A failed key decode must not consume the current iterator entry. 211 uint16_t key_too_large{0}; 212 BOOST_CHECK(!it->GetKey(key_too_large)); 213 214 uint8_t key_res; 215 uint256 val_res; 216 217 BOOST_REQUIRE(it->GetKey(key_res)); 218 BOOST_REQUIRE(it->GetValue(val_res)); 219 BOOST_CHECK_EQUAL(key_res, key); 220 BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString()); 221 222 it->Next(); 223 224 BOOST_REQUIRE(it->GetKey(key_res)); 225 BOOST_REQUIRE(it->GetValue(val_res)); 226 BOOST_CHECK_EQUAL(key_res, key2); 227 BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString()); 228 229 it->Next(); 230 BOOST_CHECK_EQUAL(it->Valid(), false); 231 } 232 } 233 234 // Test that we do not obfuscation if there is existing data. 235 BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) 236 { 237 // We're going to share this fs::path between two wrappers 238 fs::path ph = m_args.GetDataDirBase() / "existing_data_no_obfuscate"; 239 fs::create_directories(ph); 240 241 // Set up a non-obfuscated wrapper to write some initial data. 242 std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false}); 243 uint8_t key{'k'}; 244 uint256 in = m_rng.rand256(); 245 uint256 res; 246 247 dbw->Write(key, in); 248 BOOST_CHECK(dbw->Read(key, res)); 249 BOOST_CHECK_EQUAL(res.ToString(), in.ToString()); 250 251 // Call the destructor to free leveldb LOCK 252 dbw.reset(); 253 254 // Now, set up another wrapper that wants to obfuscate the same directory 255 CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = true}); 256 257 // Check that the key/val we wrote with unobfuscated wrapper exists and 258 // is readable. 259 uint256 res2; 260 BOOST_CHECK(odbw.Read(key, res2)); 261 BOOST_CHECK_EQUAL(res2.ToString(), in.ToString()); 262 263 BOOST_CHECK(!odbw.IsEmpty()); 264 BOOST_CHECK(!dbwrapper_private::GetObfuscation(odbw)); // The key should be an empty string 265 266 uint256 in2 = m_rng.rand256(); 267 uint256 res3; 268 269 // Check that we can write successfully 270 odbw.Write(key, in2); 271 BOOST_CHECK(odbw.Read(key, res3)); 272 BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString()); 273 } 274 275 // Ensure that we start obfuscating during a reindex. 276 BOOST_AUTO_TEST_CASE(existing_data_reindex) 277 { 278 // We're going to share this fs::path between two wrappers 279 fs::path ph = m_args.GetDataDirBase() / "existing_data_reindex"; 280 fs::create_directories(ph); 281 282 // Set up a non-obfuscated wrapper to write some initial data. 283 std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false}); 284 uint8_t key{'k'}; 285 uint256 in = m_rng.rand256(); 286 uint256 res; 287 288 dbw->Write(key, in); 289 BOOST_CHECK(dbw->Read(key, res)); 290 BOOST_CHECK_EQUAL(res.ToString(), in.ToString()); 291 292 // Call the destructor to free leveldb LOCK 293 dbw.reset(); 294 295 // Simulate a -reindex by wiping the existing data store 296 CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = true, .obfuscate = true}); 297 298 // Check that the key/val we wrote with unobfuscated wrapper doesn't exist 299 uint256 res2; 300 BOOST_CHECK(!odbw.Read(key, res2)); 301 BOOST_CHECK(dbwrapper_private::GetObfuscation(odbw)); 302 303 uint256 in2 = m_rng.rand256(); 304 uint256 res3; 305 306 // Check that we can write successfully 307 odbw.Write(key, in2); 308 BOOST_CHECK(odbw.Read(key, res3)); 309 BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString()); 310 } 311 312 BOOST_AUTO_TEST_CASE(iterator_ordering) 313 { 314 fs::path ph = m_args.GetDataDirBase() / "iterator_ordering"; 315 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB, .memory_only = true, .wipe_data = false, .obfuscate = false}); 316 for (int x=0x00; x<256; ++x) { 317 uint8_t key = x; 318 uint32_t value = x*x; 319 if (!(x & 1)) dbw.Write(key, value); 320 } 321 322 // Check that creating an iterator creates a snapshot 323 std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper&>(dbw).NewIterator()); 324 325 for (unsigned int x=0x00; x<256; ++x) { 326 uint8_t key = x; 327 uint32_t value = x*x; 328 if (x & 1) dbw.Write(key, value); 329 } 330 331 for (const int seek_start : {0x00, 0x80}) { 332 it->Seek((uint8_t)seek_start); 333 for (unsigned int x=seek_start; x<255; ++x) { 334 uint8_t key; 335 uint32_t value; 336 BOOST_CHECK(it->Valid()); 337 if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure 338 break; 339 BOOST_CHECK(it->GetKey(key)); 340 if (x & 1) { 341 BOOST_CHECK_EQUAL(key, x + 1); 342 continue; 343 } 344 BOOST_CHECK(it->GetValue(value)); 345 BOOST_CHECK_EQUAL(key, x); 346 BOOST_CHECK_EQUAL(value, x*x); 347 it->Next(); 348 } 349 BOOST_CHECK(!it->Valid()); 350 } 351 } 352 353 struct StringContentsSerializer { 354 // Used to make two serialized objects the same while letting them have different lengths 355 // This is a terrible idea 356 std::string str; 357 StringContentsSerializer() = default; 358 explicit StringContentsSerializer(const std::string& inp) : str(inp) {} 359 360 template<typename Stream> 361 void Serialize(Stream& s) const 362 { 363 for (size_t i = 0; i < str.size(); i++) { 364 s << uint8_t(str[i]); 365 } 366 } 367 368 template<typename Stream> 369 void Unserialize(Stream& s) 370 { 371 str.clear(); 372 uint8_t c{0}; 373 while (!s.empty()) { 374 s >> c; 375 str.push_back(c); 376 } 377 } 378 }; 379 380 BOOST_AUTO_TEST_CASE(iterator_string_ordering) 381 { 382 fs::path ph = m_args.GetDataDirBase() / "iterator_string_ordering"; 383 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB, .memory_only = true, .wipe_data = false, .obfuscate = false}); 384 for (int x = 0; x < 10; ++x) { 385 for (int y = 0; y < 10; ++y) { 386 std::string key{ToString(x)}; 387 for (int z = 0; z < y; ++z) 388 key += key; 389 uint32_t value = x*x; 390 dbw.Write(StringContentsSerializer{key}, value); 391 } 392 } 393 394 std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper&>(dbw).NewIterator()); 395 for (const int seek_start : {0, 5}) { 396 it->Seek(StringContentsSerializer{ToString(seek_start)}); 397 for (unsigned int x = seek_start; x < 10; ++x) { 398 for (int y = 0; y < 10; ++y) { 399 std::string exp_key{ToString(x)}; 400 for (int z = 0; z < y; ++z) 401 exp_key += exp_key; 402 StringContentsSerializer key; 403 uint32_t value; 404 BOOST_CHECK(it->Valid()); 405 if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure 406 break; 407 BOOST_CHECK(it->GetKey(key)); 408 BOOST_CHECK(it->GetValue(value)); 409 BOOST_CHECK_EQUAL(key.str, exp_key); 410 BOOST_CHECK_EQUAL(value, x*x); 411 it->Next(); 412 } 413 } 414 BOOST_CHECK(!it->Valid()); 415 } 416 } 417 418 BOOST_AUTO_TEST_CASE(unicodepath) 419 { 420 // Attempt to create a database with a UTF8 character in the path. 421 // On Windows this test will fail if the directory is created using 422 // the ANSI CreateDirectoryA call and the code page isn't UTF8. 423 // It will succeed if created with CreateDirectoryW. 424 fs::path ph = m_args.GetDataDirBase() / "test_runner_₿_🏃_20191128_104644"; 425 CDBWrapper dbw({.path = ph, .cache_bytes = 1_MiB}); 426 427 fs::path lockPath = ph / "LOCK"; 428 BOOST_CHECK(fs::exists(lockPath)); 429 } 430 431 432 BOOST_AUTO_TEST_SUITE_END()