Branch data Line data Source code
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/setup_common.h>
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>
15 : :
16 : : #include <cstddef>
17 : : #include <fstream>
18 : : #include <memory>
19 : : #include <span>
20 : : #include <string>
21 : : #include <string_view>
22 : : #include <utility>
23 : : #include <vector>
24 : :
25 : 0 : inline std::ostream& operator<<(std::ostream& os, const std::pair<const SerializeData, SerializeData>& kv)
26 : : {
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 : : }
32 : :
33 : : namespace wallet {
34 : :
35 : 22 : inline std::span<const std::byte> StringBytes(std::string_view str)
36 : : {
37 : 22 : return std::as_bytes(std::span{str});
38 : : }
39 : :
40 : 14 : static SerializeData StringData(std::string_view str)
41 : : {
42 : 14 : auto bytes = StringBytes(str);
43 : 14 : return SerializeData{bytes.begin(), bytes.end()};
44 : : }
45 : :
46 : 8 : static void CheckPrefix(DatabaseBatch& batch, std::span<const std::byte> prefix, MockableData expected)
47 : : {
48 : 8 : std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
49 : 8 : MockableData actual;
50 : 68 : 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 [ + - + - : 60 : BOOST_CHECK(status == DatabaseCursor::Status::MORE);
+ - ]
55 [ + - + - : 60 : BOOST_CHECK(
+ - + - +
- ]
56 : : actual.emplace(SerializeData(key.begin(), key.end()), SerializeData(value.begin(), value.end())).second);
57 : 38 : }
58 [ + - + - : 16 : 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 : 4 : static std::vector<std::unique_ptr<WalletDatabase>> TestDatabases(const fs::path& path_root)
64 : : {
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 [ + - + - : 12 : dbs.emplace_back(MakeSQLiteDatabase(path_root / "sqlite", options, status, error));
+ - + - ]
71 [ + - + - ]: 4 : dbs.emplace_back(CreateMockableWalletDatabase());
72 : 4 : return dbs;
73 : 4 : }
74 : :
75 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_range_test)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
76 : : {
77 : : // Test each supported db
78 [ + + ]: 3 : 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 [ + + ]: 140 : for (const auto& prefix : prefixes) {
85 [ + - + - : 240 : BOOST_CHECK(handler->Write(std::make_pair(prefix, i), i));
+ - + - ]
86 : : }
87 : : }
88 : :
89 : : // Now read all the items by prefix and verify that each element gets parsed correctly
90 [ + + ]: 14 : 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 [ + - + - : 24 : BOOST_CHECK(cursor->Next(key, value) == DatabaseCursor::Status::DONE);
+ - ]
112 : 12 : }
113 [ + - ]: 2 : handler.reset();
114 [ + - ]: 2 : database->Close();
115 : 3 : }
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 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(db_cursor_prefix_byte_test)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
123 : : {
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 [ + - + + ]: 3 : 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 [ + - + - : 44 : for (const auto& [k, v] : {e, p, ps, f, fs, ff, ffs}) {
+ - + - +
- + - + -
- + + + -
- ]
137 [ - + ]: 14 : batch->Write(std::span{k}, std::span{v});
138 [ + + - - ]: 16 : }
139 : :
140 [ + - + - : 18 : CheckPrefix(*batch, StringBytes(""), {e, p, ps, f, fs, ff, ffs});
+ + - - ]
141 [ + - + - : 8 : CheckPrefix(*batch, StringBytes("prefix"), {p, ps});
+ + - - ]
142 [ + - + + : 12 : CheckPrefix(*batch, StringBytes("\xff"), {f, fs, ff, ffs});
- - ]
143 [ + - + - : 8 : CheckPrefix(*batch, StringBytes("\xff\xff"), {ff, ffs});
+ + - - ]
144 [ + - ]: 2 : batch.reset();
145 [ + - ]: 2 : database->Close();
146 : 3 : }
147 [ + - + - : 15 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- - - - -
- - - - ]
148 : :
149 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(db_availability_after_write_error)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
150 : : {
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 [ + + ]: 3 : 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 [ + - + - : 4 : BOOST_CHECK(batch->Write(key, value));
+ - + - ]
161 : : // Attempt to overwrite the record (expect failure)
162 [ + - + - : 4 : BOOST_CHECK(!batch->Write(key, value2, /*fOverwrite=*/false));
+ - + - ]
163 : : // Successfully overwrite the record
164 [ + - + - : 4 : BOOST_CHECK(batch->Write(key, value2, /*fOverwrite=*/true));
+ - + - ]
165 : : // Sanity-check; read and verify the overwritten value
166 [ + - ]: 2 : std::string read_value;
167 [ + - + - : 4 : BOOST_CHECK(batch->Read(key, read_value));
+ - + - ]
168 [ + - + - ]: 2 : BOOST_CHECK_EQUAL(read_value, value2);
169 : 3 : }
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 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(erase_prefix)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
175 : : {
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 [ + - + + ]: 3 : 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 : : }
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 [ + - + - : 8 : BOOST_CHECK(batch->Write(make_key(key, value), value));
+ - + - +
- + - ]
192 [ + - + - : 8 : BOOST_CHECK(batch->Write(make_key(key, value2), value2));
+ - + - +
- + - ]
193 [ + - + - : 8 : BOOST_CHECK(batch->Write(make_key(key2, value), value));
+ - + - +
- + - ]
194 [ + - + - : 8 : BOOST_CHECK(batch->Write(make_key(value, key), value));
+ - + - +
- + - ]
195 : :
196 : : // Erase the ones with the same prefix and verify result
197 [ + - + - : 4 : BOOST_CHECK(batch->TxnBegin());
+ - + - ]
198 [ + - + - : 4 : BOOST_CHECK(batch->ErasePrefix(DataStream() << key));
+ - + - +
- ]
199 [ + - + - : 4 : BOOST_CHECK(batch->TxnCommit());
+ - + - ]
200 : :
201 [ + - + - : 8 : BOOST_CHECK(!batch->Exists(make_key(key, value)));
+ - + - +
- + - ]
202 [ + - + - : 8 : BOOST_CHECK(!batch->Exists(make_key(key, value2)));
+ - + - +
- + - ]
203 : : // Also verify that entries with a different prefix were not erased
204 [ + - + - : 8 : BOOST_CHECK(batch->Exists(make_key(key2, value)));
+ - + - +
- + - ]
205 [ + - + - : 8 : BOOST_CHECK(batch->Exists(make_key(value, key)));
+ - + - +
- ]
206 : 3 : }
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 : : }
223 : : };
224 : :
225 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(txn_close_failure_dangling_txn)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
226 : : {
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 [ + - + - : 3 : 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 [ + - + - : 2 : BOOST_CHECK(batch->TxnBegin());
+ - + - ]
239 [ + - + - : 2 : BOOST_CHECK(batch->Write(key, value));
+ - + - ]
240 : : // Set a handler to prevent txn abortion during destruction.
241 : : // Mimicking a db statement execution failure.
242 [ + - + - : 2 : 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 [ + - + - : 2 : 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 [ + - + - : 2 : BOOST_CHECK(batch2->Write(key2, value));
+ - + - ]
254 : : // The first key must not exist
255 [ + - + - : 2 : BOOST_CHECK(!batch2->Exists(key));
+ - ]
256 [ + - ]: 3 : }
257 : :
258 [ + - + - : 7 : BOOST_AUTO_TEST_CASE(concurrent_txn_dont_interfere)
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- ]
259 : : {
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 [ + - + - : 3 : 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 [ + - + - : 2 : BOOST_CHECK(handler->TxnBegin());
+ - + - ]
274 [ + - + - : 2 : BOOST_CHECK(handler->Write(key, value));
+ - + - ]
275 [ + - + - : 2 : 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 [ + - + - : 2 : BOOST_CHECK(handler2->Exists(key));
+ - + - ]
280 : :
281 : : // Attempt to commit the handler txn calling the handler2 methods.
282 : : // Which, must not be possible.
283 [ + - + - : 2 : BOOST_CHECK(!handler2->TxnCommit());
+ - + - ]
284 [ + - + - : 2 : BOOST_CHECK(!handler2->TxnAbort());
+ - + - ]
285 : :
286 : : // Only the first handler can commit the changes.
287 [ + - + - : 2 : BOOST_CHECK(handler->TxnCommit());
+ - + - ]
288 : : // And, once commit is completed, handler2 can read the record
289 [ + - ]: 1 : std::string read_value;
290 [ + - + - : 2 : 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 [ + - + - : 2 : BOOST_CHECK(handler->Write(key, value2, /*fOverwrite=*/true));
+ - + - ]
296 [ + - + - : 2 : BOOST_CHECK(handler2->Read(key, read_value));
+ - + - ]
297 [ + - + - ]: 1 : BOOST_CHECK_EQUAL(read_value, value2);
298 : 2 : }
299 : :
300 : : BOOST_AUTO_TEST_SUITE_END()
301 : : } // namespace wallet
|