Skip to content

Commit 095142d

Browse files
committed
[wallet] keypool mark-used and topup
This commit adds basic keypool mark-used and topup: - try to topup the keypool on initial load - if a key in the keypool is used, mark all keys before that as used and try to top up
1 parent c25d90f commit 095142d

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

src/wallet/wallet.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "consensus/consensus.h"
1313
#include "consensus/validation.h"
1414
#include "fs.h"
15+
#include "init.h"
1516
#include "key.h"
1617
#include "keystore.h"
1718
#include "validation.h"
@@ -1053,6 +1054,30 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI
10531054
if (fExisted && !fUpdate) return false;
10541055
if (fExisted || IsMine(tx) || IsFromMe(tx))
10551056
{
1057+
/* Check if any keys in the wallet keypool that were supposed to be unused
1058+
* have appeared in a new transaction. If so, remove those keys from the keypool.
1059+
* This can happen when restoring an old wallet backup that does not contain
1060+
* the mostly recently created transactions from newer versions of the wallet.
1061+
*/
1062+
1063+
// loop though all outputs
1064+
for (const CTxOut& txout: tx.vout) {
1065+
// extract addresses and check if they match with an unused keypool key
1066+
std::vector<CKeyID> vAffected;
1067+
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
1068+
for (const CKeyID &keyid : vAffected) {
1069+
std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
1070+
if (mi != m_pool_key_to_index.end()) {
1071+
LogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
1072+
MarkReserveKeysAsUsed(mi->second);
1073+
1074+
if (!TopUpKeyPool()) {
1075+
LogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
1076+
}
1077+
}
1078+
}
1079+
}
1080+
10561081
CWalletTx wtx(this, ptx);
10571082

10581083
// Get merkle branch if transaction was found in a block
@@ -3611,6 +3636,28 @@ void CReserveKey::ReturnKey()
36113636
vchPubKey = CPubKey();
36123637
}
36133638

3639+
void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id)
3640+
{
3641+
AssertLockHeld(cs_wallet);
3642+
bool internal = setInternalKeyPool.count(keypool_id);
3643+
if (!internal) assert(setExternalKeyPool.count(keypool_id));
3644+
std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : &setExternalKeyPool;
3645+
auto it = setKeyPool->begin();
3646+
3647+
CWalletDB walletdb(*dbw);
3648+
while (it != std::end(*setKeyPool)) {
3649+
const int64_t& index = *(it);
3650+
if (index > keypool_id) break; // set*KeyPool is ordered
3651+
3652+
CKeyPool keypool;
3653+
if (walletdb.ReadPool(index, keypool)) { //TODO: This should be unnecessary
3654+
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
3655+
}
3656+
walletdb.ErasePool(index);
3657+
it = setKeyPool->erase(it);
3658+
}
3659+
}
3660+
36143661
bool CWallet::HasUnusedKeys(int min_keys) const
36153662
{
36163663
return setExternalKeyPool.size() >= min_keys && (setInternalKeyPool.size() >= min_keys || !CanSupportFeature(FEATURE_HD_SPLIT));
@@ -3989,6 +4036,9 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
39894036

39904037
RegisterValidationInterface(walletInstance);
39914038

4039+
// Try to top up keypool. No-op if the wallet is locked.
4040+
walletInstance->TopUpKeyPool();
4041+
39924042
CBlockIndex *pindexRescan = chainActive.Genesis();
39934043
if (!GetBoolArg("-rescan", false))
39944044
{

src/wallet/wallet.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,10 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
977977
void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey);
978978
bool GetKeyFromPool(CPubKey &key, bool internal = false);
979979
int64_t GetOldestKeyPoolTime();
980+
/**
981+
* Marks all keys in the keypool up to and including reserve_key as used.
982+
*/
983+
void MarkReserveKeysAsUsed(int64_t keypool_id);
980984
const std::map<CKeyID, int64_t>& GetAllReserveKeys() const { return m_pool_key_to_index; }
981985
/** Does the wallet have at least min_keys in the keypool? */
982986
bool HasUnusedKeys(int min_keys) const;

test/functional/wallet-hd.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
assert_equal,
1010
connect_nodes_bi,
1111
)
12-
import os
1312
import shutil
1413

1514

@@ -72,10 +71,12 @@ def run_test (self):
7271

7372
self.log.info("Restore backup ...")
7473
self.stop_node(1)
75-
os.remove(self.options.tmpdir + "/node1/regtest/wallet.dat")
74+
# we need to delete the complete regtest directory
75+
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
76+
shutil.rmtree(tmpdir + "/node1/regtest/blocks")
77+
shutil.rmtree(tmpdir + "/node1/regtest/chainstate")
7678
shutil.copyfile(tmpdir + "/hd.bak", tmpdir + "/node1/regtest/wallet.dat")
7779
self.nodes[1] = self.start_node(1, self.options.tmpdir, self.extra_args[1])
78-
#connect_nodes_bi(self.nodes, 0, 1)
7980

8081
# Assert that derivation is deterministic
8182
hd_add_2 = None
@@ -85,11 +86,12 @@ def run_test (self):
8586
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(_+1)+"'")
8687
assert_equal(hd_info_2["hdmasterkeyid"], masterkeyid)
8788
assert_equal(hd_add, hd_add_2)
89+
connect_nodes_bi(self.nodes, 0, 1)
90+
self.sync_all()
8891

8992
# Needs rescan
9093
self.stop_node(1)
9194
self.nodes[1] = self.start_node(1, self.options.tmpdir, self.extra_args[1] + ['-rescan'])
92-
#connect_nodes_bi(self.nodes, 0, 1)
9395
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
9496

9597
# send a tx and make sure its using the internal chain for the changeoutput

0 commit comments

Comments
 (0)