mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-12 06:58:57 +01:00
walletdb: Refactor legacy wallet record loading into its own function
Instead of loading legacy wallet records as we come across them when iterating the database, load them explicitly. Exception handling for these records changes to a per-record type basis, rather than globally. This results in some records now failing with a critical error rather than a non-critical one.
This commit is contained in:
@@ -301,10 +301,8 @@ class CWalletScanState {
|
|||||||
public:
|
public:
|
||||||
unsigned int nKeys{0};
|
unsigned int nKeys{0};
|
||||||
unsigned int nCKeys{0};
|
unsigned int nCKeys{0};
|
||||||
unsigned int nWatchKeys{0};
|
|
||||||
unsigned int nKeyMeta{0};
|
unsigned int nKeyMeta{0};
|
||||||
unsigned int m_unknown_records{0};
|
unsigned int m_unknown_records{0};
|
||||||
bool fIsEncrypted{false};
|
|
||||||
bool fAnyUnordered{false};
|
bool fAnyUnordered{false};
|
||||||
std::vector<uint256> vWalletUpgrade;
|
std::vector<uint256> vWalletUpgrade;
|
||||||
std::map<OutputType, uint256> m_active_external_spks;
|
std::map<OutputType, uint256> m_active_external_spks;
|
||||||
@@ -312,10 +310,8 @@ public:
|
|||||||
std::map<uint256, DescriptorCache> m_descriptor_caches;
|
std::map<uint256, DescriptorCache> m_descriptor_caches;
|
||||||
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
|
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<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
|
||||||
std::map<uint160, CHDChain> m_hd_chains;
|
|
||||||
bool tx_corrupt{false};
|
bool tx_corrupt{false};
|
||||||
bool descriptor_unknown{false};
|
bool descriptor_unknown{false};
|
||||||
bool unexpected_legacy_entry{false};
|
|
||||||
|
|
||||||
CWalletScanState() = default;
|
CWalletScanState() = default;
|
||||||
};
|
};
|
||||||
@@ -477,10 +473,8 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
|||||||
// Taking advantage of the fact that pair serialization
|
// Taking advantage of the fact that pair serialization
|
||||||
// is just the two items serialized one after the other
|
// is just the two items serialized one after the other
|
||||||
ssKey >> strType;
|
ssKey >> strType;
|
||||||
// Legacy entries in descriptor wallets are not allowed, abort immediately
|
|
||||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && DBKeys::LEGACY_TYPES.count(strType) > 0) {
|
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && DBKeys::LEGACY_TYPES.count(strType) > 0) {
|
||||||
wss.unexpected_legacy_entry = true;
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (strType == DBKeys::NAME) {
|
if (strType == DBKeys::NAME) {
|
||||||
std::string strAddress;
|
std::string strAddress;
|
||||||
@@ -545,97 +539,16 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (strType == DBKeys::WATCHS) {
|
} else if (strType == DBKeys::WATCHS) {
|
||||||
wss.nWatchKeys++;
|
|
||||||
CScript script;
|
|
||||||
ssKey >> script;
|
|
||||||
uint8_t fYes;
|
|
||||||
ssValue >> fYes;
|
|
||||||
if (fYes == '1') {
|
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script);
|
|
||||||
}
|
|
||||||
} else if (strType == DBKeys::KEY) {
|
} else if (strType == DBKeys::KEY) {
|
||||||
wss.nKeys++;
|
wss.nKeys++;
|
||||||
if (!LoadKey(pwallet, ssKey, ssValue, strErr)) return false;
|
|
||||||
} else if (strType == DBKeys::MASTER_KEY) {
|
} else if (strType == DBKeys::MASTER_KEY) {
|
||||||
if (!LoadEncryptionKey(pwallet, ssKey, ssValue, strErr)) return false;
|
if (!LoadEncryptionKey(pwallet, ssKey, ssValue, strErr)) return false;
|
||||||
} else if (strType == DBKeys::CRYPTED_KEY) {
|
} else if (strType == DBKeys::CRYPTED_KEY) {
|
||||||
wss.nCKeys++;
|
wss.nCKeys++;
|
||||||
if (!LoadCryptedKey(pwallet, ssKey, ssValue, strErr)) return false;
|
|
||||||
wss.fIsEncrypted = true;
|
|
||||||
} else if (strType == DBKeys::KEYMETA) {
|
} else if (strType == DBKeys::KEYMETA) {
|
||||||
CPubKey vchPubKey;
|
|
||||||
ssKey >> vchPubKey;
|
|
||||||
CKeyMetadata keyMeta;
|
|
||||||
ssValue >> keyMeta;
|
|
||||||
wss.nKeyMeta++;
|
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") {
|
|
||||||
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)) {
|
|
||||||
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 (internal) {
|
|
||||||
chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
|
|
||||||
chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index + 1);
|
|
||||||
} else {
|
|
||||||
chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strType == DBKeys::WATCHMETA) {
|
} else if (strType == DBKeys::WATCHMETA) {
|
||||||
CScript script;
|
|
||||||
ssKey >> script;
|
|
||||||
CKeyMetadata keyMeta;
|
|
||||||
ssValue >> keyMeta;
|
|
||||||
wss.nKeyMeta++;
|
wss.nKeyMeta++;
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta);
|
|
||||||
} else if (strType == DBKeys::DEFAULTKEY) {
|
} else if (strType == DBKeys::DEFAULTKEY) {
|
||||||
// We don't want or need the default key, but if there is one set,
|
// 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
|
// we want to make sure that it is valid so that we can detect corruption
|
||||||
@@ -646,22 +559,7 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (strType == DBKeys::POOL) {
|
} else if (strType == DBKeys::POOL) {
|
||||||
int64_t nIndex;
|
|
||||||
ssKey >> nIndex;
|
|
||||||
CKeyPool keypool;
|
|
||||||
ssValue >> keypool;
|
|
||||||
|
|
||||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool);
|
|
||||||
} else if (strType == DBKeys::CSCRIPT) {
|
} 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) {
|
} else if (strType == DBKeys::ORDERPOSNEXT) {
|
||||||
ssValue >> pwallet->nOrderPosNext;
|
ssValue >> pwallet->nOrderPosNext;
|
||||||
} else if (strType == DBKeys::DESTDATA) {
|
} else if (strType == DBKeys::DESTDATA) {
|
||||||
@@ -684,7 +582,6 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
|||||||
pwallet->LoadAddressReceiveRequest(dest, strKey.substr(2), strValue);
|
pwallet->LoadAddressReceiveRequest(dest, strKey.substr(2), strValue);
|
||||||
}
|
}
|
||||||
} else if (strType == DBKeys::HDCHAIN) {
|
} else if (strType == DBKeys::HDCHAIN) {
|
||||||
if (!LoadHDChain(pwallet, ssValue, strErr)) return false;
|
|
||||||
} else if (strType == DBKeys::OLD_KEY) {
|
} else if (strType == DBKeys::OLD_KEY) {
|
||||||
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
|
strErr = "Found unsupported 'wkey' record, try loading with version 0.18";
|
||||||
return false;
|
return false;
|
||||||
@@ -803,7 +700,6 @@ ReadKeyValue(CWallet* pwallet, DataStream& ssKey, CDataStream& ssValue,
|
|||||||
wss.nCKeys++;
|
wss.nCKeys++;
|
||||||
|
|
||||||
wss.m_descriptor_crypt_keys.insert(std::make_pair(std::make_pair(desc_id, pubkey.GetID()), std::make_pair(pubkey, privkey)));
|
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::LOCKED_UTXO) {
|
} else if (strType == DBKeys::LOCKED_UTXO) {
|
||||||
uint256 hash;
|
uint256 hash;
|
||||||
uint32_t n;
|
uint32_t n;
|
||||||
@@ -855,6 +751,268 @@ static DBErrors LoadWalletFlags(CWallet* pwallet, DatabaseBatch& batch) EXCLUSIV
|
|||||||
return DBErrors::LOAD_OK;
|
return DBErrors::LOAD_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct LoadResult
|
||||||
|
{
|
||||||
|
DBErrors m_result{DBErrors::LOAD_OK};
|
||||||
|
int m_records{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
using LoadFunc = std::function<DBErrors(CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err)>;
|
||||||
|
static LoadResult LoadRecords(CWallet* pwallet, DatabaseBatch& batch, const std::string& key, LoadFunc load_func)
|
||||||
|
{
|
||||||
|
LoadResult result;
|
||||||
|
DataStream ssKey;
|
||||||
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||||
|
|
||||||
|
DataStream prefix;
|
||||||
|
prefix << key;
|
||||||
|
std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
|
||||||
|
if (!cursor) {
|
||||||
|
pwallet->WalletLogPrintf("Error getting database cursor for '%s' records\n", key);
|
||||||
|
result.m_result = DBErrors::CORRUPT;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
|
||||||
|
if (status == DatabaseCursor::Status::DONE) {
|
||||||
|
break;
|
||||||
|
} else if (status == DatabaseCursor::Status::FAIL) {
|
||||||
|
pwallet->WalletLogPrintf("Error reading next '%s' record for wallet database\n", key);
|
||||||
|
result.m_result = DBErrors::CORRUPT;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
std::string type;
|
||||||
|
ssKey >> type;
|
||||||
|
assert(type == key);
|
||||||
|
std::string error;
|
||||||
|
DBErrors record_res = load_func(pwallet, ssKey, ssValue, error);
|
||||||
|
if (record_res != DBErrors::LOAD_OK) {
|
||||||
|
pwallet->WalletLogPrintf("%s\n", error);
|
||||||
|
}
|
||||||
|
result.m_result = std::max(result.m_result, record_res);
|
||||||
|
++result.m_records;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch, int last_client) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
|
||||||
|
{
|
||||||
|
AssertLockHeld(pwallet->cs_wallet);
|
||||||
|
DBErrors result = DBErrors::LOAD_OK;
|
||||||
|
|
||||||
|
// Make sure descriptor wallets don't have any legacy records
|
||||||
|
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
|
||||||
|
for (const auto& type : DBKeys::LEGACY_TYPES) {
|
||||||
|
DataStream key;
|
||||||
|
CDataStream value(SER_DISK, CLIENT_VERSION);
|
||||||
|
|
||||||
|
DataStream prefix;
|
||||||
|
prefix << type;
|
||||||
|
std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor(prefix);
|
||||||
|
if (!cursor) {
|
||||||
|
pwallet->WalletLogPrintf("Error getting database cursor for '%s' records\n", type);
|
||||||
|
return DBErrors::CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseCursor::Status status = cursor->Next(key, value);
|
||||||
|
if (status != DatabaseCursor::Status::DONE) {
|
||||||
|
pwallet->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());
|
||||||
|
return DBErrors::UNEXPECTED_LEGACY_ENTRY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load HD Chain
|
||||||
|
// Note: There should only be one HDCHAIN record with no data following the type
|
||||||
|
LoadResult hd_chain_res = LoadRecords(pwallet, batch, DBKeys::HDCHAIN,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
return LoadHDChain(pwallet, value, err) ? DBErrors:: LOAD_OK : DBErrors::CORRUPT;
|
||||||
|
});
|
||||||
|
result = std::max(result, hd_chain_res.m_result);
|
||||||
|
|
||||||
|
// Load unencrypted keys
|
||||||
|
LoadResult key_res = LoadRecords(pwallet, batch, DBKeys::KEY,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
return LoadKey(pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;
|
||||||
|
});
|
||||||
|
result = std::max(result, key_res.m_result);
|
||||||
|
|
||||||
|
// Load encrypted keys
|
||||||
|
LoadResult ckey_res = LoadRecords(pwallet, batch, DBKeys::CRYPTED_KEY,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
return LoadCryptedKey(pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;
|
||||||
|
});
|
||||||
|
result = std::max(result, ckey_res.m_result);
|
||||||
|
|
||||||
|
// Load scripts
|
||||||
|
LoadResult script_res = LoadRecords(pwallet, batch, DBKeys::CSCRIPT,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& strErr) {
|
||||||
|
uint160 hash;
|
||||||
|
key >> hash;
|
||||||
|
CScript script;
|
||||||
|
value >> script;
|
||||||
|
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCScript(script))
|
||||||
|
{
|
||||||
|
strErr = "Error reading wallet database: LegacyScriptPubKeyMan::LoadCScript failed";
|
||||||
|
return DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
});
|
||||||
|
result = std::max(result, script_res.m_result);
|
||||||
|
|
||||||
|
// Check whether rewrite is needed
|
||||||
|
if (ckey_res.m_records > 0) {
|
||||||
|
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
|
||||||
|
if (last_client == 40000 || last_client == 50000) result = std::max(result, DBErrors::NEED_REWRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load keymeta
|
||||||
|
std::map<uint160, CHDChain> hd_chains;
|
||||||
|
LoadResult keymeta_res = LoadRecords(pwallet, batch, DBKeys::KEYMETA,
|
||||||
|
[&hd_chains] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& strErr) {
|
||||||
|
CPubKey vchPubKey;
|
||||||
|
key >> vchPubKey;
|
||||||
|
CKeyMetadata keyMeta;
|
||||||
|
value >> keyMeta;
|
||||||
|
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") {
|
||||||
|
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 DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
if (path[0] != 0x80000000) {
|
||||||
|
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);
|
||||||
|
return DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000)) {
|
||||||
|
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);
|
||||||
|
return DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
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 DBErrors::NONCRITICAL_ERROR;
|
||||||
|
}
|
||||||
|
internal = path[1] == (1 | 0x80000000);
|
||||||
|
index = path[2] & ~0x80000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert a new CHDChain, or get the one that already exists
|
||||||
|
auto [ins, inserted] = hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());
|
||||||
|
CHDChain& chain = ins->second;
|
||||||
|
if (inserted) {
|
||||||
|
// 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 (internal) {
|
||||||
|
chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
|
||||||
|
chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index + 1);
|
||||||
|
} else {
|
||||||
|
chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
});
|
||||||
|
result = std::max(result, keymeta_res.m_result);
|
||||||
|
|
||||||
|
// Set inactive chains
|
||||||
|
if (!hd_chains.empty()) {
|
||||||
|
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
|
||||||
|
if (legacy_spkm) {
|
||||||
|
for (const auto& [hd_seed_id, chain] : hd_chains) {
|
||||||
|
if (hd_seed_id != legacy_spkm->GetHDChain().seed_id) {
|
||||||
|
legacy_spkm->AddInactiveHDChain(chain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
|
||||||
|
result = DBErrors::CORRUPT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load watchonly scripts
|
||||||
|
LoadResult watch_script_res = LoadRecords(pwallet, batch, DBKeys::WATCHS,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
CScript script;
|
||||||
|
key >> script;
|
||||||
|
uint8_t fYes;
|
||||||
|
value >> fYes;
|
||||||
|
if (fYes == '1') {
|
||||||
|
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script);
|
||||||
|
}
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
});
|
||||||
|
result = std::max(result, watch_script_res.m_result);
|
||||||
|
|
||||||
|
// Load watchonly meta
|
||||||
|
LoadResult watch_meta_res = LoadRecords(pwallet, batch, DBKeys::WATCHMETA,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
CScript script;
|
||||||
|
key >> script;
|
||||||
|
CKeyMetadata keyMeta;
|
||||||
|
value >> keyMeta;
|
||||||
|
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(CScriptID(script), keyMeta);
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
});
|
||||||
|
result = std::max(result, watch_meta_res.m_result);
|
||||||
|
|
||||||
|
// Load keypool
|
||||||
|
LoadResult pool_res = LoadRecords(pwallet, batch, DBKeys::POOL,
|
||||||
|
[] (CWallet* pwallet, DataStream& key, CDataStream& value, std::string& err) {
|
||||||
|
int64_t nIndex;
|
||||||
|
key >> nIndex;
|
||||||
|
CKeyPool keypool;
|
||||||
|
value >> keypool;
|
||||||
|
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex, keypool);
|
||||||
|
return DBErrors::LOAD_OK;
|
||||||
|
});
|
||||||
|
result = std::max(result, pool_res.m_result);
|
||||||
|
|
||||||
|
if (result <= DBErrors::NONCRITICAL_ERROR) {
|
||||||
|
// Only do logging and time first key update if there were no critical errors
|
||||||
|
pwallet->WalletLogPrintf("Legacy Wallet Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total.\n",
|
||||||
|
key_res.m_records, ckey_res.m_records, keymeta_res.m_records, key_res.m_records + ckey_res.m_records);
|
||||||
|
|
||||||
|
// nTimeFirstKey is only reliable if all keys have metadata
|
||||||
|
if (pwallet->IsLegacy() && (key_res.m_records + ckey_res.m_records + watch_script_res.m_records) != (keymeta_res.m_records + watch_meta_res.m_records)) {
|
||||||
|
auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan();
|
||||||
|
if (spk_man) {
|
||||||
|
LOCK(spk_man->cs_KeyStore);
|
||||||
|
spk_man->UpdateTimeFirstKey(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
||||||
{
|
{
|
||||||
CWalletScanState wss;
|
CWalletScanState wss;
|
||||||
@@ -883,6 +1041,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Load legacy wallet keys
|
||||||
|
result = std::max(LoadLegacyWalletRecords(pwallet, *m_batch, last_client), result);
|
||||||
|
|
||||||
// Get cursor
|
// Get cursor
|
||||||
std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
|
std::unique_ptr<DatabaseCursor> cursor = m_batch->GetNewCursor();
|
||||||
if (!cursor)
|
if (!cursor)
|
||||||
@@ -909,17 +1070,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||||||
std::string strType, strErr;
|
std::string strType, strErr;
|
||||||
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
|
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
|
||||||
{
|
{
|
||||||
if (wss.unexpected_legacy_entry) {
|
|
||||||
strErr = strprintf("Error: Unexpected legacy entry found in descriptor wallet %s. ", pwallet->GetName());
|
|
||||||
strErr += "The wallet might have been tampered with or created with malicious intent.";
|
|
||||||
pwallet->WalletLogPrintf("%s\n", strErr);
|
|
||||||
return DBErrors::UNEXPECTED_LEGACY_ENTRY;
|
|
||||||
}
|
|
||||||
// losing keys is considered a catastrophic error, anything else
|
// losing keys is considered a catastrophic error, anything else
|
||||||
// we assume the user can live with:
|
// we assume the user can live with:
|
||||||
if (strType == DBKeys::KEY ||
|
if (strType == DBKeys::MASTER_KEY ||
|
||||||
strType == DBKeys::MASTER_KEY ||
|
|
||||||
strType == DBKeys::CRYPTED_KEY ||
|
|
||||||
strType == DBKeys::DEFAULTKEY) {
|
strType == DBKeys::DEFAULTKEY) {
|
||||||
result = DBErrors::CORRUPT;
|
result = DBErrors::CORRUPT;
|
||||||
} else if (wss.tx_corrupt) {
|
} else if (wss.tx_corrupt) {
|
||||||
@@ -946,6 +1099,8 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||||||
pwallet->WalletLogPrintf("%s\n", strErr);
|
pwallet->WalletLogPrintf("%s\n", strErr);
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
// Exceptions that can be ignored or treated as non-critical are handled by the individual loading functions.
|
||||||
|
// Any uncaught exceptions will be caught here and treated as critical.
|
||||||
result = DBErrors::CORRUPT;
|
result = DBErrors::CORRUPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,22 +1143,9 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||||||
pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n",
|
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);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const uint256& hash : wss.vWalletUpgrade)
|
for (const uint256& hash : wss.vWalletUpgrade)
|
||||||
WriteTx(pwallet->mapWallet.at(hash));
|
WriteTx(pwallet->mapWallet.at(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 (!has_last_client || last_client != CLIENT_VERSION) // Update
|
if (!has_last_client || last_client != CLIENT_VERSION) // Update
|
||||||
m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);
|
m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);
|
||||||
|
|
||||||
@@ -1026,20 +1168,6 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||||||
result = DBErrors::CORRUPT;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,19 +42,21 @@ struct WalletContext;
|
|||||||
|
|
||||||
static const bool DEFAULT_FLUSHWALLET = true;
|
static const bool DEFAULT_FLUSHWALLET = true;
|
||||||
|
|
||||||
/** Error statuses for the wallet database */
|
/** Error statuses for the wallet database.
|
||||||
enum class DBErrors
|
* Values are in order of severity. When multiple errors occur, the most severe (highest value) will be returned.
|
||||||
|
*/
|
||||||
|
enum class DBErrors : int
|
||||||
{
|
{
|
||||||
LOAD_OK,
|
LOAD_OK = 0,
|
||||||
CORRUPT,
|
NEED_RESCAN = 1,
|
||||||
NONCRITICAL_ERROR,
|
NEED_REWRITE = 2,
|
||||||
TOO_NEW,
|
EXTERNAL_SIGNER_SUPPORT_REQUIRED = 3,
|
||||||
EXTERNAL_SIGNER_SUPPORT_REQUIRED,
|
NONCRITICAL_ERROR = 4,
|
||||||
LOAD_FAIL,
|
TOO_NEW = 5,
|
||||||
NEED_REWRITE,
|
UNKNOWN_DESCRIPTOR = 6,
|
||||||
NEED_RESCAN,
|
LOAD_FAIL = 7,
|
||||||
UNKNOWN_DESCRIPTOR,
|
UNEXPECTED_LEGACY_ENTRY = 8,
|
||||||
UNEXPECTED_LEGACY_ENTRY
|
CORRUPT = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace DBKeys {
|
namespace DBKeys {
|
||||||
|
|||||||
Reference in New Issue
Block a user