Skip to content

Commit 6419bdf

Browse files
committed
Merge bitcoin#23093: Add ability to flush keypool and always flush when upgrading non-HD to HD
6531599 test: Add check that newkeypool flushes change addresses too (Samuel Dobson) 84fa19c Add release notes for keypool flush changes (Samuel Dobson) f9603ee Add test for flushing keypool with newkeypool (Samuel Dobson) 6f6f7bb Make legacy wallet upgrades from non-HD to HD always flush the keypool (Samuel Dobson) 2434b10 Fix outdated keypool size default (Samuel Dobson) 22cc797 Add newkeypool RPC to flush the keypool (Samuel Dobson) Pull request description: This PR makes two main changes: 1) Adds a new RPC `newkeypool` which will entirely flush and refill the keypool. 2) When upgradewallet is called on old, non-HD wallets upgrading them to HD, we now always flush the keypool and generate a new one, to immediately start using the HD generated keys. This PR is motivated by a number of users with old, pre-compressed-key wallets upgrading them and being confused about why they still can't generate p2sh-segwit or bech32 addresses -- this is due to uncompressed keys remaining in the keypool post-upgrade and being illegal in these newer address formats. There is currently no easy way to flush the keypool other than to call `getnewaddress` a hundred/thousand times or an ugly hack of using a `sethdseed` call. ACKs for top commit: laanwj: re-ACK 6531599 meshcollider: Added new commit 6531599 to avoid invalidating previous ACKs. instagibbs: ACK bitcoin@6531599 Tree-SHA512: 50c79c5d42dd27ab0ecdbfdc4071fdaa1b2dbb2f9195ed325b007106ff19226419ce57fe5b1539c0c24101b12f5e034bbcfb7bbb0451b766cb1071295383d774
2 parents a845f1c + 6531599 commit 6419bdf

File tree

5 files changed

+59
-18
lines changed

5 files changed

+59
-18
lines changed

doc/release-notes-23093.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Notable changes
2+
===============
3+
4+
Updated RPCs
5+
------------
6+
7+
- `upgradewallet` will now automatically flush the keypool if upgrading
8+
from a non-HD wallet to an HD wallet, to immediately start using the
9+
newly-generated HD keys.
10+
- a new RPC `newkeypool` has been added, which will flush (entirely
11+
clear and refill) the keypool.

src/wallet/rpcwallet.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1854,7 +1854,7 @@ static RPCHelpMan keypoolrefill()
18541854
"\nFills the keypool."+
18551855
HELP_REQUIRING_PASSPHRASE,
18561856
{
1857-
{"newsize", RPCArg::Type::NUM, RPCArg::Default{100}, "The new keypool size"},
1857+
{"newsize", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%u, or as set by -keypool", DEFAULT_KEYPOOL_SIZE)}, "The new keypool size"},
18581858
},
18591859
RPCResult{RPCResult::Type::NONE, "", ""},
18601860
RPCExamples{
@@ -1893,6 +1893,33 @@ static RPCHelpMan keypoolrefill()
18931893
}
18941894

18951895

1896+
static RPCHelpMan newkeypool()
1897+
{
1898+
return RPCHelpMan{"newkeypool",
1899+
"\nEntirely clears and refills the keypool."+
1900+
HELP_REQUIRING_PASSPHRASE,
1901+
{},
1902+
RPCResult{RPCResult::Type::NONE, "", ""},
1903+
RPCExamples{
1904+
HelpExampleCli("newkeypool", "")
1905+
+ HelpExampleRpc("newkeypool", "")
1906+
},
1907+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1908+
{
1909+
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
1910+
if (!pwallet) return NullUniValue;
1911+
1912+
LOCK(pwallet->cs_wallet);
1913+
1914+
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
1915+
spk_man.NewKeyPool();
1916+
1917+
return NullUniValue;
1918+
},
1919+
};
1920+
}
1921+
1922+
18961923
static RPCHelpMan walletpassphrase()
18971924
{
18981925
return RPCHelpMan{"walletpassphrase",
@@ -4875,6 +4902,7 @@ static const CRPCCommand commands[] =
48754902
{ "wallet", &listwallets, },
48764903
{ "wallet", &loadwallet, },
48774904
{ "wallet", &lockunspent, },
4905+
{ "wallet", &newkeypool, },
48784906
{ "wallet", &removeprunedfunds, },
48794907
{ "wallet", &rescanblockchain, },
48804908
{ "wallet", &send, },

src/wallet/scriptpubkeyman.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual
489489
}
490490
// Regenerate the keypool if upgraded to HD
491491
if (hd_upgrade) {
492-
if (!TopUp()) {
492+
if (!NewKeyPool()) {
493493
error = _("Unable to generate keys");
494494
return false;
495495
}

test/functional/wallet_keypool.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@ def run_test(self):
138138
assert_equal(wi['keypoolsize_hd_internal'], 100)
139139
assert_equal(wi['keypoolsize'], 100)
140140

141+
if not self.options.descriptors:
142+
# Check that newkeypool entirely flushes the keypool
143+
start_keypath = nodes[0].getaddressinfo(nodes[0].getnewaddress())['hdkeypath']
144+
start_change_keypath = nodes[0].getaddressinfo(nodes[0].getrawchangeaddress())['hdkeypath']
145+
# flush keypool and get new addresses
146+
nodes[0].newkeypool()
147+
end_keypath = nodes[0].getaddressinfo(nodes[0].getnewaddress())['hdkeypath']
148+
end_change_keypath = nodes[0].getaddressinfo(nodes[0].getrawchangeaddress())['hdkeypath']
149+
# The new keypath index should be 100 more than the old one
150+
new_index = int(start_keypath.rsplit('/', 1)[1][:-1]) + 100
151+
new_change_index = int(start_change_keypath.rsplit('/', 1)[1][:-1]) + 100
152+
assert_equal(end_keypath, "m/0'/0'/" + str(new_index) + "'")
153+
assert_equal(end_change_keypath, "m/0'/1'/" + str(new_change_index) + "'")
154+
141155
# create a blank wallet
142156
nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
143157
w2 = nodes[0].get_wallet_rpc('w2')

test/functional/wallet_upgradewallet.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -234,18 +234,13 @@ def copy_split_hd():
234234
assert_equal(1, hd_chain_version)
235235
seed_id = bytearray(seed_id)
236236
seed_id.reverse()
237-
old_kvs = new_kvs
238-
# First 2 keys should still be non-HD
239-
for i in range(0, 2):
240-
info = wallet.getaddressinfo(wallet.getnewaddress())
241-
assert 'hdkeypath' not in info
242-
assert 'hdseedid' not in info
243-
# Next key should be HD
237+
238+
# New keys (including change) should be HD (the two old keys have been flushed)
244239
info = wallet.getaddressinfo(wallet.getnewaddress())
245240
assert_equal(seed_id.hex(), info['hdseedid'])
246241
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])
247242
prev_seed_id = info['hdseedid']
248-
# Change key should be the same keypool
243+
# Change key should be HD and from the same keypool
249244
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
250245
assert_equal(prev_seed_id, info['hdseedid'])
251246
assert_equal('m/0\'/0\'/1\'', info['hdkeypath'])
@@ -291,14 +286,7 @@ def copy_split_hd():
291286
hd_chain_version, external_counter, seed_id, internal_counter = struct.unpack('<iI20sI', hd_chain)
292287
assert_equal(2, hd_chain_version)
293288
assert_equal(2, internal_counter)
294-
# Drain the keypool by fetching one external key and one change key. Should still be the same keypool
295-
info = wallet.getaddressinfo(wallet.getnewaddress())
296-
assert 'hdseedid' not in info
297-
assert 'hdkeypath' not in info
298-
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
299-
assert 'hdseedid' not in info
300-
assert 'hdkeypath' not in info
301-
# The next addresses are HD and should be on different HD chains
289+
# The next addresses are HD and should be on different HD chains (the one remaining key in each pool should have been flushed)
302290
info = wallet.getaddressinfo(wallet.getnewaddress())
303291
ext_id = info['hdseedid']
304292
assert_equal('m/0\'/0\'/0\'', info['hdkeypath'])

0 commit comments

Comments
 (0)