Merge bitcoin/bitcoin#29510: wallet: getrawchangeaddress and getnewaddress failures should not affect keypools for descriptor wallets

e073f1dfda7a2a2cb2be9fe2a1d576f122596021 test: make sure keypool sizes do not change on `getrawchangeaddress`/`getnewaddress` failures (UdjinM6)
367bb7a80cc71130995672c853d4a6e0134721d6 wallet: Avoid updating `ReserveDestination::nIndex` when `GetReservedDestination` fails (UdjinM6)

Pull request description:

  I think the expected behaviour of `getrawchangeaddress` and `getnewaddress` RPCs is that their failure should not affect keypool in any way. At least that's how legacy wallets work, you can confirm this behaviour by running `wallet_keypool.py --legacy-wallet` on master with e073f1dfda7a2a2cb2be9fe2a1d576f122596021 applied on top. However running `wallet_keypool.py --descriptors` on the same commit results in the following failure:
  ```
    File "/path/to/bitcoin/test/functional/test_framework/test_framework.py", line 131, in main
      self.run_test()
    File "/path/to/bitcoin/test/functional/wallet_keypool.py", line 114, in run_test
      assert_equal(kp_size_before, kp_size_after)
    File "/path/to/bitcoin/test/functional/test_framework/util.py", line 57, in assert_equal
      raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
  AssertionError: not([18, 24] == [19, 24])
  ```

  This happens because we pass `nIndex` (which is a class member) into `GetReservedDestination` and since it's passed by reference we get an updated value back, so `nIndex` won't be equal `-1` anymore, no matter if the function failed or succeeded. This means that `ReturnDestination` (called by dtor of `ReserveDestination`) will try to return something we did not actually reserve.

  The fix is to simply use a temporary variable instead of a class member and only update `nIndex` when `op_address` actually has value, basically do it the same way we do for other class members (`address` and `fInternal`) already.

ACKs for top commit:
  achow101:
    ACK e073f1dfda7a2a2cb2be9fe2a1d576f122596021
  josibake:
    ACK e073f1dfda

Tree-SHA512: 1128288a60dd4d8f306ef6f7ac66cdfeae3c9cc35c66ecada2d78fa61ac759f2a757b70fc3976ba8b5081200942b58dfabc184c01ccf911af40ba8c145344651
This commit is contained in:
Ava Chow 2024-02-29 13:16:12 -05:00 committed by Konstantin Akimov
parent 85fa37068f
commit 6b71f274ae
No known key found for this signature in database
GPG Key ID: 2176C4A5D01EA524
2 changed files with 17 additions and 1 deletions

View File

@ -4206,9 +4206,11 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool fInte
m_spk_man->TopUp(); m_spk_man->TopUp();
CKeyPool keypool; CKeyPool keypool;
if (!m_spk_man->GetReservedDestination(fInternalIn, address, nIndex, keypool)) { int64_t index;
if (!m_spk_man->GetReservedDestination(fInternalIn, address, index, keypool)) {
return false; return false;
} }
nIndex = index;
fInternal = keypool.fInternal; fInternal = keypool.fInternal;
} }
dest = address; dest = address;

View File

@ -101,12 +101,19 @@ class KeyPoolTest(BitcoinTestFramework):
nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress()
nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress()
nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress()
# remember keypool sizes
wi = nodes[0].getwalletinfo()
kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
# the next one should fail # the next one should fail
try: try:
nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress()
raise AssertionError('Keypool should be exhausted after six addresses') raise AssertionError('Keypool should be exhausted after six addresses')
except JSONRPCException as e: except JSONRPCException as e:
assert e.error['code']==-12 assert e.error['code']==-12
# check that keypool sizes did not change
wi = nodes[0].getwalletinfo()
kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
assert_equal(kp_size_before, kp_size_after)
addr = set() addr = set()
# drain the external keys # drain the external keys
@ -117,12 +124,19 @@ class KeyPoolTest(BitcoinTestFramework):
addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress())
assert len(addr) == 6 assert len(addr) == 6
# remember keypool sizes
wi = nodes[0].getwalletinfo()
kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
# the next one should fail # the next one should fail
try: try:
addr = nodes[0].getnewaddress() addr = nodes[0].getnewaddress()
raise AssertionError('Keypool should be exhausted after six addresses') raise AssertionError('Keypool should be exhausted after six addresses')
except JSONRPCException as e: except JSONRPCException as e:
assert e.error['code']==-12 assert e.error['code']==-12
# check that keypool sizes did not change
wi = nodes[0].getwalletinfo()
kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
assert_equal(kp_size_before, kp_size_after)
# refill keypool with three new addresses # refill keypool with three new addresses
nodes[0].walletpassphrase('test', 1) nodes[0].walletpassphrase('test', 1)