mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-18 22:35:39 +01:00
Merge #16528: Native Descriptor Wallets using DescriptorScriptPubKeyMan
223588b1bbAdd a --descriptors option to various tests (Andrew Chow)869f7ab30atests: Add RPCOverloadWrapper which overloads some disabled RPCs (Andrew Chow)cf06062859Correctly check for default wallet (Andrew Chow)886e0d75f5Implement CWallet::IsSpentKey for non-LegacySPKMans (Andrew Chow)3c19fdd2a2Return error when no ScriptPubKeyMan is available for specified type (Andrew Chow)388ba94231Change wallet_encryption.py to use signmessage instead of dumpprivkey (Andrew Chow)1346e14831Functional tests for descriptor wallets (Andrew Chow)f193ea889dadd importdescriptors RPC and tests for native descriptor wallets (Hugo Nguyen)ce24a94494Add IsLegacy to CWallet so that the GUI knows whether to show watchonly (Andrew Chow)1cb42b22b1Generate new descriptors when encrypting (Andrew Chow)82ae02b165Be able to create new wallets with DescriptorScriptPubKeyMans as backing (Andrew Chow)b713baa75aImplement GetMetadata in DescriptorScriptPubKeyMan (Andrew Chow)8b9603bd0bChange GetMetadata to use unique_ptr<CKeyMetadata> (Andrew Chow)72a9540df9Implement FillPSBT in DescriptorScriptPubKeyMan (Andrew Chow)84b4978c02Implement SignMessage for descriptor wallets (Andrew Chow)bde7c9fa38Implement SignTransaction in DescriptorScriptPubKeyMan (Andrew Chow)d50c8ddd41Implement GetSolvingProvider for DescriptorScriptPubKeyMan (Andrew Chow)f1ca5feb4aImplement GetKeypoolOldestTime and only display it if greater than 0 (Andrew Chow)586b57a9a6Implement ReturnDestination in DescriptorScriptPubKeyMan (Andrew Chow)f866957979Implement GetReservedDestination in DescriptorScriptPubKeyMan (Andrew Chow)a775f7c7fdImplement Unlock and Encrypt in DescriptorScriptPubKeyMan (Andrew Chow)bfdd073486Implement GetNewDestination for DescriptorScriptPubKeyMan (Andrew Chow)58c7651821Implement TopUp in DescriptorScriptPubKeyMan (Andrew Chow)e014886a34Implement SetupGeneration for DescriptorScriptPubKeyMan (Andrew Chow)46dfb99768Implement writing descriptorkeys, descriptorckeys, and descriptors to wallet file (Andrew Chow)4cb9b69be0Implement several simple functions in DescriptorScriptPubKeyMan (Andrew Chow)d1ec3e4f19Add IsSingleType to Descriptors (Andrew Chow)953feb3d27Implement loading of keys for DescriptorScriptPubKeyMan (Andrew Chow)2363e9fcaaLoad the descriptor cache from the wallet file (Andrew Chow)46c46aebb7Implement GetID for DescriptorScriptPubKeyMan (Andrew Chow)ec2f9e1178Implement IsHDEnabled in DescriptorScriptPubKeyMan (Andrew Chow)741122d4c1Implement MarkUnusedAddresses in DescriptorScriptPubKeyMan (Andrew Chow)2db7ca765cImplement IsMine for DescriptorScriptPubKeyMan (Andrew Chow)db7177af8cAdd LoadDescriptorScriptPubKeyMan and SetActiveScriptPubKeyMan to CWallet (Andrew Chow)78f8a92910Implement SetType in DescriptorScriptPubKeyMan (Andrew Chow)834de0300cStore WalletDescriptor in DescriptorScriptPubKeyMan (Andrew Chow)d8132669e1Add a lock cs_desc_man for DescriptorScriptPubKeyMan (Andrew Chow)3194a7f88aIntroduce WalletDescriptor class (Andrew Chow)6b13cd3fa8Create LegacyScriptPubKeyMan when not a descriptor wallet (Andrew Chow)aeac157c9dReturn nullptr from GetLegacyScriptPubKeyMan if descriptor wallet (Andrew Chow)96accc73f0Add WALLET_FLAG_DESCRIPTORS (Andrew Chow)6b8119af53Introduce DescriptorScriptPubKeyMan as a dummy class (Andrew Chow)06620302c7Introduce SetType function to tell ScriptPubKeyMans the type and internal-ness of it (Andrew Chow) Pull request description: Introducing the wallet of the glorious future (again): native descriptor wallets. With native descriptor wallets, addresses are generated from descriptors. Instead of generating keys and deriving addresses from keys, addresses come from the scriptPubKeys produced by a descriptor. Native descriptor wallets will be optional for now and can only be created by using `createwallet`. Descriptor wallets will store descriptors, master keys from the descriptor, and descriptor cache entries. Keys are derived from descriptors on the fly. In order to allow choosing different address types, 6 descriptors are needed for normal use. There is a pair of primary and change descriptors for each of the 3 address types. With the default keypool size of 1000, each descriptor has 1000 scriptPubKeys and descriptor cache entries pregenerated. This has a side effect of making wallets large since 6000 pubkeys are written to the wallet by default, instead of the current 2000. scriptPubKeys are kept only in memory and are generated every time a descriptor is loaded. By default, we use the standard BIP 44, 49, 84 derivation paths with an external and internal derivation chain for each. Descriptors can also be imported with a new `importdescriptors` RPC. Native descriptor wallets use the `ScriptPubKeyMan` interface introduced in #16341 to add a `DescriptorScriptPubKeyMan`. This defines a different IsMine which uses the simpler model of "does this scriptPubKey exist in this wallet". Furthermore, `DescriptorScriptPubKeyMan` does not have watchonly, so with native descriptor wallets, it is not possible to have a wallet with both watchonly and non-watchonly things. Rather a wallet with `disable_private_keys` needs to be used for watchonly things. A `--descriptor` option was added to some tests (`wallet_basic.py`, `wallet_encryption.py`, `wallet_keypool.py`, `wallet_keypool_topup.py`, and `wallet_labels.py`) to allow for these tests to use descriptor wallets. Additionally, several RPCs are disabled for descriptor wallets (`importprivkey`, `importpubkey`, `importaddress`, `importmulti`, `addmultisigaddress`, `dumpprivkey`, `dumpwallet`, `importwallet`, and `sethdseed`). ACKs for top commit: Sjors: utACK223588b1bb(rebased, nits addressed) jonatack: Code review re-ACK223588b1bb. fjahr: re-ACK223588b1bbinstagibbs: light re-ACK223588bmeshcollider: Code review ACK223588b1bbTree-SHA512: 59bc52aeddbb769ed5f420d5d240d8137847ac821b588eb616b34461253510c1717d6a70bab8765631738747336ae06f45ba39603ccd17f483843e5ed9a90986
This commit is contained in:
@@ -94,7 +94,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||
|
||||
# w1: regular wallet, created on master: update this test when default
|
||||
# wallets can no longer be opened by older versions.
|
||||
node_master.createwallet(wallet_name="w1")
|
||||
node_master.rpc.createwallet(wallet_name="w1")
|
||||
wallet = node_master.get_wallet_rpc("w1")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
@@ -120,17 +120,17 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||
self.nodes[1].abandontransaction(tx3_id)
|
||||
|
||||
# w1_v19: regular wallet, created with v0.19
|
||||
node_v19.createwallet(wallet_name="w1_v19")
|
||||
node_v19.rpc.createwallet(wallet_name="w1_v19")
|
||||
wallet = node_v19.get_wallet_rpc("w1_v19")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
assert info['keypoolsize'] > 0
|
||||
# Use addmultisigaddress (see #18075)
|
||||
address_18075 = wallet.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"]
|
||||
address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"]
|
||||
assert wallet.getaddressinfo(address_18075)["solvable"]
|
||||
|
||||
# w1_v18: regular wallet, created with v0.18
|
||||
node_v18.createwallet(wallet_name="w1_v18")
|
||||
node_v18.rpc.createwallet(wallet_name="w1_v18")
|
||||
wallet = node_v18.get_wallet_rpc("w1_v18")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
@@ -139,21 +139,21 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||
# w2: wallet with private keys disabled, created on master: update this
|
||||
# test when default wallets private keys disabled can no longer be
|
||||
# opened by older versions.
|
||||
node_master.createwallet(wallet_name="w2", disable_private_keys=True)
|
||||
node_master.rpc.createwallet(wallet_name="w2", disable_private_keys=True)
|
||||
wallet = node_master.get_wallet_rpc("w2")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled'] == False
|
||||
assert info['keypoolsize'] == 0
|
||||
|
||||
# w2_v19: wallet with private keys disabled, created with v0.19
|
||||
node_v19.createwallet(wallet_name="w2_v19", disable_private_keys=True)
|
||||
node_v19.rpc.createwallet(wallet_name="w2_v19", disable_private_keys=True)
|
||||
wallet = node_v19.get_wallet_rpc("w2_v19")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled'] == False
|
||||
assert info['keypoolsize'] == 0
|
||||
|
||||
# w2_v18: wallet with private keys disabled, created with v0.18
|
||||
node_v18.createwallet(wallet_name="w2_v18", disable_private_keys=True)
|
||||
node_v18.rpc.createwallet(wallet_name="w2_v18", disable_private_keys=True)
|
||||
wallet = node_v18.get_wallet_rpc("w2_v18")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled'] == False
|
||||
@@ -161,21 +161,21 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||
|
||||
# w3: blank wallet, created on master: update this
|
||||
# test when default blank wallets can no longer be opened by older versions.
|
||||
node_master.createwallet(wallet_name="w3", blank=True)
|
||||
node_master.rpc.createwallet(wallet_name="w3", blank=True)
|
||||
wallet = node_master.get_wallet_rpc("w3")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
assert info['keypoolsize'] == 0
|
||||
|
||||
# w3_v19: blank wallet, created with v0.19
|
||||
node_v19.createwallet(wallet_name="w3_v19", blank=True)
|
||||
node_v19.rpc.createwallet(wallet_name="w3_v19", blank=True)
|
||||
wallet = node_v19.get_wallet_rpc("w3_v19")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
assert info['keypoolsize'] == 0
|
||||
|
||||
# w3_v18: blank wallet, created with v0.18
|
||||
node_v18.createwallet(wallet_name="w3_v18", blank=True)
|
||||
node_v18.rpc.createwallet(wallet_name="w3_v18", blank=True)
|
||||
wallet = node_v18.get_wallet_rpc("w3_v18")
|
||||
info = wallet.getwalletinfo()
|
||||
assert info['private_keys_enabled']
|
||||
@@ -318,7 +318,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework):
|
||||
|
||||
self.log.info("Test wallet upgrade path...")
|
||||
# u1: regular wallet, created with v0.17
|
||||
node_v17.createwallet(wallet_name="u1_v17")
|
||||
node_v17.rpc.createwallet(wallet_name="u1_v17")
|
||||
wallet = node_v17.get_wallet_rpc("u1_v17")
|
||||
address = wallet.getnewaddress("bech32")
|
||||
info = wallet.getaddressinfo(address)
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test multisig RPCs"""
|
||||
|
||||
from test_framework.authproxy import JSONRPCException
|
||||
from test_framework.descriptors import descsum_create, drop_origins
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_raises_rpc_error,
|
||||
assert_equal,
|
||||
)
|
||||
from test_framework.key import ECPubKey
|
||||
from test_framework.key import ECPubKey, ECKey, bytes_to_wif
|
||||
|
||||
import binascii
|
||||
import decimal
|
||||
@@ -28,10 +29,14 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def get_keys(self):
|
||||
self.pub = []
|
||||
self.priv = []
|
||||
node0, node1, node2 = self.nodes
|
||||
add = [node1.getnewaddress() for _ in range(self.nkeys)]
|
||||
self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add]
|
||||
self.priv = [node1.dumpprivkey(a) for a in add]
|
||||
for _ in range(self.nkeys):
|
||||
k = ECKey()
|
||||
k.generate()
|
||||
self.pub.append(k.get_pubkey().get_bytes().hex())
|
||||
self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
|
||||
self.final = node2.getnewaddress()
|
||||
|
||||
def run_test(self):
|
||||
@@ -64,17 +69,20 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
pk_obj.compressed = False
|
||||
pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
|
||||
|
||||
node0.createwallet(wallet_name='wmulti0', disable_private_keys=True)
|
||||
wmulti0 = node0.get_wallet_rpc('wmulti0')
|
||||
|
||||
# Check all permutations of keys because order matters apparently
|
||||
for keys in itertools.permutations([pk0, pk1, pk2]):
|
||||
# Results should be the same as this legacy one
|
||||
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
|
||||
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'legacy')['address'])
|
||||
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'legacy')['address'])
|
||||
|
||||
# Generate addresses with the segwit types. These should all make legacy addresses
|
||||
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'bech32')['address'])
|
||||
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'p2sh-segwit')['address'])
|
||||
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'bech32')['address'])
|
||||
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
|
||||
assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'bech32')['address'])
|
||||
assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'p2sh-segwit')['address'])
|
||||
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'bech32')['address'])
|
||||
assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
|
||||
|
||||
self.log.info('Testing sortedmulti descriptors with BIP 67 test vectors')
|
||||
with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_bip67.json'), encoding='utf-8') as f:
|
||||
@@ -89,6 +97,8 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address'])
|
||||
|
||||
def check_addmultisigaddress_errors(self):
|
||||
if self.options.descriptors:
|
||||
return
|
||||
self.log.info('Check that addmultisigaddress fails when the private keys are missing')
|
||||
addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)]
|
||||
assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
|
||||
@@ -115,6 +125,15 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
|
||||
def do_multisig(self):
|
||||
node0, node1, node2 = self.nodes
|
||||
if 'wmulti' not in node1.listwallets():
|
||||
try:
|
||||
node1.loadwallet('wmulti')
|
||||
except JSONRPCException as e:
|
||||
if e.error['code'] == -18 and 'Wallet wmulti not found' in e.error['message']:
|
||||
node1.createwallet(wallet_name='wmulti', disable_private_keys=True)
|
||||
else:
|
||||
raise
|
||||
wmulti = node1.get_wallet_rpc('wmulti')
|
||||
|
||||
# Construct the expected descriptor
|
||||
desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub))
|
||||
@@ -134,7 +153,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
assert madd[0:4] == "bcrt" # actually a bech32 address
|
||||
|
||||
# compare against addmultisigaddress
|
||||
msigw = node1.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
|
||||
msigw = wmulti.addmultisigaddress(self.nsigs, self.pub, None, self.output_type)
|
||||
maddw = msigw["address"]
|
||||
mredeemw = msigw["redeemScript"]
|
||||
assert_equal(desc, drop_origins(msigw['descriptor']))
|
||||
@@ -194,6 +213,8 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
|
||||
txinfo = node0.getrawtransaction(tx, True, blk)
|
||||
self.log.info("n/m=%d/%d %s size=%d vsize=%d weight=%d" % (self.nsigs, self.nkeys, self.output_type, txinfo["size"], txinfo["vsize"], txinfo["weight"]))
|
||||
|
||||
wmulti.unloadwallet()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
RpcCreateMultiSigTest().main()
|
||||
|
||||
@@ -48,18 +48,23 @@ class PSBTTest(BitcoinTestFramework):
|
||||
disconnect_nodes(offline_node, 2)
|
||||
disconnect_nodes(mining_node, 0)
|
||||
|
||||
# Create watchonly on online_node
|
||||
online_node.createwallet(wallet_name='wonline', disable_private_keys=True)
|
||||
wonline = online_node.get_wallet_rpc('wonline')
|
||||
w2 = online_node.get_wallet_rpc('')
|
||||
|
||||
# Mine a transaction that credits the offline address
|
||||
offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
|
||||
online_addr = online_node.getnewaddress(address_type="p2sh-segwit")
|
||||
online_node.importaddress(offline_addr, "", False)
|
||||
online_addr = w2.getnewaddress(address_type="p2sh-segwit")
|
||||
wonline.importaddress(offline_addr, "", False)
|
||||
mining_node.sendtoaddress(address=offline_addr, amount=1.0)
|
||||
mining_node.generate(nblocks=1)
|
||||
self.sync_blocks([mining_node, online_node])
|
||||
|
||||
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
|
||||
utxos = online_node.listunspent(addresses=[offline_addr])
|
||||
raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
|
||||
psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
|
||||
utxos = wonline.listunspent(addresses=[offline_addr])
|
||||
raw = wonline.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
|
||||
psbt = wonline.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
|
||||
assert "non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0]
|
||||
|
||||
# Have the offline node sign the PSBT (which will update the UTXO to segwit)
|
||||
@@ -72,6 +77,8 @@ class PSBTTest(BitcoinTestFramework):
|
||||
self.sync_blocks([mining_node, online_node])
|
||||
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
|
||||
|
||||
wonline.unloadwallet()
|
||||
|
||||
# Reconnect
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
connect_nodes(self.nodes[0], 2)
|
||||
@@ -89,13 +96,23 @@ class PSBTTest(BitcoinTestFramework):
|
||||
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
|
||||
self.nodes[0].sendrawtransaction(final_tx)
|
||||
|
||||
# Create p2sh, p2wpkh, and p2wsh addresses
|
||||
# Get pubkeys
|
||||
pubkey0 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress())['pubkey']
|
||||
pubkey1 = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['pubkey']
|
||||
pubkey2 = self.nodes[2].getaddressinfo(self.nodes[2].getnewaddress())['pubkey']
|
||||
p2sh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
|
||||
p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
|
||||
p2sh_p2wsh = self.nodes[1].addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
|
||||
|
||||
# Setup watchonly wallets
|
||||
self.nodes[2].createwallet(wallet_name='wmulti', disable_private_keys=True)
|
||||
wmulti = self.nodes[2].get_wallet_rpc('wmulti')
|
||||
|
||||
# Create all the addresses
|
||||
p2sh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "legacy")['address']
|
||||
p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "bech32")['address']
|
||||
p2sh_p2wsh = wmulti.addmultisigaddress(2, [pubkey0, pubkey1, pubkey2], "", "p2sh-segwit")['address']
|
||||
if not self.options.descriptors:
|
||||
wmulti.importaddress(p2sh)
|
||||
wmulti.importaddress(p2wsh)
|
||||
wmulti.importaddress(p2sh_p2wsh)
|
||||
p2wpkh = self.nodes[1].getnewaddress("", "bech32")
|
||||
p2pkh = self.nodes[1].getnewaddress("", "legacy")
|
||||
p2sh_p2wpkh = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
||||
@@ -146,11 +163,14 @@ class PSBTTest(BitcoinTestFramework):
|
||||
assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.nodes[1].getnewaddress():29.99}, 0, {"feeRate": 10})
|
||||
|
||||
# partially sign multisig things with node 1
|
||||
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.nodes[1].getnewaddress():29.99})['psbt']
|
||||
psbtx = wmulti.walletcreatefundedpsbt(inputs=[{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], outputs={self.nodes[1].getnewaddress():29.99}, options={'changeAddress': self.nodes[1].getrawchangeaddress()})['psbt']
|
||||
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
|
||||
psbtx = walletprocesspsbt_out['psbt']
|
||||
assert_equal(walletprocesspsbt_out['complete'], False)
|
||||
|
||||
# Unload wmulti, we don't need it anymore
|
||||
wmulti.unloadwallet()
|
||||
|
||||
# partially sign with node 2. This should be complete and sendable
|
||||
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
|
||||
assert_equal(walletprocesspsbt_out['complete'], True)
|
||||
@@ -297,7 +317,7 @@ class PSBTTest(BitcoinTestFramework):
|
||||
|
||||
# Signer tests
|
||||
for i, signer in enumerate(signers):
|
||||
self.nodes[2].createwallet("wallet{}".format(i))
|
||||
self.nodes[2].createwallet(wallet_name="wallet{}".format(i))
|
||||
wrpc = self.nodes[2].get_wallet_rpc("wallet{}".format(i))
|
||||
for key in signer['privkeys']:
|
||||
wrpc.importprivkey(key)
|
||||
|
||||
@@ -8,6 +8,8 @@ keys, and is trivially vulnerable to side channel attacks. Do not use for
|
||||
anything but tests."""
|
||||
import random
|
||||
|
||||
from .address import byte_to_base58
|
||||
|
||||
def modinv(a, n):
|
||||
"""Compute the modular inverse of a modulo n
|
||||
|
||||
@@ -384,3 +386,14 @@ class ECKey():
|
||||
rb = r.to_bytes((r.bit_length() + 8) // 8, 'big')
|
||||
sb = s.to_bytes((s.bit_length() + 8) // 8, 'big')
|
||||
return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb
|
||||
|
||||
def bytes_to_wif(b, compressed=True):
|
||||
if compressed:
|
||||
b += b'\x01'
|
||||
return byte_to_base58(b, 239)
|
||||
|
||||
def generate_wif_key():
|
||||
# Makes a WIF privkey for imports
|
||||
k = ECKey()
|
||||
k.generate()
|
||||
return bytes_to_wif(k.get_bytes(), k.is_compressed)
|
||||
|
||||
@@ -165,6 +165,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
help="run nodes under the valgrind memory error detector: expect at least a ~10x slowdown, valgrind 3.14 or later required")
|
||||
parser.add_argument("--randomseed", type=int,
|
||||
help="set a random seed for deterministically reproducing a previous test run")
|
||||
parser.add_argument("--descriptors", default=False, action="store_true",
|
||||
help="Run test using a descriptor wallet")
|
||||
self.add_options(parser)
|
||||
self.options = parser.parse_args()
|
||||
|
||||
@@ -333,11 +335,23 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
|
||||
def setup_nodes(self):
|
||||
"""Override this method to customize test node setup"""
|
||||
extra_args = None
|
||||
extra_args = [[]] * self.num_nodes
|
||||
wallets = [[]] * self.num_nodes
|
||||
if hasattr(self, "extra_args"):
|
||||
extra_args = self.extra_args
|
||||
wallets = [[x for x in eargs if x.startswith('-wallet=')] for eargs in extra_args]
|
||||
extra_args = [x + ['-nowallet'] for x in extra_args]
|
||||
self.add_nodes(self.num_nodes, extra_args)
|
||||
self.start_nodes()
|
||||
for i, n in enumerate(self.nodes):
|
||||
n.extra_args.pop()
|
||||
if '-wallet=0' in n.extra_args or '-nowallet' in n.extra_args or '-disablewallet' in n.extra_args or not self.is_wallet_compiled():
|
||||
continue
|
||||
if '-wallet=' not in wallets[i] and not any([x.startswith('-wallet=') for x in wallets[i]]):
|
||||
wallets[i].append('-wallet=')
|
||||
for w in wallets[i]:
|
||||
wallet_name = w.split('=', 1)[1]
|
||||
n.createwallet(wallet_name=wallet_name, descriptors=self.options.descriptors)
|
||||
self.import_deterministic_coinbase_privkeys()
|
||||
if not self.setup_clean_chain:
|
||||
for n in self.nodes:
|
||||
@@ -408,6 +422,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
use_cli=self.options.usecli,
|
||||
start_perf=self.options.perf,
|
||||
use_valgrind=self.options.valgrind,
|
||||
descriptors=self.options.descriptors,
|
||||
))
|
||||
|
||||
def start_node(self, i, *args, **kwargs):
|
||||
@@ -547,6 +562,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
||||
bitcoin_cli=self.options.bitcoincli,
|
||||
coverage_dir=None,
|
||||
cwd=self.options.tmpdir,
|
||||
descriptors=self.options.descriptors,
|
||||
))
|
||||
self.start_node(CACHE_NODE_ID)
|
||||
cache_node = self.nodes[CACHE_NODE_ID]
|
||||
|
||||
@@ -22,6 +22,7 @@ import shlex
|
||||
import sys
|
||||
|
||||
from .authproxy import JSONRPCException
|
||||
from .descriptors import descsum_create
|
||||
from .util import (
|
||||
MAX_NODES,
|
||||
append_config,
|
||||
@@ -61,7 +62,7 @@ class TestNode():
|
||||
To make things easier for the test writer, any unrecognised messages will
|
||||
be dispatched to the RPC connection."""
|
||||
|
||||
def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None):
|
||||
def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, descriptors=False):
|
||||
"""
|
||||
Kwargs:
|
||||
start_perf (bool): If True, begin profiling the node with `perf` as soon as
|
||||
@@ -79,6 +80,7 @@ class TestNode():
|
||||
self.binary = bitcoind
|
||||
self.coverage_dir = coverage_dir
|
||||
self.cwd = cwd
|
||||
self.descriptors = descriptors
|
||||
if extra_conf is not None:
|
||||
append_config(datadir, extra_conf)
|
||||
# Most callers will just need to add extra args to the standard list below.
|
||||
@@ -170,10 +172,10 @@ class TestNode():
|
||||
def __getattr__(self, name):
|
||||
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance."""
|
||||
if self.use_cli:
|
||||
return getattr(self.cli, name)
|
||||
return getattr(RPCOverloadWrapper(self.cli, True, self.descriptors), name)
|
||||
else:
|
||||
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
|
||||
return getattr(self.rpc, name)
|
||||
return getattr(RPCOverloadWrapper(self.rpc, descriptors=self.descriptors), name)
|
||||
|
||||
def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs):
|
||||
"""Start the node."""
|
||||
@@ -265,11 +267,11 @@ class TestNode():
|
||||
|
||||
def get_wallet_rpc(self, wallet_name):
|
||||
if self.use_cli:
|
||||
return self.cli("-rpcwallet={}".format(wallet_name))
|
||||
return RPCOverloadWrapper(self.cli("-rpcwallet={}".format(wallet_name)), True, self.descriptors)
|
||||
else:
|
||||
assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
|
||||
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name))
|
||||
return self.rpc / wallet_path
|
||||
return RPCOverloadWrapper(self.rpc / wallet_path, descriptors=self.descriptors)
|
||||
|
||||
def stop_node(self, expected_stderr='', wait=0):
|
||||
"""Stop the node."""
|
||||
@@ -595,3 +597,118 @@ class TestNodeCLI():
|
||||
return json.loads(cli_stdout, parse_float=decimal.Decimal)
|
||||
except json.JSONDecodeError:
|
||||
return cli_stdout.rstrip("\n")
|
||||
|
||||
class RPCOverloadWrapper():
|
||||
def __init__(self, rpc, cli=False, descriptors=False):
|
||||
self.rpc = rpc
|
||||
self.is_cli = cli
|
||||
self.descriptors = descriptors
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.rpc, name)
|
||||
|
||||
def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase=None, avoid_reuse=None, descriptors=None):
|
||||
if self.is_cli:
|
||||
if disable_private_keys is None:
|
||||
disable_private_keys = 'null'
|
||||
if blank is None:
|
||||
blank = 'null'
|
||||
if passphrase is None:
|
||||
passphrase = ''
|
||||
if avoid_reuse is None:
|
||||
avoid_reuse = 'null'
|
||||
if descriptors is None:
|
||||
descriptors = self.descriptors
|
||||
return self.__getattr__('createwallet')(wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors)
|
||||
|
||||
def importprivkey(self, privkey, label=None, rescan=None):
|
||||
wallet_info = self.getwalletinfo()
|
||||
if self.is_cli:
|
||||
if label is None:
|
||||
label = 'null'
|
||||
if rescan is None:
|
||||
rescan = 'null'
|
||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||
return self.__getattr__('importprivkey')(privkey, label, rescan)
|
||||
desc = descsum_create('combo(' + privkey + ')')
|
||||
req = [{
|
||||
'desc': desc,
|
||||
'timestamp': 0 if rescan else 'now',
|
||||
'label': label if label else ''
|
||||
}]
|
||||
import_res = self.importdescriptors(req)
|
||||
if not import_res[0]['success']:
|
||||
raise JSONRPCException(import_res[0]['error'])
|
||||
|
||||
def addmultisigaddress(self, nrequired, keys, label=None, address_type=None):
|
||||
wallet_info = self.getwalletinfo()
|
||||
if self.is_cli:
|
||||
if label is None:
|
||||
label = 'null'
|
||||
if address_type is None:
|
||||
address_type = 'null'
|
||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||
return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type)
|
||||
cms = self.createmultisig(nrequired, keys, address_type)
|
||||
req = [{
|
||||
'desc': cms['descriptor'],
|
||||
'timestamp': 0,
|
||||
'label': label if label else ''
|
||||
}]
|
||||
import_res = self.importdescriptors(req)
|
||||
if not import_res[0]['success']:
|
||||
raise JSONRPCException(import_res[0]['error'])
|
||||
return cms
|
||||
|
||||
def importpubkey(self, pubkey, label=None, rescan=None):
|
||||
wallet_info = self.getwalletinfo()
|
||||
if self.is_cli:
|
||||
if label is None:
|
||||
label = 'null'
|
||||
if rescan is None:
|
||||
rescan = 'null'
|
||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||
return self.__getattr__('importpubkey')(pubkey, label, rescan)
|
||||
desc = descsum_create('combo(' + pubkey + ')')
|
||||
req = [{
|
||||
'desc': desc,
|
||||
'timestamp': 0 if rescan else 'now',
|
||||
'label': label if label else ''
|
||||
}]
|
||||
import_res = self.importdescriptors(req)
|
||||
if not import_res[0]['success']:
|
||||
raise JSONRPCException(import_res[0]['error'])
|
||||
|
||||
def importaddress(self, address, label=None, rescan=None, p2sh=None):
|
||||
wallet_info = self.getwalletinfo()
|
||||
if self.is_cli:
|
||||
if label is None:
|
||||
label = 'null'
|
||||
if rescan is None:
|
||||
rescan = 'null'
|
||||
if p2sh is None:
|
||||
p2sh = 'null'
|
||||
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
|
||||
return self.__getattr__('importaddress')(address, label, rescan, p2sh)
|
||||
is_hex = False
|
||||
try:
|
||||
int(address ,16)
|
||||
is_hex = True
|
||||
desc = descsum_create('raw(' + address + ')')
|
||||
except:
|
||||
desc = descsum_create('addr(' + address + ')')
|
||||
reqs = [{
|
||||
'desc': desc,
|
||||
'timestamp': 0 if rescan else 'now',
|
||||
'label': label if label else ''
|
||||
}]
|
||||
if is_hex and p2sh:
|
||||
reqs.append({
|
||||
'desc': descsum_create('p2sh(raw(' + address + '))'),
|
||||
'timestamp': 0 if rescan else 'now',
|
||||
'label': label if label else ''
|
||||
})
|
||||
import_res = self.importdescriptors(reqs)
|
||||
for res in import_res:
|
||||
if not res['success']:
|
||||
raise JSONRPCException(res['error'])
|
||||
|
||||
@@ -13,6 +13,10 @@ from test_framework.address import (
|
||||
script_to_p2sh_p2wsh,
|
||||
script_to_p2wsh,
|
||||
)
|
||||
from test_framework.key import (
|
||||
bytes_to_wif,
|
||||
ECKey,
|
||||
)
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
OP_0,
|
||||
@@ -66,6 +70,25 @@ def get_key(node):
|
||||
p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(),
|
||||
p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey))
|
||||
|
||||
def get_generate_key():
|
||||
"""Generate a fresh key
|
||||
|
||||
Returns a named tuple of privkey, pubkey and all address and scripts."""
|
||||
eckey = ECKey()
|
||||
eckey.generate()
|
||||
privkey = bytes_to_wif(eckey.get_bytes())
|
||||
pubkey = eckey.get_pubkey().get_bytes().hex()
|
||||
pkh = hash160(hex_str_to_bytes(pubkey))
|
||||
return Key(privkey=privkey,
|
||||
pubkey=pubkey,
|
||||
p2pkh_script=CScript([OP_DUP, OP_HASH160, pkh, OP_EQUALVERIFY, OP_CHECKSIG]).hex(),
|
||||
p2pkh_addr=key_to_p2pkh(pubkey),
|
||||
p2wpkh_script=CScript([OP_0, pkh]).hex(),
|
||||
p2wpkh_addr=key_to_p2wpkh(pubkey),
|
||||
p2sh_p2wpkh_script=CScript([OP_HASH160, hash160(CScript([OP_0, pkh])), OP_EQUAL]).hex(),
|
||||
p2sh_p2wpkh_redeem_script=CScript([OP_0, pkh]).hex(),
|
||||
p2sh_p2wpkh_addr=key_to_p2sh_p2wpkh(pubkey))
|
||||
|
||||
def get_multisig(node):
|
||||
"""Generate a fresh 2-of-3 multisig on node
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ BASE_SCRIPTS = [
|
||||
# Scripts that are run by default.
|
||||
# Longest test should go first, to favor running tests in parallel
|
||||
'wallet_hd.py',
|
||||
'wallet_hd.py --descriptors',
|
||||
'wallet_backup.py',
|
||||
# vv Tests less than 5m vv
|
||||
'mining_getblocktemplate_longpoll.py',
|
||||
@@ -86,7 +87,9 @@ BASE_SCRIPTS = [
|
||||
'feature_segwit.py',
|
||||
# vv Tests less than 2m vv
|
||||
'wallet_basic.py',
|
||||
'wallet_basic.py --descriptors',
|
||||
'wallet_labels.py',
|
||||
'wallet_labels.py --descriptors',
|
||||
'p2p_segwit.py',
|
||||
'p2p_timeouts.py',
|
||||
'p2p_tx_download.py',
|
||||
@@ -109,6 +112,7 @@ BASE_SCRIPTS = [
|
||||
'feature_abortnode.py',
|
||||
# vv Tests less than 30s vv
|
||||
'wallet_keypool_topup.py',
|
||||
'wallet_keypool_topup.py --descriptors',
|
||||
'feature_fee_estimation.py',
|
||||
'interface_zmq.py',
|
||||
'interface_bitcoin_cli.py',
|
||||
@@ -122,6 +126,7 @@ BASE_SCRIPTS = [
|
||||
'interface_rest.py',
|
||||
'mempool_spend_coinbase.py',
|
||||
'wallet_avoidreuse.py',
|
||||
'wallet_avoidreuse.py --descriptors',
|
||||
'mempool_reorg.py',
|
||||
'mempool_persist.py',
|
||||
'wallet_multiwallet.py',
|
||||
@@ -134,6 +139,7 @@ BASE_SCRIPTS = [
|
||||
'interface_http.py',
|
||||
'interface_rpc.py',
|
||||
'rpc_psbt.py',
|
||||
'rpc_psbt.py --descriptors',
|
||||
'rpc_users.py',
|
||||
'rpc_whitelist.py',
|
||||
'feature_proxy.py',
|
||||
@@ -147,6 +153,8 @@ BASE_SCRIPTS = [
|
||||
'p2p_addr_relay.py',
|
||||
'rpc_net.py',
|
||||
'wallet_keypool.py',
|
||||
'wallet_keypool.py --descriptors',
|
||||
'wallet_descriptor.py',
|
||||
'p2p_mempool.py',
|
||||
'p2p_filter.py',
|
||||
'rpc_setban.py',
|
||||
@@ -168,6 +176,7 @@ BASE_SCRIPTS = [
|
||||
'mempool_packages.py',
|
||||
'mempool_package_onemore.py',
|
||||
'rpc_createmultisig.py',
|
||||
'rpc_createmultisig.py --descriptors',
|
||||
'feature_versionbits_warning.py',
|
||||
'rpc_preciousblock.py',
|
||||
'wallet_importprunedfunds.py',
|
||||
@@ -180,6 +189,7 @@ BASE_SCRIPTS = [
|
||||
'mempool_expiry.py',
|
||||
'wallet_import_rescan.py',
|
||||
'wallet_import_with_label.py',
|
||||
'wallet_importdescriptors.py',
|
||||
'rpc_bind.py --ipv4',
|
||||
'rpc_bind.py --ipv6',
|
||||
'rpc_bind.py --nonloopback',
|
||||
@@ -190,6 +200,7 @@ BASE_SCRIPTS = [
|
||||
'wallet_listsinceblock.py',
|
||||
'p2p_leak.py',
|
||||
'wallet_encryption.py',
|
||||
'wallet_encryption.py --descriptors',
|
||||
'feature_dersig.py',
|
||||
'feature_cltv.py',
|
||||
'rpc_uptime.py',
|
||||
|
||||
@@ -133,7 +133,7 @@ class AvoidReuseTest(BitcoinTestFramework):
|
||||
tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"
|
||||
|
||||
# Create a wallet with disable_private_keys set; this should work
|
||||
self.nodes[1].createwallet(tempwallet, True)
|
||||
self.nodes[1].createwallet(wallet_name=tempwallet, disable_private_keys=True)
|
||||
w = self.nodes[1].get_wallet_rpc(tempwallet)
|
||||
|
||||
# Attempt to unset the disable_private_keys flag; this should not work
|
||||
@@ -249,43 +249,44 @@ class AvoidReuseTest(BitcoinTestFramework):
|
||||
# getbalances should show no used, 5 btc trusted
|
||||
assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5})
|
||||
|
||||
# For the second send, we transmute it to a related single-key address
|
||||
# to make sure it's also detected as re-use
|
||||
fund_spk = self.nodes[0].getaddressinfo(fundaddr)["scriptPubKey"]
|
||||
fund_decoded = self.nodes[0].decodescript(fund_spk)
|
||||
if second_addr_type == "p2sh-segwit":
|
||||
new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"]
|
||||
elif second_addr_type == "bech32":
|
||||
new_fundaddr = fund_decoded["segwit"]["addresses"][0]
|
||||
else:
|
||||
new_fundaddr = fundaddr
|
||||
assert_equal(second_addr_type, "legacy")
|
||||
if not self.options.descriptors:
|
||||
# For the second send, we transmute it to a related single-key address
|
||||
# to make sure it's also detected as re-use
|
||||
fund_spk = self.nodes[0].getaddressinfo(fundaddr)["scriptPubKey"]
|
||||
fund_decoded = self.nodes[0].decodescript(fund_spk)
|
||||
if second_addr_type == "p2sh-segwit":
|
||||
new_fundaddr = fund_decoded["segwit"]["p2sh-segwit"]
|
||||
elif second_addr_type == "bech32":
|
||||
new_fundaddr = fund_decoded["segwit"]["addresses"][0]
|
||||
else:
|
||||
new_fundaddr = fundaddr
|
||||
assert_equal(second_addr_type, "legacy")
|
||||
|
||||
self.nodes[0].sendtoaddress(new_fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
self.nodes[0].sendtoaddress(new_fundaddr, 10)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
|
||||
# getbalances should show 10 used, 5 btc trusted
|
||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})
|
||||
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
|
||||
# getbalances should show 10 used, 5 btc trusted
|
||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5})
|
||||
|
||||
# node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
|
||||
# node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
|
||||
|
||||
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
|
||||
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
|
||||
|
||||
self.nodes[1].sendtoaddress(retaddr, 4)
|
||||
self.nodes[1].sendtoaddress(retaddr, 4)
|
||||
|
||||
# listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
|
||||
# getbalances should show 10 used, 1 btc trusted
|
||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1})
|
||||
# listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
|
||||
assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
|
||||
# getbalances should show 10 used, 1 btc trusted
|
||||
assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1})
|
||||
|
||||
# node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 1, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
|
||||
# node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
|
||||
assert_approx(self.nodes[1].getbalance(), 1, 0.001)
|
||||
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
|
||||
|
||||
def test_getbalances_used(self):
|
||||
'''
|
||||
|
||||
@@ -49,6 +49,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
return self.nodes[0].decoderawtransaction(txn)['vsize']
|
||||
|
||||
def run_test(self):
|
||||
|
||||
# Check that there's no UTXO on none of the nodes
|
||||
assert_equal(len(self.nodes[0].listunspent()), 0)
|
||||
assert_equal(len(self.nodes[1].listunspent()), 0)
|
||||
@@ -219,7 +220,7 @@ class WalletTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[2].getbalance(), node_2_bal)
|
||||
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].gettransaction(txid)['hex']))
|
||||
|
||||
self.start_node(3)
|
||||
self.start_node(3, self.nodes[3].extra_args)
|
||||
connect_nodes(self.nodes[0], 3)
|
||||
self.sync_all()
|
||||
|
||||
@@ -315,57 +316,59 @@ class WalletTest(BitcoinTestFramework):
|
||||
# This will raise an exception since generate does not accept a string
|
||||
assert_raises_rpc_error(-1, "not an integer", self.nodes[0].generate, "2")
|
||||
|
||||
# This will raise an exception for the invalid private key format
|
||||
assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
|
||||
if not self.options.descriptors:
|
||||
|
||||
# This will raise an exception for importing an address with the PS2H flag
|
||||
temp_address = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
||||
assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True)
|
||||
# This will raise an exception for the invalid private key format
|
||||
assert_raises_rpc_error(-5, "Invalid private key encoding", self.nodes[0].importprivkey, "invalid")
|
||||
|
||||
# This will raise an exception for attempting to dump the private key of an address you do not own
|
||||
assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address)
|
||||
# This will raise an exception for importing an address with the PS2H flag
|
||||
temp_address = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
||||
assert_raises_rpc_error(-5, "Cannot use the p2sh flag with an address - use a script instead", self.nodes[0].importaddress, temp_address, "label", False, True)
|
||||
|
||||
# This will raise an exception for attempting to get the private key of an invalid Bitcoin address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid")
|
||||
# This will raise an exception for attempting to dump the private key of an address you do not own
|
||||
assert_raises_rpc_error(-3, "Address does not refer to a key", self.nodes[0].dumpprivkey, temp_address)
|
||||
|
||||
# This will raise an exception for attempting to set a label for an invalid Bitcoin address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label")
|
||||
# This will raise an exception for attempting to get the private key of an invalid Bitcoin address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].dumpprivkey, "invalid")
|
||||
|
||||
# This will raise an exception for importing an invalid address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
|
||||
# This will raise an exception for attempting to set a label for an invalid Bitcoin address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].setlabel, "invalid address", "label")
|
||||
|
||||
# This will raise an exception for attempting to import a pubkey that isn't in hex
|
||||
assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
|
||||
# This will raise an exception for importing an invalid address
|
||||
assert_raises_rpc_error(-5, "Invalid Bitcoin address or script", self.nodes[0].importaddress, "invalid")
|
||||
|
||||
# This will raise an exception for importing an invalid pubkey
|
||||
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
|
||||
# This will raise an exception for attempting to import a pubkey that isn't in hex
|
||||
assert_raises_rpc_error(-5, "Pubkey must be a hex string", self.nodes[0].importpubkey, "not hex")
|
||||
|
||||
# Import address and private key to check correct behavior of spendable unspents
|
||||
# 1. Send some coins to generate new UTXO
|
||||
address_to_import = self.nodes[2].getnewaddress()
|
||||
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
# This will raise an exception for importing an invalid pubkey
|
||||
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
|
||||
|
||||
# 2. Import address from node2 to node1
|
||||
self.nodes[1].importaddress(address_to_import)
|
||||
# Import address and private key to check correct behavior of spendable unspents
|
||||
# 1. Send some coins to generate new UTXO
|
||||
address_to_import = self.nodes[2].getnewaddress()
|
||||
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all(self.nodes[0:3])
|
||||
|
||||
# 3. Validate that the imported address is watch-only on node1
|
||||
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
|
||||
# 2. Import address from node2 to node1
|
||||
self.nodes[1].importaddress(address_to_import)
|
||||
|
||||
# 4. Check that the unspents after import are not spendable
|
||||
assert_array_result(self.nodes[1].listunspent(),
|
||||
{"address": address_to_import},
|
||||
{"spendable": False})
|
||||
# 3. Validate that the imported address is watch-only on node1
|
||||
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
|
||||
|
||||
# 5. Import private key of the previously imported address on node1
|
||||
priv_key = self.nodes[2].dumpprivkey(address_to_import)
|
||||
self.nodes[1].importprivkey(priv_key)
|
||||
# 4. Check that the unspents after import are not spendable
|
||||
assert_array_result(self.nodes[1].listunspent(),
|
||||
{"address": address_to_import},
|
||||
{"spendable": False})
|
||||
|
||||
# 6. Check that the unspents are now spendable on node1
|
||||
assert_array_result(self.nodes[1].listunspent(),
|
||||
{"address": address_to_import},
|
||||
{"spendable": True})
|
||||
# 5. Import private key of the previously imported address on node1
|
||||
priv_key = self.nodes[2].dumpprivkey(address_to_import)
|
||||
self.nodes[1].importprivkey(priv_key)
|
||||
|
||||
# 6. Check that the unspents are now spendable on node1
|
||||
assert_array_result(self.nodes[1].listunspent(),
|
||||
{"address": address_to_import},
|
||||
{"spendable": True})
|
||||
|
||||
# Mine a block from node0 to an address from node1
|
||||
coinbase_addr = self.nodes[1].getnewaddress()
|
||||
@@ -460,7 +463,8 @@ class WalletTest(BitcoinTestFramework):
|
||||
# Try with walletrejectlongchains
|
||||
# Double chain limit but require combining inputs, so we pass SelectCoinsMinConf
|
||||
self.stop_node(0)
|
||||
self.start_node(0, extra_args=["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)])
|
||||
extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]
|
||||
self.start_node(0, extra_args=extra_args)
|
||||
|
||||
# wait for loadmempool
|
||||
timeout = 10
|
||||
|
||||
144
test/functional/wallet_descriptor.py
Executable file
144
test/functional/wallet_descriptor.py
Executable file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 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 descriptor wallet function."""
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error
|
||||
)
|
||||
|
||||
|
||||
class WalletDescriptorTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.setup_clean_chain = True
|
||||
self.num_nodes = 1
|
||||
self.extra_args = [['-keypool=100']]
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
# Make a descriptor wallet
|
||||
self.log.info("Making a descriptor wallet")
|
||||
self.nodes[0].createwallet(wallet_name="desc1", descriptors=True)
|
||||
self.nodes[0].unloadwallet("")
|
||||
|
||||
# A descriptor wallet should have 100 addresses * 3 types = 300 keys
|
||||
self.log.info("Checking wallet info")
|
||||
wallet_info = self.nodes[0].getwalletinfo()
|
||||
assert_equal(wallet_info['keypoolsize'], 300)
|
||||
assert_equal(wallet_info['keypoolsize_hd_internal'], 300)
|
||||
assert 'keypoololdest' not in wallet_info
|
||||
|
||||
# Check that getnewaddress works
|
||||
self.log.info("Test that getnewaddress and getrawchangeaddress work")
|
||||
addr = self.nodes[0].getnewaddress("", "legacy")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('pkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/44\'/1\'/0\'/0/0')
|
||||
|
||||
addr = self.nodes[0].getnewaddress("", "p2sh-segwit")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('sh(wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/49\'/1\'/0\'/0/0')
|
||||
|
||||
addr = self.nodes[0].getnewaddress("", "bech32")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/0/0')
|
||||
|
||||
# Check that getrawchangeaddress works
|
||||
addr = self.nodes[0].getrawchangeaddress("legacy")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('pkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/44\'/1\'/0\'/1/0')
|
||||
|
||||
addr = self.nodes[0].getrawchangeaddress("p2sh-segwit")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('sh(wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/49\'/1\'/0\'/1/0')
|
||||
|
||||
addr = self.nodes[0].getrawchangeaddress("bech32")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/1/0')
|
||||
|
||||
# Make a wallet to receive coins at
|
||||
self.nodes[0].createwallet(wallet_name="desc2", descriptors=True)
|
||||
recv_wrpc = self.nodes[0].get_wallet_rpc("desc2")
|
||||
send_wrpc = self.nodes[0].get_wallet_rpc("desc1")
|
||||
|
||||
# Generate some coins
|
||||
send_wrpc.generatetoaddress(101, send_wrpc.getnewaddress())
|
||||
|
||||
# Make transactions
|
||||
self.log.info("Test sending and receiving")
|
||||
addr = recv_wrpc.getnewaddress()
|
||||
send_wrpc.sendtoaddress(addr, 10)
|
||||
|
||||
# Make sure things are disabled
|
||||
self.log.info("Test disabled RPCs")
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importprivkey, "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW")
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importpubkey, send_wrpc.getaddressinfo(send_wrpc.getnewaddress()))
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importaddress, recv_wrpc.getnewaddress())
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importmulti, [])
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.addmultisigaddress, 1, [recv_wrpc.getnewaddress()])
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpprivkey, recv_wrpc.getnewaddress())
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.dumpwallet, 'wallet.dump')
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.importwallet, 'wallet.dump')
|
||||
assert_raises_rpc_error(-4, "This type of wallet does not support this command", recv_wrpc.rpc.sethdseed)
|
||||
|
||||
self.log.info("Test encryption")
|
||||
# Get the master fingerprint before encrypt
|
||||
info1 = send_wrpc.getaddressinfo(send_wrpc.getnewaddress())
|
||||
|
||||
# Encrypt wallet 0
|
||||
send_wrpc.encryptwallet('pass')
|
||||
send_wrpc.walletpassphrase('pass', 10)
|
||||
addr = send_wrpc.getnewaddress()
|
||||
info2 = send_wrpc.getaddressinfo(addr)
|
||||
assert info1['hdmasterfingerprint'] != info2['hdmasterfingerprint']
|
||||
send_wrpc.walletlock()
|
||||
assert 'hdmasterfingerprint' in send_wrpc.getaddressinfo(send_wrpc.getnewaddress())
|
||||
info3 = send_wrpc.getaddressinfo(addr)
|
||||
assert_equal(info2['desc'], info3['desc'])
|
||||
|
||||
self.log.info("Test that getnewaddress still works after keypool is exhausted in an encrypted wallet")
|
||||
for i in range(0, 500):
|
||||
send_wrpc.getnewaddress()
|
||||
|
||||
self.log.info("Test that unlock is needed when deriving only hardened keys in an encrypted wallet")
|
||||
send_wrpc.walletpassphrase('pass', 10)
|
||||
send_wrpc.importdescriptors([{
|
||||
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h)#y4dfsj7n",
|
||||
"timestamp": "now",
|
||||
"range": [0,10],
|
||||
"active": True
|
||||
}])
|
||||
send_wrpc.walletlock()
|
||||
# Exhaust keypool of 100
|
||||
for i in range(0, 100):
|
||||
send_wrpc.getnewaddress(address_type='bech32')
|
||||
# This should now error
|
||||
assert_raises_rpc_error(-12, "Keypool ran out, please call keypoolrefill first", send_wrpc.getnewaddress, '', 'bech32')
|
||||
|
||||
self.log.info("Test born encrypted wallets")
|
||||
self.nodes[0].createwallet('desc_enc', False, False, 'pass', False, True)
|
||||
enc_rpc = self.nodes[0].get_wallet_rpc('desc_enc')
|
||||
enc_rpc.getnewaddress() # Makes sure that we can get a new address from a born encrypted wallet
|
||||
|
||||
self.log.info("Test blank descriptor wallets")
|
||||
self.nodes[0].createwallet(wallet_name='desc_blank', blank=True, descriptors=True)
|
||||
blank_rpc = self.nodes[0].get_wallet_rpc('desc_blank')
|
||||
assert_raises_rpc_error(-4, 'This wallet has no available keys', blank_rpc.getnewaddress)
|
||||
|
||||
self.log.info("Test descriptor wallet with disabled private keys")
|
||||
self.nodes[0].createwallet(wallet_name='desc_no_priv', disable_private_keys=True, descriptors=True)
|
||||
nopriv_rpc = self.nodes[0].get_wallet_rpc('desc_no_priv')
|
||||
assert_raises_rpc_error(-4, 'This wallet has no available keys', nopriv_rpc.getnewaddress)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletDescriptorTest().main ()
|
||||
@@ -8,7 +8,6 @@ import time
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
assert_greater_than,
|
||||
assert_greater_than_or_equal,
|
||||
@@ -27,10 +26,10 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
||||
passphrase2 = "SecondWalletPassphrase"
|
||||
|
||||
# Make sure the wallet isn't encrypted first
|
||||
address = self.nodes[0].getnewaddress()
|
||||
privkey = self.nodes[0].dumpprivkey(address)
|
||||
assert_equal(privkey[:1], "c")
|
||||
assert_equal(len(privkey), 52)
|
||||
msg = "test message"
|
||||
address = self.nodes[0].getnewaddress(address_type='legacy')
|
||||
sig = self.nodes[0].signmessage(address, msg)
|
||||
assert self.nodes[0].verifymessage(address, sig, msg)
|
||||
assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrase was called", self.nodes[0].walletpassphrase, 'ff', 1)
|
||||
assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", self.nodes[0].walletpassphrasechange, 'ff', 'ff')
|
||||
|
||||
@@ -39,33 +38,36 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
||||
self.nodes[0].encryptwallet(passphrase)
|
||||
|
||||
# Test that the wallet is encrypted
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signmessage, address, msg)
|
||||
assert_raises_rpc_error(-15, "Error: running with an encrypted wallet, but encryptwallet was called.", self.nodes[0].encryptwallet, 'ff')
|
||||
assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrase, '', 1)
|
||||
assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrasechange, '', 'ff')
|
||||
|
||||
# Check that walletpassphrase works
|
||||
self.nodes[0].walletpassphrase(passphrase, 2)
|
||||
assert_equal(privkey, self.nodes[0].dumpprivkey(address))
|
||||
sig = self.nodes[0].signmessage(address, msg)
|
||||
assert self.nodes[0].verifymessage(address, sig, msg)
|
||||
|
||||
# Check that the timeout is right
|
||||
time.sleep(3)
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signmessage, address, msg)
|
||||
|
||||
# Test wrong passphrase
|
||||
assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase + "wrong", 10)
|
||||
|
||||
# Test walletlock
|
||||
self.nodes[0].walletpassphrase(passphrase, 84600)
|
||||
assert_equal(privkey, self.nodes[0].dumpprivkey(address))
|
||||
sig = self.nodes[0].signmessage(address, msg)
|
||||
assert self.nodes[0].verifymessage(address, sig, msg)
|
||||
self.nodes[0].walletlock()
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].dumpprivkey, address)
|
||||
assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signmessage, address, msg)
|
||||
|
||||
# Test passphrase changes
|
||||
self.nodes[0].walletpassphrasechange(passphrase, passphrase2)
|
||||
assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", self.nodes[0].walletpassphrase, passphrase, 10)
|
||||
self.nodes[0].walletpassphrase(passphrase2, 10)
|
||||
assert_equal(privkey, self.nodes[0].dumpprivkey(address))
|
||||
sig = self.nodes[0].signmessage(address, msg)
|
||||
assert self.nodes[0].verifymessage(address, sig, msg)
|
||||
self.nodes[0].walletlock()
|
||||
|
||||
# Test timeout bounds
|
||||
|
||||
@@ -27,17 +27,21 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
|
||||
def run_test(self):
|
||||
# Make sure we use hd, keep masterkeyid
|
||||
masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
assert_equal(len(masterkeyid), 40)
|
||||
hd_fingerprint = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())['hdmasterfingerprint']
|
||||
assert_equal(len(hd_fingerprint), 8)
|
||||
|
||||
# create an internal key
|
||||
change_addr = self.nodes[1].getrawchangeaddress()
|
||||
change_addrV= self.nodes[1].getaddressinfo(change_addr)
|
||||
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key
|
||||
if self.options.descriptors:
|
||||
assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/0")
|
||||
else:
|
||||
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key
|
||||
|
||||
# Import a non-HD private key in the HD wallet
|
||||
non_hd_add = self.nodes[0].getnewaddress()
|
||||
self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add))
|
||||
non_hd_add = 'bcrt1qmevj8zfx0wdvp05cqwkmr6mxkfx60yezwjksmt'
|
||||
non_hd_key = 'cS9umN9w6cDMuRVYdbkfE4c7YUFLJRoXMfhQ569uY4odiQbVN8Rt'
|
||||
self.nodes[1].importprivkey(non_hd_key)
|
||||
|
||||
# This should be enough to keep the master key and the non-HD key
|
||||
self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, "hd.bak"))
|
||||
@@ -48,11 +52,14 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
self.nodes[0].generate(101)
|
||||
hd_add = None
|
||||
NUM_HD_ADDS = 10
|
||||
for i in range(NUM_HD_ADDS):
|
||||
for i in range(1, NUM_HD_ADDS + 1):
|
||||
hd_add = self.nodes[1].getnewaddress()
|
||||
hd_info = self.nodes[1].getaddressinfo(hd_add)
|
||||
assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'")
|
||||
assert_equal(hd_info["hdseedid"], masterkeyid)
|
||||
if self.options.descriptors:
|
||||
assert_equal(hd_info["hdkeypath"], "m/84'/1'/0'/0/" + str(i))
|
||||
else:
|
||||
assert_equal(hd_info["hdkeypath"], "m/0'/0'/"+str(i)+"'")
|
||||
assert_equal(hd_info["hdmasterfingerprint"], hd_fingerprint)
|
||||
self.nodes[0].sendtoaddress(hd_add, 1)
|
||||
self.nodes[0].generate(1)
|
||||
self.nodes[0].sendtoaddress(non_hd_add, 1)
|
||||
@@ -61,7 +68,10 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
# create an internal key (again)
|
||||
change_addr = self.nodes[1].getrawchangeaddress()
|
||||
change_addrV= self.nodes[1].getaddressinfo(change_addr)
|
||||
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key
|
||||
if self.options.descriptors:
|
||||
assert_equal(change_addrV["hdkeypath"], "m/84'/1'/0'/1/1")
|
||||
else:
|
||||
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key
|
||||
|
||||
self.sync_all()
|
||||
assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1)
|
||||
@@ -72,16 +82,19 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
# otherwise node1 would auto-recover all funds in flag the keypool keys as used
|
||||
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks"))
|
||||
shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate"))
|
||||
shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, "wallets", "wallet.dat"))
|
||||
shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join(self.nodes[1].datadir, self.chain, 'wallets', "wallet.dat"))
|
||||
self.start_node(1)
|
||||
|
||||
# Assert that derivation is deterministic
|
||||
hd_add_2 = None
|
||||
for i in range(NUM_HD_ADDS):
|
||||
for i in range(1, NUM_HD_ADDS + 1):
|
||||
hd_add_2 = self.nodes[1].getnewaddress()
|
||||
hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2)
|
||||
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'")
|
||||
assert_equal(hd_info_2["hdseedid"], masterkeyid)
|
||||
if self.options.descriptors:
|
||||
assert_equal(hd_info_2["hdkeypath"], "m/84'/1'/0'/0/" + str(i))
|
||||
else:
|
||||
assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/"+str(i)+"'")
|
||||
assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint)
|
||||
assert_equal(hd_add, hd_add_2)
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
self.sync_all()
|
||||
@@ -117,41 +130,45 @@ class WalletHDTest(BitcoinTestFramework):
|
||||
if out['value'] != 1:
|
||||
keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['addresses'][0])['hdkeypath']
|
||||
|
||||
assert_equal(keypath[0:7], "m/0'/1'")
|
||||
if self.options.descriptors:
|
||||
assert_equal(keypath[0:14], "m/84'/1'/0'/1/")
|
||||
else:
|
||||
assert_equal(keypath[0:7], "m/0'/1'")
|
||||
|
||||
# Generate a new HD seed on node 1 and make sure it is set
|
||||
orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
self.nodes[1].sethdseed()
|
||||
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
assert orig_masterkeyid != new_masterkeyid
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool
|
||||
self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key
|
||||
if not self.options.descriptors:
|
||||
# Generate a new HD seed on node 1 and make sure it is set
|
||||
orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
self.nodes[1].sethdseed()
|
||||
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
assert orig_masterkeyid != new_masterkeyid
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is the first from the keypool
|
||||
self.nodes[1].keypoolrefill(1) # Fill keypool with 1 key
|
||||
|
||||
# Set a new HD seed on node 1 without flushing the keypool
|
||||
new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress())
|
||||
orig_masterkeyid = new_masterkeyid
|
||||
self.nodes[1].sethdseed(False, new_seed)
|
||||
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
assert orig_masterkeyid != new_masterkeyid
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid'])
|
||||
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool
|
||||
# Set a new HD seed on node 1 without flushing the keypool
|
||||
new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress())
|
||||
orig_masterkeyid = new_masterkeyid
|
||||
self.nodes[1].sethdseed(False, new_seed)
|
||||
new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid']
|
||||
assert orig_masterkeyid != new_masterkeyid
|
||||
addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo(addr)['hdseedid'])
|
||||
assert_equal(self.nodes[1].getaddressinfo(addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Make sure the new address continues previous keypool
|
||||
|
||||
# Check that the next address is from the new seed
|
||||
self.nodes[1].keypoolrefill(1)
|
||||
next_addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid'])
|
||||
assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool
|
||||
assert next_addr != addr
|
||||
# Check that the next address is from the new seed
|
||||
self.nodes[1].keypoolrefill(1)
|
||||
next_addr = self.nodes[1].getnewaddress()
|
||||
assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)['hdseedid'])
|
||||
assert_equal(self.nodes[1].getaddressinfo(next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Make sure the new address is not from previous keypool
|
||||
assert next_addr != addr
|
||||
|
||||
# Sethdseed parameter validity
|
||||
assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0)
|
||||
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif")
|
||||
assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool")
|
||||
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True)
|
||||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
|
||||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
|
||||
# Sethdseed parameter validity
|
||||
assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0)
|
||||
assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif")
|
||||
assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool")
|
||||
assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True)
|
||||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
|
||||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletHDTest().main ()
|
||||
|
||||
444
test/functional/wallet_importdescriptors.py
Executable file
444
test/functional/wallet_importdescriptors.py
Executable file
@@ -0,0 +1,444 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2019 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 the importdescriptors RPC.
|
||||
|
||||
Test importdescriptors by generating keys on node0, importing the corresponding
|
||||
descriptors on node1 and then testing the address info for the different address
|
||||
variants.
|
||||
|
||||
- `get_generate_key()` is called to generate keys and return the privkeys,
|
||||
pubkeys and all variants of scriptPubKey and address.
|
||||
- `test_importdesc()` is called to send an importdescriptors call to node1, test
|
||||
success, and (if unsuccessful) test the error code and error message returned.
|
||||
- `test_address()` is called to call getaddressinfo for an address on node1
|
||||
and test the values returned."""
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.descriptors import descsum_create
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
find_vout_for_address,
|
||||
)
|
||||
from test_framework.wallet_util import (
|
||||
get_generate_key,
|
||||
test_address,
|
||||
)
|
||||
|
||||
class ImportDescriptorsTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 2
|
||||
self.extra_args = [["-addresstype=legacy"],
|
||||
["-addresstype=bech32", "-keypool=5"]
|
||||
]
|
||||
self.setup_clean_chain = True
|
||||
|
||||
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):
|
||||
"""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
|
||||
|
||||
result = wrpc.importdescriptors([req])
|
||||
observed_warnings = []
|
||||
if 'warnings' in result[0]:
|
||||
observed_warnings = result[0]['warnings']
|
||||
assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings)))
|
||||
assert_equal(result[0]['success'], success)
|
||||
if error_code is not None:
|
||||
assert_equal(result[0]['error']['code'], error_code)
|
||||
assert_equal(result[0]['error']['message'], error_message)
|
||||
|
||||
def run_test(self):
|
||||
self.log.info('Setting up wallets')
|
||||
self.nodes[0].createwallet(wallet_name='w0', disable_private_keys=False)
|
||||
w0 = self.nodes[0].get_wallet_rpc('w0')
|
||||
|
||||
self.nodes[1].createwallet(wallet_name='w1', disable_private_keys=True, blank=True, descriptors=True)
|
||||
w1 = self.nodes[1].get_wallet_rpc('w1')
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
self.nodes[1].createwallet(wallet_name="wpriv", disable_private_keys=False, blank=True, descriptors=True)
|
||||
wpriv = self.nodes[1].get_wallet_rpc("wpriv")
|
||||
assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
self.log.info('Mining coins')
|
||||
w0.generatetoaddress(101, w0.getnewaddress())
|
||||
|
||||
# RPC importdescriptors -----------------------------------------------
|
||||
|
||||
# # Test import fails if no descriptor present
|
||||
key = get_generate_key()
|
||||
self.log.info("Import should fail if a descriptor is not provided")
|
||||
self.test_importdesc({"timestamp": "now"},
|
||||
success=False,
|
||||
error_code=-8,
|
||||
error_message='Descriptor not found.')
|
||||
|
||||
# # Test importing of a P2PKH descriptor
|
||||
key = get_generate_key()
|
||||
self.log.info("Should import a p2pkh descriptor")
|
||||
self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
|
||||
"timestamp": "now",
|
||||
"label": "Descriptor import test"},
|
||||
success=True)
|
||||
test_address(w1,
|
||||
key.p2pkh_addr,
|
||||
solvable=True,
|
||||
ismine=True,
|
||||
labels=["Descriptor import test"])
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
self.log.info("Internal addresses cannot have labels")
|
||||
self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"),
|
||||
"timestamp": "now",
|
||||
"internal": True,
|
||||
"label": "Descriptor import test"},
|
||||
success=False,
|
||||
error_code=-8,
|
||||
error_message="Internal addresses should not have a label")
|
||||
|
||||
# # Test importing of a P2SH-P2WPKH descriptor
|
||||
key = get_generate_key()
|
||||
self.log.info("Should not import a p2sh-p2wpkh descriptor without checksum")
|
||||
self.test_importdesc({"desc": "sh(wpkh(" + key.pubkey + "))",
|
||||
"timestamp": "now"
|
||||
},
|
||||
success=False,
|
||||
error_code=-5,
|
||||
error_message="Missing checksum")
|
||||
|
||||
self.log.info("Should not import a p2sh-p2wpkh descriptor that has range specified")
|
||||
self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
|
||||
"timestamp": "now",
|
||||
"range": 1,
|
||||
},
|
||||
success=False,
|
||||
error_code=-8,
|
||||
error_message="Range should not be specified for an un-ranged descriptor")
|
||||
|
||||
self.log.info("Should not import a p2sh-p2wpkh descriptor and have it set to active")
|
||||
self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
|
||||
"timestamp": "now",
|
||||
"active": True,
|
||||
},
|
||||
success=False,
|
||||
error_code=-8,
|
||||
error_message="Active descriptors must be ranged")
|
||||
|
||||
self.log.info("Should import a (non-active) p2sh-p2wpkh descriptor")
|
||||
self.test_importdesc({"desc": descsum_create("sh(wpkh(" + key.pubkey + "))"),
|
||||
"timestamp": "now",
|
||||
"active": False,
|
||||
},
|
||||
success=True)
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
test_address(w1,
|
||||
key.p2sh_p2wpkh_addr,
|
||||
ismine=True,
|
||||
solvable=True)
|
||||
|
||||
# # Test importing of a multisig descriptor
|
||||
key1 = get_generate_key()
|
||||
key2 = get_generate_key()
|
||||
self.log.info("Should import a 1-of-2 bare multisig from descriptor")
|
||||
self.test_importdesc({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"),
|
||||
"timestamp": "now"},
|
||||
success=True)
|
||||
self.log.info("Should not treat individual keys from the imported bare multisig as watchonly")
|
||||
test_address(w1,
|
||||
key1.p2pkh_addr,
|
||||
ismine=False)
|
||||
|
||||
# # Test ranged descriptors
|
||||
xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg"
|
||||
xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H"
|
||||
addresses = ["2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1'
|
||||
addresses += ["bcrt1qrd3n235cj2czsfmsuvqqpr3lu6lg0ju7scl8gn", "bcrt1qfqeppuvj0ww98r6qghmdkj70tv8qpchehegrg8"] # wpkh subscripts corresponding to the above addresses
|
||||
desc = "sh(wpkh(" + xpub + "/0/0/*" + "))"
|
||||
|
||||
self.log.info("Ranged descriptors cannot have labels")
|
||||
self.test_importdesc({"desc":descsum_create(desc),
|
||||
"timestamp": "now",
|
||||
"range": [0, 100],
|
||||
"label": "test"},
|
||||
success=False,
|
||||
error_code=-8,
|
||||
error_message='Ranged descriptors should not have a label')
|
||||
|
||||
self.log.info("Private keys required for private keys enabled wallet")
|
||||
self.test_importdesc({"desc":descsum_create(desc),
|
||||
"timestamp": "now",
|
||||
"range": [0, 100]},
|
||||
success=False,
|
||||
error_code=-4,
|
||||
error_message='Cannot import descriptor without private keys to a wallet with private keys enabled',
|
||||
wallet=wpriv)
|
||||
|
||||
self.log.info("Ranged descriptor import should warn without a specified range")
|
||||
self.test_importdesc({"desc": descsum_create(desc),
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
warnings=['Range not given, using default keypool range'])
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
# # Test importing of a ranged descriptor with xpriv
|
||||
self.log.info("Should not import a ranged descriptor that includes xpriv into a watch-only wallet")
|
||||
desc = "sh(wpkh(" + xpriv + "/0'/0'/*'" + "))"
|
||||
self.test_importdesc({"desc": descsum_create(desc),
|
||||
"timestamp": "now",
|
||||
"range": 1},
|
||||
success=False,
|
||||
error_code=-4,
|
||||
error_message='Cannot import private keys to a wallet with private keys disabled')
|
||||
for address in addresses:
|
||||
test_address(w1,
|
||||
address,
|
||||
ismine=False,
|
||||
solvable=False)
|
||||
|
||||
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1},
|
||||
success=False, error_code=-8, error_message='End of range is too high')
|
||||
|
||||
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]},
|
||||
success=False, error_code=-8, error_message='Range should be greater or equal than 0')
|
||||
|
||||
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]},
|
||||
success=False, error_code=-8, error_message='End of range is too high')
|
||||
|
||||
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]},
|
||||
success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end')
|
||||
|
||||
self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]},
|
||||
success=False, error_code=-8, error_message='Range is too large')
|
||||
|
||||
# Make sure ranged imports import keys in order
|
||||
w1 = self.nodes[1].get_wallet_rpc('w1')
|
||||
self.log.info('Key ranges should be imported in order')
|
||||
xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY"
|
||||
addresses = [
|
||||
'bcrt1qtmp74ayg7p24uslctssvjm06q5phz4yrxucgnv', # m/0'/0'/0
|
||||
'bcrt1q8vprchan07gzagd5e6v9wd7azyucksq2xc76k8', # m/0'/0'/1
|
||||
'bcrt1qtuqdtha7zmqgcrr26n2rqxztv5y8rafjp9lulu', # m/0'/0'/2
|
||||
'bcrt1qau64272ymawq26t90md6an0ps99qkrse58m640', # m/0'/0'/3
|
||||
'bcrt1qsg97266hrh6cpmutqen8s4s962aryy77jp0fg0', # m/0'/0'/4
|
||||
]
|
||||
|
||||
self.test_importdesc({'desc': descsum_create('wpkh([80002067/0h/0h]' + xpub + '/*)'),
|
||||
'active': True,
|
||||
'range' : [0, 2],
|
||||
'timestamp': 'now'
|
||||
},
|
||||
success=True)
|
||||
self.test_importdesc({'desc': descsum_create('sh(wpkh([abcdef12/0h/0h]' + xpub + '/*))'),
|
||||
'active': True,
|
||||
'range' : [0, 2],
|
||||
'timestamp': 'now'
|
||||
},
|
||||
success=True)
|
||||
self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
|
||||
'active': True,
|
||||
'range' : [0, 2],
|
||||
'timestamp': 'now'
|
||||
},
|
||||
success=True)
|
||||
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
|
||||
for i, expected_addr in enumerate(addresses):
|
||||
received_addr = w1.getnewaddress('', 'bech32')
|
||||
assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'bech32')
|
||||
assert_equal(received_addr, expected_addr)
|
||||
bech32_addr_info = w1.getaddressinfo(received_addr)
|
||||
assert_equal(bech32_addr_info['desc'][:23], 'wpkh([80002067/0\'/0\'/{}]'.format(i))
|
||||
|
||||
shwpkh_addr = w1.getnewaddress('', 'p2sh-segwit')
|
||||
shwpkh_addr_info = w1.getaddressinfo(shwpkh_addr)
|
||||
assert_equal(shwpkh_addr_info['desc'][:26], 'sh(wpkh([abcdef12/0\'/0\'/{}]'.format(i))
|
||||
|
||||
pkh_addr = w1.getnewaddress('', 'legacy')
|
||||
pkh_addr_info = w1.getaddressinfo(pkh_addr)
|
||||
assert_equal(pkh_addr_info['desc'][:22], 'pkh([12345678/0\'/0\'/{}]'.format(i))
|
||||
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 4 * 3) # After retrieving a key, we don't refill the keypool again, so it's one less for each address type
|
||||
w1.keypoolrefill()
|
||||
assert_equal(w1.getwalletinfo()['keypoolsize'], 5 * 3)
|
||||
|
||||
# Check active=False default
|
||||
self.log.info('Check imported descriptors are not active by default')
|
||||
self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'),
|
||||
'range' : [0, 2],
|
||||
'timestamp': 'now',
|
||||
'internal': True
|
||||
},
|
||||
success=True)
|
||||
assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress, 'legacy')
|
||||
|
||||
# # Test importing a descriptor containing a WIF private key
|
||||
wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh"
|
||||
address = "2MuhcG52uHPknxDgmGPsV18jSHFBnnRgjPg"
|
||||
desc = "sh(wpkh(" + wif_priv + "))"
|
||||
self.log.info("Should import a descriptor with a WIF private key as spendable")
|
||||
self.test_importdesc({"desc": descsum_create(desc),
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
wallet=wpriv)
|
||||
test_address(wpriv,
|
||||
address,
|
||||
solvable=True,
|
||||
ismine=True)
|
||||
txid = w0.sendtoaddress(address, 49.99995540)
|
||||
w0.generatetoaddress(6, w0.getnewaddress())
|
||||
self.sync_blocks()
|
||||
tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], {w0.getnewaddress(): 49.999})
|
||||
signed_tx = wpriv.signrawtransactionwithwallet(tx)
|
||||
w1.sendrawtransaction(signed_tx['hex'])
|
||||
|
||||
# Make sure that we can use import and use multisig as addresses
|
||||
self.log.info('Test that multisigs can be imported, signed for, and getnewaddress\'d')
|
||||
self.nodes[1].createwallet(wallet_name="wmulti_priv", disable_private_keys=False, blank=True, descriptors=True)
|
||||
wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv")
|
||||
assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#m2sr93jn",
|
||||
"active": True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
wallet=wmulti_priv)
|
||||
self.test_importdesc({"desc":"wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#q3sztvx5",
|
||||
"active": True,
|
||||
"internal" : True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
wallet=wmulti_priv)
|
||||
|
||||
assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) # Range end (1000) is inclusive, so 1001 addresses generated
|
||||
addr = wmulti_priv.getnewaddress('', 'bech32')
|
||||
assert_equal(addr, 'bcrt1qdt0qy5p7dzhxzmegnn4ulzhard33s2809arjqgjndx87rv5vd0fq2czhy8') # Derived at m/84'/0'/0'/0
|
||||
change_addr = wmulti_priv.getrawchangeaddress('bech32')
|
||||
assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e')
|
||||
assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000)
|
||||
txid = w0.sendtoaddress(addr, 10)
|
||||
self.nodes[0].generate(6)
|
||||
send_txid = wmulti_priv.sendtoaddress(w0.getnewaddress(), 8)
|
||||
decoded = wmulti_priv.decoderawtransaction(wmulti_priv.gettransaction(send_txid)['hex'])
|
||||
assert_equal(len(decoded['vin'][0]['txinwitness']), 4)
|
||||
self.nodes[0].generate(6)
|
||||
self.sync_all()
|
||||
|
||||
self.nodes[1].createwallet(wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True)
|
||||
wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub")
|
||||
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0)
|
||||
|
||||
self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#tsry0s5e",
|
||||
"active": True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
wallet=wmulti_pub)
|
||||
self.test_importdesc({"desc":"wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#c08a2rzv",
|
||||
"active": True,
|
||||
"internal" : True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
wallet=wmulti_pub)
|
||||
|
||||
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) # The first one was already consumed by previous import and is detected as used
|
||||
addr = wmulti_pub.getnewaddress('', 'bech32')
|
||||
assert_equal(addr, 'bcrt1qp8s25ckjl7gr6x2q3dx3tn2pytwp05upkjztk6ey857tt50r5aeqn6mvr9') # Derived at m/84'/0'/0'/1
|
||||
change_addr = wmulti_pub.getrawchangeaddress('bech32')
|
||||
assert_equal(change_addr, 'bcrt1qt9uhe3a9hnq7vajl7a094z4s3crm9ttf8zw3f5v9gr2nyd7e3lnsy44n8e')
|
||||
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)
|
||||
txid = w0.sendtoaddress(addr, 10)
|
||||
vout = find_vout_for_address(self.nodes[0], txid, addr)
|
||||
self.nodes[0].generate(6)
|
||||
self.sync_all()
|
||||
assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
|
||||
|
||||
self.log.info("Multisig with distributed keys")
|
||||
self.nodes[1].createwallet(wallet_name="wmulti_priv1", descriptors=True)
|
||||
wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1")
|
||||
res = wmulti_priv1.importdescriptors([
|
||||
{
|
||||
"desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
|
||||
"active": True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"
|
||||
},
|
||||
{
|
||||
"desc": descsum_create("wsh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
|
||||
"active": True,
|
||||
"internal" : True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"
|
||||
}])
|
||||
assert_equal(res[0]['success'], True)
|
||||
assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
|
||||
assert_equal(res[1]['success'], True)
|
||||
assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
|
||||
|
||||
self.nodes[1].createwallet(wallet_name='wmulti_priv2', blank=True, descriptors=True)
|
||||
wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2')
|
||||
res = wmulti_priv2.importdescriptors([
|
||||
{
|
||||
"desc": descsum_create("wsh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"),
|
||||
"active": True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"
|
||||
},
|
||||
{
|
||||
"desc": descsum_create("wsh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"),
|
||||
"active": True,
|
||||
"internal" : True,
|
||||
"range": 1000,
|
||||
"next_index": 0,
|
||||
"timestamp": "now"
|
||||
}])
|
||||
assert_equal(res[0]['success'], True)
|
||||
assert_equal(res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
|
||||
assert_equal(res[1]['success'], True)
|
||||
assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')
|
||||
|
||||
rawtx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9.999})
|
||||
tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx)
|
||||
assert_equal(tx_signed_1['complete'], False)
|
||||
tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1['hex'])
|
||||
assert_equal(tx_signed_2['complete'], True)
|
||||
self.nodes[1].sendrawtransaction(tx_signed_2['hex'])
|
||||
|
||||
self.log.info("Combo descriptors cannot be active")
|
||||
self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
|
||||
"active": True,
|
||||
"range": 1,
|
||||
"timestamp": "now"},
|
||||
success=False,
|
||||
error_code=-4,
|
||||
error_message="Combo descriptors cannot be set to active")
|
||||
|
||||
self.log.info("Descriptors with no type cannot be active")
|
||||
self.test_importdesc({"desc": descsum_create("pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
|
||||
"active": True,
|
||||
"range": 1,
|
||||
"timestamp": "now"},
|
||||
success=True,
|
||||
warnings=["Unknown output type, cannot set descriptor to active."])
|
||||
|
||||
if __name__ == '__main__':
|
||||
ImportDescriptorsTest().main()
|
||||
@@ -22,16 +22,63 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||
addr_before_encrypting = nodes[0].getnewaddress()
|
||||
addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)
|
||||
wallet_info_old = nodes[0].getwalletinfo()
|
||||
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']
|
||||
if not self.options.descriptors:
|
||||
assert addr_before_encrypting_data['hdseedid'] == wallet_info_old['hdseedid']
|
||||
|
||||
# Encrypt wallet and wait to terminate
|
||||
nodes[0].encryptwallet('test')
|
||||
if self.options.descriptors:
|
||||
# Import hardened derivation only descriptors
|
||||
nodes[0].walletpassphrase('test', 10)
|
||||
nodes[0].importdescriptors([
|
||||
{
|
||||
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h)#y4dfsj7n",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1h/*h)#a0nyvl0k",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/2h/*h))#lmeu2axg",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True
|
||||
},
|
||||
{
|
||||
"desc": "wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/3h/*h)#jkl636gm",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True,
|
||||
"internal": True
|
||||
},
|
||||
{
|
||||
"desc": "pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/4h/*h)#l3crwaus",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True,
|
||||
"internal": True
|
||||
},
|
||||
{
|
||||
"desc": "sh(wpkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/5h/*h))#qg8wa75f",
|
||||
"timestamp": "now",
|
||||
"range": [0,0],
|
||||
"active": True,
|
||||
"internal": True
|
||||
}
|
||||
])
|
||||
nodes[0].walletlock()
|
||||
# Keep creating keys
|
||||
addr = nodes[0].getnewaddress()
|
||||
addr_data = nodes[0].getaddressinfo(addr)
|
||||
wallet_info = nodes[0].getwalletinfo()
|
||||
assert addr_before_encrypting_data['hdseedid'] != wallet_info['hdseedid']
|
||||
assert addr_data['hdseedid'] == wallet_info['hdseedid']
|
||||
assert addr_before_encrypting_data['hdmasterfingerprint'] != addr_data['hdmasterfingerprint']
|
||||
if not self.options.descriptors:
|
||||
assert addr_data['hdseedid'] == wallet_info['hdseedid']
|
||||
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
|
||||
|
||||
# put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
|
||||
@@ -39,8 +86,12 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||
nodes[0].keypoolrefill(6)
|
||||
nodes[0].walletlock()
|
||||
wi = nodes[0].getwalletinfo()
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 6)
|
||||
assert_equal(wi['keypoolsize'], 6)
|
||||
if self.options.descriptors:
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 18)
|
||||
assert_equal(wi['keypoolsize'], 18)
|
||||
else:
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 6)
|
||||
assert_equal(wi['keypoolsize'], 6)
|
||||
|
||||
# drain the internal keys
|
||||
nodes[0].getrawchangeaddress()
|
||||
@@ -80,11 +131,15 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||
nodes[0].walletpassphrase('test', 100)
|
||||
nodes[0].keypoolrefill(100)
|
||||
wi = nodes[0].getwalletinfo()
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 100)
|
||||
assert_equal(wi['keypoolsize'], 100)
|
||||
if self.options.descriptors:
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 300)
|
||||
assert_equal(wi['keypoolsize'], 300)
|
||||
else:
|
||||
assert_equal(wi['keypoolsize_hd_internal'], 100)
|
||||
assert_equal(wi['keypoolsize'], 100)
|
||||
|
||||
# create a blank wallet
|
||||
nodes[0].createwallet(wallet_name='w2', blank=True)
|
||||
nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
|
||||
w2 = nodes[0].get_wallet_rpc('w2')
|
||||
|
||||
# refer to initial wallet as w1
|
||||
@@ -92,8 +147,11 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||
|
||||
# import private key and fund it
|
||||
address = addr.pop()
|
||||
privkey = w1.dumpprivkey(address)
|
||||
res = w2.importmulti([{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}])
|
||||
desc = w1.getaddressinfo(address)['desc']
|
||||
if self.options.descriptors:
|
||||
res = w2.importdescriptors([{'desc': desc, 'timestamp': 'now'}])
|
||||
else:
|
||||
res = w2.importmulti([{'desc': desc, 'timestamp': 'now'}])
|
||||
assert_equal(res[0]['success'], True)
|
||||
w1.walletpassphrase('test', 100)
|
||||
|
||||
|
||||
@@ -79,7 +79,15 @@ class KeypoolRestoreTest(BitcoinTestFramework):
|
||||
assert_equal(self.nodes[idx].getbalance(), 15)
|
||||
assert_equal(self.nodes[idx].listtransactions()[0]['category'], "receive")
|
||||
# Check that we have marked all keys up to the used keypool key as used
|
||||
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress())['hdkeypath'], "m/0'/0'/110'")
|
||||
if self.options.descriptors:
|
||||
if output_type == 'legacy':
|
||||
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/44'/1'/0'/0/110")
|
||||
elif output_type == 'p2sh-segwit':
|
||||
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/49'/1'/0'/0/110")
|
||||
elif output_type == 'bech32':
|
||||
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/84'/1'/0'/0/110")
|
||||
else:
|
||||
assert_equal(self.nodes[idx].getaddressinfo(self.nodes[idx].getnewaddress(address_type=output_type))['hdkeypath'], "m/0'/0'/110'")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -115,15 +115,16 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||
assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "")
|
||||
|
||||
# Check that addmultisigaddress can assign labels.
|
||||
for label in labels:
|
||||
addresses = []
|
||||
for x in range(10):
|
||||
addresses.append(node.getnewaddress())
|
||||
multisig_address = node.addmultisigaddress(5, addresses, label.name)['address']
|
||||
label.add_address(multisig_address)
|
||||
label.purpose[multisig_address] = "send"
|
||||
label.verify(node)
|
||||
node.generate(101)
|
||||
if not self.options.descriptors:
|
||||
for label in labels:
|
||||
addresses = []
|
||||
for x in range(10):
|
||||
addresses.append(node.getnewaddress())
|
||||
multisig_address = node.addmultisigaddress(5, addresses, label.name)['address']
|
||||
label.add_address(multisig_address)
|
||||
label.purpose[multisig_address] = "send"
|
||||
label.verify(node)
|
||||
node.generate(101)
|
||||
|
||||
# Check that setlabel can change the label of an address from a
|
||||
# different label.
|
||||
|
||||
Reference in New Issue
Block a user