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