mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 06:28:31 +01:00
Merge bitcoin/bitcoin#23304: wallet: Derive inactive HD chains in additional places
c4d76c6faatests: Tests for inactive HD chains (Andrew Chow)8077862c5ewallet: Refactor TopUp to be able to top up inactive chains too (Andrew Chow)70134eb34fwallet: Properly set hd chain counters when loading (Andrew Chow)961b9e4e40wallet: Parse hdKeypath if key_origin is not available (Andrew Chow)0652ee73ecAdd size check on meta.key_origin.path (Rob Fielding) Pull request description: Currently inactive HD chains are only derived from at the time a key in that chain is found to have been used. However, at that time, the wallet may not be able to derive keys (e.g. it is locked). Currently we would just move on and not derive any new keys, however this could result in missing funds. This PR resolves this problem by adding memory only variables to `CHDChain` which track the highest known index. `TopUp` is modified to always try to top up the inactive HD chains, and this process will use the new variables to determine how much to top up. In this way, after an encrypted wallet is unlocked, the inactive HD chains will be topped up and hopefully funds will not be missed. Note that because these variables are not persisted to disk (because `CHDChain`s for inactive HD chains are not written to disk), if an encrypted wallet is not unlocked in the same session as a key from an inactive chain is found to be used, then it will not be topped up later unless more keys are found. Additionally, wallets which do not have upgraded key metadata will not derive any keys from inactive HD chains. This is resolved by using the derivation path string in `CKeyMetadata.hdKeypath` to determine what indexes to derive. ACKs for top commit: laanwj: Code review ACKc4d76c6faaTree-SHA512: b2b572ad7f1b1b2847edece09f7583543d63997e18ae32764e5a27ad608dd64b9bdb2d84ea27137894e986a8e82f047a3dba9c8015b74f5f179961911f0c4095
This commit is contained in:
@@ -322,8 +322,6 @@ bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t i
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
if (m_storage.IsLocked()) return false;
|
||||
|
||||
auto it = m_inactive_hd_chains.find(seed_id);
|
||||
if (it == m_inactive_hd_chains.end()) {
|
||||
return false;
|
||||
@@ -331,27 +329,14 @@ bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t i
|
||||
|
||||
CHDChain& chain = it->second;
|
||||
|
||||
// Top up key pool
|
||||
int64_t target_size = std::max(gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 1);
|
||||
|
||||
// "size" of the keypools. Not really the size, actually the difference between index and the chain counter
|
||||
// Since chain counter is 1 based and index is 0 based, one of them needs to be offset by 1.
|
||||
int64_t kp_size = (internal ? chain.nInternalChainCounter : chain.nExternalChainCounter) - (index + 1);
|
||||
|
||||
// make sure the keypool fits the user-selected target (-keypool)
|
||||
int64_t missing = std::max(target_size - kp_size, (int64_t) 0);
|
||||
|
||||
if (missing > 0) {
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
for (int64_t i = missing; i > 0; --i) {
|
||||
GenerateNewKey(batch, chain, internal);
|
||||
}
|
||||
if (internal) {
|
||||
WalletLogPrintf("inactive seed with id %s added %d internal keys\n", HexStr(seed_id), missing);
|
||||
} else {
|
||||
WalletLogPrintf("inactive seed with id %s added %d keys\n", HexStr(seed_id), missing);
|
||||
}
|
||||
if (internal) {
|
||||
chain.m_next_internal_index = std::max(chain.m_next_internal_index, index + 1);
|
||||
} else {
|
||||
chain.m_next_external_index = std::max(chain.m_next_external_index, index + 1);
|
||||
}
|
||||
|
||||
TopUpChain(chain, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -383,11 +368,26 @@ std::vector<WalletDestination> LegacyScriptPubKeyMan::MarkUnusedAddresses(const
|
||||
if (it != mapKeyMetadata.end()){
|
||||
CKeyMetadata meta = it->second;
|
||||
if (!meta.hd_seed_id.IsNull() && meta.hd_seed_id != m_hd_chain.seed_id) {
|
||||
bool internal = (meta.key_origin.path[1] & ~BIP32_HARDENED_KEY_LIMIT) != 0;
|
||||
int64_t index = meta.key_origin.path[2] & ~BIP32_HARDENED_KEY_LIMIT;
|
||||
std::vector<uint32_t> path;
|
||||
if (meta.has_key_origin) {
|
||||
path = meta.key_origin.path;
|
||||
} else if (!ParseHDKeypath(meta.hdKeypath, path)) {
|
||||
WalletLogPrintf("%s: Adding inactive seed keys failed, invalid hdKeypath: %s\n",
|
||||
__func__,
|
||||
meta.hdKeypath);
|
||||
}
|
||||
if (path.size() != 3) {
|
||||
WalletLogPrintf("%s: Adding inactive seed keys failed, invalid path size: %d, has_key_origin: %s\n",
|
||||
__func__,
|
||||
path.size(),
|
||||
meta.has_key_origin);
|
||||
} else {
|
||||
bool internal = (path[1] & ~BIP32_HARDENED_KEY_LIMIT) != 0;
|
||||
int64_t index = path[2] & ~BIP32_HARDENED_KEY_LIMIT;
|
||||
|
||||
if (!TopUpInactiveHDChain(meta.hd_seed_id, index, internal)) {
|
||||
WalletLogPrintf("%s: Adding inactive seed keys failed\n", __func__);
|
||||
if (!TopUpInactiveHDChain(meta.hd_seed_id, index, internal)) {
|
||||
WalletLogPrintf("%s: Adding inactive seed keys failed\n", __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1265,47 +1265,72 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
|
||||
if (!CanGenerateKeys()) {
|
||||
return false;
|
||||
}
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
if (m_storage.IsLocked()) return false;
|
||||
|
||||
// Top up key pool
|
||||
unsigned int nTargetSize;
|
||||
if (kpSize > 0)
|
||||
nTargetSize = kpSize;
|
||||
else
|
||||
nTargetSize = std::max(gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
|
||||
|
||||
// count amount of available keys (internal, external)
|
||||
// make sure the keypool of external and internal keys fits the user selected target (-keypool)
|
||||
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setExternalKeyPool.size(), (int64_t) 0);
|
||||
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - (int64_t)setInternalKeyPool.size(), (int64_t) 0);
|
||||
|
||||
if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT))
|
||||
{
|
||||
// don't create extra internal keys
|
||||
missingInternal = 0;
|
||||
}
|
||||
bool internal = false;
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
for (int64_t i = missingInternal + missingExternal; i--;)
|
||||
{
|
||||
if (i < missingInternal) {
|
||||
internal = true;
|
||||
}
|
||||
|
||||
CPubKey pubkey(GenerateNewKey(batch, m_hd_chain, internal));
|
||||
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
||||
}
|
||||
if (missingInternal + missingExternal > 0) {
|
||||
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
|
||||
if (!TopUpChain(m_hd_chain, kpSize)) {
|
||||
return false;
|
||||
}
|
||||
for (auto& [chain_id, chain] : m_inactive_hd_chains) {
|
||||
if (!TopUpChain(chain, kpSize)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
NotifyCanGetAddressesChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LegacyScriptPubKeyMan::TopUpChain(CHDChain& chain, unsigned int kpSize)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
if (m_storage.IsLocked()) return false;
|
||||
|
||||
// Top up key pool
|
||||
unsigned int nTargetSize;
|
||||
if (kpSize > 0) {
|
||||
nTargetSize = kpSize;
|
||||
} else {
|
||||
nTargetSize = std::max(gArgs.GetIntArg("-keypool", DEFAULT_KEYPOOL_SIZE), int64_t{0});
|
||||
}
|
||||
int64_t target = std::max((int64_t) nTargetSize, int64_t{1});
|
||||
|
||||
// count amount of available keys (internal, external)
|
||||
// make sure the keypool of external and internal keys fits the user selected target (-keypool)
|
||||
int64_t missingExternal;
|
||||
int64_t missingInternal;
|
||||
if (chain == m_hd_chain) {
|
||||
missingExternal = std::max(target - (int64_t)setExternalKeyPool.size(), int64_t{0});
|
||||
missingInternal = std::max(target - (int64_t)setInternalKeyPool.size(), int64_t{0});
|
||||
} else {
|
||||
missingExternal = std::max(target - (chain.nExternalChainCounter - chain.m_next_external_index), int64_t{0});
|
||||
missingInternal = std::max(target - (chain.nInternalChainCounter - chain.m_next_internal_index), int64_t{0});
|
||||
}
|
||||
|
||||
if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
|
||||
// don't create extra internal keys
|
||||
missingInternal = 0;
|
||||
}
|
||||
bool internal = false;
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
for (int64_t i = missingInternal + missingExternal; i--;) {
|
||||
if (i < missingInternal) {
|
||||
internal = true;
|
||||
}
|
||||
|
||||
CPubKey pubkey(GenerateNewKey(batch, chain, internal));
|
||||
if (chain == m_hd_chain) {
|
||||
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
||||
}
|
||||
}
|
||||
if (missingInternal + missingExternal > 0) {
|
||||
if (chain == m_hd_chain) {
|
||||
WalletLogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size());
|
||||
} else {
|
||||
WalletLogPrintf("inactive seed with id %s added %d external keys, %d internal keys\n", HexStr(chain.seed_id), missingExternal, missingInternal);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const bool internal, WalletBatch& batch)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
Reference in New Issue
Block a user