mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-12-13 20:36:21 +01:00
664657ed13bugfix: disallow label for ranged descriptors & allow external non-ranged descriptors to have label (scgbckbone) Pull request description: Motivation: * ranged descriptors MUST not be able to have label (current impl allows it) * external non-ranged descriptor MUST be able to have label (current impl disallows it, **if** `internal=false` is provided via importdescriptor user data) Repro steps: * create blank wallet and import descriptors * external has `label=test` (not internal) ``` conn = bitcoind.create_wallet(wallet_name=w_name, disable_private_keys=True, blank=True, passphrase=None, avoid_reuse=False, descriptors=True) descriptors = [ { "timestamp": "now", "label": "test", "active": True, "desc": "wpkh([0f056943/84h/1h/0h]tpubDC7jGaaSE66Pn4dgtbAAstde4bCyhSUs4r3P8WhMVvPByvcRrzrwqSvpF9Ghx83Z1LfVugGRrSBko5UEKELCz9HoMv5qKmGq3fqnnbS5E9r/0/*)#erexmnep", "internal": False }, { "desc": "wpkh([0f056943/84h/1h/0h]tpubDC7jGaaSE66Pn4dgtbAAstde4bCyhSUs4r3P8WhMVvPByvcRrzrwqSvpF9Ghx83Z1LfVugGRrSBko5UEKELCz9HoMv5qKmGq3fqnnbS5E9r/1/*)#ghu8xxfe", "active": True, "internal": True, "timestamp": "now" }, ] r = conn.importdescriptors(descriptors) print(r) ``` response: ``` [{'error': {'code': -8, 'message': 'Internal addresses should not have a label'}, 'success': False, 'warnings': ['Range not given, using default keypool range']}, {'success': True, 'warnings': ['Range not given, using default keypool range']}] ``` But in above, ONLY external has a label. If you remove `internal: False` from external descriptor import object - it will import no problem: ``` [{'success': True, 'warnings': ['Range not given, using default keypool range']}, {'success': True, 'warnings': ['Range not given, using default keypool range']}] ``` Even tho it should NOT, as the descriptor is ranged. Current implementation relies on checking user provided data to decide whether desc is ranged. ACKs for top commit: achow101: ACK664657ed13rkrux: lgtm crACK664657ed13Tree-SHA512: 9e70aea620019c29950ba417d4ae38d65cd94a4f6fcabbc021d67b031de1c44c27d6f6f5cb7e6950a099eb6e58bed9be764d4c6347195daeccb14a5d95c123b2
81 lines
3.8 KiB
Python
Executable File
81 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2024 The Bitcoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""Test that descriptor wallets rescan mempool transactions properly when importing."""
|
|
|
|
from test_framework.address import (
|
|
address_to_scriptpubkey,
|
|
ADDRESS_BCRT1_UNSPENDABLE,
|
|
)
|
|
from test_framework.messages import COIN
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import assert_equal
|
|
from test_framework.wallet import MiniWallet
|
|
from test_framework.wallet_util import test_address
|
|
|
|
|
|
class WalletRescanUnconfirmed(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.num_nodes = 1
|
|
|
|
def skip_test_if_missing_module(self):
|
|
self.skip_if_no_wallet()
|
|
|
|
def run_test(self):
|
|
self.log.info("Create wallets and mine initial chain")
|
|
node = self.nodes[0]
|
|
tester_wallet = MiniWallet(node)
|
|
|
|
node.createwallet(wallet_name='w0', disable_private_keys=False)
|
|
w0 = node.get_wallet_rpc('w0')
|
|
|
|
self.log.info("Create a parent tx and mine it in a block that will later be disconnected")
|
|
parent_address = w0.getnewaddress()
|
|
tx_parent_to_reorg = tester_wallet.send_to(
|
|
from_node=node,
|
|
scriptPubKey=address_to_scriptpubkey(parent_address),
|
|
amount=COIN,
|
|
)
|
|
assert tx_parent_to_reorg["txid"] in node.getrawmempool()
|
|
block_to_reorg = self.generate(tester_wallet, 1)[0]
|
|
assert_equal(len(node.getrawmempool()), 0)
|
|
node.syncwithvalidationinterfacequeue()
|
|
assert_equal(w0.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 1)
|
|
|
|
# Create an unconfirmed child transaction from the parent tx, sending all
|
|
# the funds to an unspendable address. Importantly, no change output is created so the
|
|
# transaction can't be recognized using its outputs. The wallet rescan needs to know the
|
|
# inputs of the transaction to detect it, so the parent must be processed before the child.
|
|
w0_utxos = w0.listunspent()
|
|
|
|
self.log.info("Create a child tx and wait for it to propagate to all mempools")
|
|
# The only UTXO available to spend is tx_parent_to_reorg.
|
|
assert_equal(len(w0_utxos), 1)
|
|
assert_equal(w0_utxos[0]["txid"], tx_parent_to_reorg["txid"])
|
|
tx_child_unconfirmed_sweep = w0.sendall(recipients=[ADDRESS_BCRT1_UNSPENDABLE], options={"locktime":0})
|
|
assert tx_child_unconfirmed_sweep["txid"] in node.getrawmempool()
|
|
node.syncwithvalidationinterfacequeue()
|
|
|
|
self.log.info("Mock a reorg, causing parent to re-enter mempools after its child")
|
|
node.invalidateblock(block_to_reorg)
|
|
assert tx_parent_to_reorg["txid"] in node.getrawmempool()
|
|
|
|
self.log.info("Import descriptor wallet on another node")
|
|
# descriptor is ranged - label not allowed
|
|
descriptors_to_import = [{"desc": w0.getaddressinfo(parent_address)['parent_desc'], "timestamp": 0}]
|
|
|
|
node.createwallet(wallet_name="w1", disable_private_keys=True)
|
|
w1 = node.get_wallet_rpc("w1")
|
|
w1.importdescriptors(descriptors_to_import)
|
|
|
|
self.log.info("Check that the importing node has properly rescanned mempool transactions")
|
|
# Check that parent address is correctly determined as ismine
|
|
test_address(w1, parent_address, solvable=True, ismine=True)
|
|
# This would raise a JSONRPCError if the transactions were not identified as belonging to the wallet.
|
|
assert_equal(w1.gettransaction(tx_parent_to_reorg["txid"])["confirmations"], 0)
|
|
assert_equal(w1.gettransaction(tx_child_unconfirmed_sweep["txid"])["confirmations"], 0)
|
|
|
|
if __name__ == '__main__':
|
|
WalletRescanUnconfirmed(__file__).main()
|