Coverage Report

Created: 2026-04-29 19:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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