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