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.
501pwallet->WalletLogPrintf("%s\n", error);502}503result.m_result = std::max(result.m_result, record_res);504++result.m_records;505}506return result;507}509static LoadResult LoadRecords(CWallet* pwallet, DatabaseBatch& batch, const std::string& key, LoadFunc load_func)510{511DataStream prefix;512prefix << key;513return LoadRecords(pwallet, batch, key, prefix, load_func);514}516bool HasLegacyRecords(CWallet& wallet)517{518const auto& batch = wallet.GetDatabase().MakeBatch();519return HasLegacyRecords(wallet, *batch);520}522bool HasLegacyRecords(CWallet& wallet, DatabaseBatch& batch)523{524for (const auto& type : DBKeys::LEGACY_TYPES) {525DataStream key;526DataStream value{};527DataStream prefix;529prefix << type;530std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);531if (!cursor) {532// Could only happen on a closed db, which means there is an error in the code flow.533throw std::runtime_error(strprintf("Error getting database cursor for '%s' records", type));534}536DatabaseCursor::Status status = cursor->Next(key, value);537if (status != DatabaseCursor::Status::DONE) {538return true;539}540}541return false;542}544static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, int last_client) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)545{546AssertLockHeld(pwallet->cs_wallet);547DBErrors result = DBErrors::LOAD_OK;549// Make sure descriptor wallets don't have any legacy records550if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {551if (HasLegacyRecords(*pwallet, batch)) {552pwallet->WalletLogPrintf("Error: Unexpected legacy entry found in descriptor wallet %s. The wallet might have been tampered with or created with malicious intent.\n", pwallet->GetName());553return DBErrors::UNEXPECTED_LEGACY_ENTRY;554}556return DBErrors::LOAD_OK;557}559// Load HD Chain560// Note: There should only be one HDCHAIN record with no data following the type561LoadResult hd_chain_res = LoadRecords(pwallet, batch, DBKeys::HDCHAIN,562[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {563return LoadHDChain(pwallet, value, err) ? DBErrors:: LOAD_OK : DBErrors::CORRUPT;564});565result = std::max(result, hd_chain_res.m_result);567// Load unencrypted keys568LoadResult key_res = LoadRecords(pwallet, batch, DBKeys::KEY,569[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {570return LoadKey(pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;571});572result = std::max(result, key_res.m_result);574// Load encrypted keys575LoadResult ckey_res = LoadRecords(pwallet, batch, DBKeys::CRYPTED_KEY,576[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {577return LoadCryptedKey(pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;578});579result = std::max(result, ckey_res.m_result);581// Load scripts582LoadResult script_res = LoadRecords(pwallet, batch, DBKeys::CSCRIPT,583[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& strErr) {584uint160 hash;585key >> hash;586CScript script;587value >> script;588if (!pwallet->GetOrCreateLegacyDataSPKM()->LoadCScript(script))589{590strErr = "Error reading wallet database: LegacyDataSPKM::LoadCScript failed";591return DBErrors::NONCRITICAL_ERROR;592}593return DBErrors::LOAD_OK;594});595result = std::max(result, script_res.m_result);597// Load keymeta598std::map<uint160, CHDChain> hd_chains;599LoadResult keymeta_res = LoadRecords(pwallet, batch, DBKeys::KEYMETA,600[&hd_chains] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& strErr) {601CPubKey vchPubKey;602key >> vchPubKey;603CKeyMetadata keyMeta;604value >> keyMeta;605pwallet->GetOrCreateLegacyDataSPKM()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);607// Extract some CHDChain info from this metadata if it has any608if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {609// Get the path from the key origin or from the path string610// Not applicable when path is "s" or "m" as those indicate a seed611// See https://github.com/bitcoin/bitcoin/pull/12924612bool internal = false;613uint32_t index = 0;614if (keyMeta.hdKeypath != "s" && keyMeta.hdKeypath != "m") {615std::vector<uint32_t> path;616if (keyMeta.has_key_origin) {617// We have a key origin, so pull it from its path vector618path = keyMeta.key_origin.path;619} else {620// No key origin, have to parse the string621if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {622strErr = "Error reading wallet database: keymeta with invalid HD keypath";623return DBErrors::NONCRITICAL_ERROR;624}625}627// Extract the index and internal from the path628// Path string is m/0'/k'/i'629// Path vector is [0', k', i'] (but as ints OR'd with the hardened bit630// k == 0 for external, 1 for internal. i is the index631if (path.size() != 3) {632strErr = "Error reading wallet database: keymeta found with unexpected path";633return DBErrors::NONCRITICAL_ERROR;634}635if (path[0] != BIP32_HARDENED_FLAG) {636strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);637return DBErrors::NONCRITICAL_ERROR;638}639if (path[1] != BIP32_HARDENED_FLAG && path[1] != (1 | BIP32_HARDENED_FLAG)) {640strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);641return DBErrors::NONCRITICAL_ERROR;642}643if ((path[2] & BIP32_HARDENED_FLAG) == 0) {644strErr = strprintf("Unexpected path index of 0x%08x (expected to be greater than or equal to 0x80000000)", path[2]);645return DBErrors::NONCRITICAL_ERROR;646}647internal = path[1] == (1 | BIP32_HARDENED_FLAG);648index = path[2] & ~BIP32_HARDENED_FLAG;649}651// Insert a new CHDChain, or get the one that already exists652auto [ins, inserted] = hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());653CHDChain& chain = ins->second;654if (inserted) {655// For new chains, we want to default to VERSION_HD_BASE until we see an internal656chain.nVersion = CHDChain::VERSION_HD_BASE;657chain.seed_id = keyMeta.hd_seed_id;658}659if (internal) {660chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;661chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index + 1);662} else {663chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index + 1);664}665}666return DBErrors::LOAD_OK;667});668result = std::max(result, keymeta_res.m_result);670// Set inactive chains671if (!hd_chains.empty()) {672LegacyDataSPKM* legacy_spkm = pwallet->GetLegacyDataSPKM();673if (legacy_spkm) {674for (const auto& [hd_seed_id, chain] : hd_chains) {675if (hd_seed_id != legacy_spkm->GetHDChain().seed_id) {676legacy_spkm->AddInactiveHDChain(chain);677}678}679} else {680pwallet->WalletLogPrintf("Inactive HD chains found but no LegacyDataSPKM\n");681result = DBErrors::CORRUPT;682}683}685// Load watchonly scripts686LoadResult watch_script_res = LoadRecords(pwallet, batch, DBKeys::WATCHS,687[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {688CScript script;689key >> script;690uint8_t fYes;691value >> fYes;692if (fYes == '1') {693pwallet->GetOrCreateLegacyDataSPKM()->LoadWatchOnly(script);694}695return DBErrors::LOAD_OK;696});697result = std::max(result, watch_script_res.m_result);699// Load watchonly meta700LoadResult watch_meta_res = LoadRecords(pwallet, batch, DBKeys::WATCHMETA,701[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {702CScript script;703key >> script;704CKeyMetadata keyMeta;705value >> keyMeta;706pwallet->GetOrCreateLegacyDataSPKM()->LoadScriptMetadata(CScriptID(script), keyMeta);707return DBErrors::LOAD_OK;708});709result = std::max(result, watch_meta_res.m_result);711// Deal with old "wkey" and "defaultkey" records.712// These are not actually loaded, but we need to check for them714// We don't want or need the default key, but if there is one set,715// we want to make sure that it is valid so that we can detect corruption716// Note: There should only be one DEFAULTKEY with nothing trailing the type717LoadResult default_key_res = LoadRecords(pwallet, batch, DBKeys::DEFAULTKEY,718[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {719CPubKey default_pubkey;720try {721value >> default_pubkey;722} catch (const std::exception& e) {723err = e.what();724return DBErrors::CORRUPT;725}726if (!default_pubkey.IsValid()) {727err = "Error reading wallet database: Default Key corrupt";728return DBErrors::CORRUPT;729}730return DBErrors::LOAD_OK;731});732result = std::max(result, default_key_res.m_result);734// "wkey" records are unsupported, if we see any, throw an error735LoadResult wkey_res = LoadRecords(pwallet, batch, DBKeys::OLD_KEY,736[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {737err = "Found unsupported 'wkey' record, try loading with version 0.18";738return DBErrors::LOAD_FAIL;739});740result = std::max(result, wkey_res.m_result);742if (result <= DBErrors::NONCRITICAL_ERROR) {743// Only do logging and time first key update if there were no critical errors744pwallet->WalletLogPrintf("Legacy Wallet Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total.\n",745key_res.m_records, ckey_res.m_records, keymeta_res.m_records, key_res.m_records + ckey_res.m_records);746}748return result;749}