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