Merge #10235: Track keypool entries as internal vs external in memory

d40a72ccb Clarify *(--.end()) iterator semantics in CWallet::TopUpKeyPool (Matt Corallo)
28301b978 Meet code style on lines changed in the previous commit (Matt Corallo)
4a3fc3562 Track keypool entries as internal vs external in memory (Matt Corallo)

Pull request description:

  This is an alternative version of #10184. As @jonasschnelli points out there, the performance regressions are pretty minimal, but given that this is a pretty simple, mechanical change, its probably worth doing.

Tree-SHA512: e83f9ebf2998f8164d1b2eebe5e6dcdeadea8c30b7612861f830758c08bf4093cd6a67b3bcfa9cfcb139e5e0b106fc8898a975fc69f334981aefc756568ab613
This commit is contained in:
Pieter Wuille 2017-07-15 13:30:48 -07:00
commit 5cfdda2503
No known key found for this signature in database
GPG key ID: A636E97631F767E0
2 changed files with 115 additions and 103 deletions

View file

@ -2977,7 +2977,8 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
if (dbw->Rewrite("\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
setInternalKeyPool.clear();
setExternalKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
@ -3005,7 +3006,8 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
{
if (dbw->Rewrite("\x04pool"))
{
setKeyPool.clear();
setInternalKeyPool.clear();
setExternalKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
@ -3030,7 +3032,8 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
if (dbw->Rewrite("\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
setInternalKeyPool.clear();
setExternalKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
@ -3114,9 +3117,16 @@ bool CWallet::NewKeyPool()
{
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
for (int64_t nIndex : setKeyPool)
for (int64_t nIndex : setInternalKeyPool) {
walletdb.ErasePool(nIndex);
setKeyPool.clear();
}
setInternalKeyPool.clear();
for (int64_t nIndex : setExternalKeyPool) {
walletdb.ErasePool(nIndex);
}
setExternalKeyPool.clear();
if (!TopUpKeyPool()) {
return false;
@ -3128,25 +3138,8 @@ bool CWallet::NewKeyPool()
size_t CWallet::KeypoolCountExternalKeys()
{
AssertLockHeld(cs_wallet); // setKeyPool
// immediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
return setKeyPool.size();
CWalletDB walletdb(*dbw);
// count amount of external keys
size_t amountE = 0;
for(const int64_t& id : setKeyPool)
{
CKeyPool tmpKeypool;
if (!walletdb.ReadPool(id, tmpKeypool))
throw std::runtime_error(std::string(__func__) + ": read failed");
amountE += !tmpKeypool.fInternal;
}
return amountE;
AssertLockHeld(cs_wallet); // setExternalKeyPool
return setExternalKeyPool.size();
}
bool CWallet::TopUpKeyPool(unsigned int kpSize)
@ -3166,10 +3159,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
// count amount of available keys (internal, external)
// make sure the keypool of external and internal keys fits the user selected target (-keypool)
int64_t amountExternal = KeypoolCountExternalKeys();
int64_t amountInternal = setKeyPool.size() - amountExternal;
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0);
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountInternal, (int64_t) 0);
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() || !CanSupportFeature(FEATURE_HD_SPLIT))
{
@ -3181,20 +3172,32 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
for (int64_t i = missingInternal + missingExternal; i--;)
{
int64_t nEnd = 1;
if (i < missingInternal)
if (i < missingInternal) {
internal = true;
if (!setKeyPool.empty())
nEnd = *(--setKeyPool.end()) + 1;
}
if (!setInternalKeyPool.empty()) {
nEnd = *(setInternalKeyPool.rbegin()) + 1;
}
if (!setExternalKeyPool.empty()) {
nEnd = std::max(nEnd, *(setExternalKeyPool.rbegin()) + 1);
}
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal)))
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
setKeyPool.insert(nEnd);
LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setKeyPool.size(), internal);
if (internal) {
setInternalKeyPool.insert(nEnd);
} else {
setExternalKeyPool.insert(nEnd);
}
LogPrintf("keypool added key %d, size=%u (%u internal), new key is %s\n", nEnd, setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size(), internal ? "internal" : "external");
}
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal)
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
@ -3204,30 +3207,30 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool int
if (!IsLocked())
TopUpKeyPool();
bool fReturningInternal = IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT) && fRequestedInternal;
std::set<int64_t>& setKeyPool = fReturningInternal ? setInternalKeyPool : setExternalKeyPool;
// Get the oldest key
if(setKeyPool.empty())
return;
CWalletDB walletdb(*dbw);
// try to find a key that matches the internal/external filter
for(const int64_t& id : setKeyPool)
{
CKeyPool tmpKeypool;
if (!walletdb.ReadPool(id, tmpKeypool))
auto it = setKeyPool.begin();
nIndex = *it;
setKeyPool.erase(it);
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed");
if (!HaveKey(tmpKeypool.vchPubKey.GetID()))
}
if (!HaveKey(keypool.vchPubKey.GetID())) {
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT) || tmpKeypool.fInternal == internal)
{
nIndex = id;
keypool = tmpKeypool;
setKeyPool.erase(id);
}
if (keypool.fInternal != fReturningInternal) {
throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
}
assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
return;
}
}
}
}
@ -3239,12 +3242,16 @@ void CWallet::KeepKey(int64_t nIndex)
LogPrintf("keypool keep %d\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex)
void CWallet::ReturnKey(int64_t nIndex, bool fInternal)
{
// Return to key pool
{
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
if (fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
}
LogPrintf("keypool return %d\n", nIndex);
}
@ -3268,46 +3275,33 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
return true;
}
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) {
if (setKeyPool.empty()) {
return GetTime();
}
CKeyPool keypool;
int64_t nIndex = *(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
}
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
}
int64_t CWallet::GetOldestKeyPoolTime()
{
LOCK(cs_wallet);
// if the keypool is empty, return <NOW>
if (setKeyPool.empty())
return GetTime();
CKeyPool keypool;
CWalletDB walletdb(*dbw);
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT))
{
// if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key)
int64_t now = GetTime();
int64_t oldest_external = now, oldest_internal = now;
for(const int64_t& id : setKeyPool)
{
if (!walletdb.ReadPool(id, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed");
}
if (keypool.fInternal && keypool.nTime < oldest_internal) {
oldest_internal = keypool.nTime;
}
else if (!keypool.fInternal && keypool.nTime < oldest_external) {
oldest_external = keypool.nTime;
}
if (oldest_internal != now && oldest_external != now) {
break;
}
}
return std::max(oldest_internal, oldest_external);
}
// load oldest key from keypool, get time and return
int64_t nIndex = *(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool))
throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb);
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) {
oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey);
}
return oldestKey;
}
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
@ -3468,6 +3462,7 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
else {
return false;
}
fInternal = keypool.fInternal;
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
@ -3484,12 +3479,25 @@ void CReserveKey::KeepKey()
void CReserveKey::ReturnKey()
{
if (nIndex != -1)
pwallet->ReturnKey(nIndex);
if (nIndex != -1) {
pwallet->ReturnKey(nIndex, fInternal);
}
nIndex = -1;
vchPubKey = CPubKey();
}
static void LoadReserveKeysToSet(std::set<CKeyID>& setAddress, const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) {
for (const int64_t& id : setKeyPool)
{
CKeyPool keypool;
if (!walletdb.ReadPool(id, keypool))
throw std::runtime_error(std::string(__func__) + ": read failed");
assert(keypool.vchPubKey.IsValid());
CKeyID keyID = keypool.vchPubKey.GetID();
setAddress.insert(keyID);
}
}
void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
{
setAddress.clear();
@ -3497,16 +3505,13 @@ void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
CWalletDB walletdb(*dbw);
LOCK2(cs_main, cs_wallet);
for (const int64_t& id : setKeyPool)
{
CKeyPool keypool;
if (!walletdb.ReadPool(id, keypool))
throw std::runtime_error(std::string(__func__) + ": read failed");
assert(keypool.vchPubKey.IsValid());
CKeyID keyID = keypool.vchPubKey.GetID();
if (!HaveKey(keyID))
LoadReserveKeysToSet(setAddress, setInternalKeyPool, walletdb);
LoadReserveKeysToSet(setAddress, setExternalKeyPool, walletdb);
for (const CKeyID& keyID : setAddress) {
if (!HaveKey(keyID)) {
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
setAddress.insert(keyID);
}
}
}

View file

@ -699,7 +699,8 @@ private:
/* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false);
std::set<int64_t> setKeyPool;
std::set<int64_t> setInternalKeyPool;
std::set<int64_t> setExternalKeyPool;
int64_t nTimeFirstKey;
@ -744,7 +745,11 @@ public:
void LoadKeyPool(int nIndex, const CKeyPool &keypool)
{
setKeyPool.insert(nIndex);
if (keypool.fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
// If no metadata exists yet, create a default with the pool key's
// creation time. Note that this may be overwritten by actually
@ -974,9 +979,9 @@ public:
bool NewKeyPool();
size_t KeypoolCountExternalKeys();
bool TopUpKeyPool(unsigned int kpSize = 0);
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal);
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
void KeepKey(int64_t nIndex);
void ReturnKey(int64_t nIndex);
void ReturnKey(int64_t nIndex, bool fInternal);
bool GetKeyFromPool(CPubKey &key, bool internal = false);
int64_t GetOldestKeyPoolTime();
void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
@ -1030,8 +1035,8 @@ public:
unsigned int GetKeyPoolSize()
{
AssertLockHeld(cs_wallet); // setKeyPool
return setKeyPool.size();
AssertLockHeld(cs_wallet); // set{Ex,In}ternalKeyPool
return setInternalKeyPool.size() + setExternalKeyPool.size();
}
bool SetDefaultKey(const CPubKey &vchPubKey);
@ -1135,11 +1140,13 @@ protected:
CWallet* pwallet;
int64_t nIndex;
CPubKey vchPubKey;
bool fInternal;
public:
CReserveKey(CWallet* pwalletIn)
{
nIndex = -1;
pwallet = pwalletIn;
fInternal = false;
}
CReserveKey() = default;