Merge bitcoin/bitcoin#32845: rpc, test: Fix JSON parsing errors in unloadwallet and getdescriptoractivity RPCs

c5c1960f93 doc: Add release notes for changes in RPCs (pablomartin4btc)
90fd5acbe5 rpc, test: Fix error message in getdescriptoractivity (pablomartin4btc)
39fef1d203 test: Add missing logging info for each test (pablomartin4btc)
53ac704efd rpc, test: Fix error message in unloadwallet (pablomartin4btc)
1fc3a8e8e7 rpc, test: Add EnsureUniqueWalletName tests (pablomartin4btc)
b635bc0896 rpc, util: Add EnsureUniqueWalletName (pablomartin4btc)

Pull request description:

  Currently, `unloadwallet` RPC call fails with a JSON parsing error when no `wallet_name` argument is provided. This behavior is misleading because the error originates from a low-level JSON type mismatch, rather than clearly indicating that the wallet name or RPC endpoint (`-rpcwallet=...`) is missing. Also, found out that the [issue](https://github.com/bitcoin/bitcoin/pull/13111#issuecomment-398831543) was noticed during its implementation but never addressed.

  In addition, I've verified all RPC commands calls finding that `getdescriptoractivity` had the same problem, but related to the array input types (blockhashes & descriptors), so I've corrected that RPC as well. For consistency I've added the missing logging info for each test case in `test/functional/rpc_getdescriptoractivity.py` in preparation for the new test.

  **_-Before_**
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc unloadwallet
  error code: -3
  error message:
  JSON value of type number is not of expected type string
  ```
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc getdescriptoractivity
  error code: -3
  error message:
  JSON value of type null is not of expected type array
  ```
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc getdescriptoractivity '[]'
  error code: -3
  error message:
  JSON value of type null is not of expected type array
  ```
  **_-After_**
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc unloadwallet
  error code: -8
  error message:
  Either the RPC endpoint wallet or the wallet name parameter must be provided
  ```
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc getdescriptoractivity
  error code: -1
  error message:
  getdescriptoractivity ["blockhash",...] [scanobjects,...] ( include_mempool )

  Get spend and receive activity associated with a set of descriptors for a set of blocks. This command pairs well with the `relevant_blocks` output of `scanblocks()`.
  This call may take several minutes. If you encounter timeouts, try specifying no RPC timeout (bitcoin-cli -rpcclienttimeout=0)

  Arguments:
  1. blockhashes                   (json array, required) The list of blockhashes to examine for activity. Order doesn't matter. Must be along main chain or an error is thrown.

       [
         "blockhash",              (string) A valid blockhash
         ...
       ]
  2. scanobjects                   (json array, required) Array of scan objects. Every scan object is either a string descriptor or an object:
       [
         "descriptor",             (string) An output descriptor
         {                         (json object) An object with output descriptor and metadata
           "desc": "str",          (string, required) An output descriptor
           "range": n or [n,n],    (numeric or array, optional, default=1000) The range of HD chain indexes to explore (either end or [begin,end])
         },
         ...
       ]
  3. include_mempool               (boolean, optional, default=true) Whether to include unconfirmed activity

  ...
  ```
  ```
  ./build/bin/bitcoin-cli -regtest -datadir=/tmp/btc getdescriptoractivity '[]'
  error code: -1
  error message:
  getdescriptoractivity ["blockhash",...] [scanobjects,...] ( include_mempool )

  ...
  ```

ACKs for top commit:
  achow101:
    ACK c5c1960f93
  stickies-v:
    re-ACK c5c1960f93
  furszy:
    ACK c5c1960f93

Tree-SHA512: e831ff1acbfd15d2ce3a69bb408cce94664c0b63b2aa2f4627a05c6c052241ae3b5cc238219ef1b30afb489a4a3f4c3030e2168b0c8f08b4d20805d050d810f5
This commit is contained in:
Ava Chow
2025-07-25 12:46:13 -07:00
10 changed files with 118 additions and 32 deletions

View File

@@ -31,13 +31,16 @@ class GetBlocksActivityTest(BitcoinTestFramework):
self.test_confirmed_and_unconfirmed(node, wallet)
self.test_receive_then_spend(node, wallet)
self.test_no_address(node, wallet)
self.test_required_args(node)
def test_no_activity(self, node):
self.log.info("Test that no activity is found for an unused address")
_, _, addr_1 = getnewdestination()
result = node.getdescriptoractivity([], [f"addr({addr_1})"], True)
assert_equal(len(result['activity']), 0)
def test_activity_in_block(self, node, wallet):
self.log.info("Test that receive activity is correctly reported in a mined block")
_, spk_1, addr_1 = getnewdestination(address_type='bech32m')
txid = wallet.send_to(from_node=node, scriptPubKey=spk_1, amount=1 * COIN)['txid']
blockhash = self.generate(node, 1)[0]
@@ -67,6 +70,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
def test_no_mempool_inclusion(self, node, wallet):
self.log.info("Test that mempool transactions are not included when include_mempool argument is False")
_, spk_1, addr_1 = getnewdestination()
wallet.send_to(from_node=node, scriptPubKey=spk_1, amount=1 * COIN)
@@ -81,6 +85,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
assert_equal(len(result['activity']), 0)
def test_multiple_addresses(self, node, wallet):
self.log.info("Test querying multiple addresses returns all activity correctly")
_, spk_1, addr_1 = getnewdestination()
_, spk_2, addr_2 = getnewdestination()
wallet.send_to(from_node=node, scriptPubKey=spk_1, amount=1 * COIN)
@@ -113,6 +118,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
assert a2['amount'] == 2.0
def test_invalid_blockhash(self, node, wallet):
self.log.info("Test that passing an invalid blockhash raises appropriate RPC error")
self.generate(node, 20) # Generate to get more fees
_, spk_1, addr_1 = getnewdestination()
@@ -125,6 +131,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
node.getdescriptoractivity, [invalid_blockhash], [f"addr({addr_1})"], True)
def test_invalid_descriptor(self, node, wallet):
self.log.info("Test that an invalid descriptor raises the correct RPC error")
blockhash = self.generate(node, 1)[0]
_, _, addr_1 = getnewdestination()
@@ -133,6 +140,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
node.getdescriptoractivity, [blockhash], [f"addrx({addr_1})"], True)
def test_confirmed_and_unconfirmed(self, node, wallet):
self.log.info("Test that both confirmed and unconfirmed transactions are reported correctly")
self.generate(node, 20) # Generate to get more fees
_, spk_1, addr_1 = getnewdestination()
@@ -162,6 +170,7 @@ class GetBlocksActivityTest(BitcoinTestFramework):
def test_receive_then_spend(self, node, wallet):
"""Also important because this tests multiple blockhashes."""
self.log.info("Test receive and spend activities across different blocks are reported consistently")
self.generate(node, 20) # Generate to get more fees
sent1 = wallet.send_self_transfer(from_node=node)
@@ -189,11 +198,13 @@ class GetBlocksActivityTest(BitcoinTestFramework):
assert_equal(result, node.getdescriptoractivity(
[blockhash_1, blockhash_2], [wallet.get_descriptor()], True))
self.log.info("Test that duplicated blockhash request does not report duplicated results")
# Test that duplicating a blockhash yields the same result.
assert_equal(result, node.getdescriptoractivity(
[blockhash_1, blockhash_2, blockhash_2], [wallet.get_descriptor()], True))
def test_no_address(self, node, wallet):
self.log.info("Test that activity is still reported for scripts without an associated address")
raw_wallet = MiniWallet(self.nodes[0], mode=MiniWalletMode.RAW_P2PK)
self.generate(raw_wallet, 100)
@@ -221,6 +232,11 @@ class GetBlocksActivityTest(BitcoinTestFramework):
assert_equal(list(a2['output_spk'].keys()), ['asm', 'desc', 'hex', 'type'])
assert a2['amount'] == Decimal(no_addr_tx["tx"].vout[0].nValue) / COIN
def test_required_args(self, node):
self.log.info("Test that required arguments must be passed")
assert_raises_rpc_error(-1, "getdescriptoractivity", node.getdescriptoractivity)
assert_raises_rpc_error(-1, "getdescriptoractivity", node.getdescriptoractivity, [])
if __name__ == '__main__':
GetBlocksActivityTest(__file__).main()

View File

@@ -524,8 +524,8 @@ class WalletMigrationTest(BitcoinTestFramework):
def test_nonexistent(self):
self.log.info("Check migratewallet errors for nonexistent wallets")
default = self.master_node.get_wallet_rpc(self.default_wallet_name)
assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", default.migratewallet, "someotherwallet")
assert_raises_rpc_error(-8, "Either RPC endpoint wallet or wallet_name parameter must be provided", self.master_node.migratewallet)
assert_raises_rpc_error(-8, "The RPC endpoint wallet and the wallet name parameter specify different wallets", default.migratewallet, "someotherwallet")
assert_raises_rpc_error(-8, "Either the RPC endpoint wallet or the wallet name parameter must be provided", self.master_node.migratewallet)
assert_raises_rpc_error(-4, "Error: Wallet does not exist", self.master_node.migratewallet, "notawallet")
def test_unloaded_by_path(self):

View File

@@ -343,10 +343,10 @@ class MultiWalletTest(BitcoinTestFramework):
self.log.info("Test dynamic wallet unloading")
# Test `unloadwallet` errors
assert_raises_rpc_error(-3, "JSON value of type null is not of expected type string", self.nodes[0].unloadwallet)
assert_raises_rpc_error(-8, "Either the RPC endpoint wallet or the wallet name parameter must be provided", self.nodes[0].unloadwallet)
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy")
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet)
assert_raises_rpc_error(-8, "RPC endpoint wallet and wallet_name parameter specify different wallets", w1.unloadwallet, "w2"),
assert_raises_rpc_error(-8, "The RPC endpoint wallet and the wallet name parameter specify different wallets", w1.unloadwallet, "w2"),
# Successfully unload the specified wallet name
self.nodes[0].unloadwallet("w1")