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