migrate.cpp raw

   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      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      {
 113          s >> lsn_file;
 114          s >> lsn_offset;
 115          s >> page_num;
 116          s >> magic;
 117          s >> version;
 118          s >> pagesize;
 119          s >> encrypt_algo;
 120  
 121          other_endian = magic == BTREE_MAGIC_OE;
 122  
 123          uint8_t uint8_type;
 124          s >> uint8_type;
 125          type = static_cast<PageType>(uint8_type);
 126  
 127          s >> metaflags;
 128          s >> unused1;
 129          s >> free_list;
 130          s >> last_page;
 131          s >> partitions;
 132          s >> key_count;
 133          s >> record_count;
 134  
 135          uint32_t uint32_flags;
 136          s >> uint32_flags;
 137          if (other_endian) {
 138              uint32_flags = internal_bswap_32(uint32_flags);
 139          }
 140          flags = static_cast<BTreeFlags>(uint32_flags);
 141  
 142          s >> uid;
 143          s >> unused2;
 144          s >> minkey;
 145          s >> re_len;
 146          s >> re_pad;
 147          s >> root;
 148          s >> unused3;
 149          s >> crypto_magic;
 150          s >> trash;
 151          s >> iv;
 152          s >> chksum;
 153  
 154          if (other_endian) {
 155              lsn_file = internal_bswap_32(lsn_file);
 156              lsn_offset = internal_bswap_32(lsn_offset);
 157              page_num = internal_bswap_32(page_num);
 158              magic = internal_bswap_32(magic);
 159              version = internal_bswap_32(version);
 160              pagesize = internal_bswap_32(pagesize);
 161              free_list = internal_bswap_32(free_list);
 162              last_page = internal_bswap_32(last_page);
 163              partitions = internal_bswap_32(partitions);
 164              key_count = internal_bswap_32(key_count);
 165              record_count = internal_bswap_32(record_count);
 166              unused2 = internal_bswap_32(unused2);
 167              minkey = internal_bswap_32(minkey);
 168              re_len = internal_bswap_32(re_len);
 169              re_pad = internal_bswap_32(re_pad);
 170              root = internal_bswap_32(root);
 171              crypto_magic = internal_bswap_32(crypto_magic);
 172          }
 173  
 174          // Page number must match
 175          if (page_num != expected_page_num) {
 176              throw std::runtime_error("Meta page number mismatch");
 177          }
 178  
 179          // Check magic
 180          if (magic != BTREE_MAGIC) {
 181              throw std::runtime_error("Not a BDB file");
 182          }
 183  
 184          // Only version 9 is supported
 185          if (version != 9) {
 186              throw std::runtime_error("Unsupported BDB data file version number");
 187          }
 188  
 189          // Page size must be 512 <= pagesize <= 64k, and be a power of 2
 190          if (pagesize < 512 || pagesize > 65536 || (pagesize & (pagesize - 1)) != 0) {
 191              throw std::runtime_error("Bad page size");
 192          }
 193  
 194          // Page type must be the btree type
 195          if (type != PageType::BTREE_META) {
 196              throw std::runtime_error("Unexpected page type, should be 9 (BTree Metadata)");
 197          }
 198  
 199          // Only supported meta-flag is subdatabase
 200          if (flags != BTreeFlags::SUBDB) {
 201              throw std::runtime_error("Unexpected database flags, should only be 0x20 (subdatabases)");
 202          }
 203      }
 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      RecordHeader(bool other_endian) : other_endian(other_endian) {}
 219      RecordHeader() = delete;
 220  
 221      template <typename Stream>
 222      void Unserialize(Stream& s)
 223      {
 224          s >> len;
 225  
 226          uint8_t uint8_type;
 227          s >> uint8_type;
 228          type = static_cast<RecordType>(uint8_type & ~static_cast<uint8_t>(RecordType::DELETE_FLAG));
 229          deleted = uint8_type & static_cast<uint8_t>(RecordType::DELETE_FLAG);
 230  
 231          if (other_endian) {
 232              len = internal_bswap_16(len);
 233          }
 234      }
 235  };
 236  
 237  /** Class for data in the record directly */
 238  class DataRecord
 239  {
 240  public:
 241      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      {
 251          data.resize(m_header.len);
 252          s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
 253      }
 254  };
 255  
 256  /** Class for records representing internal nodes of the BTree. */
 257  class InternalRecord
 258  {
 259  public:
 260      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      {
 275          s >> unused;
 276          s >> page_num;
 277          s >> records;
 278  
 279          data.resize(m_header.len);
 280          s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
 281  
 282          if (m_header.other_endian) {
 283              page_num = internal_bswap_32(page_num);
 284              records = internal_bswap_32(records);
 285          }
 286      }
 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      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      {
 310          s >> unused2;
 311          s >> page_number;
 312          s >> item_len;
 313  
 314          if (m_header.other_endian) {
 315              page_number = internal_bswap_32(page_number);
 316              item_len = internal_bswap_32(item_len);
 317          }
 318      }
 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      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      {
 346          s >> lsn_file;
 347          s >> lsn_offset;
 348          s >> page_num;
 349          s >> prev_page;
 350          s >> next_page;
 351          s >> entries;
 352          s >> hf_offset;
 353          s >> level;
 354  
 355          uint8_t uint8_type;
 356          s >> uint8_type;
 357          type = static_cast<PageType>(uint8_type);
 358  
 359          if (other_endian) {
 360              lsn_file = internal_bswap_32(lsn_file);
 361              lsn_offset = internal_bswap_32(lsn_offset);
 362              page_num = internal_bswap_32(page_num);
 363              prev_page = internal_bswap_32(prev_page);
 364              next_page = internal_bswap_32(next_page);
 365              entries = internal_bswap_16(entries);
 366              hf_offset = internal_bswap_16(hf_offset);
 367          }
 368  
 369          if (expected_page_num != page_num) {
 370              throw std::runtime_error("Page number mismatch");
 371          }
 372          if ((type != PageType::OVERFLOW_DATA && level < 1) || (type == PageType::OVERFLOW_DATA && level != 0)) {
 373              throw std::runtime_error("Bad btree level");
 374          }
 375      }
 376  };
 377  
 378  /** A page of records in the database */
 379  class RecordsPage
 380  {
 381  public:
 382      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      {
 393          // Current position within the page
 394          int64_t pos = PageHeader::SIZE;
 395  
 396          // Get the items
 397          for (uint32_t i = 0; i < m_header.entries; ++i) {
 398              // Get the index
 399              uint16_t index;
 400              s >> index;
 401              if (m_header.other_endian) {
 402                  index = internal_bswap_16(index);
 403              }
 404              indexes.push_back(index);
 405              pos += sizeof(uint16_t);
 406  
 407              // Go to the offset from the index
 408              int64_t to_jump = index - pos;
 409              if (to_jump < 0) {
 410                  throw std::runtime_error("Data record position not in page");
 411              }
 412              s.ignore(to_jump);
 413  
 414              // Read the record
 415              RecordHeader rec_hdr(m_header.other_endian);
 416              s >> rec_hdr;
 417              to_jump += RecordHeader::SIZE;
 418  
 419              switch (rec_hdr.type) {
 420              case RecordType::KEYDATA: {
 421                  DataRecord record(rec_hdr);
 422                  s >> record;
 423                  records.emplace_back(record);
 424                  to_jump += rec_hdr.len;
 425                  break;
 426              }
 427              case RecordType::OVERFLOW_DATA: {
 428                  OverflowRecord record(rec_hdr);
 429                  s >> record;
 430                  records.emplace_back(record);
 431                  to_jump += OverflowRecord::SIZE;
 432                  break;
 433              }
 434              default:
 435                  throw std::runtime_error("Unknown record type in records page");
 436              }
 437  
 438              // Go back to the indexes
 439              s.seek(-to_jump, SEEK_CUR);
 440          }
 441      }
 442  };
 443  
 444  /** A page containing overflow data */
 445  class OverflowPage
 446  {
 447  public:
 448      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      {
 463          data.resize(m_header.hf_offset);
 464          s.read(std::as_writable_bytes(std::span(data.data(), data.size())));
 465      }
 466  };
 467  
 468  /** A page of records in the database */
 469  class InternalPage
 470  {
 471  public:
 472      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      {
 483          // Current position within the page
 484          int64_t pos = PageHeader::SIZE;
 485  
 486          // Get the items
 487          for (uint32_t i = 0; i < m_header.entries; ++i) {
 488              // Get the index
 489              uint16_t index;
 490              s >> index;
 491              if (m_header.other_endian) {
 492                  index = internal_bswap_16(index);
 493              }
 494              indexes.push_back(index);
 495              pos += sizeof(uint16_t);
 496  
 497              // Go to the offset from the index
 498              int64_t to_jump = index - pos;
 499              if (to_jump < 0) {
 500                  throw std::runtime_error("Internal record position not in page");
 501              }
 502              s.ignore(to_jump);
 503  
 504              // Read the record
 505              RecordHeader rec_hdr(m_header.other_endian);
 506              s >> rec_hdr;
 507              to_jump += RecordHeader::SIZE;
 508  
 509              if (rec_hdr.type != RecordType::KEYDATA) {
 510                  throw std::runtime_error("Unknown record type in internal page");
 511              }
 512              InternalRecord record(rec_hdr);
 513              s >> record;
 514              records.emplace_back(record);
 515              to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;
 516  
 517              // Go back to the indexes
 518              s.seek(-to_jump, SEEK_CUR);
 519          }
 520      }
 521  };
 522  
 523  static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)
 524  {
 525      int64_t pos = int64_t{page_num} * page_size;
 526      s.seek(pos, SEEK_SET);
 527  }
 528  
 529  void BerkeleyRODatabase::Open()
 530  {
 531      // Open the file
 532      FILE* file = fsbridge::fopen(m_filepath, "rb");
 533      AutoFile db_file(file);
 534      if (db_file.IsNull()) {
 535          throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");
 536      }
 537  
 538      uint32_t page_size = 4096; // Default page size
 539  
 540      // Read the outer metapage
 541      // Expected page number is 0
 542      MetaPage outer_meta(0);
 543      db_file >> outer_meta;
 544      page_size = outer_meta.pagesize;
 545  
 546      // Verify the size of the file is a multiple of the page size
 547      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      uint32_t expected_last_page{uint32_t((size / page_size) - 1)};
 559      if (outer_meta.last_page != expected_last_page) {
 560          throw std::runtime_error("Last page number could not fit in file");
 561      }
 562  
 563      // Make sure encryption is disabled
 564      if (outer_meta.encrypt_algo != 0) {
 565          throw std::runtime_error("BDB builtin encryption is not supported");
 566      }
 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      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          uint32_t file;
 574          uint32_t offset;
 575          SeekToPage(db_file, i, page_size);
 576          db_file >> file >> offset;
 577          if (outer_meta.other_endian) {
 578              file = internal_bswap_32(file);
 579              offset = internal_bswap_32(offset);
 580          }
 581          if (file != 0 || offset != 1) {
 582              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          }
 584      }
 585  
 586      // Read the root page
 587      SeekToPage(db_file, outer_meta.root, page_size);
 588      PageHeader header(outer_meta.root, outer_meta.other_endian);
 589      db_file >> header;
 590      if (header.type != PageType::BTREE_LEAF) {
 591          throw std::runtime_error("Unexpected outer database root page type");
 592      }
 593      if (header.entries != 2) {
 594          throw std::runtime_error("Unexpected number of entries in outer database root page");
 595      }
 596      RecordsPage page(header);
 597      db_file >> page;
 598  
 599      // First record should be the string "main"
 600      if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {
 601          throw std::runtime_error("Subdatabase has an unexpected name");
 602      }
 603      // Check length of page number for subdatabase location
 604      if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {
 605          throw std::runtime_error("Subdatabase page number has unexpected length");
 606      }
 607  
 608      // Read subdatabase page number
 609      // It is written as a big endian 32 bit number
 610      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      if (main_db_page > outer_meta.last_page) {
 614          throw std::runtime_error("Page number is greater than database last page");
 615      }
 616  
 617      // Read the inner metapage
 618      SeekToPage(db_file, main_db_page, page_size);
 619      MetaPage inner_meta(main_db_page);
 620      db_file >> inner_meta;
 621  
 622      if (inner_meta.pagesize != page_size) {
 623          throw std::runtime_error("Unexpected page size");
 624      }
 625  
 626      if (inner_meta.last_page > outer_meta.last_page) {
 627          throw std::runtime_error("Subdatabase last page is greater than database last page");
 628      }
 629      uint64_t max_data_size = static_cast<uint64_t>(outer_meta.last_page) * page_size;
 630  
 631      // Make sure encryption is disabled
 632      if (inner_meta.encrypt_algo != 0) {
 633          throw std::runtime_error("BDB builtin encryption is not supported");
 634      }
 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      SeekToPage(db_file, inner_meta.root, page_size);
 639      PageHeader root_header(inner_meta.root, inner_meta.other_endian);
 640      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      std::vector<std::pair<uint32_t, uint32_t>> pages{{inner_meta.root, root_header.level}};
 645      while (pages.size() > 0) {
 646          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          pages.pop_back();
 654          SeekToPage(db_file, curr_page, page_size);
 655          PageHeader header(curr_page, inner_meta.other_endian);
 656          db_file >> header;
 657          if (header.level != expected_level) {
 658              throw std::runtime_error("BTree page has an unexpected level");
 659          }
 660          switch (header.type) {
 661          case PageType::BTREE_INTERNAL: {
 662              InternalPage int_page(header);
 663              db_file >> int_page;
 664              for (const InternalRecord& rec : int_page.records) {
 665                  if (rec.m_header.deleted) continue;
 666                  pages.emplace_back(rec.page_num, header.level - 1);
 667              }
 668              break;
 669          }
 670          case PageType::BTREE_LEAF: {
 671              if (header.level != 1) {
 672                  throw std::runtime_error("BTree Leaf page is not at level 1");
 673              }
 674              RecordsPage rec_page(header);
 675              db_file >> rec_page;
 676              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                  throw std::runtime_error("Records page has odd number of records");
 679              }
 680              bool is_key = true;
 681              std::vector<std::byte> key;
 682              for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
 683                  std::vector<std::byte> data;
 684                  if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
 685                      if (drec->m_header.deleted) continue;
 686                      data = drec->data;
 687                  } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
 688                      if (orec->m_header.deleted) continue;
 689                      uint32_t next_page = orec->page_number;
 690                      if (orec->item_len > max_data_size) {
 691                          throw std::runtime_error("Overflow record has an impossible length");
 692                      }
 693                      while (next_page != 0) {
 694                          SeekToPage(db_file, next_page, page_size);
 695                          PageHeader opage_header(next_page, inner_meta.other_endian);
 696                          db_file >> opage_header;
 697                          if (opage_header.type != PageType::OVERFLOW_DATA) {
 698                              throw std::runtime_error("Bad overflow record page type");
 699                          }
 700                          OverflowPage opage(opage_header);
 701                          db_file >> opage;
 702                          data.insert(data.end(), opage.data.begin(), opage.data.end());
 703                          if (data.size() > orec->item_len) {
 704                              throw std::runtime_error("Overflow record data is larger than stated size");
 705                          }
 706                          next_page = opage_header.next_page;
 707                      }
 708                  }
 709  
 710                  if (is_key) {
 711                      key = data;
 712                  } else {
 713                      m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
 714                      key.clear();
 715                  }
 716                  is_key = !is_key;
 717              }
 718              break;
 719          }
 720          default:
 721              throw std::runtime_error("Unexpected page type");
 722          }
 723      }
 724  }
 725  
 726  std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch()
 727  {
 728      return std::make_unique<BerkeleyROBatch>(*this);
 729  }
 730  
 731  bool BerkeleyRODatabase::Backup(const std::string& dest) const
 732  {
 733      fs::path src(m_filepath);
 734      fs::path dst(fs::PathFromString(dest));
 735  
 736      if (fs::is_directory(dst)) {
 737          dst = BDBDataFile(dst);
 738      }
 739      try {
 740          if (fs::exists(dst) && fs::equivalent(src, dst)) {
 741              LogWarning("cannot backup to wallet source file %s", fs::PathToString(dst));
 742              return false;
 743          }
 744  
 745          fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
 746          LogInfo("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
 747          return true;
 748      } catch (const fs::filesystem_error& e) {
 749          LogWarning("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), e.code().message());
 750          return false;
 751      }
 752  }
 753  
 754  bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
 755  {
 756      SerializeData key_data{key.begin(), key.end()};
 757      const auto it{m_database.m_records.find(key_data)};
 758      if (it == m_database.m_records.end()) {
 759          return false;
 760      }
 761      auto val = it->second;
 762      value.clear();
 763      value.write(std::span(val));
 764      return true;
 765  }
 766  
 767  bool BerkeleyROBatch::HasKey(DataStream&& key)
 768  {
 769      SerializeData key_data{key.begin(), key.end()};
 770      return m_database.m_records.contains(key_data);
 771  }
 772  
 773  BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, std::span<const std::byte> prefix)
 774      : m_database(database)
 775  {
 776      std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
 777  }
 778  
 779  DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
 780  {
 781      if (m_cursor == m_cursor_end) {
 782          return DatabaseCursor::Status::DONE;
 783      }
 784      ssKey.write(std::span(m_cursor->first));
 785      ssValue.write(std::span(m_cursor->second));
 786      m_cursor++;
 787      return DatabaseCursor::Status::MORE;
 788  }
 789  
 790  std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(std::span<const std::byte> prefix)
 791  {
 792      return std::make_unique<BerkeleyROCursor>(m_database, prefix);
 793  }
 794  
 795  std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
 796  {
 797      fs::path data_file = BDBDataFile(path);
 798      try {
 799          std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
 800          status = DatabaseStatus::SUCCESS;
 801          return db;
 802      } catch (const std::runtime_error& e) {
 803          error.original = e.what();
 804          status = DatabaseStatus::FAILED_LOAD;
 805          return nullptr;
 806      }
 807  }
 808  } // namespace wallet
 809