Coverage Report

Created: 2026-07-23 20:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/wallet/migrate.cpp
Line
Count
Source
1
// Copyright (c) 2024-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 <compat/byteswap.h>
6
#include <crypto/common.h>
7
#include <streams.h>
8
#include <util/log.h>
9
#include <util/translation.h>
10
#include <wallet/migrate.h>
11
12
#include <array>
13
#include <cstddef>
14
#include <optional>
15
#include <stdexcept>
16
#include <variant>
17
#include <vector>
18
19
namespace wallet {
20
// Magic bytes in both endianness's
21
constexpr uint32_t BTREE_MAGIC = 0x00053162;    // If the file endianness matches our system, we see this magic
22
constexpr uint32_t BTREE_MAGIC_OE = 0x62310500; // If the file endianness is the other one, we will see this magic
23
24
// Subdatabase name
25
static const std::vector<std::byte> SUBDATABASE_NAME = {std::byte{'m'}, std::byte{'a'}, std::byte{'i'}, std::byte{'n'}};
26
27
enum class PageType : uint8_t {
28
    /*
29
     * BDB has several page types, most of which we do not use
30
     * They are listed here for completeness, but commented out
31
     * to avoid opening something unintended.
32
    INVALID = 0,         // Invalid page type
33
    DUPLICATE = 1,       // Duplicate. Deprecated and no longer used
34
    HASH_UNSORTED = 2,   // Hash pages. Deprecated.
35
    RECNO_INTERNAL = 4,  // Recno internal
36
    RECNO_LEAF = 6,      // Recno leaf
37
    HASH_META = 8,       // Hash metadata
38
    QUEUE_META = 10,     // Queue Metadata
39
    QUEUE_DATA = 11,     // Queue Data
40
    DUPLICATE_LEAF = 12, // Off-page duplicate leaf
41
    HASH_SORTED = 13,    // Sorted hash page
42
    */
43
    BTREE_INTERNAL = 3, // BTree internal
44
    BTREE_LEAF = 5,     // BTree leaf
45
    OVERFLOW_DATA = 7,  // Overflow
46
    BTREE_META = 9,     // BTree metadata
47
};
48
49
enum class RecordType : uint8_t {
50
    KEYDATA = 1,
51
    // DUPLICATE = 2,       Unused as our databases do not support duplicate records
52
    OVERFLOW_DATA = 3,
53
    DELETE_FLAG = 0x80, // Indicate this record is deleted. This is OR'd with the real type.
54
};
55
56
enum class BTreeFlags : uint32_t {
57
    /*
58
     * BTree databases have feature flags, but we do not use them except for
59
     * subdatabases. The unused flags are included for completeness, but commented out
60
     * to avoid accidental use.
61
    DUP = 1,         // Duplicates
62
    RECNO = 2,       // Recno tree
63
    RECNUM = 4,      // BTree: Maintain record counts
64
    FIXEDLEN = 8,    // Recno: fixed length records
65
    RENUMBER = 0x10, // Recno: renumber on insert/delete
66
    DUPSORT = 0x40,  // Duplicates are sorted
67
    COMPRESS = 0x80, // Compressed
68
    */
69
    SUBDB = 0x20, // Subdatabases
70
};
71
72
/** Berkeley DB BTree metadata page layout */
73
class MetaPage
74
{
75
public:
76
    uint32_t lsn_file;             // Log Sequence Number file
77
    uint32_t lsn_offset;           // Log Sequence Number offset
78
    uint32_t page_num;             // Current page number
79
    uint32_t magic;                // Magic number
80
    uint32_t version;              // Version
81
    uint32_t pagesize;             // Page size
82
    uint8_t encrypt_algo;          // Encryption algorithm
83
    PageType type;                 // Page type
84
    uint8_t metaflags;             // Meta-only flags
85
    uint8_t unused1;               // Unused
86
    uint32_t free_list;            // Free list page number
87
    uint32_t last_page;            // Page number of last page in db
88
    uint32_t partitions;           // Number of partitions
89
    uint32_t key_count;            // Cached key count
90
    uint32_t record_count;         // Cached record count
91
    BTreeFlags flags;              // Flags
92
    std::array<std::byte, 20> uid; // 20 byte unique file ID
93
    uint32_t unused2;              // Unused
94
    uint32_t minkey;               // Minimum key
95
    uint32_t re_len;               // Recno: fixed length record length
96
    uint32_t re_pad;               // Recno: fixed length record pad
97
    uint32_t root;                 // Root page number
98
    char unused3[368];             // 92 * 4 bytes of unused space
99
    uint32_t crypto_magic;         // Crypto magic number
100
    char trash[12];                // 3 * 4 bytes of trash space
101
    unsigned char iv[20];          // Crypto IV
102
    unsigned char chksum[16];      // Checksum
103
104
    bool other_endian;
105
    uint32_t expected_page_num;
106
107
96
    MetaPage(uint32_t expected_page_num) : expected_page_num(expected_page_num) {}
108
    MetaPage() = delete;
109
110
    template <typename Stream>
111
    void Unserialize(Stream& s)
112
96
    {
113
96
        s >> lsn_file;
114
96
        s >> lsn_offset;
115
96
        s >> page_num;
116
96
        s >> magic;
117
96
        s >> version;
118
96
        s >> pagesize;
119
96
        s >> encrypt_algo;
120
121
96
        other_endian = magic == BTREE_MAGIC_OE;
122
123
96
        uint8_t uint8_type;
124
96
        s >> uint8_type;
125
96
        type = static_cast<PageType>(uint8_type);
126
127
96
        s >> metaflags;
128
96
        s >> unused1;
129
96
        s >> free_list;
130
96
        s >> last_page;
131
96
        s >> partitions;
132
96
        s >> key_count;
133
96
        s >> record_count;
134
135
96
        uint32_t uint32_flags;
136
96
        s >> uint32_flags;
137
96
        if (other_endian) {
138
0
            uint32_flags = internal_bswap_32(uint32_flags);
139
0
        }
140
96
        flags = static_cast<BTreeFlags>(uint32_flags);
141
142
96
        s >> uid;
143
96
        s >> unused2;
144
96
        s >> minkey;
145
96
        s >> re_len;
146
96
        s >> re_pad;
147
96
        s >> root;
148
96
        s >> unused3;
149
96
        s >> crypto_magic;
150
96
        s >> trash;
151
96
        s >> iv;
152
96
        s >> chksum;
153
154
96
        if (other_endian) {
155
0
            lsn_file = internal_bswap_32(lsn_file);
156
0
            lsn_offset = internal_bswap_32(lsn_offset);
157
0
            page_num = internal_bswap_32(page_num);
158
0
            magic = internal_bswap_32(magic);
159
0
            version = internal_bswap_32(version);
160
0
            pagesize = internal_bswap_32(pagesize);
161
0
            free_list = internal_bswap_32(free_list);
162
0
            last_page = internal_bswap_32(last_page);
163
0
            partitions = internal_bswap_32(partitions);
164
0
            key_count = internal_bswap_32(key_count);
165
0
            record_count = internal_bswap_32(record_count);
166
0
            unused2 = internal_bswap_32(unused2);
167
0
            minkey = internal_bswap_32(minkey);
168
0
            re_len = internal_bswap_32(re_len);
169
0
            re_pad = internal_bswap_32(re_pad);
170
0
            root = internal_bswap_32(root);
171
0
            crypto_magic = internal_bswap_32(crypto_magic);
172
0
        }
173
174
        // Page number must match
175
96
        if (page_num != expected_page_num) {
176
0
            throw std::runtime_error("Meta page number mismatch");
177
0
        }
178
179
        // Check magic
180
96
        if (magic != BTREE_MAGIC) {
181
0
            throw std::runtime_error("Not a BDB file");
182
0
        }
183
184
        // Only version 9 is supported
185
96
        if (version != 9) {
186
0
            throw std::runtime_error("Unsupported BDB data file version number");
187
0
        }
188
189
        // Page size must be 512 <= pagesize <= 64k, and be a power of 2
190
96
        if (pagesize < 512 || pagesize > 65536 || (pagesize & (pagesize - 1)) != 0) {
191
0
            throw std::runtime_error("Bad page size");
192
0
        }
193
194
        // Page type must be the btree type
195
96
        if (type != PageType::BTREE_META) {
196
0
            throw std::runtime_error("Unexpected page type, should be 9 (BTree Metadata)");
197
0
        }
198
199
        // Only supported meta-flag is subdatabase
200
96
        if (flags != BTreeFlags::SUBDB) {
201
0
            throw std::runtime_error("Unexpected database flags, should only be 0x20 (subdatabases)");
202
0
        }
203
96
    }
204
};
205
206
/** General class for records in a BDB BTree database. Contains common fields. */
207
class RecordHeader
208
{
209
public:
210
    uint16_t len;    // Key/data item length
211
    RecordType type; // Page type (BDB has this; includes a DELETE_FLAG that we track separately)
212
    bool deleted;    // Whether the DELETE_FLAG was set on type
213
214
    static constexpr size_t SIZE = 3; // The record header is 3 bytes
215
216
    bool other_endian;
217
218
2.75k
    RecordHeader(bool other_endian) : other_endian(other_endian) {}
219
    RecordHeader() = delete;
220
221
    template <typename Stream>
222
    void Unserialize(Stream& s)
223
2.75k
    {
224
2.75k
        s >> len;
225
226
2.75k
        uint8_t uint8_type;
227
2.75k
        s >> uint8_type;
228
2.75k
        type = static_cast<RecordType>(uint8_type & ~static_cast<uint8_t>(RecordType::DELETE_FLAG));
229
2.75k
        deleted = uint8_type & static_cast<uint8_t>(RecordType::DELETE_FLAG);
230
231
2.75k
        if (other_endian) {
232
0
            len = internal_bswap_16(len);
233
0
        }
234
2.75k
    }
235
};
236
237
/** Class for data in the record directly */
238
class DataRecord
239
{
240
public:
241
2.70k
    DataRecord(const RecordHeader& header) : m_header(header) {}
242
    DataRecord() = delete;
243
244
    RecordHeader m_header;
245
246
    std::vector<std::byte> data; // Variable length key/data item
247
248
    template <typename Stream>
249
    void Unserialize(Stream& s)
250
2.70k
    {
251
2.70k
        data.resize(m_header.len);
252
2.70k
        s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
253
2.70k
    }
254
};
255
256
/** Class for records representing internal nodes of the BTree. */
257
class InternalRecord
258
{
259
public:
260
42
    InternalRecord(const RecordHeader& header) : m_header(header) {}
261
    InternalRecord() = delete;
262
263
    RecordHeader m_header;
264
265
    uint8_t unused;              // Padding, unused
266
    uint32_t page_num;           // Page number of referenced page
267
    uint32_t records;            // Subtree record count
268
    std::vector<std::byte> data; // Variable length key item
269
270
    static constexpr size_t FIXED_SIZE = 9; // Size of fixed data is 9 bytes
271
272
    template <typename Stream>
273
    void Unserialize(Stream& s)
274
42
    {
275
42
        s >> unused;
276
42
        s >> page_num;
277
42
        s >> records;
278
279
42
        data.resize(m_header.len);
280
42
        s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
281
282
42
        if (m_header.other_endian) {
283
0
            page_num = internal_bswap_32(page_num);
284
0
            records = internal_bswap_32(records);
285
0
        }
286
42
    }
287
};
288
289
/** Class for records representing overflow records of the BTree.
290
 * Overflow records point to a page which contains the data in the record.
291
 * Those pages may point to further pages with the rest of the data if it does not fit
292
 * in one page */
293
class OverflowRecord
294
{
295
public:
296
0
    OverflowRecord(const RecordHeader& header) : m_header(header) {}
297
    OverflowRecord() = delete;
298
299
    RecordHeader m_header;
300
301
    uint8_t unused2;      // Padding, unused
302
    uint32_t page_number; // Page number where data begins
303
    uint32_t item_len;    // Total length of item
304
305
    static constexpr size_t SIZE = 9; // Overflow record is always 9 bytes
306
307
    template <typename Stream>
308
    void Unserialize(Stream& s)
309
0
    {
310
0
        s >> unused2;
311
0
        s >> page_number;
312
0
        s >> item_len;
313
314
0
        if (m_header.other_endian) {
315
0
            page_number = internal_bswap_32(page_number);
316
0
            item_len = internal_bswap_32(item_len);
317
0
        }
318
0
    }
319
};
320
321
/** A generic data page in the database. Contains fields common to all data pages. */
322
class PageHeader
323
{
324
public:
325
    uint32_t lsn_file;   // Log Sequence Number file
326
    uint32_t lsn_offset; // Log Sequence Number offset
327
    uint32_t page_num;   // Current page number
328
    uint32_t prev_page;  // Previous page number
329
    uint32_t next_page;  // Next page number
330
    uint16_t entries;    // Number of items on the page
331
    uint16_t hf_offset;  // High free byte page offset
332
    uint8_t level;       // Btree page level
333
    PageType type;       // Page type
334
335
    static constexpr int64_t SIZE = 26; // The header is 26 bytes
336
337
    uint32_t expected_page_num;
338
    bool other_endian;
339
340
186
    PageHeader(uint32_t page_num, bool other_endian) : expected_page_num(page_num), other_endian(other_endian) {}
341
    PageHeader() = delete;
342
343
    template <typename Stream>
344
    void Unserialize(Stream& s)
345
186
    {
346
186
        s >> lsn_file;
347
186
        s >> lsn_offset;
348
186
        s >> page_num;
349
186
        s >> prev_page;
350
186
        s >> next_page;
351
186
        s >> entries;
352
186
        s >> hf_offset;
353
186
        s >> level;
354
355
186
        uint8_t uint8_type;
356
186
        s >> uint8_type;
357
186
        type = static_cast<PageType>(uint8_type);
358
359
186
        if (other_endian) {
360
0
            lsn_file = internal_bswap_32(lsn_file);
361
0
            lsn_offset = internal_bswap_32(lsn_offset);
362
0
            page_num = internal_bswap_32(page_num);
363
0
            prev_page = internal_bswap_32(prev_page);
364
0
            next_page = internal_bswap_32(next_page);
365
0
            entries = internal_bswap_16(entries);
366
0
            hf_offset = internal_bswap_16(hf_offset);
367
0
        }
368
369
186
        if (expected_page_num != page_num) {
370
0
            throw std::runtime_error("Page number mismatch");
371
0
        }
372
186
        if ((type != PageType::OVERFLOW_DATA && level < 1) || (type == PageType::OVERFLOW_DATA && level != 0)) {
373
0
            throw std::runtime_error("Bad btree level");
374
0
        }
375
186
    }
376
};
377
378
/** A page of records in the database */
379
class RecordsPage
380
{
381
public:
382
124
    RecordsPage(const PageHeader& header) : m_header(header) {}
383
    RecordsPage() = delete;
384
385
    PageHeader m_header;
386
387
    std::vector<uint16_t> indexes;
388
    std::vector<std::variant<DataRecord, OverflowRecord>> records;
389
390
    template <typename Stream>
391
    void Unserialize(Stream& s)
392
124
    {
393
        // Current position within the page
394
124
        int64_t pos = PageHeader::SIZE;
395
396
        // Get the items
397
2.83k
        for (uint32_t i = 0; i < m_header.entries; ++i) {
398
            // Get the index
399
2.70k
            uint16_t index;
400
2.70k
            s >> index;
401
2.70k
            if (m_header.other_endian) {
402
0
                index = internal_bswap_16(index);
403
0
            }
404
2.70k
            indexes.push_back(index);
405
2.70k
            pos += sizeof(uint16_t);
406
407
            // Go to the offset from the index
408
2.70k
            int64_t to_jump = index - pos;
409
2.70k
            if (to_jump < 0) {
410
0
                throw std::runtime_error("Data record position not in page");
411
0
            }
412
2.70k
            s.ignore(to_jump);
413
414
            // Read the record
415
2.70k
            RecordHeader rec_hdr(m_header.other_endian);
416
2.70k
            s >> rec_hdr;
417
2.70k
            to_jump += RecordHeader::SIZE;
418
419
2.70k
            switch (rec_hdr.type) {
420
2.70k
            case RecordType::KEYDATA: {
421
2.70k
                DataRecord record(rec_hdr);
422
2.70k
                s >> record;
423
2.70k
                records.emplace_back(record);
424
2.70k
                to_jump += rec_hdr.len;
425
2.70k
                break;
426
0
            }
427
0
            case RecordType::OVERFLOW_DATA: {
428
0
                OverflowRecord record(rec_hdr);
429
0
                s >> record;
430
0
                records.emplace_back(record);
431
0
                to_jump += OverflowRecord::SIZE;
432
0
                break;
433
0
            }
434
0
            default:
435
0
                throw std::runtime_error("Unknown record type in records page");
436
2.70k
            }
437
438
            // Go back to the indexes
439
2.70k
            s.seek(-to_jump, SEEK_CUR);
440
2.70k
        }
441
124
    }
442
};
443
444
/** A page containing overflow data */
445
class OverflowPage
446
{
447
public:
448
0
    OverflowPage(const PageHeader& header) : m_header(header) {}
449
    OverflowPage() = delete;
450
451
    PageHeader m_header;
452
453
    // BDB overloads some page fields to store overflow page data
454
    // hf_offset contains the length of the overflow data stored on this page
455
    // entries contains a reference count for references to this item
456
457
    // The overflow data itself. Begins immediately following header
458
    std::vector<std::byte> data;
459
460
    template <typename Stream>
461
    void Unserialize(Stream& s)
462
0
    {
463
0
        data.resize(m_header.hf_offset);
464
0
        s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
465
0
    }
466
};
467
468
/** A page of records in the database */
469
class InternalPage
470
{
471
public:
472
14
    InternalPage(const PageHeader& header) : m_header(header) {}
473
    InternalPage() = delete;
474
475
    PageHeader m_header;
476
477
    std::vector<uint16_t> indexes;
478
    std::vector<InternalRecord> records;
479
480
    template <typename Stream>
481
    void Unserialize(Stream& s)
482
14
    {
483
        // Current position within the page
484
14
        int64_t pos = PageHeader::SIZE;
485
486
        // Get the items
487
56
        for (uint32_t i = 0; i < m_header.entries; ++i) {
488
            // Get the index
489
42
            uint16_t index;
490
42
            s >> index;
491
42
            if (m_header.other_endian) {
492
0
                index = internal_bswap_16(index);
493
0
            }
494
42
            indexes.push_back(index);
495
42
            pos += sizeof(uint16_t);
496
497
            // Go to the offset from the index
498
42
            int64_t to_jump = index - pos;
499
42
            if (to_jump < 0) {
500
0
                throw std::runtime_error("Internal record position not in page");
501
0
            }
502
42
            s.ignore(to_jump);
503
504
            // Read the record
505
42
            RecordHeader rec_hdr(m_header.other_endian);
506
42
            s >> rec_hdr;
507
42
            to_jump += RecordHeader::SIZE;
508
509
42
            if (rec_hdr.type != RecordType::KEYDATA) {
510
0
                throw std::runtime_error("Unknown record type in internal page");
511
0
            }
512
42
            InternalRecord record(rec_hdr);
513
42
            s >> record;
514
42
            records.emplace_back(record);
515
42
            to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;
516
517
            // Go back to the indexes
518
42
            s.seek(-to_jump, SEEK_CUR);
519
42
        }
520
14
    }
521
};
522
523
static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)
524
468
{
525
468
    int64_t pos = int64_t{page_num} * page_size;
526
468
    s.seek(pos, SEEK_SET);
527
468
}
528
529
void BerkeleyRODatabase::Open()
530
48
{
531
    // Open the file
532
48
    FILE* file = fsbridge::fopen(m_filepath, "rb");
533
48
    AutoFile db_file(file);
534
48
    if (db_file.IsNull()) {
535
0
        throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");
536
0
    }
537
538
48
    uint32_t page_size = 4096; // Default page size
539
540
    // Read the outer metapage
541
    // Expected page number is 0
542
48
    MetaPage outer_meta(0);
543
48
    db_file >> outer_meta;
544
48
    page_size = outer_meta.pagesize;
545
546
    // Verify the size of the file is a multiple of the page size
547
48
    const int64_t size{db_file.size()};
548
549
    // Since BDB stores everything in a page, the file size should be a multiple of the page size;
550
    // However, BDB doesn't actually check that this is the case, and enforcing this check results
551
    // in us rejecting a database that BDB would not, so this check needs to be excluded.
552
    // This is left commented out as a reminder to not accidentally implement this in the future.
553
    // if (size % page_size != 0) {
554
    //     throw std::runtime_error("File size is not a multiple of page size");
555
    // }
556
557
    // Check the last page number
558
48
    uint32_t expected_last_page{uint32_t((size / page_size) - 1)};
559
48
    if (outer_meta.last_page != expected_last_page) {
560
0
        throw std::runtime_error("Last page number could not fit in file");
561
0
    }
562
563
    // Make sure encryption is disabled
564
48
    if (outer_meta.encrypt_algo != 0) {
565
0
        throw std::runtime_error("BDB builtin encryption is not supported");
566
0
    }
567
568
    // Check all Log Sequence Numbers (LSN) point to file 0 and offset 1 which indicates that the LSNs were
569
    // reset and that the log files are not necessary to get all of the data in the database.
570
282
    for (uint32_t i = 0; i <= outer_meta.last_page; ++i) {
571
        // The LSN is composed of 2 32-bit ints, the first is a file id, the second an offset
572
        // It will always be the first 8 bytes of a page, so we deserialize it directly for every page
573
234
        uint32_t file;
574
234
        uint32_t offset;
575
234
        SeekToPage(db_file, i, page_size);
576
234
        db_file >> file >> offset;
577
234
        if (outer_meta.other_endian) {
578
0
            file = internal_bswap_32(file);
579
0
            offset = internal_bswap_32(offset);
580
0
        }
581
234
        if (file != 0 || offset != 1) {
582
0
            throw std::runtime_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support");
583
0
        }
584
234
    }
585
586
    // Read the root page
587
48
    SeekToPage(db_file, outer_meta.root, page_size);
588
48
    PageHeader header(outer_meta.root, outer_meta.other_endian);
589
48
    db_file >> header;
590
48
    if (header.type != PageType::BTREE_LEAF) {
591
0
        throw std::runtime_error("Unexpected outer database root page type");
592
0
    }
593
48
    if (header.entries != 2) {
594
0
        throw std::runtime_error("Unexpected number of entries in outer database root page");
595
0
    }
596
48
    RecordsPage page(header);
597
48
    db_file >> page;
598
599
    // First record should be the string "main"
600
48
    if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {
601
0
        throw std::runtime_error("Subdatabase has an unexpected name");
602
0
    }
603
    // Check length of page number for subdatabase location
604
48
    if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {
605
0
        throw std::runtime_error("Subdatabase page number has unexpected length");
606
0
    }
607
608
    // Read subdatabase page number
609
    // It is written as a big endian 32 bit number
610
48
    uint32_t main_db_page = ReadBE32(std::get<DataRecord>(page.records.at(1)).data.data());
611
612
    // The main database is in a page that doesn't exist
613
48
    if (main_db_page > outer_meta.last_page) {
614
0
        throw std::runtime_error("Page number is greater than database last page");
615
0
    }
616
617
    // Read the inner metapage
618
48
    SeekToPage(db_file, main_db_page, page_size);
619
48
    MetaPage inner_meta(main_db_page);
620
48
    db_file >> inner_meta;
621
622
48
    if (inner_meta.pagesize != page_size) {
623
0
        throw std::runtime_error("Unexpected page size");
624
0
    }
625
626
48
    if (inner_meta.last_page > outer_meta.last_page) {
627
0
        throw std::runtime_error("Subdatabase last page is greater than database last page");
628
0
    }
629
48
    uint64_t max_data_size = static_cast<uint64_t>(outer_meta.last_page) * page_size;
630
631
    // Make sure encryption is disabled
632
48
    if (inner_meta.encrypt_algo != 0) {
633
0
        throw std::runtime_error("BDB builtin encryption is not supported");
634
0
    }
635
636
    // Read the root's level from its header
637
    // Note that we will read the root page twice in order to process it.
638
48
    SeekToPage(db_file, inner_meta.root, page_size);
639
48
    PageHeader root_header(inner_meta.root, inner_meta.other_endian);
640
48
    db_file >> root_header;
641
642
    // Do a DFS through the BTree, starting at root
643
    // We track the expected level of each page in order to avoid loops
644
48
    std::vector<std::pair<uint32_t, uint32_t>> pages{{inner_meta.root, root_header.level}};
645
138
    while (pages.size() > 0) {
646
90
        auto [curr_page, expected_level] = pages.back();
647
        // It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
648
        // last page. While we should be checking this, we can't.
649
        // This is left commented out as a reminder to not accidentally implement this in the future.
650
        // if (curr_page > inner_meta.last_page) {
651
        //     throw std::runtime_error("Page number is greater than subdatabase last page");
652
        // }
653
90
        pages.pop_back();
654
90
        SeekToPage(db_file, curr_page, page_size);
655
90
        PageHeader header(curr_page, inner_meta.other_endian);
656
90
        db_file >> header;
657
90
        if (header.level != expected_level) {
658
0
            throw std::runtime_error("BTree page has an unexpected level");
659
0
        }
660
90
        switch (header.type) {
661
14
        case PageType::BTREE_INTERNAL: {
662
14
            InternalPage int_page(header);
663
14
            db_file >> int_page;
664
42
            for (const InternalRecord& rec : int_page.records) {
665
42
                if (rec.m_header.deleted) continue;
666
42
                pages.emplace_back(rec.page_num, header.level - 1);
667
42
            }
668
14
            break;
669
0
        }
670
76
        case PageType::BTREE_LEAF: {
671
76
            if (header.level != 1) {
672
0
                throw std::runtime_error("BTree Leaf page is not at level 1");
673
0
            }
674
76
            RecordsPage rec_page(header);
675
76
            db_file >> rec_page;
676
76
            if (rec_page.records.size() % 2 != 0) {
677
                // BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected
678
0
                throw std::runtime_error("Records page has odd number of records");
679
0
            }
680
76
            bool is_key = true;
681
76
            std::vector<std::byte> key;
682
2.61k
            for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
683
2.61k
                std::vector<std::byte> data;
684
2.61k
                if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
685
2.61k
                    if (drec->m_header.deleted) continue;
686
2.61k
                    data = drec->data;
687
2.61k
                } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
688
0
                    if (orec->m_header.deleted) continue;
689
0
                    uint32_t next_page = orec->page_number;
690
0
                    if (orec->item_len > max_data_size) {
691
0
                        throw std::runtime_error("Overflow record has an impossible length");
692
0
                    }
693
0
                    while (next_page != 0) {
694
0
                        SeekToPage(db_file, next_page, page_size);
695
0
                        PageHeader opage_header(next_page, inner_meta.other_endian);
696
0
                        db_file >> opage_header;
697
0
                        if (opage_header.type != PageType::OVERFLOW_DATA) {
698
0
                            throw std::runtime_error("Bad overflow record page type");
699
0
                        }
700
0
                        OverflowPage opage(opage_header);
701
0
                        db_file >> opage;
702
0
                        data.insert(data.end(), opage.data.begin(), opage.data.end());
703
0
                        if (data.size() > orec->item_len) {
704
0
                            throw std::runtime_error("Overflow record data is larger than stated size");
705
0
                        }
706
0
                        next_page = opage_header.next_page;
707
0
                    }
708
0
                }
709
710
2.61k
                if (is_key) {
711
1.30k
                    key = data;
712
1.30k
                } else {
713
1.30k
                    m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
714
1.30k
                    key.clear();
715
1.30k
                }
716
2.61k
                is_key = !is_key;
717
2.61k
            }
718
76
            break;
719
76
        }
720
76
        default:
721
0
            throw std::runtime_error("Unexpected page type");
722
90
        }
723
90
    }
724
48
}
725
726
std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch()
727
94
{
728
94
    return std::make_unique<BerkeleyROBatch>(*this);
729
94
}
730
731
bool BerkeleyRODatabase::Backup(const std::string& dest) const
732
48
{
733
48
    fs::path src(m_filepath);
734
48
    fs::path dst(fs::PathFromString(dest));
735
736
48
    if (fs::is_directory(dst)) {
737
0
        dst = BDBDataFile(dst);
738
0
    }
739
48
    try {
740
48
        if (fs::exists(dst) && fs::equivalent(src, dst)) {
741
0
            LogWarning("cannot backup to wallet source file %s", fs::PathToString(dst));
742
0
            return false;
743
0
        }
744
745
48
        fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
746
48
        LogInfo("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
747
48
        return true;
748
48
    } catch (const fs::filesystem_error& e) {
749
0
        LogWarning("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), e.code().message());
750
0
        return false;
751
0
    }
752
48
}
753
754
bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
755
96
{
756
96
    SerializeData key_data{key.begin(), key.end()};
757
96
    const auto it{m_database.m_records.find(key_data)};
758
96
    if (it == m_database.m_records.end()) {
759
0
        return false;
760
0
    }
761
96
    auto val = it->second;
762
96
    value.clear();
763
96
    value.write(std::span(val));
764
96
    return true;
765
96
}
766
767
bool BerkeleyROBatch::HasKey(DataStream&& key)
768
0
{
769
0
    SerializeData key_data{key.begin(), key.end()};
770
0
    return m_database.m_records.contains(key_data);
771
0
}
772
773
BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, std::span<const std::byte> prefix)
774
957
    : m_database(database)
775
957
{
776
957
    std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
777
957
}
778
779
DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
780
3.17k
{
781
3.17k
    if (m_cursor == m_cursor_end) {
782
957
        return DatabaseCursor::Status::DONE;
783
957
    }
784
2.21k
    ssKey.write(std::span(m_cursor->first));
785
2.21k
    ssValue.write(std::span(m_cursor->second));
786
2.21k
    m_cursor++;
787
2.21k
    return DatabaseCursor::Status::MORE;
788
3.17k
}
789
790
std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(std::span<const std::byte> prefix)
791
912
{
792
912
    return std::make_unique<BerkeleyROCursor>(m_database, prefix);
793
912
}
794
795
std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
796
48
{
797
48
    fs::path data_file = BDBDataFile(path);
798
48
    try {
799
48
        std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
800
48
        status = DatabaseStatus::SUCCESS;
801
48
        return db;
802
48
    } catch (const std::runtime_error& e) {
803
0
        error.original = e.what();
804
0
        status = DatabaseStatus::FAILED_LOAD;
805
0
        return nullptr;
806
0
    }
807
48
}
808
} // namespace wallet