diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d9ded216ea01..fc34e0b6a370 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -108,7 +108,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } -CPubKey CWallet::GenerateNewKey(uint32_t nAccountIndex, bool fInternal) +CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, uint32_t nAccountIndex, bool fInternal) { AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -122,14 +122,15 @@ CPubKey CWallet::GenerateNewKey(uint32_t nAccountIndex, bool fInternal) CPubKey pubkey; // use HD key derivation if HD was enabled during wallet creation if (IsHDEnabled()) { - DeriveNewChildKey(metadata, secret, nAccountIndex, fInternal); + DeriveNewChildKey(walletdb, metadata, secret, nAccountIndex, fInternal); pubkey = secret.GetPubKey(); } else { secret.MakeNewKey(fCompressed); // Compressed public keys were introduced in version 0.6.0 - if (fCompressed) + if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY); + } pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); @@ -138,13 +139,14 @@ CPubKey CWallet::GenerateNewKey(uint32_t nAccountIndex, bool fInternal) mapKeyMetadata[pubkey.GetID()] = metadata; UpdateTimeFirstKey(nCreationTime); - if (!AddKeyPubKey(secret, pubkey)) + if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed"); + } } return pubkey; } -void CWallet::DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal) +void CWallet::DeriveNewChildKey(CWalletDB &walletdb, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal) { CHDChain hdChainTmp; if (!GetHDChain(hdChainTmp)) { @@ -193,15 +195,15 @@ void CWallet::DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, u throw std::runtime_error(std::string(__func__) + ": SetAccount failed"); if (IsCrypted()) { - if (!SetCryptedHDChain(hdChainCurrent, false)) + if (!SetCryptedHDChain(walletdb, hdChainCurrent, false)) throw std::runtime_error(std::string(__func__) + ": SetCryptedHDChain failed"); } else { - if (!SetHDChain(hdChainCurrent, false)) + if (!SetHDChain(walletdb, hdChainCurrent, false)) throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); } - if (!AddHDPubKey(childKey.Neuter(), fInternal)) + if (!AddHDPubKey(walletdb, childKey.Neuter(), fInternal)) throw std::runtime_error(std::string(__func__) + ": AddHDPubKey failed"); } @@ -263,7 +265,7 @@ bool CWallet::LoadHDPubKey(const CHDPubKey &hdPubKey) return true; } -bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal) +bool CWallet::AddHDPubKey(CWalletDB &walletdb, const CExtPubKey &extPubKey, bool fInternal) { AssertLockHeld(cs_wallet); @@ -285,32 +287,53 @@ bool CWallet::AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal) if (HaveWatchOnly(script)) RemoveWatchOnly(script); - return CWalletDB(*dbw).WriteHDPubKey(hdPubKey, mapKeyMetadata[extPubKey.pubkey.GetID()]); + return walletdb.WriteHDPubKey(hdPubKey, mapKeyMetadata[extPubKey.pubkey.GetID()]); } -bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey& secret, const CPubKey &pubkey) { AssertLockHeld(cs_wallet); // mapKeyMetadata - if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) - return false; + // CCryptoKeyStore has no concept of wallet databases, but calls AddCryptedKey + // which is overridden below. To avoid flushes, the database handle is + // tunneled through to it. + bool needsDB = !pwalletdbEncryption; + if (needsDB) { + pwalletdbEncryption = &walletdb; + } + if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) { + if (needsDB) pwalletdbEncryption = NULL; + return false; + return false; + } + if (needsDB) pwalletdbEncryption = NULL; // check if we need to remove from watch-only CScript script; script = GetScriptForDestination(pubkey.GetID()); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } script = GetScriptForRawPubKey(pubkey); - if (HaveWatchOnly(script)) + if (HaveWatchOnly(script)) { RemoveWatchOnly(script); + } if (!IsCrypted()) { - return CWalletDB(*dbw).WriteKey(pubkey, - secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); + return walletdb.WriteKey(pubkey, + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); } return true; } + +bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey) +{ + CWalletDB walletdb(*dbw); + + return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey); +} + bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { @@ -802,7 +825,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) assert(hdChainCurrent.GetID() == hdChainCrypted.GetID()); assert(hdChainCurrent.GetSeedHash() != hdChainCrypted.GetSeedHash()); - assert(SetCryptedHDChain(hdChainCrypted, false)); + assert(SetCryptedHDChain(*pwalletdbEncryption, hdChainCrypted, false)); } // Encryption was introduced in version 0.4.0 @@ -1598,8 +1621,8 @@ void CWallet::GenerateNewHDChain() } newHdChain.Debug(__func__); - if (!SetHDChain(newHdChain, false)) - throw std::runtime_error(std::string(__func__) + ": SetHDChain failed"); + if (!SetHDChainSingle(newHdChain, false)) + throw std::runtime_error(std::string(__func__) + ": SetHDChainSingle failed"); // clean up gArgs.ForceRemoveArg("-hdseed"); @@ -1607,20 +1630,20 @@ void CWallet::GenerateNewHDChain() gArgs.ForceRemoveArg("-mnemonicpassphrase"); } -bool CWallet::SetHDChain(const CHDChain& chain, bool memonly) +bool CWallet::SetHDChain(CWalletDB &walletdb, const CHDChain& chain, bool memonly) { LOCK(cs_wallet); if (!CCryptoKeyStore::SetHDChain(chain)) return false; - if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain)) + if (!memonly && !walletdb.WriteHDChain(chain)) throw std::runtime_error(std::string(__func__) + ": WriteHDChain failed"); return true; } -bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) +bool CWallet::SetCryptedHDChain(CWalletDB &walletdb, const CHDChain& chain, bool memonly) { LOCK(cs_wallet); @@ -1632,7 +1655,7 @@ bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) if (!pwalletdbEncryption->WriteCryptedHDChain(chain)) throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed"); } else { - if (!CWalletDB(*dbw).WriteCryptedHDChain(chain)) + if (!walletdb.WriteCryptedHDChain(chain)) throw std::runtime_error(std::string(__func__) + ": WriteCryptedHDChain failed"); } } @@ -1640,6 +1663,18 @@ bool CWallet::SetCryptedHDChain(const CHDChain& chain, bool memonly) return true; } +bool CWallet::SetHDChainSingle(const CHDChain& chain, bool memonly) +{ + CWalletDB walletdb(*dbw); + return SetHDChain(walletdb, chain, memonly); +} + +bool CWallet::SetCryptedHDChainSingle(const CHDChain& chain, bool memonly) +{ + CWalletDB walletdb(*dbw); + return SetCryptedHDChain(walletdb, chain, memonly); +} + bool CWallet::GetDecryptedHDChain(CHDChain& hdChainRet) { LOCK(cs_wallet); @@ -4206,15 +4241,21 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize) nEnd = std::max(nEnd, *(--setExternalKeyPool.end()) + 1); } // TODO: implement keypools for all accounts? - if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(0, fInternal), fInternal))) + if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(walletdb, 0, fInternal), fInternal))) { throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); + } if (fInternal) { setInternalKeyPool.insert(nEnd); } else { setExternalKeyPool.insert(nEnd); } - LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setInternalKeyPool.size() + setExternalKeyPool.size(), fInternal); + + if (missingInternal + missingExternal > 0) { + LogPrintf("keypool added %d keys (%d internal), size=%u (%u internal)\n", + missingInternal + missingExternal, missingInternal, + setInternalKeyPool.size() + setExternalKeyPool.size(), setInternalKeyPool.size()); + } double dProgress = 100.f * nEnd / (nTargetSize + 1); std::string strMsg = strprintf(_("Loading wallet... (%3.2f %%)"), dProgress); @@ -4285,18 +4326,20 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal) LogPrintf("keypool return %d\n", nIndex); } -bool CWallet::GetKeyFromPool(CPubKey& result, bool fInternal) +bool CWallet::GetKeyFromPool(CPubKey& result, bool internal) { CKeyPool keypool; { LOCK(cs_wallet); int64_t nIndex = 0; - ReserveKeyFromKeyPool(nIndex, keypool, fInternal); + ReserveKeyFromKeyPool(nIndex, keypool, internal); if (nIndex == -1) { if (IsLocked(true)) return false; // TODO: implement keypool for all accouts? - result = GenerateNewKey(0, fInternal); + + CWalletDB walletdb(*dbw); + result = GenerateNewKey(walletdb, 0, internal); return true; } KeepKey(nIndex); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0edff49158fe..122397fee099 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -745,7 +745,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = NULL, int posInBlock = 0); /* HD derive new child key (on internal or external chain) */ - void DeriveNewChildKey(const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/); + void DeriveNewChildKey(CWalletDB &walletdb, const CKeyMetadata& metadata, CKey& secretRet, uint32_t nAccountIndex, bool fInternal /*= false*/); std::set setInternalKeyPool; std::set setExternalKeyPool; @@ -949,7 +949,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface * keystore implementation * Generate a new key */ - CPubKey GenerateNewKey(uint32_t nAccountIndex, bool fInternal /*= false*/); + CPubKey GenerateNewKey(CWalletDB& walletdb, uint32_t nAccountIndex, bool fInternal /*= false*/); //! HaveKey implementation that also checks the mapHdPubKeys bool HaveKey(const CKeyID &address) const override; //! GetPubKey implementation that also checks the mapHdPubKeys @@ -957,11 +957,12 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface //! GetKey implementation that can derive a HD private key on the fly bool GetKey(const CKeyID &address, CKey& keyOut) const override; //! Adds a HDPubKey into the wallet(database) - bool AddHDPubKey(const CExtPubKey &extPubKey, bool fInternal); + bool AddHDPubKey(CWalletDB &walletdb, const CExtPubKey &extPubKey, bool fInternal); //! loads a HDPubKey into the wallets memory bool LoadHDPubKey(const CHDPubKey &hdPubKey); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; + bool AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey& key, const CPubKey &pubkey); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) @@ -1232,8 +1233,15 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /* Generates a new HD chain */ void GenerateNewHDChain(); /* Set the HD chain model (chain child index counters) */ - bool SetHDChain(const CHDChain& chain, bool memonly); - bool SetCryptedHDChain(const CHDChain& chain, bool memonly); + bool SetHDChain(CWalletDB &walletdb, const CHDChain& chain, bool memonly); + bool SetCryptedHDChain(CWalletDB &walletdb, const CHDChain& chain, bool memonly); + /** + * Set the HD chain model (chain child index counters) using temporary wallet db object + * which causes db flush every time these methods are used + */ + bool SetHDChainSingle(const CHDChain& chain, bool memonly); + bool SetCryptedHDChainSingle(const CHDChain& chain, bool memonly); + bool GetDecryptedHDChain(CHDChain& hdChainRet); void NotifyTransactionLock(const CTransaction &tx, const llmq::CInstantSendLock& islock) override; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 4a8375135e20..e6d436291acb 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -502,7 +502,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { CHDChain chain; ssValue >> chain; - if (!pwallet->SetHDChain(chain, true)) + if (!pwallet->SetHDChainSingle(chain, true)) { strErr = "Error reading wallet database: SetHDChain failed"; return false; @@ -512,7 +512,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { CHDChain chain; ssValue >> chain; - if (!pwallet->SetCryptedHDChain(chain, true)) + if (!pwallet->SetCryptedHDChainSingle(chain, true)) { strErr = "Error reading wallet database: SetHDCryptedChain failed"; return false;