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