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 <boost/test/unit_test.hpp>
6
8#include <util/check.h>
9#include <util/fs.h>
10#include <util/translation.h>
11#include <wallet/sqlite.h>
12#include <wallet/migrate.h>
13#include <wallet/test/util.h>
14#include <wallet/walletutil.h> // for WALLET_FLAG_DESCRIPTORS
15
16#include <fstream>
17#include <memory>
18#include <string>
19
20inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
21{
22 std::span key{kv.first}, value{kv.second};
23 os << "(\"" << std::string_view{reinterpret_cast<const char*>(key.data()), key.size()} << "\", \""
24 << std::string_view{reinterpret_cast<const char*>(value.data()), value.size()} << "\")";
25 return os;
26}
27
28namespace wallet {
29
30inline std::span<const std::byte> StringBytes(std::string_view str)
31{
32 return std::as_bytes(std::span{str});
33}
34
35static SerializeData StringData(std::string_view str)
36{
37 auto bytes = StringBytes(str);
38 return SerializeData{bytes.begin(), bytes.end()};
39}
40
41static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected)
42{
43 std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
44 MockableData actual;
45 while (true) {
46 DataStream key, value;
47 DatabaseCursor::Status status = cursor->Next(key, value);
48 if (status == DatabaseCursor::Status::DONE) break;
51 actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
52 }
53 BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.end(), expected.begin(), expected.end());
54}
55
57
58static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
59{
60 std::vector<std::unique_ptr<WalletDatabase>> dbs;
61 DatabaseOptions options;
62 DatabaseStatus status;
63 bilingual_str error;
64 // Unable to test BerkeleyRO since we cannot create a new BDB database to open
65 dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
66 dbs.emplace_back(CreateMockableWalletDatabase());
67 return dbs;
68}
69
70BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
71{
72 // Test each supported db
73 for (const auto& database : TestDatabases(m_path_root)) {
74 std::vector<std::string> prefixes = {"", "FIRST", "SECOND", "P\xfe\xff", "P\xff\x01", "\xff\xff"};
75
76 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
77 // Write elements to it
78 for (unsigned int i = 0; i < 10; i++) {
79 for (const auto& prefix : prefixes) {
80 BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
81 }
82 }
83
84 // Now read all the items by prefix and verify that each element gets parsed correctly
85 for (const auto& prefix : prefixes) {
86 DataStream s_prefix;
87 s_prefix << prefix;
88 std::unique_ptr<DatabaseCursor> cursor = handler->GetNewPrefixCursor(s_prefix);
89 DataStream key;
90 DataStream value;
91 for (int i = 0; i < 10; i++) {
92 DatabaseCursor::Status status = cursor->Next(key, value);
94
95 std::string key_back;
96 unsigned int i_back;
97 key >> key_back >> i_back;
98 BOOST_CHECK_EQUAL(key_back, prefix);
99
100 unsigned int value_back;
101 value >> value_back;
102 BOOST_CHECK_EQUAL(value_back, i_back);
103 }
104
105 // Let's now read it once more, it should return DONE
106 BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
107 }
108 handler.reset();
109 database->Close();
110 }
111}
112
113// Lower level DatabaseBase::GetNewPrefixCursor test, to cover cases that aren't
114// covered in the higher level test above. The higher level test uses
115// serialized strings which are prefixed with string length, so it doesn't test
116// truly empty prefixes or prefixes that begin with \xff
117BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
118{
119 const MockableData::value_type
120 e{StringData(""), StringData("e")},
121 p{StringData("prefix"), StringData("p")},
122 ps{StringData("prefixsuffix"), StringData("ps")},
123 f{StringData("\xff"), StringData("f")},
124 fs{StringData("\xffsuffix"), StringData("fs")},
125 ff{StringData("\xff\xff"), StringData("ff")},
126 ffs{StringData("\xff\xffsuffix"), StringData("ffs")};
127 for (const auto& database : TestDatabases(m_path_root)) {
128 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
129
130 // Write elements to it if not berkeleyro
131 for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
132 batch->Write(std::span{k}, std::span{v});
133 }
134
135 CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
136 CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
137 CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
138 CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
139 batch.reset();
140 database->Close();
141 }
142}
143
144BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
145{
146 // Ensures the database remains accessible without deadlocking after a write error.
147 // To simulate the behavior, record overwrites are disallowed, and the test verifies
148 // that the database remains active after failing to store an existing record.
149 for (const auto& database : TestDatabases(m_path_root)) {
150 // Write original record
151 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
152 std::string key = "key";
153 std::string value = "value";
154 std::string value2 = "value_2";
155 BOOST_CHECK(batch->Write(key, value));
156 // Attempt to overwrite the record (expect failure)
157 BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false));
158 // Successfully overwrite the record
159 BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true));
160 // Sanity-check; read and verify the overwritten value
161 std::string read_value;
162 BOOST_CHECK(batch->Read(key, read_value));
163 BOOST_CHECK_EQUAL(read_value, value2);
164 }
165}
166
167// Verify 'ErasePrefix' functionality using db keys similar to the ones used by the wallet.
168// Keys are in the form of std::pair<TYPE, ENTRY_ID>
170{
171 const std::string key = "key";
172 const std::string key2 = "key2";
173 const std::string value = "value";
174 const std::string value2 = "value_2";
175 auto make_key = [](std::string type, std::string id) { return std::make_pair(type, id); };
176
177 for (const auto& database : TestDatabases(m_path_root)) {
178 if (dynamic_cast<BerkeleyRODatabase*>(database.get())) {
179 // Skip this test if BerkeleyRO
180 continue;
181 }
182 std::unique_ptr<DatabaseBatch> batch = database->MakeBatch();
183
184 // Write two entries with the same key type prefix, a third one with a different prefix
185 // and a fourth one with the type-id values inverted
186 BOOST_CHECK(batch->Write(make_key(key, value), value));
187 BOOST_CHECK(batch->Write(make_key(key, value2), value2));
188 BOOST_CHECK(batch->Write(make_key(key2, value), value));
189 BOOST_CHECK(batch->Write(make_key(value, key), value));
190
191 // Erase the ones with the same prefix and verify result
192 BOOST_CHECK(batch->TxnBegin());
193 BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
194 BOOST_CHECK(batch->TxnCommit());
195
196 BOOST_CHECK(!batch->Exists(make_key(key, value)));
197 BOOST_CHECK(!batch->Exists(make_key(key, value2)));
198 // Also verify that entries with a different prefix were not erased
199 BOOST_CHECK(batch->Exists(make_key(key2, value)));
200 BOOST_CHECK(batch->Exists(make_key(value, key)));
201 }
202}
203
204// Test-only statement execution error
205constexpr int TEST_SQLITE_ERROR = -999;
206
208{
209private:
211 std::set<std::string> m_blocked_statements;
212public:
213 DbExecBlocker(std::set<std::string> blocked_statements) : m_blocked_statements(blocked_statements) {}
214 int Exec(SQLiteDatabase& database, const std::string& statement) override {
215 if (m_blocked_statements.contains(statement)) return TEST_SQLITE_ERROR;
216 return m_base_exec.Exec(database, statement);
217 }
218};
219
220BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn)
221{
222 // Verifies that there is no active dangling, to-be-reversed db txn
223 // after the batch object that initiated it is destroyed.
224 DatabaseOptions options;
225 DatabaseStatus status;
226 bilingual_str error;
227 std::unique_ptr<SQLiteDatabase> database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
228
229 std::string key = "key";
230 std::string value = "value";
231
232 std::unique_ptr<SQLiteBatch> batch = std::make_unique<SQLiteBatch>(*database);
233 BOOST_CHECK(batch->TxnBegin());
234 BOOST_CHECK(batch->Write(key, value));
235 // Set a handler to prevent txn abortion during destruction.
236 // Mimicking a db statement execution failure.
237 batch->SetExecHandler(std::make_unique<DbExecBlocker>(std::set<std::string>{"ROLLBACK TRANSACTION"}));
238 // Destroy batch
239 batch.reset();
240
241 // Ensure there is no dangling, to-be-reversed db txn
242 BOOST_CHECK(!database->HasActiveTxn());
243
244 // And, just as a sanity check; verify that new batchs only write what they suppose to write
245 // and nothing else.
246 std::string key2 = "key2";
247 std::unique_ptr<SQLiteBatch> batch2 = std::make_unique<SQLiteBatch>(*database);
248 BOOST_CHECK(batch2->Write(key2, value));
249 // The first key must not exist
250 BOOST_CHECK(!batch2->Exists(key));
251}
252
253BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere)
254{
255 std::string key = "key";
256 std::string value = "value";
257 std::string value2 = "value_2";
258
259 DatabaseOptions options;
260 DatabaseStatus status;
261 bilingual_str error;
262 const auto& database = MakeSQLiteDatabase(m_path_root / "sqlite", options, status, error);
263
264 std::unique_ptr<DatabaseBatch> handler = Assert(database)->MakeBatch();
265
266 // Verify concurrent db transactions does not interfere between each other.
267 // Start db txn, write key and check the key does exist within the db txn.
268 BOOST_CHECK(handler->TxnBegin());
269 BOOST_CHECK(handler->Write(key, value));
270 BOOST_CHECK(handler->Exists(key));
271
272 // But, the same key, does not exist in another handler
273 std::unique_ptr<DatabaseBatch> handler2 = Assert(database)->MakeBatch();
274 BOOST_CHECK(handler2->Exists(key));
275
276 // Attempt to commit the handler txn calling the handler2 methods.
277 // Which, must not be possible.
278 BOOST_CHECK(!handler2->TxnCommit());
279 BOOST_CHECK(!handler2->TxnAbort());
280
281 // Only the first handler can commit the changes.
282 BOOST_CHECK(handler->TxnCommit());
283 // And, once commit is completed, handler2 can read the record
284 std::string read_value;
285 BOOST_CHECK(handler2->Read(key, read_value));
286 BOOST_CHECK_EQUAL(read_value, value);
287
288 // Also, once txn is committed, single write statements are re-enabled.
289 // Which means that handler2 can read the record changes directly.
290 BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true));
291 BOOST_CHECK(handler2->Read(key, read_value));
292 BOOST_CHECK_EQUAL(read_value, value2);
293}
294
296} // namespace wallet
#define Assert(val)
Identity function.
Definition: check.h:106
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
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:210
DbExecBlocker(std::set< std::string > blocked_statements)
Definition: db_tests.cpp:213
int Exec(SQLiteDatabase &database, const std::string &statement) override
Definition: db_tests.cpp:214
std::set< std::string > m_blocked_statements
Definition: db_tests.cpp:211
An instance of this class represents one SQLite3 database.
Definition: sqlite.h:101
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
An instance of this class represents one database.
Definition: db.h:130
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:20
static SerializeData StringData(std::string_view str)
Definition: db_tests.cpp:35
std::unique_ptr< SQLiteDatabase > MakeSQLiteDatabase(const fs::path &path, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error)
Definition: sqlite.cpp:694
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase(MockableData records)
Definition: util.cpp:187
BOOST_AUTO_TEST_CASE(bnb_test)
static std::vector< std::unique_ptr< WalletDatabase > > TestDatabases(const fs::path &path_root)
Definition: db_tests.cpp:58
static void CheckPrefix(DatabaseBatch &batch, std::span< const std::byte > prefix, MockableData expected)
Definition: db_tests.cpp:41
std::span< const std::byte > StringBytes(std::string_view str)
Definition: db_tests.cpp:30
std::map< SerializeData, SerializeData, std::less<> > MockableData
Definition: util.h:47
constexpr int TEST_SQLITE_ERROR
Definition: db_tests.cpp:205
DatabaseStatus
Definition: db.h:183
#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