Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
501}502s.ignore(to_jump);504// Read the record505RecordHeader rec_hdr(m_header.other_endian);506s >> rec_hdr;507to_jump += RecordHeader::SIZE;509if (rec_hdr.type != RecordType::KEYDATA) {510throw std::runtime_error("Unknown record type in internal page");511}512InternalRecord record(rec_hdr);513s >> record;514records.emplace_back(record);515to_jump += InternalRecord::FIXED_SIZE + rec_hdr.len;517// Go back to the indexes518s.seek(-to_jump, SEEK_CUR);519}520}521};523static void SeekToPage(AutoFile& s, uint32_t page_num, uint32_t page_size)524{525int64_t pos = int64_t{page_num} * page_size;526s.seek(pos, SEEK_SET);527}529void BerkeleyRODatabase::Open()530{531// Open the file532FILE* file = fsbridge::fopen(m_filepath, "rb");533AutoFile db_file(file);534if (db_file.IsNull()) {535throw std::runtime_error("BerkeleyRODatabase: Failed to open database file");536}538uint32_t page_size = 4096; // Default page size540// Read the outer metapage541// Expected page number is 0542MetaPage outer_meta(0);543db_file >> outer_meta;544page_size = outer_meta.pagesize;546// Verify the size of the file is a multiple of the page size547const int64_t size{db_file.size()};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 results551// 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// }557// Check the last page number558uint32_t expected_last_page{uint32_t((size / page_size) - 1)};559if (outer_meta.last_page != expected_last_page) {560throw std::runtime_error("Last page number could not fit in file");561}563// Make sure encryption is disabled564if (outer_meta.encrypt_algo != 0) {565throw std::runtime_error("BDB builtin encryption is not supported");566}568// Check all Log Sequence Numbers (LSN) point to file 0 and offset 1 which indicates that the LSNs were569// reset and that the log files are not necessary to get all of the data in the database.570for (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 offset572// It will always be the first 8 bytes of a page, so we deserialize it directly for every page573uint32_t file;574uint32_t offset;575SeekToPage(db_file, i, page_size);576db_file >> file >> offset;577if (outer_meta.other_endian) {578file = internal_bswap_32(file);579offset = internal_bswap_32(offset);580}581if (file != 0 || offset != 1) {582throw 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}586// Read the root page587SeekToPage(db_file, outer_meta.root, page_size);588PageHeader header(outer_meta.root, outer_meta.other_endian);589db_file >> header;590if (header.type != PageType::BTREE_LEAF) {591throw std::runtime_error("Unexpected outer database root page type");592}593if (header.entries != 2) {594throw std::runtime_error("Unexpected number of entries in outer database root page");595}596RecordsPage page(header);597db_file >> page;599// First record should be the string "main"600if (!std::holds_alternative<DataRecord>(page.records.at(0)) || std::get<DataRecord>(page.records.at(0)).data != SUBDATABASE_NAME) {601throw std::runtime_error("Subdatabase has an unexpected name");602}603// Check length of page number for subdatabase location604if (!std::holds_alternative<DataRecord>(page.records.at(1)) || std::get<DataRecord>(page.records.at(1)).m_header.len != 4) {605throw std::runtime_error("Subdatabase page number has unexpected length");606}608// Read subdatabase page number609// It is written as a big endian 32 bit number610uint32_t main_db_page = ReadBE32(std::get<DataRecord>(page.records.at(1)).data.data());612// The main database is in a page that doesn't exist613if (main_db_page > outer_meta.last_page) {614throw std::runtime_error("Page number is greater than database last page");615}617// Read the inner metapage618SeekToPage(db_file, main_db_page, page_size);619MetaPage inner_meta(main_db_page);620db_file >> inner_meta;622if (inner_meta.pagesize != page_size) {623throw std::runtime_error("Unexpected page size");624}626if (inner_meta.last_page > outer_meta.last_page) {627throw std::runtime_error("Subdatabase last page is greater than database last page");628}629uint64_t max_data_size = static_cast<uint64_t>(outer_meta.last_page) * page_size;631// Make sure encryption is disabled632if (inner_meta.encrypt_algo != 0) {633throw std::runtime_error("BDB builtin encryption is not supported");634}636// Read the root's level from its header637// Note that we will read the root page twice in order to process it.638SeekToPage(db_file, inner_meta.root, page_size);639PageHeader root_header(inner_meta.root, inner_meta.other_endian);640db_file >> root_header;642// Do a DFS through the BTree, starting at root643// We track the expected level of each page in order to avoid loops644std::vector<std::pair<uint32_t, uint32_t>> pages{{inner_meta.root, root_header.level}};645while (pages.size() > 0) {646auto [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 correct648// 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// }653pages.pop_back();654SeekToPage(db_file, curr_page, page_size);655PageHeader header(curr_page, inner_meta.other_endian);656db_file >> header;657if (header.level != expected_level) {658throw std::runtime_error("BTree page has an unexpected level");659}660switch (header.type) {661case PageType::BTREE_INTERNAL: {662InternalPage int_page(header);663db_file >> int_page;664for (const InternalRecord& rec : int_page.records) {665if (rec.m_header.deleted) continue;666pages.emplace_back(rec.page_num, header.level - 1);667}668break;669}670case PageType::BTREE_LEAF: {671if (header.level != 1) {672throw std::runtime_error("BTree Leaf page is not at level 1");673}674RecordsPage rec_page(header);675db_file >> rec_page;676if (rec_page.records.size() % 2 != 0) {677// BDB stores key value pairs in consecutive records, thus an odd number of records is unexpected678throw std::runtime_error("Records page has odd number of records");679}680bool is_key = true;681std::vector<std::byte> key;682for (const std::variant<DataRecord, OverflowRecord>& rec : rec_page.records) {683std::vector<std::byte> data;684if (const DataRecord* drec = std::get_if<DataRecord>(&rec)) {685if (drec->m_header.deleted) continue;686data = drec->data;687} else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {688if (orec->m_header.deleted) continue;689uint32_t next_page = orec->page_number;690if (orec->item_len > max_data_size) {691throw std::runtime_error("Overflow record has an impossible length");692}693while (next_page != 0) {694SeekToPage(db_file, next_page, page_size);695PageHeader opage_header(next_page, inner_meta.other_endian);696db_file >> opage_header;697if (opage_header.type != PageType::OVERFLOW_DATA) {698throw std::runtime_error("Bad overflow record page type");699}700OverflowPage opage(opage_header);701db_file >> opage;702data.insert(data.end(), opage.data.begin(), opage.data.end());703if (data.size() > orec->item_len) {704throw std::runtime_error("Overflow record data is larger than stated size");705}706next_page = opage_header.next_page;707}708}710if (is_key) {711key = data;712} else {713m_records.emplace(SerializeData{key.begin(), key.end()}, SerializeData{data.begin(), data.end()});714key.clear();715}716is_key = !is_key;717}718break;719}720default:721throw std::runtime_error("Unexpected page type");722}723}724}726std::unique_ptr<DatabaseBatch> BerkeleyRODatabase::MakeBatch()727{728return std::make_unique<BerkeleyROBatch>(*this);729}731bool BerkeleyRODatabase::Backup(const std::string& dest) const732{733fs::path src(m_filepath);734fs::path dst(fs::PathFromString(dest));736if (fs::is_directory(dst)) {737dst = BDBDataFile(dst);738}739try {740if (fs::exists(dst) && fs::equivalent(src, dst)) {741LogWarning("cannot backup to wallet source file %s", fs::PathToString(dst));742return false;743}745fs::copy_file(src, dst, fs::copy_options::overwrite_existing);746LogInfo("copied %s to %s\n", fs::PathToString(m_filepath), fs::PathToString(dst));747return true;748} catch (const fs::filesystem_error& e) {749LogWarning("error copying %s to %s - %s\n", fs::PathToString(m_filepath), fs::PathToString(dst), e.code().message());750return false;