Merge bitcoin/bitcoin#35630: test: Add importdescriptors rpc error test coverage
What changed, and why it matters
This commit only adds new test cases to Bitcoin Core's functional test suite. It does not change any production wallet, node, or RPC code. The tests verify that the importdescriptors RPC reports errors in the right order, rejects bad timestamps, and handles locked wallets correctly. There is no security fix or vulnerability being introduced.
No action required. This is a routine test-coverage addition. Reviewers may optionally run the updated functional test to confirm it passes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit is a pure test-file change (test/functional/wallet_importdescriptors.py). It extends an existing helper to assert top-level RPC exceptions and adds three test scenarios: (1) per-item errors preserve request/response order, (2) missing/invalid timestamps raise a global RPC error rather than a per-item error, and (3) a locked wallet rejects an empty importdescriptors request while an unlocked wallet accepts it. No C++/Python production code is modified.
Changed components
test/functional/wallet_importdescriptors.pyInspect captured patch +77 / −1
### test/functional/wallet_importdescriptors.py
@@ -50,14 +50,22 @@ def set_test_params(self):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- def test_importdesc(self, req, success, error_code=None, error_message=None, warnings=None, wallet=None):
+ def test_importdesc(self, req, success, global_error=False, error_code=None, error_message=None, warnings=None, wallet=None):
"""Run importdescriptors and assert success"""
if warnings is None:
warnings = []
wrpc = self.nodes[1].get_wallet_rpc('w1')
if wallet is not None:
wrpc = wallet
+ if global_error and not success:
+ try:
+ result = wrpc.importdescriptors([req])
+ except JSONRPCException as e:
+ assert_equal(e.error["code"], error_code)
+ assert_equal(e.error["message"], error_message)
+ return
+
result = wrpc.importdescriptors([req])
observed_warnings = []
if 'warnings' in result[0]:
@@ -122,6 +130,45 @@ def test_import_unused_noprivs(self):
wallet=wallet)
wallet.unloadwallet()
+ def test_per_item_errors_are_reported_in_order(self):
+ self.log.info("Test that import results are in the same order as the original request")
+ self.nodes[0].createwallet(wallet_name="test_order_import", blank=True)
+ wallet = self.nodes[0].get_wallet_rpc('test_order_import')
+ whitespace_pubkey = f" {get_generate_key().pubkey}"
+ cases = [
+ ({
+ "timestamp": "now"
+ }, [False, "Descriptor not found."]),
+ ({
+ "desc": descsum_create(f"pkh({get_generate_key().privkey})"),
+ "timestamp": 1,
+ "label": "Valid descriptor 1",
+ }, [True]),
+ ({
+ "desc": descsum_create(f"pkh({get_generate_key().privkey})"),
+ "timestamp": "now",
+ "internal": True,
+ }, [True]),
+ ({
+ "desc": descsum_create(f"pkh({get_generate_key().pubkey})"),
+ "timestamp": "now",
+ "label": "Invalid descriptor 2",
+ "internal": True,
+ }, [False, "Internal addresses should not have a label"]),
+ ({
+ "desc": descsum_create(f"pkh({whitespace_pubkey})"),
+ "timestamp": "now",
+ "internal": True,
+ }, [False, f"pkh(): Key '{whitespace_pubkey}' is invalid due to whitespace"]),
+ ]
+
+ descriptors, expected = map(list, zip(*cases))
+ results = wallet.importdescriptors(descriptors)
+ for i, result in enumerate(results):
+ assert_equal(result["success"], expected[i][0])
+ if not result["success"]:
+ assert_equal(result["error"]["message"], expected[i][1])
+
def test_rescan_fails_import(self):
xpriv = ExtendedPrivateKey.generate().to_string()
@@ -227,6 +274,25 @@ def run_test(self):
error_code=-8,
error_message='Descriptor not found.')
+ # Test import fails if one timestamp is invalid or missing
+ self.log.info("Import should fail if timestamp is missing or an invalid timestamp is present in the request")
+ key = get_generate_key()
+ import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"), "label": "Descriptor import test"}
+ self.test_importdesc(import_request,
+ success=False,
+ global_error=True,
+ error_code=-3,
+ error_message="Missing required timestamp field for key")
+
+ import_request = {"desc": descsum_create("pkh(" + key.pubkey + ")"),
+ "timestamp": "this_is_not_a_valid_timestamp",
+ "label": "Descriptor import test"}
+ self.test_importdesc(import_request,
+ success=False,
+ global_error=True,
+ error_code=-3,
+ error_message='Expected number or "now" timestamp value for key. got type string')
+
# # Test importing of a P2PKH descriptor
key = get_generate_key()
self.log.info("Should import a p2pkh descriptor")
@@ -878,6 +944,15 @@ def run_test(self):
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.",
encrypted_wallet.importdescriptors, [descriptor])
+ self.log.info("A locked wallet rejects an empty importdescriptors request")
+ assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.",
+ encrypted_wallet.importdescriptors, [])
+
+ self.log.info("An unlocked wallet accepts an empty importdescriptors request")
+ self.nodes[0].createwallet("unencrypted_wallet", blank=True)
+ unencrypted_wallet = self.nodes[0].get_wallet_rpc("unencrypted_wallet")
+ assert_equal(unencrypted_wallet.importdescriptors([]), [])
+
descriptor["timestamp"] = 0
descriptor["next_index"] = 0
@@ -994,6 +1069,7 @@ def run_test(self):
self.test_import_unused_key()
self.test_import_unused_key_existing()
self.test_import_unused_noprivs()
+ self.test_per_item_errors_are_reported_in_order()
self.test_rescan_fails_import()
if __name__ == '__main__':Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.