/ src / test / dbwrapper_tests.cpp
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()