/ src / wallet / migrate.cpp
migrate.cpp
  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 <logging.h>
  8  #include <streams.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  
630      // Make sure encryption is disabled
631      if (inner_meta.encrypt_algo != 0) {
632          throw std::runtime_error("BDB builtin encryption is not supported");
633      }
634  
635      // Do a DFS through the BTree, starting at root
636      std::vector<uint32_t> pages{inner_meta.root};
637      while (pages.size() > 0) {
638          uint32_t curr_page = pages.back();
639          // It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
640          // last page. While we should be checking this, we can't.
641          // This is left commented out as a reminder to not accidentally implement this in the future.
642          // if (curr_page > inner_meta.last_page) {
643          //     throw std::runtime_error("Page number is greater than subdatabase last page");
644          // }
645          pages.pop_back();
646          SeekToPage(db_file, curr_page, page_size);
647          PageHeader header(curr_page, inner_meta.other_endian);
648          db_file >> header;
649          switch (header.type) {
650          case PageType::BTREE_INTERNAL: {
651              InternalPage int_page(header);
652              db_file >> int_page;
653              for (const InternalRecord& rec : int_page.records) {
654                  if (rec.m_header.deleted) continue;
655                  pages.push_back(rec.page_num);
656              }
657              break;
658          }
659          case PageType::BTREE_LEAF: {
660              RecordsPage rec_page(header);
661              db_file >> rec_page;
662              if (rec_page.records.size() % 2 != 0) {
663                  // BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected
664                  throw std::runtime_error("Records page has odd number of records");
665              }
666              bool is_key = true;
667              std::vector<std::byte> key;
668              for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {
669                  std::vector<std::byte> data;
670                  if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {
671                      if (drec->m_header.deleted) continue;
672                      data = drec->data;
673                  } else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
674                      if (orec->m_header.deleted) continue;
675                      uint32_t next_page = orec->page_number;
676                      while (next_page != 0) {
677                          SeekToPage(db_file, next_page, page_size);
678                          PageHeader opage_header(next_page, inner_meta.other_endian);
679                          db_file >> opage_header;
680                          if (opage_header.type != PageType::OVERFLOW_DATA) {
681                              throw std::runtime_error("Bad overflow record page type");
682                          }
683                          OverflowPage opage(opage_header);
684                          db_file >> opage;
685                          data.insert(data.end(), opage.data.begin(), opage.data.end());
686                          next_page = opage_header.next_page;
687                      }
688                  }
689  
690                  if (is_key) {
691                      key = data;
692                  } else {
693                      m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});
694                      key.clear();
695                  }
696                  is_key = !is_key;
697              }
698              break;
699          }
700          default:
701              throw std::runtime_error("Unexpected page type");
702          }
703      }
704  }
705  
706  std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch()
707  {
708      return std::make_unique<BerkeleyROBatch>(*this);
709  }
710  
711  bool BerkeleyRODatabase::Backup(const std::string& dest) const
712  {
713      fs::path src(m_filepath);
714      fs::path dst(fs::PathFromString(dest));
715  
716      if (fs::is_directory(dst)) {
717          dst = BDBDataFile(dst);
718      }
719      try {
720          if (fs::exists(dst) && fs::equivalent(src, dst)) {
721              LogWarning("cannot backup to wallet source file %s", fs::PathToString(dst));
722              return false;
723          }
724  
725          fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
726          LogInfo("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));
727          return true;
728      } catch (const fs::filesystem_error& e) {
729          LogWarning("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), e.code().message());
730          return false;
731      }
732  }
733  
734  bool BerkeleyROBatch::ReadKey(DataStream&& key, DataStream& value)
735  {
736      SerializeData key_data{key.begin(), key.end()};
737      const auto it{m_database.m_records.find(key_data)};
738      if (it == m_database.m_records.end()) {
739          return false;
740      }
741      auto val = it->second;
742      value.clear();
743      value.write(std::span(val));
744      return true;
745  }
746  
747  bool BerkeleyROBatch::HasKey(DataStream&& key)
748  {
749      SerializeData key_data{key.begin(), key.end()};
750      return m_database.m_records.contains(key_data);
751  }
752  
753  BerkeleyROCursor::BerkeleyROCursor(const BerkeleyRODatabase& database, std::span<const std::byte> prefix)
754      : m_database(database)
755  {
756      std::tie(m_cursor, m_cursor_end) = m_database.m_records.equal_range(BytePrefix{prefix});
757  }
758  
759  DatabaseCursor::Status BerkeleyROCursor::Next(DataStream& ssKey, DataStream& ssValue)
760  {
761      if (m_cursor == m_cursor_end) {
762          return DatabaseCursor::Status::DONE;
763      }
764      ssKey.write(std::span(m_cursor->first));
765      ssValue.write(std::span(m_cursor->second));
766      m_cursor++;
767      return DatabaseCursor::Status::MORE;
768  }
769  
770  std::unique_ptr<DatabaseCursor> BerkeleyROBatch::GetNewPrefixCursor(std::span<const std::byte> prefix)
771  {
772      return std::make_unique<BerkeleyROCursor>(m_database, prefix);
773  }
774  
775  std::unique_ptr<BerkeleyRODatabase> MakeBerkeleyRODatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error)
776  {
777      fs::path data_file = BDBDataFile(path);
778      try {
779          std::unique_ptr<BerkeleyRODatabase> db = std::make_unique<BerkeleyRODatabase>(data_file);
780          status = DatabaseStatus::SUCCESS;
781          return db;
782      } catch (const std::runtime_error& e) {
783          error.original = e.what();
784          status = DatabaseStatus::FAILED_LOAD;
785          return nullptr;
786      }
787  }
788  } // namespace wallet