mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 22:50:59 +01:00
Merge #11022: Basic keypool topup
d34957e[wallet] [tests] Add keypool topup functional test (Jonas Schnelli)095142d[wallet] keypool mark-used and topup (John Newbery)c25d90f[wallet] Add HasUnusedKeys() helper (John Newbery)f2123e3[wallet] Cache keyid -> keypool id mappings (John Newbery)83f1ec3[wallet] Don't hold cs_LastBlockFile while calling setBestChain (John Newbery)2376bfc[wallet] [moveonly] Move LoadKeyPool to cpp (Matt Corallo)cab8557[wallet] [moveonly] Move CAffectedKeysVisitor (Jonas Schnelli) Pull request description: This PR contains the first part of #10882 : - if a key from the keypool is used, mark all keys up to that key as used, and then try to top up the keypool - top up the keypool on startup Notably, it does not stop the node or prevent the best block from advancing if the keypool drops below a threshold (which means that transactions may be missed and funds lost if restoring from an old HD wallet backup). Tree-SHA512: ac681fefeaf7ec2aab2fa1da93d12273ea80bd05eb48d7b3b551ea6e5d975dd97ba7de52b7fba52993823280ac4079cc36cf78a27dac708107ebf8fb6326142b
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include "consensus/consensus.h"
|
||||
#include "consensus/validation.h"
|
||||
#include "fs.h"
|
||||
#include "init.h"
|
||||
#include "key.h"
|
||||
#include "keystore.h"
|
||||
#include "validation.h"
|
||||
@@ -80,6 +81,38 @@ std::string COutput::ToString() const
|
||||
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
|
||||
}
|
||||
|
||||
class CAffectedKeysVisitor : public boost::static_visitor<void> {
|
||||
private:
|
||||
const CKeyStore &keystore;
|
||||
std::vector<CKeyID> &vKeys;
|
||||
|
||||
public:
|
||||
CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
|
||||
|
||||
void Process(const CScript &script) {
|
||||
txnouttype type;
|
||||
std::vector<CTxDestination> vDest;
|
||||
int nRequired;
|
||||
if (ExtractDestinations(script, type, vDest, nRequired)) {
|
||||
for (const CTxDestination &dest : vDest)
|
||||
boost::apply_visitor(*this, dest);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const CKeyID &keyId) {
|
||||
if (keystore.HaveKey(keyId))
|
||||
vKeys.push_back(keyId);
|
||||
}
|
||||
|
||||
void operator()(const CScriptID &scriptId) {
|
||||
CScript script;
|
||||
if (keystore.GetCScript(scriptId, script))
|
||||
Process(script);
|
||||
}
|
||||
|
||||
void operator()(const CNoDestination &none) {}
|
||||
};
|
||||
|
||||
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
@@ -1021,6 +1054,30 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
|
||||
if (fExisted && !fUpdate) return false;
|
||||
if (fExisted || IsMine(tx) || IsFromMe(tx))
|
||||
{
|
||||
/* Check if any keys in the wallet keypool that were supposed to be unused
|
||||
* have appeared in a new transaction. If so, remove those keys from the keypool.
|
||||
* This can happen when restoring an old wallet backup that does not contain
|
||||
* the mostly recently created transactions from newer versions of the wallet.
|
||||
*/
|
||||
|
||||
// loop though all outputs
|
||||
for (const CTxOut& txout: tx.vout) {
|
||||
// extract addresses and check if they match with an unused keypool key
|
||||
std::vector<CKeyID> vAffected;
|
||||
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
|
||||
for (const CKeyID &keyid : vAffected) {
|
||||
std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
|
||||
if (mi != m_pool_key_to_index.end()) {
|
||||
LogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
|
||||
MarkReserveKeysAsUsed(mi->second);
|
||||
|
||||
if (!TopUpKeyPool()) {
|
||||
LogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CWalletTx wtx(this, ptx);
|
||||
|
||||
// Get merkle branch if transaction was found in a block
|
||||
@@ -3050,6 +3107,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
|
||||
LOCK(cs_wallet);
|
||||
setInternalKeyPool.clear();
|
||||
setExternalKeyPool.clear();
|
||||
m_pool_key_to_index.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.
|
||||
@@ -3079,6 +3137,7 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
|
||||
{
|
||||
setInternalKeyPool.clear();
|
||||
setExternalKeyPool.clear();
|
||||
m_pool_key_to_index.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.
|
||||
@@ -3105,6 +3164,7 @@ DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
|
||||
LOCK(cs_wallet);
|
||||
setInternalKeyPool.clear();
|
||||
setExternalKeyPool.clear();
|
||||
m_pool_key_to_index.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.
|
||||
@@ -3199,6 +3259,8 @@ bool CWallet::NewKeyPool()
|
||||
}
|
||||
setExternalKeyPool.clear();
|
||||
|
||||
m_pool_key_to_index.clear();
|
||||
|
||||
if (!TopUpKeyPool()) {
|
||||
return false;
|
||||
}
|
||||
@@ -3213,6 +3275,25 @@ size_t CWallet::KeypoolCountExternalKeys()
|
||||
return setExternalKeyPool.size();
|
||||
}
|
||||
|
||||
void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
if (keypool.fInternal) {
|
||||
setInternalKeyPool.insert(nIndex);
|
||||
} else {
|
||||
setExternalKeyPool.insert(nIndex);
|
||||
}
|
||||
m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
|
||||
m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
|
||||
|
||||
// If no metadata exists yet, create a default with the pool key's
|
||||
// creation time. Note that this may be overwritten by actually
|
||||
// stored metadata for that key later, which is fine.
|
||||
CKeyID keyid = keypool.vchPubKey.GetID();
|
||||
if (mapKeyMetadata.count(keyid) == 0)
|
||||
mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
||||
}
|
||||
|
||||
bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
{
|
||||
{
|
||||
@@ -3249,7 +3330,8 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); // How in the hell did you use so many keys?
|
||||
int64_t index = ++m_max_keypool_index;
|
||||
|
||||
if (!walletdb.WritePool(index, CKeyPool(GenerateNewKey(walletdb, internal), internal))) {
|
||||
CPubKey pubkey(GenerateNewKey(walletdb, internal));
|
||||
if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) {
|
||||
throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
|
||||
}
|
||||
|
||||
@@ -3258,6 +3340,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
|
||||
} else {
|
||||
setExternalKeyPool.insert(index);
|
||||
}
|
||||
m_pool_key_to_index[pubkey.GetID()] = index;
|
||||
}
|
||||
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());
|
||||
@@ -3299,6 +3382,7 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRe
|
||||
}
|
||||
|
||||
assert(keypool.vchPubKey.IsValid());
|
||||
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
|
||||
LogPrintf("keypool reserve %d\n", nIndex);
|
||||
}
|
||||
}
|
||||
@@ -3311,7 +3395,7 @@ void CWallet::KeepKey(int64_t nIndex)
|
||||
LogPrintf("keypool keep %d\n", nIndex);
|
||||
}
|
||||
|
||||
void CWallet::ReturnKey(int64_t nIndex, bool fInternal)
|
||||
void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey)
|
||||
{
|
||||
// Return to key pool
|
||||
{
|
||||
@@ -3321,6 +3405,7 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal)
|
||||
} else {
|
||||
setExternalKeyPool.insert(nIndex);
|
||||
}
|
||||
m_pool_key_to_index[pubkey.GetID()] = nIndex;
|
||||
}
|
||||
LogPrintf("keypool return %d\n", nIndex);
|
||||
}
|
||||
@@ -3550,39 +3635,37 @@ void CReserveKey::KeepKey()
|
||||
void CReserveKey::ReturnKey()
|
||||
{
|
||||
if (nIndex != -1) {
|
||||
pwallet->ReturnKey(nIndex, fInternal);
|
||||
pwallet->ReturnKey(nIndex, fInternal, vchPubKey);
|
||||
}
|
||||
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)
|
||||
{
|
||||
void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
|
||||
{
|
||||
AssertLockHeld(cs_wallet);
|
||||
bool internal = setInternalKeyPool.count(keypool_id);
|
||||
if (!internal) assert(setExternalKeyPool.count(keypool_id));
|
||||
std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool;
|
||||
auto it = setKeyPool->begin();
|
||||
|
||||
CWalletDB walletdb(*dbw);
|
||||
while (it != std::end(*setKeyPool)) {
|
||||
const int64_t& index = *(it);
|
||||
if (index > keypool_id) break; // set*KeyPool is ordered
|
||||
|
||||
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);
|
||||
if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary
|
||||
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
|
||||
}
|
||||
walletdb.ErasePool(index);
|
||||
it = setKeyPool->erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
|
||||
bool CWallet::HasUnusedKeys(int min_keys) 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");
|
||||
}
|
||||
}
|
||||
return setExternalKeyPool.size() >= min_keys && (setInternalKeyPool.size() >= min_keys || !CanSupportFeature(FEATURE_HD_SPLIT));
|
||||
}
|
||||
|
||||
void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script)
|
||||
@@ -3634,38 +3717,6 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts) const
|
||||
|
||||
/** @} */ // end of Actions
|
||||
|
||||
class CAffectedKeysVisitor : public boost::static_visitor<void> {
|
||||
private:
|
||||
const CKeyStore &keystore;
|
||||
std::vector<CKeyID> &vKeys;
|
||||
|
||||
public:
|
||||
CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
|
||||
|
||||
void Process(const CScript &script) {
|
||||
txnouttype type;
|
||||
std::vector<CTxDestination> vDest;
|
||||
int nRequired;
|
||||
if (ExtractDestinations(script, type, vDest, nRequired)) {
|
||||
for (const CTxDestination &dest : vDest)
|
||||
boost::apply_visitor(*this, dest);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(const CKeyID &keyId) {
|
||||
if (keystore.HaveKey(keyId))
|
||||
vKeys.push_back(keyId);
|
||||
}
|
||||
|
||||
void operator()(const CScriptID &scriptId) {
|
||||
CScript script;
|
||||
if (keystore.GetCScript(scriptId, script))
|
||||
Process(script);
|
||||
}
|
||||
|
||||
void operator()(const CNoDestination &none) {}
|
||||
};
|
||||
|
||||
void CWallet::GetKeyBirthTimes(std::map<CTxDestination, int64_t> &mapKeyBirth) const {
|
||||
AssertLockHeld(cs_wallet); // mapKeyMetadata
|
||||
mapKeyBirth.clear();
|
||||
@@ -3990,6 +4041,9 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
||||
|
||||
RegisterValidationInterface(walletInstance);
|
||||
|
||||
// Try to top up keypool. No-op if the wallet is locked.
|
||||
walletInstance->TopUpKeyPool();
|
||||
|
||||
CBlockIndex *pindexRescan = chainActive.Genesis();
|
||||
if (!GetBoolArg("-rescan", false))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user