git / moonbite-core / src/wallet/walletdb.cpp · raw

// Copyright (c) 2009-2010 Satoshi Nakamoto// Copyright (c) 2009-2020 The Bitcoin Core developers// Distributed under the MIT software license, see the accompanying// file COPYING or http://www.opensource.org/licenses/mit-license.php.#include <wallet/walletdb.h>#include <fs.h>#include <key_io.h>#include <protocol.h>#include <serialize.h>#include <sync.h>#include <util/bip32.h>#include <util/system.h>#include <util/time.h>#include <util/translation.h>#include <wallet/bdb.h>#ifdef USE_SQLITE#include <wallet/sqlite.h>#endif#include <wallet/wallet.h>#include <atomic>#include <string>namespace DBKeys {const std::string ACENTRY{"acentry"};const std::string ACTIVEEXTERNALSPK{"activeexternalspk"};const std::string ACTIVEINTERNALSPK{"activeinternalspk"};const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};const std::string BESTBLOCK{"bestblock"};const std::string COIN{"mweb_coin"};const std::string CRYPTED_KEY{"ckey"};const std::string CSCRIPT{"cscript"};const std::string DEFAULTKEY{"defaultkey"};const std::string DESTDATA{"destdata"};const std::string FLAGS{"flags"};const std::string HDCHAIN{"hdchain"};const std::string KEYMETA{"keymeta"};const std::string KEY{"key"};const std::string MASTER_KEY{"mkey"};const std::string MINVERSION{"minversion"};const std::string NAME{"name"};const std::string OLD_KEY{"wkey"};const std::string ORDERPOSNEXT{"orderposnext"};const std::string POOL{"pool"};const std::string PURPOSE{"purpose"};const std::string SETTINGS{"settings"};const std::string TX{"tx"};const std::string VERSION{"version"};const std::string WALLETDESCRIPTOR{"walletdescriptor"};const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"};const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};const std::string WATCHMETA{"watchmeta"};const std::string WATCHS{"watchs"};} // namespace DBKeysconst int CHDChain::VERSION_HD_BASE;const int CHDChain::VERSION_HD_CHAIN_SPLIT;const int CHDChain::VERSION_HD_MWEB;const int CHDChain::VERSION_HD_MWEB_WATCH;const int CHDChain::CURRENT_VERSION;//// WalletBatch//bool WalletBatch::WriteName(const std::string& strAddress, const std::string& strName){    return WriteIC(std::make_pair(DBKeys::NAME, strAddress), strName);}bool WalletBatch::EraseName(const std::string& strAddress){    // This should only be used for sending addresses, never for receiving addresses,    // receiving addresses must always have an address book entry if they're not change return.    return EraseIC(std::make_pair(DBKeys::NAME, strAddress));}bool WalletBatch::WritePurpose(const std::string& strAddress, const std::string& strPurpose){    return WriteIC(std::make_pair(DBKeys::PURPOSE, strAddress), strPurpose);}bool WalletBatch::ErasePurpose(const std::string& strAddress){    return EraseIC(std::make_pair(DBKeys::PURPOSE, strAddress));}bool WalletBatch::WriteTx(const CWalletTx& wtx){    return WriteIC(std::make_pair(DBKeys::TX, wtx.GetHash()), wtx);}bool WalletBatch::EraseTx(uint256 hash){    return EraseIC(std::make_pair(DBKeys::TX, hash));}bool WalletBatch::WriteKeyMetadata(const CKeyMetadata& meta, const CPubKey& pubkey, const bool overwrite){    return WriteIC(std::make_pair(DBKeys::KEYMETA, pubkey), meta, overwrite);}bool WalletBatch::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta){    if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {        return false;    }    // hash pubkey/privkey to accelerate wallet load    std::vector<unsigned char> vchKey;    vchKey.reserve(vchPubKey.size() + vchPrivKey.size());    vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());    vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());    return WriteIC(std::make_pair(DBKeys::KEY, vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey)), false);}bool WalletBatch::WriteCryptedKey(const CPubKey& vchPubKey,                                const std::vector<unsigned char>& vchCryptedSecret,                                const CKeyMetadata &keyMeta){    if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) {        return false;    }    // Compute a checksum of the encrypted key    uint256 checksum = Hash(vchCryptedSecret);    const auto key = std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey);    if (!WriteIC(key, std::make_pair(vchCryptedSecret, checksum), false)) {        // It may already exist, so try writing just the checksum        std::vector<unsigned char> val;        if (!m_batch->Read(key, val)) {            return false;        }        if (!WriteIC(key, std::make_pair(val, checksum), true)) {            return false;        }    }    EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));    return true;}bool WalletBatch::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey){    return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);}bool WalletBatch::WriteCScript(const uint160& hash, const CScript& redeemScript){    return WriteIC(std::make_pair(DBKeys::CSCRIPT, hash), redeemScript, false);}bool WalletBatch::WriteMWEBCoin(const mw::Coin& coin){    // MW: TODO - Handle multiple coins with matching output IDs    return WriteIC(std::make_pair(DBKeys::COIN, coin.output_id), coin, true);}bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta){    if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) {        return false;    }    return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1');}bool WalletBatch::EraseWatchOnly(const CScript &dest){    if (!EraseIC(std::make_pair(DBKeys::WATCHMETA, dest))) {        return false;    }    return EraseIC(std::make_pair(DBKeys::WATCHS, dest));}bool WalletBatch::WriteBestBlock(const CBlockLocator& locator){    WriteIC(DBKeys::BESTBLOCK, CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan    return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator);}bool WalletBatch::ReadBestBlock(CBlockLocator& locator){    if (m_batch->Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) return true;    return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, locator);}bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext){    return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext);}bool WalletBatch::ReadPool(int64_t nPool, CKeyPool& keypool){    return m_batch->Read(std::make_pair(DBKeys::POOL, nPool), keypool);}bool WalletBatch::WritePool(int64_t nPool, const CKeyPool& keypool){    return WriteIC(std::make_pair(DBKeys::POOL, nPool), keypool);}bool WalletBatch::ErasePool(int64_t nPool){    return EraseIC(std::make_pair(DBKeys::POOL, nPool));}bool WalletBatch::WriteMinVersion(int nVersion){    return WriteIC(DBKeys::MINVERSION, nVersion);}bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256& id, bool internal){    std::string key = internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK;    return WriteIC(make_pair(key, type), id);}bool WalletBatch::WriteDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const CPrivKey& privkey){    // hash pubkey/privkey to accelerate wallet load    std::vector<unsigned char> key;    key.reserve(pubkey.size() + privkey.size());    key.insert(key.end(), pubkey.begin(), pubkey.end());    key.insert(key.end(), privkey.begin(), privkey.end());    return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)), std::make_pair(privkey, Hash(key)), false);}bool WalletBatch::WriteCryptedDescriptorKey(const uint256& desc_id, const CPubKey& pubkey, const std::vector<unsigned char>& secret){    if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY, std::make_pair(desc_id, pubkey)), secret, false)) {        return false;    }    EraseIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY, std::make_pair(desc_id, pubkey)));    return true;}bool WalletBatch::WriteDescriptor(const uint256& desc_id, const WalletDescriptor& descriptor){    return WriteIC(make_pair(DBKeys::WALLETDESCRIPTOR, desc_id), descriptor);}bool WalletBatch::WriteDescriptorDerivedCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index, uint32_t der_index){    std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);    xpub.Encode(ser_xpub.data());    return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), std::make_pair(key_exp_index, der_index)), ser_xpub);}bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey& xpub, const uint256& desc_id, uint32_t key_exp_index){    std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);    xpub.Encode(ser_xpub.data());    return WriteIC(std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id), key_exp_index), ser_xpub);}class CWalletScanState {public:    unsigned int nKeys{0};    unsigned int nCKeys{0};    unsigned int nWatchKeys{0};    unsigned int nKeyMeta{0};    unsigned int m_unknown_records{0};    bool fIsEncrypted{false};    bool fAnyUnordered{false};    std::vector<uint256> vWalletUpgrade;    std::vector<uint256> vWalletRemove;    std::map<OutputType, uint256> m_active_external_spks;    std::map<OutputType, uint256> m_active_internal_spks;    std::map<uint256, DescriptorCache> m_descriptor_caches;    std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;    std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;    std::map<uint160, CHDChain> m_hd_chains;    CWalletScanState() {    }};static boolReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,             CWalletScanState &wss, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn = nullptr) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet){    try {        // Unserialize        // Taking advantage of the fact that pair serialization        // is just the two items serialized one after the other        ssKey >> strType;        // If we have a filter, check if this matches the filter        if (filter_fn && !filter_fn(strType)) {            return true;        }        if (strType == DBKeys::NAME) {            std::string strAddress;            ssKey >> strAddress;            std::string label;            ssValue >> label;            pwallet->m_address_book[DecodeDestination(strAddress)].SetLabel(label);        } else if (strType == DBKeys::PURPOSE) {            std::string strAddress;            ssKey >> strAddress;            ssValue >> pwallet->m_address_book[DecodeDestination(strAddress)].purpose;        } else if (strType == DBKeys::TX) {            uint256 hash;            ssKey >> hash;            // LoadToWallet call below creates a new CWalletTx that fill_wtx            // callback fills with transaction metadata.            auto fill_wtx = [&](CWalletTx& wtx, bool new_tx) {                assert(new_tx);                ssValue >> wtx;                if (wtx.GetHash() != hash) {					// We previously calculated hash for mweb_wtx_info in an impractical way.					// We changed to just using the output ID as hash, so need to upgrade any existing txs.                    if (wtx.mweb_wtx_info) {                        wss.vWalletRemove.push_back(hash);                        wss.vWalletUpgrade.push_back(wtx.GetHash());                    } else {                        return false;                    }                }                // Undo serialize changes in 31600                if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)                {                    if (!ssValue.empty())                    {                        char fTmp;                        char fUnused;                        std::string unused_string;                        ssValue >> fTmp >> fUnused >> unused_string;                        strErr = strprintf("LoadWallet() upgrading tx ver=%d %d %s",                                           wtx.fTimeReceivedIsTxTime, fTmp, hash.ToString());                        wtx.fTimeReceivedIsTxTime = fTmp;                    }                    else                    {                        strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString());                        wtx.fTimeReceivedIsTxTime = 0;                    }                    wss.vWalletUpgrade.push_back(hash);                }                if (wtx.nOrderPos == -1)                    wss.fAnyUnordered = true;                return true;            };            if (!pwallet->LoadToWallet(hash, fill_wtx)) {                return false;            }        } else if (strType == DBKeys::COIN) {            mw::Coin coin;            ssValue >> coin;            pwallet->GetMWWallet()->LoadToWallet(coin);            return true;        } else if (strType == DBKeys::WATCHS) {            wss.nWatchKeys++;            CScript script;            ssKey >> script;            char fYes;            ssValue >> fYes;            if (fYes == '1') {                pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script);            }        } else if (strType == DBKeys::KEY) {            CPubKey vchPubKey;            ssKey >> vchPubKey;            if (!vchPubKey.IsValid())            {                strErr = "Error reading wallet database: CPubKey corrupt";                return false;            }            CKey key;            CPrivKey pkey;            uint256 hash;            wss.nKeys++;            ssValue >> pkey;            // Old wallets store keys as DBKeys::KEY [pubkey] => [privkey]            // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key            // using EC operations as a checksum.            // Newer wallets store keys as DBKeys::KEY [pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while            // remaining backwards-compatible.            try            {                ssValue >> hash;            }            catch (...) {}            bool fSkipCheck = false;            if (!hash.IsNull())            {                // hash pubkey/privkey to accelerate wallet load                std::vector<unsigned char> vchKey;                vchKey.reserve(vchPubKey.size() + pkey.size());                vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());                vchKey.insert(vchKey.end(), pkey.begin(), pkey.end());                if (Hash(vchKey) != hash)                {                    strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt";                    return false;                }                fSkipCheck = true;            }            if (!key.Load(pkey, vchPubKey, fSkipCheck))            {                strErr = "Error reading wallet database: CPrivKey corrupt";                return false;            }            if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKey(key, vchPubKey))            {                strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadKey failed";                return false;            }        } else if (strType == DBKeys::MASTER_KEY) {            // Master encryption key is loaded into only the wallet and not any of the ScriptPubKeyMans.            unsigned int nID;            ssKey >> nID;            CMasterKey kMasterKey;            ssValue >> kMasterKey;            if(pwallet->mapMasterKeys.count(nID) != 0)            {                strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);                return false;            }            pwallet->mapMasterKeys[nID] = kMasterKey;            if (pwallet->nMasterKeyMaxID < nID)                pwallet->nMasterKeyMaxID = nID;        } else if (strType == DBKeys::CRYPTED_KEY) {            CPubKey vchPubKey;            ssKey >> vchPubKey;            if (!vchPubKey.IsValid())            {                strErr = "Error reading wallet database: CPubKey corrupt";                return false;            }            std::vector<unsigned char> vchPrivKey;            ssValue >> vchPrivKey;            // Get the checksum and check it            bool checksum_valid = false;            if (!ssValue.eof()) {                uint256 checksum;                ssValue >> checksum;                if ((checksum_valid = Hash(vchPrivKey) != checksum)) {                    strErr = "Error reading wallet database: Crypted key corrupt";                    return false;                }            }            wss.nCKeys++;            if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(vchPubKey, vchPrivKey, checksum_valid))            {                strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCryptedKey failed";                return false;            }            wss.fIsEncrypted = true;        } else if (strType == DBKeys::KEYMETA) {            CPubKey vchPubKey;            ssKey >> vchPubKey;            CKeyMetadata keyMeta;            ssValue >> keyMeta;            wss.nKeyMeta++;            pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);            // Extract some CHDChain info from this metadata if it has any            if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {                // Get the path from the key origin or from the path string                // Not applicable when path is "s" or "m" as those indicate a seed                // See https://github.com/bitcoin/bitcoin/pull/12924                bool internal = false;                uint32_t index = 0;                if (keyMeta.hdKeypath != "s" && keyMeta.hdKeypath != "m" && !keyMeta.mweb_index) {                    std::vector<uint32_t> path;                    if (keyMeta.has_key_origin) {                        // We have a key origin, so pull it from its path vector                        path = keyMeta.key_origin.path;                    } else {                        // No key origin, have to parse the string                        if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {                            strErr = "Error reading wallet database: keymeta with invalid HD keypath";                            return false;                        }                    }                    // Extract the index and internal from the path                    // Path string is m/0'/k'/i'                    // Path vector is [0', k', i'] (but as ints OR'd with the hardened bit                    // k == 0 for external, 1 for internal. i is the index                    if (path.size() != 3) {                        strErr = "Error reading wallet database: keymeta found with unexpected path";                        return false;                    }                    if (path[0] != 0x80000000) {                        strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);                        return false;                    }                    if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000) && path[1] != (100 | 0x80000000)) {                        strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);                        return false;                    }                    if ((path[2] & 0x80000000) == 0) {                        strErr = strprintf("Unexpected path index of 0x%08x (expected to be greater than or equal to 0x80000000)", path[2]);                        return false;                    }                    internal = path[1] == (1 | 0x80000000);                    index = path[2] & ~0x80000000;                }                // Insert a new CHDChain, or get the one that already exists                auto ins = wss.m_hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());                CHDChain& chain = ins.first->second;                if (ins.second) {                    // For new chains, we want to default to VERSION_HD_BASE until we see an internal                    chain.nVersion = CHDChain::VERSION_HD_BASE;                    chain.seed_id = keyMeta.hd_seed_id;                }                if (!!keyMeta.mweb_index) {                    chain.nVersion = std::max(chain.nVersion, CHDChain::VERSION_HD_MWEB_WATCH);                    chain.nMWEBIndexCounter = std::max(chain.nMWEBIndexCounter, *keyMeta.mweb_index);                } else if (internal) {                    chain.nVersion = std::max(chain.nVersion, CHDChain::VERSION_HD_CHAIN_SPLIT);                    chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index);                } else {                    chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index);                }            }        } else if (strType == DBKeys::WATCHMETA) {            CScript script;            ssKey >> script;            CKeyMetadata keyMeta;            ssValue >> keyMeta;            wss.nKeyMeta++;            pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta);        } else if (strType == DBKeys::DEFAULTKEY) {            // We don't want or need the default key, but if there is one set,            // we want to make sure that it is valid so that we can detect corruption            CPubKey vchPubKey;            ssValue >> vchPubKey;            if (!vchPubKey.IsValid()) {                strErr = "Error reading wallet database: Default Key corrupt";                return false;            }        } else if (strType == DBKeys::POOL) {            int64_t nIndex;            ssKey >> nIndex;            CKeyPool keypool;            ssValue >> keypool;            pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool);        } else if (strType == DBKeys::CSCRIPT) {            uint160 hash;            ssKey >> hash;            CScript script;            ssValue >> script;            if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCScript(script))            {                strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCScript failed";                return false;            }        } else if (strType == DBKeys::ORDERPOSNEXT) {            ssValue >> pwallet->nOrderPosNext;        } else if (strType == DBKeys::DESTDATA) {            std::string strAddress, strKey, strValue;            ssKey >> strAddress;            ssKey >> strKey;            ssValue >> strValue;            pwallet->LoadDestData(DecodeDestination(strAddress), strKey, strValue);        } else if (strType == DBKeys::HDCHAIN) {            CHDChain chain;            ssValue >> chain;            pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);        } else if (strType == DBKeys::FLAGS) {            uint64_t flags;            ssValue >> flags;            if (!pwallet->LoadWalletFlags(flags)) {                strErr = "Error reading wallet database: Unknown non-tolerable wallet flags found";                return false;            }        } else if (strType == DBKeys::OLD_KEY) {            strErr = "Found unsupported 'wkey' record, try loading with version 0.18";            return false;        } else if (strType == DBKeys::ACTIVEEXTERNALSPK || strType == DBKeys::ACTIVEINTERNALSPK) {            uint8_t type;            ssKey >> type;            uint256 id;            ssValue >> id;            bool internal = strType == DBKeys::ACTIVEINTERNALSPK;            auto& spk_mans = internal ? wss.m_active_internal_spks : wss.m_active_external_spks;            if (spk_mans.count(static_cast<OutputType>(type)) > 0) {                strErr = "Multiple ScriptPubKeyMans specified for a single type";                return false;            }            spk_mans[static_cast<OutputType>(type)] = id;        } else if (strType == DBKeys::WALLETDESCRIPTOR) {            uint256 id;            ssKey >> id;            WalletDescriptor desc;            ssValue >> desc;            if (wss.m_descriptor_caches.count(id) == 0) {                wss.m_descriptor_caches[id] = DescriptorCache();            }            pwallet->LoadDescriptorScriptPubKeyMan(id, desc);        } else if (strType == DBKeys::WALLETDESCRIPTORCACHE) {            bool parent = true;            uint256 desc_id;            uint32_t key_exp_index;            uint32_t der_index;            ssKey >> desc_id;            ssKey >> key_exp_index;            // if the der_index exists, it's a derived xpub            try            {                ssKey >> der_index;                parent = false;            }            catch (...) {}            std::vector<unsigned char> ser_xpub(BIP32_EXTKEY_SIZE);            ssValue >> ser_xpub;            CExtPubKey xpub;            xpub.Decode(ser_xpub.data());            if (parent) {                wss.m_descriptor_caches[desc_id].CacheParentExtPubKey(key_exp_index, xpub);            } else {                wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(key_exp_index, der_index, xpub);            }        } else if (strType == DBKeys::WALLETDESCRIPTORKEY) {            uint256 desc_id;            CPubKey pubkey;            ssKey >> desc_id;            ssKey >> pubkey;            if (!pubkey.IsValid())            {                strErr = "Error reading wallet database: CPubKey corrupt";                return false;            }            CKey key;            CPrivKey pkey;            uint256 hash;            wss.nKeys++;            ssValue >> pkey;            ssValue >> hash;            // hash pubkey/privkey to accelerate wallet load            std::vector<unsigned char> to_hash;            to_hash.reserve(pubkey.size() + pkey.size());            to_hash.insert(to_hash.end(), pubkey.begin(), pubkey.end());            to_hash.insert(to_hash.end(), pkey.begin(), pkey.end());            if (Hash(to_hash) != hash)            {                strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt";                return false;            }            if (!key.Load(pkey, pubkey, true))            {                strErr = "Error reading wallet database: CPrivKey corrupt";                return false;            }            wss.m_descriptor_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), key));        } else if (strType == DBKeys::WALLETDESCRIPTORCKEY) {            uint256 desc_id;            CPubKey pubkey;            ssKey >> desc_id;            ssKey >> pubkey;            if (!pubkey.IsValid())            {                strErr = "Error reading wallet database: CPubKey corrupt";                return false;            }            std::vector<unsigned char> privkey;            ssValue >> privkey;            wss.nCKeys++;            wss.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey)));            wss.fIsEncrypted = true;        } else if (strType != DBKeys::BESTBLOCK && strType != DBKeys::BESTBLOCK_NOMERKLE &&                   strType != DBKeys::MINVERSION && strType != DBKeys::ACENTRY &&                   strType != DBKeys::VERSION && strType != DBKeys::SETTINGS) {            wss.m_unknown_records++;        }    } catch (const std::exception& e) {        if (strErr.empty()) {            strErr = e.what();        }        return false;    } catch (...) {        if (strErr.empty()) {            strErr = "Caught unknown exception in ReadKeyValue";        }        return false;    }    return true;}bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, std::string& strType, std::string& strErr, const KeyFilterFn& filter_fn){    CWalletScanState dummy_wss;    LOCK(pwallet->cs_wallet);    return ReadKeyValue(pwallet, ssKey, ssValue, dummy_wss, strType, strErr, filter_fn);}bool WalletBatch::IsKeyType(const std::string& strType){    return (strType == DBKeys::KEY ||            strType == DBKeys::MASTER_KEY || strType == DBKeys::CRYPTED_KEY);}DBErrors WalletBatch::LoadWallet(CWallet* pwallet){    CWalletScanState wss;    bool fNoncriticalErrors = false;    DBErrors result = DBErrors::LOAD_OK;    LOCK(pwallet->cs_wallet);    try {        int nMinVersion = 0;        if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {            if (nMinVersion > FEATURE_LATEST)                return DBErrors::TOO_NEW;            pwallet->LoadMinVersion(nMinVersion);        }        // Get cursor        if (!m_batch->StartCursor())        {            pwallet->WalletLogPrintf("Error getting wallet database cursor\n");            return DBErrors::CORRUPT;        }        while (true)        {            // Read next record            CDataStream ssKey(SER_DISK, CLIENT_VERSION);            CDataStream ssValue(SER_DISK, CLIENT_VERSION);            bool complete;            bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);            if (complete) {                break;            }            else if (!ret)            {                m_batch->CloseCursor();                pwallet->WalletLogPrintf("Error reading next record from wallet database\n");                return DBErrors::CORRUPT;            }            // Try to be tolerant of single corrupt records:            std::string strType, strErr;            if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))            {                // losing keys is considered a catastrophic error, anything else                // we assume the user can live with:                if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) {                    result = DBErrors::CORRUPT;                } else if (strType == DBKeys::FLAGS) {                    // reading the wallet flags can only fail if unknown flags are present                    result = DBErrors::TOO_NEW;                } else {                    // Leave other errors alone, if we try to fix them we might make things worse.                    fNoncriticalErrors = true; // ... but do warn the user there is something wrong.                    if (strType == DBKeys::TX)                        // Rescan if there is a bad transaction record:                        gArgs.SoftSetBoolArg("-rescan", true);                }            }            if (!strErr.empty())                pwallet->WalletLogPrintf("%s\n", strErr);        }    } catch (...) {        result = DBErrors::CORRUPT;    }    m_batch->CloseCursor();    // Set the active ScriptPubKeyMans    for (auto spk_man_pair : wss.m_active_external_spks) {        pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ false);    }    for (auto spk_man_pair : wss.m_active_internal_spks) {        pwallet->LoadActiveScriptPubKeyMan(spk_man_pair.second, spk_man_pair.first, /* internal */ true);    }    // Set the descriptor caches    for (auto desc_cache_pair : wss.m_descriptor_caches) {        auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);        assert(spk_man);        ((DescriptorScriptPubKeyMan*)spk_man)->SetCache(desc_cache_pair.second);    }    // Set the descriptor keys    for (auto desc_key_pair : wss.m_descriptor_keys) {        auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);        ((DescriptorScriptPubKeyMan*)spk_man)->AddKey(desc_key_pair.first.second, desc_key_pair.second);    }    for (auto desc_key_pair : wss.m_descriptor_crypt_keys) {        auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);        ((DescriptorScriptPubKeyMan*)spk_man)->AddCryptedKey(desc_key_pair.first.second, desc_key_pair.second.first, desc_key_pair.second.second);    }    if (fNoncriticalErrors && result == DBErrors::LOAD_OK)        result = DBErrors::NONCRITICAL_ERROR;    // Any wallet corruption at all: skip any rewriting or    // upgrading, we don't want to make it worse.    if (result != DBErrors::LOAD_OK)        return result;    // Last client version to open this wallet, was previously the file version number    int last_client = CLIENT_VERSION;    m_batch->Read(DBKeys::VERSION, last_client);    int wallet_version = pwallet->GetVersion();    pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client);    pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n",           wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records);    // nTimeFirstKey is only reliable if all keys have metadata    if (pwallet->IsLegacy() && (wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) {        auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan();        if (spk_man) {            LOCK(spk_man->cs_KeyStore);            spk_man->UpdateTimeFirstKey(1);        }    }    // MWEB: Load MWEB keychain    auto mweb_spk_man = pwallet->GetScriptPubKeyMan(OutputType::MWEB, false);    if (mweb_spk_man) {        mweb_spk_man->LoadMWEBKeychain();    }    for (const uint256& hash : wss.vWalletUpgrade)        WriteTx(pwallet->mapWallet.at(hash));    for (const uint256& hash : wss.vWalletRemove) {        EraseTx(hash);    }    // Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:    if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000))        return DBErrors::NEED_REWRITE;    if (last_client < CLIENT_VERSION) // Update        m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);    if (wss.fAnyUnordered)        result = pwallet->ReorderTransactions();    // Upgrade all of the wallet keymetadata to have the hd master key id    // This operation is not atomic, but if it fails, updated entries are still backwards compatible with older software    try {        pwallet->UpgradeKeyMetadata();    } catch (...) {        result = DBErrors::CORRUPT;    }    // Set the inactive chain    if (wss.m_hd_chains.size() > 0) {        LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();        if (!legacy_spkm) {            pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");            return DBErrors::CORRUPT;        }        for (const auto& chain_pair : wss.m_hd_chains) {            if (chain_pair.first != pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {                pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(chain_pair.second);            }        }    }    return result;}DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWalletTx>& vWtx){    DBErrors result = DBErrors::LOAD_OK;    try {        int nMinVersion = 0;        if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {            if (nMinVersion > FEATURE_LATEST)                return DBErrors::TOO_NEW;        }        // Get cursor        if (!m_batch->StartCursor())        {            LogPrintf("Error getting wallet database cursor\n");            return DBErrors::CORRUPT;        }        while (true)        {            // Read next record            CDataStream ssKey(SER_DISK, CLIENT_VERSION);            CDataStream ssValue(SER_DISK, CLIENT_VERSION);            bool complete;            bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);            if (complete) {                break;            } else if (!ret) {                m_batch->CloseCursor();                LogPrintf("Error reading next record from wallet database\n");                return DBErrors::CORRUPT;            }            std::string strType;            ssKey >> strType;            if (strType == DBKeys::TX) {                uint256 hash;                ssKey >> hash;                vTxHash.push_back(hash);                vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */);                ssValue >> vWtx.back();            }        }    } catch (...) {        result = DBErrors::CORRUPT;    }    m_batch->CloseCursor();    return result;}DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uint256>& vTxHashOut){    // build list of wallet TXs and hashes    std::vector<uint256> vTxHash;    std::list<CWalletTx> vWtx;    DBErrors err = FindWalletTx(vTxHash, vWtx);    if (err != DBErrors::LOAD_OK) {        return err;    }    std::sort(vTxHash.begin(), vTxHash.end());    std::sort(vTxHashIn.begin(), vTxHashIn.end());    // erase each matching wallet TX    bool delerror = false;    std::vector<uint256>::iterator it = vTxHashIn.begin();    for (const uint256& hash : vTxHash) {        while (it < vTxHashIn.end() && (*it) < hash) {            it++;        }        if (it == vTxHashIn.end()) {            break;        }        else if ((*it) == hash) {            if(!EraseTx(hash)) {                LogPrint(BCLog::WALLETDB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());                delerror = true;            }            vTxHashOut.push_back(hash);        }    }    if (delerror) {        return DBErrors::CORRUPT;    }    return DBErrors::LOAD_OK;}void MaybeCompactWalletDB(){    static std::atomic<bool> fOneThread(false);    if (fOneThread.exchange(true)) {        return;    }    for (const std::shared_ptr<CWallet>& pwallet : GetWallets()) {        WalletDatabase& dbh = pwallet->GetDBHandle();        unsigned int nUpdateCounter = dbh.nUpdateCounter;        if (dbh.nLastSeen != nUpdateCounter) {            dbh.nLastSeen = nUpdateCounter;            dbh.nLastWalletUpdate = GetTime();        }        if (dbh.nLastFlushed != nUpdateCounter && GetTime() - dbh.nLastWalletUpdate >= 2) {            if (dbh.PeriodicFlush()) {                dbh.nLastFlushed = nUpdateCounter;            }        }    }    fOneThread = false;}bool WalletBatch::WriteDestData(const std::string &address, const std::string &key, const std::string &value){    return WriteIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)), value);}bool WalletBatch::EraseDestData(const std::string &address, const std::string &key){    return EraseIC(std::make_pair(DBKeys::DESTDATA, std::make_pair(address, key)));}bool WalletBatch::WriteHDChain(const CHDChain& chain){    return WriteIC(DBKeys::HDCHAIN, chain);}bool WalletBatch::WriteWalletFlags(const uint64_t flags){    return WriteIC(DBKeys::FLAGS, flags);}bool WalletBatch::TxnBegin(){    return m_batch->TxnBegin();}bool WalletBatch::TxnCommit(){    return m_batch->TxnCommit();}bool WalletBatch::TxnAbort(){    return m_batch->TxnAbort();}std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path& path, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error){    bool exists;    try {        exists = fs::symlink_status(path).type() != fs::file_not_found;    } catch (const fs::filesystem_error& e) {        error = Untranslated(strprintf("Failed to access database path '%s': %s", path.string(), fsbridge::get_filesystem_error_message(e)));        status = DatabaseStatus::FAILED_BAD_PATH;        return nullptr;    }    Optional<DatabaseFormat> format;    if (exists) {        if (ExistsBerkeleyDatabase(path)) {            format = DatabaseFormat::BERKELEY;        }#ifdef USE_SQLITE        if (ExistsSQLiteDatabase(path)) {            if (format) {                error = Untranslated(strprintf("Failed to load database path '%s'. Data is in ambiguous format.", path.string()));                status = DatabaseStatus::FAILED_BAD_FORMAT;                return nullptr;            }            format = DatabaseFormat::SQLITE;        }#endif    } else if (options.require_existing) {        error = Untranslated(strprintf("Failed to load database path '%s'. Path does not exist.", path.string()));        status = DatabaseStatus::FAILED_NOT_FOUND;        return nullptr;    }    if (!format && options.require_existing) {        error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in recognized format.", path.string()));        status = DatabaseStatus::FAILED_BAD_FORMAT;        return nullptr;    }    if (format && options.require_create) {        error = Untranslated(strprintf("Failed to create database path '%s'. Database already exists.", path.string()));        status = DatabaseStatus::FAILED_ALREADY_EXISTS;        return nullptr;    }    // A db already exists so format is set, but options also specifies the format, so make sure they agree    if (format && options.require_format && format != options.require_format) {        error = Untranslated(strprintf("Failed to load database path '%s'. Data is not in required format.", path.string()));        status = DatabaseStatus::FAILED_BAD_FORMAT;        return nullptr;    }    // Format is not set when a db doesn't already exist, so use the format specified by the options if it is set.    if (!format && options.require_format) format = options.require_format;#ifdef USE_SQLITE    if (format && format == DatabaseFormat::SQLITE) {        return MakeSQLiteDatabase(path, options, status, error);    }#else    assert(format != DatabaseFormat::SQLITE);#endif    return MakeBerkeleyDatabase(path, options, status, error);}/** Return object for accessing dummy database with no read/write capabilities. */std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase(){    return MakeUnique<DummyDatabase>();}/** Return object for accessing temporary in-memory database. */std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(){    return MakeUnique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "");}