Skip to content

Commit

Permalink
Merge #10235: Track keypool entries as internal vs external in memory
Browse files Browse the repository at this point in the history
d40a72c Clarify *(--.end()) iterator semantics in CWallet::TopUpKeyPool (Matt Corallo)
28301b9 Meet code style on lines changed in the previous commit (Matt Corallo)
4a3fc35 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
  • Loading branch information
sipa committed Jul 15, 2017
2 parents c5904e8 + d40a72c commit 5cfdda2
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 103 deletions.
199 changes: 102 additions & 97 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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);
}
setInternalKeyPool.clear();

for (int64_t nIndex : setExternalKeyPool) {
walletdb.ErasePool(nIndex);
setKeyPool.clear();
}
setExternalKeyPool.clear();

if (!TopUpKeyPool()) {
return false;
Expand All @@ -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)
Expand All @@ -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))
{
Expand All @@ -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();
Expand All @@ -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))
throw std::runtime_error(std::string(__func__) + ": read failed");
if (!HaveKey(tmpKeypool.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);
assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
return;
}
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(keypool.vchPubKey.GetID())) {
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
}
if (keypool.fInternal != fReturningInternal) {
throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified");
}

assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
}
}

Expand All @@ -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);
}
Expand All @@ -3268,48 +3275,35 @@ bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
return true;
}

int64_t CWallet::GetOldestKeyPoolTime()
{
LOCK(cs_wallet);

// if the keypool is empty, return <NOW>
if (setKeyPool.empty())
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t>& setKeyPool, CWalletDB& walletdb) {
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))
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);

CWalletDB walletdb(*dbw);

// load oldest key from keypool, get time and return
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()
{
std::map<CTxDestination, CAmount> balances;
Expand Down Expand Up @@ -3468,6 +3462,7 @@ bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
else {
return false;
}
fInternal = keypool.fInternal;
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
Expand All @@ -3484,32 +3479,42 @@ void CReserveKey::KeepKey()

void CReserveKey::ReturnKey()
{
if (nIndex != -1)
pwallet->ReturnKey(nIndex);
if (nIndex != -1) {
pwallet->ReturnKey(nIndex, fInternal);
}
nIndex = -1;
vchPubKey = CPubKey();
}

void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
{
setAddress.clear();

CWalletDB walletdb(*dbw);

LOCK2(cs_main, cs_wallet);
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();
if (!HaveKey(keyID))
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
setAddress.insert(keyID);
}
}

void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
{
setAddress.clear();

CWalletDB walletdb(*dbw);

LOCK2(cs_main, cs_wallet);
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");
}
}
}

void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script)
{
std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this);
Expand Down
19 changes: 13 additions & 6 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
/* 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;

Expand Down Expand Up @@ -744,7 +745,11 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface

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
Expand Down Expand Up @@ -974,9 +979,9 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
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;
Expand Down Expand Up @@ -1030,8 +1035,8 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface

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);
Expand Down Expand Up @@ -1135,11 +1140,13 @@ class CReserveKey : public CReserveScript
CWallet* pwallet;
int64_t nIndex;
CPubKey vchPubKey;
bool fInternal;
public:
CReserveKey(CWallet* pwalletIn)
{
nIndex = -1;
pwallet = pwalletIn;
fInternal = false;
}

CReserveKey() = default;
Expand Down

0 comments on commit 5cfdda2

Please sign in to comment.