Bitcoin Core 29.99.0
P2P Digital Currency
db_tests.cpp
Go to the documentation of this file.
1// Copyright (c) 2018-present 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 <bitcoin-build-config.h> // IWYU pragma: keep
6
7#include <boost/test/unit_test.hpp>
8
10#include <util/check.h>
11#include <util/fs.h>
12#include <util/translation.h>
13#ifdef USE_BDB
14#include <wallet/bdb.h>
15#endif
16#include <wallet/sqlite.h>
17#include <wallet/migrate.h>
18#include <wallet/test/util.h>
19#include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
20
21#include <fstream>
22#include <memory>
23#include <string>
24
25inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
26{
27 std::span key{kv.first}, value{kv.second};
28 os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \""
29 << std::string_view{reinterpret_cast<const char*>(value.data()), value.size()} << "\")";
30 return os;
31}
32
33namespace wallet {
34
35inline std::span<const std::byte> StringBytes(std::string_view str)
36{
37 return std::as_bytes(std::span{str});
38}
39
40static SerializeData StringData(std::string_view str)
41{
42 auto bytes = StringBytes(str);
43 return SerializeData{bytes.begin(), bytes.end()};
44}
45
46static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected)
47{
48 std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
49 MockableData actual;
50 while (true) {
51 DataStream key, value;
52 DatabaseCursor::Status status = cursor->Next(key, value);
53 if (status == DatabaseCursor::Status::DONE) break;
56 actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
57 }
58 BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
59}
60
62
63#ifdef USE_BDB
64static std::shared_ptr<BerkeleyEnvironment> GetWalletEnv(const fs::path& path, fs::path& database_filename)
65{
66 fs::path data_file = BDBDataFile(path);
67 database_filename = data_file.filename();
68 return GetBerkeleyEnv(data_file.parent_path(), false);
69}
70
71BOOST_AUTO_TEST_CASE(getwalletenv_file)
72{
73 fs::path test_name = "test_name.dat";
74 const fs::path datadir = m_args.GetDataDirNet();
75 fs::path file_path = datadir / test_name;
76 std::ofstream f{file_path};
77 f.close();
78
79 fs::path filename;
80 std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(file_path, filename);
81 BOOST_CHECK_EQUAL(filename, test_name);
82 BOOST_CHECK_EQUAL(env->Directory(), datadir);
83}
84
85BOOST_AUTO_TEST_CASE(getwalletenv_directory)
86{
87 fs::path expected_name = "wallet.dat";
88 const fs::path datadir = m_args.GetDataDirNet();
89
90 fs::path filename;
91 std::shared_ptr<BerkeleyEnvironment> env = GetWalletEnv(datadir, filename);
92 BOOST_CHECK_EQUAL(filename, expected_name);
93 BOOST_CHECK_EQUAL(env->Directory(), datadir);
94}
95
96BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_multiple)
97{
98 fs::path datadir = m_args.GetDataDirNet() / "1";
99 fs::path datadir_2 = m_args.GetDataDirNet() / "2";
100 fs::path filename;
101
102 std::shared_ptr<BerkeleyEnvironment> env_1 = GetWalletEnv(datadir, filename);
103 std::shared_ptr<BerkeleyEnvironment> env_2 = GetWalletEnv(datadir, filename);
104 std::shared_ptr<BerkeleyEnvironment> env_3 = GetWalletEnv(datadir_2, filename);
105
106 BOOST_CHECK(env_1 == env_2);
107 BOOST_CHECK(env_2 != env_3);
108}
109
110BOOST_AUTO_TEST_CASE(getwalletenv_g_dbenvs_free_instance)
111{
112 fs::path datadir = gArgs.GetDataDirNet() / "1";
113 fs::path datadir_2 = gArgs.GetDataDirNet() / "2";
114 fs::path filename;
115
116 std::shared_ptr <BerkeleyEnvironment> env_1_a = GetWalletEnv(datadir, filename);
117 std::shared_ptr <BerkeleyEnvironment> env_2_a = GetWalletEnv(datadir_2, filename);
118 env_1_a.reset();
119
120 std::shared_ptr<BerkeleyEnvironment> env_1_b = GetWalletEnv(datadir, filename);
121 std::shared_ptr<BerkeleyEnvironment> env_2_b = GetWalletEnv(datadir_2, filename);
122
123 BOOST_CHECK(env_1_a != env_1_b);
124 BOOST_CHECK(env_2_a == env_2_b);
125}
126#endif
127
128static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
129{
130 std::vector<std::unique_ptr<WalletDatabase>> dbs;
131 DatabaseOptions options;
132 DatabaseStatus status;
133 bilingual_str error;
134#ifdef USE_BDB
135 dbs.emplace_back(MakeBerkeleyDatabase(path_root / "bdb", options, status, error));
136 // Needs BDB to make the DB to read
137 dbs.emplace_back(std::make_unique<BerkeleyRODatabase>(BDBDataFile(path_root / "bdb"), /*open=*/false));
138#endif
139 dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
140 dbs.emplace_back(CreateMockableWalletDatabase());
141 return dbs;
142}
143
144BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
145{
146 // Test each supported db
147 for (const auto& database : TestDatabases(m_path_root)) {
148 std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
149
150 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
151 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
152 // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
153 database->Open();
154 } else {
155 // Write elements to it if not berkeleyro
156 for (unsigned int i = 0; i < 10; i++) {
157 for (const auto& prefix : prefixes) {
158 BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
159 }
160 }
161 }
162
163 // Now read all the items by prefix and verify that each element gets parsed correctly
164 for (const auto& prefix : prefixes) {
165 DataStream s_prefix;
166 s_prefix << prefix;
167 std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix);
168 DataStream key;
169 DataStream value;
170 for (int i = 0; i < 10; i++) {
171 DatabaseCursor::Status status = cursor->Next(key, value);
173
174 std::string key_back;
175 unsigned int i_back;
176 key >> key_back >> i_back;
177 BOOST_CHECK_EQUAL(key_back, prefix);
178
179 unsigned int value_back;
180 value >> value_back;
181 BOOST_CHECK_EQUAL(value_back, i_back);
182 }
183
184 // Let's now read it once more, it should return DONE
185 BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
186 }
187 handler.reset();
188 database->Close();
189 }
190}
191
192// Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't
193// covered in the higher level test above. The higher level test uses
194// serialized strings which are prefixed with string length, so it doesn't test
195// truly empty prefixes or prefixes that begin with \xff
196BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
197{
198 const MockableData::value_type
199 e{StringData(""), StringData("e")},
200 p{StringData("prefix"), StringData("p")},
201 ps{StringData("prefixsuffix"), StringData("ps")},
202 f{StringData("\xff"), StringData("f")},
203 fs{StringData("\xffsuffix"), StringData("fs")},
204 ff{StringData("\xff\xff"), StringData("ff")},
205 ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
206 for (const auto& database : TestDatabases(m_path_root)) {
207 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
208
209 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
210 // For BerkeleyRO, open the file now. This must happen after BDB has written to the file
211 database->Open();
212 } else {
213 // Write elements to it if not berkeleyro
214 for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
215 batch->Write(std::span{k}, std::span{v});
216 }
217 }
218
219 CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
220 CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
221 CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
222 CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
223 batch.reset();
224 database->Close();
225 }
226}
227
228BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
229{
230 // Ensures the database remains accessible without deadlocking after a write error.
231 // To simulate the behavior, record overwrites are disallowed, and the test verifies
232 // that the database remains active after failing to store an existing record.
233 for (const auto& database : TestDatabases(m_path_root)) {
234 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
235 // Skip this test if BerkeleyRO
236 continue;
237 }
238 // Write original record
239 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
240 std::string key = "key";
241 std::string value = "value";
242 std::string value2 = "value_2";
243 BOOST_CHECK(batch->Write(key, value));
244 // Attempt to overwrite the record (expect failure)
245 BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false));
246 // Successfully overwrite the record
247 BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true));
248 // Sanity-check; read and verify the overwritten value
249 std::string read_value;
250 BOOST_CHECK(batch->Read(key, read_value));
251 BOOST_CHECK_EQUAL(read_value, value2);
252 }
253}
254
255// Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
256// Keys are in the form of std::pair<TYPE, ENTRY_ID>
258{
259 const std::string key = "key";
260 const std::string key2 = "key2";
261 const std::string value = "value";
262 const std::string value2 = "value_2";
263 auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
264
265 for (const auto& database : TestDatabases(m_path_root)) {
266 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
267 // Skip this test if BerkeleyRO
268 continue;
269 }
270 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
271
272 // Write two entries with the same key type prefix, a third one with a different prefix
273 // and a fourth one with the type-id values inverted
274 BOOST_CHECK(batch->Write(make_key(key, value), value));
275 BOOST_CHECK(batch->Write(make_key(key, value2), value2));
276 BOOST_CHECK(batch->Write(make_key(key2, value), value));
277 BOOST_CHECK(batch->Write(make_key(value, key), value));
278
279 // Erase the ones with the same prefix and verify result
280 BOOST_CHECK(batch->TxnBegin());
281 BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
282 BOOST_CHECK(batch->TxnCommit());
283
284 BOOST_CHECK(!batch->Exists(make_key(key, value)));
285 BOOST_CHECK(!batch->Exists(make_key(key, value2)));
286 // Also verify that entries with a different prefix were not erased
287 BOOST_CHECK(batch->Exists(make_key(key2, value)));
288 BOOST_CHECK(batch->Exists(make_key(value, key)));
289 }
290}
291
292// Test-only statement execution error
293constexpr int TEST_SQLITE_ERROR = -999;
294
296{
297private:
299 std::set<std::string> m_blocked_statements;
300public:
301 DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {}
302 int Exec(SQLiteDatabase& database, const std::string& statement) override {
303 if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR;
304 return m_base_exec.Exec(database, statement);
305 }
306};
307
308BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn)
309{
310 // Verifies that there is no active dangling, to-be-reversed db txn
311 // after the batch object that initiated it is destroyed.
312 DatabaseOptions options;
313 DatabaseStatus status;
314 bilingual_str error;
315 std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
316
317 std::string key = "key";
318 std::string value = "value";
319
320 std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database);
321 BOOST_CHECK(batch->TxnBegin());
322 BOOST_CHECK(batch->Write(key, value));
323 // Set a handler to prevent txn abortion during destruction.
324 // Mimicking a db statement execution failure.
325 batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"}));
326 // Destroy batch
327 batch.reset();
328
329 // Ensure there is no dangling, to-be-reversed db txn
330 BOOST_CHECK(!database->HasActiveTxn());
331
332 // And, just as a sanity check; verify that new batchs only write what they suppose to write
333 // and nothing else.
334 std::string key2 = "key2";
335 std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database);
336 BOOST_CHECK(batch2->Write(key2, value));
337 // The first key must not exist
338 BOOST_CHECK(!batch2->Exists(key));
339}
340
341BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere)
342{
343 std::string key = "key";
344 std::string value = "value";
345 std::string value2 = "value_2";
346
347 DatabaseOptions options;
348 DatabaseStatus status;
349 bilingual_str error;
350 const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
351
352 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
353
354 // Verify concurrent db transactions does not interfere between each other.
355 // Start db txn, write key and check the key does exist within the db txn.
356 BOOST_CHECK(handler->TxnBegin());
357 BOOST_CHECK(handler->Write(key, value));
358 BOOST_CHECK(handler->Exists(key));
359
360 // But, the same key, does not exist in another handler
361 std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch();
362 BOOST_CHECK(handler2->Exists(key));
363
364 // Attempt to commit the handler txn calling the handler2 methods.
365 // Which, must not be possible.
366 BOOST_CHECK(!handler2->TxnCommit());
367 BOOST_CHECK(!handler2->TxnAbort());
368
369 // Only the first handler can commit the changes.
370 BOOST_CHECK(handler->TxnCommit());
371 // And, once commit is completed, handler2 can read the record
372 std::string read_value;
373 BOOST_CHECK(handler2->Read(key, read_value));
374 BOOST_CHECK_EQUAL(read_value, value);
375
376 // Also, once txn is committed, single write statements are re-enabled.
377 // Which means that handler2 can read the record changes directly.
378 BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true));
379 BOOST_CHECK(handler2->Read(key, read_value));
380 BOOST_CHECK_EQUAL(read_value, value2);
381}
382
384} // namespace wallet
ArgsManager gArgs
Definition: args.cpp:42
#define Assert(val)
Identity function.
Definition: check.h:85
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:234
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:147
const_iterator begin() const
Definition: streams.h:177
const_iterator end() const
Definition: streams.h:179
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:33
path filename() const
Definition: fs.h:72
A class representing a BerkeleyDB file from which we can only read records.
Definition: migrate.h:21
RAII class that provides access to a WalletDatabase.
Definition: db.h:51
virtual std::unique_ptr< DatabaseCursor > GetNewPrefixCursor(std::span< const std::byte > prefix)=0
SQliteExecHandler m_base_exec
Definition: db_tests.cpp:298
DbExecBlocker(std::set< std::string > blocked_statements)
Definition: db_tests.cpp:301
int Exec(SQLiteDatabase &database, const std::string &statement) override
Definition: db_tests.cpp:302
std::set< std::string > m_blocked_statements
Definition: db_tests.cpp:299
An instance of this class represents one SQLite3 database.
Definition: sqlite.h:104
Class responsible for executing SQL statements in SQLite databases.
Definition: sqlite.h:42
virtual int Exec(SQLiteDatabase &database, const std::string &statement)
Definition: sqlite.cpp:388
BOOST_FIXTURE_TEST_SUITE(cuckoocache_tests, BasicTestingSetup)
Test Suite for CuckooCache.
BOOST_AUTO_TEST_SUITE_END()
std::ostream & operator<<(std::ostream &os, const std::pair< const SerializeData, SerializeData > &kv)
Definition: db_tests.cpp:25
Filesystem operations and types.
std::unique_ptr< BerkeleyDatabase > MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Return object giving access to Berkeley database at specified path.
Definition: bdb.cpp:948
static SerializeData StringData(std::string_view str)
Definition: db_tests.cpp:40
std::unique_ptr< SQLiteDatabase > MakeSQLiteDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: sqlite.cpp:694
fs::path BDBDataFile(const fs::path &wallet_path)
Definition: db.cpp:76
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
Definition: util.cpp:186
static std::vector< std::unique_ptr< WalletDatabase > > TestDatabases(const fs::path &path_root)
Definition: db_tests.cpp:128
std::shared_ptr< BerkeleyEnvironment > GetBerkeleyEnv(const fs::path &env_directory, bool use_shared_memory)
Get BerkeleyEnvironment given a directory path.
Definition: bdb.cpp:81
static void CheckPrefix(DatabaseBatch &batch, std::span< const std::byte > prefix, MockableData expected)
Definition: db_tests.cpp:46
std::span< const std::byte > StringBytes(std::string_view str)
Definition: db_tests.cpp:35
BOOST_AUTO_TEST_CASE(bnb_search_test)
std::map< SerializeData, SerializeData, std::less<> > MockableData
Definition: util.h:52
constexpr int TEST_SQLITE_ERROR
Definition: db_tests.cpp:293
DatabaseStatus
Definition: db.h:205
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
const char * prefix
Definition: rest.cpp:1009
bool(* handler)(const std::any &context, HTTPRequest *req, const std::string &strReq)
Definition: rest.cpp:1010
Basic testing setup.
Definition: setup_common.h:64
Bilingual messages:
Definition: translation.h:24
std::vector< std::byte, zero_after_free_allocator< std::byte > > SerializeData
Byte-vector that clears its contents before deletion.
Definition: zeroafterfree.h:49