Files
bitcoin/src/test/dbwrapper_tests.cpp
Ryan Ofsky 4f74c59334 util: Move util/string.h functions to util namespace
There are no changes to behavior. Changes in this commit are all additions, and
are easiest to review using "git diff -U0 --word-diff-regex=." options.

Motivation for this change is to keep util functions with really generic names
like "Split" and "Join" out of the global namespace so it is easier to see
where these functions are defined, and so they don't interfere with function
overloading, especially since the util library is a dependency of the kernel
library and intended to be used with external code.
2024-05-16 10:16:08 -05:00

404 lines
15 KiB
C++

// Copyright (c) 2012-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <dbwrapper.h>
#include <test/util/random.h>
#include <test/util/setup_common.h>
#include <uint256.h>
#include <util/string.h>
#include <memory>
#include <boost/test/unit_test.hpp>
using util::ToString;
// Test if a string consists entirely of null characters
static bool is_null_key(const std::vector<unsigned char>& key) {
bool isnull = true;
for (unsigned int i = 0; i < key.size(); i++)
isnull &= (key[i] == '\x00');
return isnull;
}
BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(dbwrapper)
{
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_obfuscate_true" : "dbwrapper_obfuscate_false");
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate});
uint8_t key{'k'};
uint256 in = InsecureRand256();
uint256 res;
// Ensure that we're doing real obfuscation when obfuscate=true
BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
BOOST_CHECK(dbw.Write(key, in));
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
}
}
BOOST_AUTO_TEST_CASE(dbwrapper_basic_data)
{
// Perform tests both obfuscated and non-obfuscated.
for (bool obfuscate : {false, true}) {
fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_1_obfuscate_true" : "dbwrapper_1_obfuscate_false");
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = false, .wipe_data = true, .obfuscate = obfuscate});
uint256 res;
uint32_t res_uint_32;
bool res_bool;
// Ensure that we're doing real obfuscation when obfuscate=true
BOOST_CHECK(obfuscate != is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
//Simulate block raw data - "b + block hash"
std::string key_block = "b" + InsecureRand256().ToString();
uint256 in_block = InsecureRand256();
BOOST_CHECK(dbw.Write(key_block, in_block));
BOOST_CHECK(dbw.Read(key_block, res));
BOOST_CHECK_EQUAL(res.ToString(), in_block.ToString());
//Simulate file raw data - "f + file_number"
std::string key_file = strprintf("f%04x", InsecureRand32());
uint256 in_file_info = InsecureRand256();
BOOST_CHECK(dbw.Write(key_file, in_file_info));
BOOST_CHECK(dbw.Read(key_file, res));
BOOST_CHECK_EQUAL(res.ToString(), in_file_info.ToString());
//Simulate transaction raw data - "t + transaction hash"
std::string key_transaction = "t" + InsecureRand256().ToString();
uint256 in_transaction = InsecureRand256();
BOOST_CHECK(dbw.Write(key_transaction, in_transaction));
BOOST_CHECK(dbw.Read(key_transaction, res));
BOOST_CHECK_EQUAL(res.ToString(), in_transaction.ToString());
//Simulate UTXO raw data - "c + transaction hash"
std::string key_utxo = "c" + InsecureRand256().ToString();
uint256 in_utxo = InsecureRand256();
BOOST_CHECK(dbw.Write(key_utxo, in_utxo));
BOOST_CHECK(dbw.Read(key_utxo, res));
BOOST_CHECK_EQUAL(res.ToString(), in_utxo.ToString());
//Simulate last block file number - "l"
uint8_t key_last_blockfile_number{'l'};
uint32_t lastblockfilenumber = InsecureRand32();
BOOST_CHECK(dbw.Write(key_last_blockfile_number, lastblockfilenumber));
BOOST_CHECK(dbw.Read(key_last_blockfile_number, res_uint_32));
BOOST_CHECK_EQUAL(lastblockfilenumber, res_uint_32);
//Simulate Is Reindexing - "R"
uint8_t key_IsReindexing{'R'};
bool isInReindexing = InsecureRandBool();
BOOST_CHECK(dbw.Write(key_IsReindexing, isInReindexing));
BOOST_CHECK(dbw.Read(key_IsReindexing, res_bool));
BOOST_CHECK_EQUAL(isInReindexing, res_bool);
//Simulate last block hash up to which UXTO covers - 'B'
uint8_t key_lastblockhash_uxto{'B'};
uint256 lastblock_hash = InsecureRand256();
BOOST_CHECK(dbw.Write(key_lastblockhash_uxto, lastblock_hash));
BOOST_CHECK(dbw.Read(key_lastblockhash_uxto, res));
BOOST_CHECK_EQUAL(lastblock_hash, res);
//Simulate file raw data - "F + filename_number + filename"
std::string file_option_tag = "F";
uint8_t filename_length = InsecureRandBits(8);
std::string filename = "randomfilename";
std::string key_file_option = strprintf("%s%01x%s", file_option_tag,filename_length,filename);
bool in_file_bool = InsecureRandBool();
BOOST_CHECK(dbw.Write(key_file_option, in_file_bool));
BOOST_CHECK(dbw.Read(key_file_option, res_bool));
BOOST_CHECK_EQUAL(res_bool, in_file_bool);
}
}
// Test batch operations
BOOST_AUTO_TEST_CASE(dbwrapper_batch)
{
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_batch_obfuscate_true" : "dbwrapper_batch_obfuscate_false");
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate});
uint8_t key{'i'};
uint256 in = InsecureRand256();
uint8_t key2{'j'};
uint256 in2 = InsecureRand256();
uint8_t key3{'k'};
uint256 in3 = InsecureRand256();
uint256 res;
CDBBatch batch(dbw);
batch.Write(key, in);
batch.Write(key2, in2);
batch.Write(key3, in3);
// Remove key3 before it's even been written
batch.Erase(key3);
BOOST_CHECK(dbw.WriteBatch(batch));
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
BOOST_CHECK(dbw.Read(key2, res));
BOOST_CHECK_EQUAL(res.ToString(), in2.ToString());
// key3 should've never been written
BOOST_CHECK(dbw.Read(key3, res) == false);
}
}
BOOST_AUTO_TEST_CASE(dbwrapper_iterator)
{
// Perform tests both obfuscated and non-obfuscated.
for (const bool obfuscate : {false, true}) {
fs::path ph = m_args.GetDataDirBase() / (obfuscate ? "dbwrapper_iterator_obfuscate_true" : "dbwrapper_iterator_obfuscate_false");
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = obfuscate});
// The two keys are intentionally chosen for ordering
uint8_t key{'j'};
uint256 in = InsecureRand256();
BOOST_CHECK(dbw.Write(key, in));
uint8_t key2{'k'};
uint256 in2 = InsecureRand256();
BOOST_CHECK(dbw.Write(key2, in2));
std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper&>(dbw).NewIterator());
// Be sure to seek past the obfuscation key (if it exists)
it->Seek(key);
uint8_t key_res;
uint256 val_res;
BOOST_REQUIRE(it->GetKey(key_res));
BOOST_REQUIRE(it->GetValue(val_res));
BOOST_CHECK_EQUAL(key_res, key);
BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString());
it->Next();
BOOST_REQUIRE(it->GetKey(key_res));
BOOST_REQUIRE(it->GetValue(val_res));
BOOST_CHECK_EQUAL(key_res, key2);
BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString());
it->Next();
BOOST_CHECK_EQUAL(it->Valid(), false);
}
}
// Test that we do not obfuscation if there is existing data.
BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate)
{
// We're going to share this fs::path between two wrappers
fs::path ph = m_args.GetDataDirBase() / "existing_data_no_obfuscate";
fs::create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false});
uint8_t key{'k'};
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Now, set up another wrapper that wants to obfuscate the same directory
CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = true});
// Check that the key/val we wrote with unobfuscated wrapper exists and
// is readable.
uint256 res2;
BOOST_CHECK(odbw.Read(key, res2));
BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());
BOOST_CHECK(!odbw.IsEmpty()); // There should be existing data
BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscateKey(odbw))); // The key should be an empty string
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
// Ensure that we start obfuscating during a reindex.
BOOST_AUTO_TEST_CASE(existing_data_reindex)
{
// We're going to share this fs::path between two wrappers
fs::path ph = m_args.GetDataDirBase() / "existing_data_reindex";
fs::create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw = std::make_unique<CDBWrapper>(DBParams{.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = false, .obfuscate = false});
uint8_t key{'k'};
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Simulate a -reindex by wiping the existing data store
CDBWrapper odbw({.path = ph, .cache_bytes = 1 << 10, .memory_only = false, .wipe_data = true, .obfuscate = true});
// Check that the key/val we wrote with unobfuscated wrapper doesn't exist
uint256 res2;
BOOST_CHECK(!odbw.Read(key, res2));
BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
BOOST_AUTO_TEST_CASE(iterator_ordering)
{
fs::path ph = m_args.GetDataDirBase() / "iterator_ordering";
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = false});
for (int x=0x00; x<256; ++x) {
uint8_t key = x;
uint32_t value = x*x;
if (!(x & 1)) BOOST_CHECK(dbw.Write(key, value));
}
// Check that creating an iterator creates a snapshot
std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper&>(dbw).NewIterator());
for (unsigned int x=0x00; x<256; ++x) {
uint8_t key = x;
uint32_t value = x*x;
if (x & 1) BOOST_CHECK(dbw.Write(key, value));
}
for (const int seek_start : {0x00, 0x80}) {
it->Seek((uint8_t)seek_start);
for (unsigned int x=seek_start; x<255; ++x) {
uint8_t key;
uint32_t value;
BOOST_CHECK(it->Valid());
if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure
break;
BOOST_CHECK(it->GetKey(key));
if (x & 1) {
BOOST_CHECK_EQUAL(key, x + 1);
continue;
}
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key, x);
BOOST_CHECK_EQUAL(value, x*x);
it->Next();
}
BOOST_CHECK(!it->Valid());
}
}
struct StringContentsSerializer {
// Used to make two serialized objects the same while letting them have different lengths
// This is a terrible idea
std::string str;
StringContentsSerializer() = default;
explicit StringContentsSerializer(const std::string& inp) : str(inp) {}
template<typename Stream>
void Serialize(Stream& s) const
{
for (size_t i = 0; i < str.size(); i++) {
s << uint8_t(str[i]);
}
}
template<typename Stream>
void Unserialize(Stream& s)
{
str.clear();
uint8_t c{0};
while (!s.eof()) {
s >> c;
str.push_back(c);
}
}
};
BOOST_AUTO_TEST_CASE(iterator_string_ordering)
{
fs::path ph = m_args.GetDataDirBase() / "iterator_string_ordering";
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20, .memory_only = true, .wipe_data = false, .obfuscate = false});
for (int x = 0; x < 10; ++x) {
for (int y = 0; y < 10; ++y) {
std::string key{ToString(x)};
for (int z = 0; z < y; ++z)
key += key;
uint32_t value = x*x;
BOOST_CHECK(dbw.Write(StringContentsSerializer{key}, value));
}
}
std::unique_ptr<CDBIterator> it(const_cast<CDBWrapper&>(dbw).NewIterator());
for (const int seek_start : {0, 5}) {
it->Seek(StringContentsSerializer{ToString(seek_start)});
for (unsigned int x = seek_start; x < 10; ++x) {
for (int y = 0; y < 10; ++y) {
std::string exp_key{ToString(x)};
for (int z = 0; z < y; ++z)
exp_key += exp_key;
StringContentsSerializer key;
uint32_t value;
BOOST_CHECK(it->Valid());
if (!it->Valid()) // Avoid spurious errors about invalid iterator's key and value in case of failure
break;
BOOST_CHECK(it->GetKey(key));
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key.str, exp_key);
BOOST_CHECK_EQUAL(value, x*x);
it->Next();
}
}
BOOST_CHECK(!it->Valid());
}
}
BOOST_AUTO_TEST_CASE(unicodepath)
{
// Attempt to create a database with a UTF8 character in the path.
// On Windows this test will fail if the directory is created using
// the ANSI CreateDirectoryA call and the code page isn't UTF8.
// It will succeed if created with CreateDirectoryW.
fs::path ph = m_args.GetDataDirBase() / "test_runner_₿_🏃_20191128_104644";
CDBWrapper dbw({.path = ph, .cache_bytes = 1 << 20});
fs::path lockPath = ph / "LOCK";
BOOST_CHECK(fs::exists(lockPath));
}
BOOST_AUTO_TEST_SUITE_END()