Merge bitcoin/bitcoin#23866: test: use MiniWallet for rpc_scantxoutset.py

e844115dea test: use MiniWallet for rpc_scantxoutset.py (Sebastian Falbesoner)
983ca0456c test: introduce `address_to_scriptpubkey` helper (Sebastian Falbesoner)
e704d4d26f test: introduce `getnewdestination` helper for generating various address types (Sebastian Falbesoner)

Pull request description:

  This PR enables one more of the non-wallet functional tests (rpc_scantxoutset.py) to be run even with the Bitcoin Core wallet disabled by using the MiniWallet instead, as proposed in #20078 and https://github.com/bitcoin/bitcoin/pull/23858#issue-1088320649 more recently.

  Reviewer's guide:
  * [commit 1/3] For replacing the getnewaddress/getaddressinfo RPC calls a helper `getnewdestination` is introduced which allows to create addresses with the common address format types ('legacy', 'p2sh-segwit' and 'bech32'), but also additionally returns the corresponding pubkey and output script.
  * [commit 2/3] In order to send to addresses with MiniWallet, a helper `address_to_scriptpubkey` is introduced. It only supports legacy addresses (Base58Check) so far, which is sufficient for the scantxoutset test.
  * [commit 3/3] With those helpers, the use of MiniWallet in the test is quite straight-forward. To avoid repeatedly specifying parameters like `from_node` to MiniWallet's `send_to` method, another test-internal helper `sendtodestination` is introduced which supports specifying the destination both as outputscript or as address.

ACKs for top commit:
  w0xlt:
    reACK [e844115](https://github.com/bitcoin/bitcoin/pull/23866/commits/e844115deae8a11c591704a84e56ed45fa81409c)

Tree-SHA512: e4823dc507019b2b8e479602963c5dddc4c78211e1d934218ee0f0e32c068ab7e44e9793307f549127946364f57d684c4ea1d70f25865ea70d30d4f3855836d0
This commit is contained in:
MarcoFalke committed 2021-12-27 11:37:45 +01:00
commit d3582f2d3b
3 files changed
+85 -54

No files matched your search

+5 -5
View File
@@ -31,7 +31,7 @@ from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.wallet import ( from test_framework.wallet import (
MiniWallet, MiniWallet,
random_p2wpkh, getnewdestination,
) )
@@ -169,14 +169,14 @@ class FilterTest(BitcoinTestFramework):
self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
filter_peer.tx_received = False filter_peer.tx_received = False
block_hash = self.generatetoscriptpubkey(random_p2wpkh()) block_hash = self.generatetoscriptpubkey(getnewdestination()[1])
filter_peer.wait_for_merkleblock(block_hash) filter_peer.wait_for_merkleblock(block_hash)
assert not filter_peer.tx_received assert not filter_peer.tx_received
self.log.info('Check that we not receive a tx if the filter does not match a mempool tx') self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
filter_peer.merkleblock_received = False filter_peer.merkleblock_received = False
filter_peer.tx_received = False filter_peer.tx_received = False
self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN) self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN)
filter_peer.sync_send_with_ping() filter_peer.sync_send_with_ping()
assert not filter_peer.merkleblock_received assert not filter_peer.merkleblock_received
assert not filter_peer.tx_received assert not filter_peer.tx_received
@@ -190,14 +190,14 @@ class FilterTest(BitcoinTestFramework):
self.log.info('Check that after deleting filter all txs get relayed again') self.log.info('Check that after deleting filter all txs get relayed again')
filter_peer.send_and_ping(msg_filterclear()) filter_peer.send_and_ping(msg_filterclear())
for _ in range(5): for _ in range(5):
txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN) txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN)
filter_peer.wait_for_tx(txid) filter_peer.wait_for_tx(txid)
self.log.info('Check that request for filtered blocks is ignored if no filter is set') self.log.info('Check that request for filtered blocks is ignored if no filter is set')
filter_peer.merkleblock_received = False filter_peer.merkleblock_received = False
filter_peer.tx_received = False filter_peer.tx_received = False
with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']): with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
block_hash = self.generatetoscriptpubkey(random_p2wpkh()) block_hash = self.generatetoscriptpubkey(getnewdestination()[1])
filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))]) filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
filter_peer.sync_with_ping() filter_peer.sync_with_ping()
assert not filter_peer.merkleblock_received assert not filter_peer.merkleblock_received
+37 -44
View File
@@ -3,12 +3,16 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the scantxoutset rpc call.""" """Test the scantxoutset rpc call."""
from test_framework.messages import COIN
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.util import assert_equal, assert_raises_rpc_error
from test_framework.wallet import (
MiniWallet,
address_to_scriptpubkey,
getnewdestination,
)
from decimal import Decimal from decimal import Decimal
import shutil
import os
def descriptors(out): def descriptors(out):
@@ -18,51 +22,41 @@ def descriptors(out):
class ScantxoutsetTest(BitcoinTestFramework): class ScantxoutsetTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 1 self.num_nodes = 1
self.setup_clean_chain = True
def skip_test_if_missing_module(self): def sendtodestination(self, destination, amount):
self.skip_if_no_wallet() # interpret strings as addresses, assume scriptPubKey otherwise
if isinstance(destination, str):
destination = address_to_scriptpubkey(destination)
self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=destination, amount=int(COIN * amount))
def run_test(self): def run_test(self):
self.log.info("Mining blocks...") self.wallet = MiniWallet(self.nodes[0])
self.generate(self.nodes[0], 110) self.wallet.rescan_utxos()
addr_P2SH_SEGWIT = self.nodes[0].getnewaddress("", "p2sh-segwit") self.log.info("Create UTXOs...")
pubk1 = self.nodes[0].getaddressinfo(addr_P2SH_SEGWIT)['pubkey'] pubk1, spk_P2SH_SEGWIT, addr_P2SH_SEGWIT = getnewdestination("p2sh-segwit")
addr_LEGACY = self.nodes[0].getnewaddress("", "legacy") pubk2, spk_LEGACY, addr_LEGACY = getnewdestination("legacy")
pubk2 = self.nodes[0].getaddressinfo(addr_LEGACY)['pubkey'] pubk3, spk_BECH32, addr_BECH32 = getnewdestination("bech32")
addr_BECH32 = self.nodes[0].getnewaddress("", "bech32") self.sendtodestination(spk_P2SH_SEGWIT, 0.001)
pubk3 = self.nodes[0].getaddressinfo(addr_BECH32)['pubkey'] self.sendtodestination(spk_LEGACY, 0.002)
txid = self.nodes[0].sendtoaddress(addr_P2SH_SEGWIT, 0.001) self.sendtodestination(spk_BECH32, 0.004)
self.nodes[0].lockunspent(unlock=False, transactions=[{"txid": txid, "vout": 0}, {"txid": txid, "vout": 1}])
txid = self.nodes[0].sendtoaddress(addr_LEGACY, 0.002)
self.nodes[0].lockunspent(unlock=False, transactions=[{"txid": txid, "vout": 0}, {"txid": txid, "vout": 1}])
txid = self.nodes[0].sendtoaddress(addr_BECH32, 0.004)
self.nodes[0].lockunspent(unlock=False, transactions=[{"txid": txid, "vout": 0}, {"txid": txid, "vout": 1}])
#send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK
self.nodes[0].sendtoaddress("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) # (m/0'/0'/0') self.sendtodestination("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) # (m/0'/0'/0')
self.nodes[0].sendtoaddress("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) # (m/0'/0'/1') self.sendtodestination("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) # (m/0'/0'/1')
self.nodes[0].sendtoaddress("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) # (m/0'/0'/1500') self.sendtodestination("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) # (m/0'/0'/1500')
self.nodes[0].sendtoaddress("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) # (m/0'/0'/0) self.sendtodestination("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) # (m/0'/0'/0)
self.nodes[0].sendtoaddress("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) # (m/0'/0'/1) self.sendtodestination("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) # (m/0'/0'/1)
self.nodes[0].sendtoaddress("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) # (m/0'/0'/1500) self.sendtodestination("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) # (m/0'/0'/1500)
self.nodes[0].sendtoaddress("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) # (m/1/1/0') self.sendtodestination("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) # (m/1/1/0')
self.nodes[0].sendtoaddress("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) # (m/1/1/1') self.sendtodestination("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) # (m/1/1/1')
self.nodes[0].sendtoaddress("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) # (m/1/1/1500') self.sendtodestination("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) # (m/1/1/1500')
self.nodes[0].sendtoaddress("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) # (m/1/1/0) self.sendtodestination("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) # (m/1/1/0)
self.nodes[0].sendtoaddress("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) # (m/1/1/1) self.sendtodestination("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) # (m/1/1/1)
self.nodes[0].sendtoaddress("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) # (m/1/1/1500) self.sendtodestination("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) # (m/1/1/1500)
self.generate(self.nodes[0], 1) self.generate(self.nodes[0], 1)
self.log.info("Stop node, remove wallet, mine again some blocks...")
self.stop_node(0)
shutil.rmtree(os.path.join(self.nodes[0].datadir, self.chain, 'wallets'))
self.start_node(0, ['-nowallet'])
self.import_deterministic_coinbase_privkeys()
self.generate(self.nodes[0], 110)
scan = self.nodes[0].scantxoutset("start", []) scan = self.nodes[0].scantxoutset("start", [])
info = self.nodes[0].gettxoutsetinfo() info = self.nodes[0].gettxoutsetinfo()
assert_equal(scan['success'], True) assert_equal(scan['success'], True)
@@ -70,14 +64,13 @@ class ScantxoutsetTest(BitcoinTestFramework):
assert_equal(scan['txouts'], info['txouts']) assert_equal(scan['txouts'], info['txouts'])
assert_equal(scan['bestblock'], info['bestblock']) assert_equal(scan['bestblock'], info['bestblock'])
self.restart_node(0, ['-nowallet'])
self.log.info("Test if we have found the non HD unspent outputs.") self.log.info("Test if we have found the non HD unspent outputs.")
assert_equal(self.nodes[0].scantxoutset("start", ["pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.002")) assert_equal(self.nodes[0].scantxoutset("start", ["pkh(" + pubk1.hex() + ")", "pkh(" + pubk2.hex() + ")", "pkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.002"))
assert_equal(self.nodes[0].scantxoutset("start", ["wpkh(" + pubk1 + ")", "wpkh(" + pubk2 + ")", "wpkh(" + pubk3 + ")"])['total_amount'], Decimal("0.004")) assert_equal(self.nodes[0].scantxoutset("start", ["wpkh(" + pubk1.hex() + ")", "wpkh(" + pubk2.hex() + ")", "wpkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.004"))
assert_equal(self.nodes[0].scantxoutset("start", ["sh(wpkh(" + pubk1 + "))", "sh(wpkh(" + pubk2 + "))", "sh(wpkh(" + pubk3 + "))"])['total_amount'], Decimal("0.001")) assert_equal(self.nodes[0].scantxoutset("start", ["sh(wpkh(" + pubk1.hex() + "))", "sh(wpkh(" + pubk2.hex() + "))", "sh(wpkh(" + pubk3.hex() + "))"])['total_amount'], Decimal("0.001"))
assert_equal(self.nodes[0].scantxoutset("start", ["combo(" + pubk1 + ")", "combo(" + pubk2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) assert_equal(self.nodes[0].scantxoutset("start", ["combo(" + pubk1.hex() + ")", "combo(" + pubk2.hex() + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007"))
assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "addr(" + addr_BECH32 + ")"])['total_amount'], Decimal("0.007")) assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "addr(" + addr_BECH32 + ")"])['total_amount'], Decimal("0.007"))
assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007"))
self.log.info("Test range validation.") self.log.info("Test range validation.")
assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].scantxoutset, "start", [{"desc": "desc", "range": -1}]) assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].scantxoutset, "start", [{"desc": "desc", "range": -1}])
+43 -5
View File
@@ -9,7 +9,13 @@ from decimal import Decimal
from enum import Enum from enum import Enum
from random import choice from random import choice
from typing import Optional from typing import Optional
from test_framework.address import create_deterministic_address_bcrt1_p2tr_op_true from test_framework.address import (
base58_to_byte,
create_deterministic_address_bcrt1_p2tr_op_true,
key_to_p2pkh,
key_to_p2sh_p2wpkh,
key_to_p2wpkh,
)
from test_framework.descriptors import descsum_create from test_framework.descriptors import descsum_create
from test_framework.key import ECKey from test_framework.key import ECKey
from test_framework.messages import ( from test_framework.messages import (
@@ -31,7 +37,11 @@ from test_framework.script import (
) )
from test_framework.script_util import ( from test_framework.script_util import (
key_to_p2pk_script, key_to_p2pk_script,
key_to_p2pkh_script,
key_to_p2sh_p2wpkh_script,
key_to_p2wpkh_script, key_to_p2wpkh_script,
keyhash_to_p2pkh_script,
scripthash_to_p2sh_script,
) )
from test_framework.util import ( from test_framework.util import (
assert_equal, assert_equal,
@@ -209,12 +219,40 @@ class MiniWallet:
return txid return txid
def random_p2wpkh(): def getnewdestination(address_type='bech32'):
"""Generate a random P2WPKH scriptPubKey. Can be used when a random destination is needed, """Generate a random destination of the specified type and return the
but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC).""" corresponding public key, scriptPubKey and address. Supported types are
'legacy', 'p2sh-segwit' and 'bech32'. Can be used when a random
destination is needed, but no compiled wallet is available (e.g. as
replacement to the getnewaddress/getaddressinfo RPCs)."""
key = ECKey() key = ECKey()
key.generate() key.generate()
return key_to_p2wpkh_script(key.get_pubkey().get_bytes()) pubkey = key.get_pubkey().get_bytes()
if address_type == 'legacy':
scriptpubkey = key_to_p2pkh_script(pubkey)
address = key_to_p2pkh(pubkey)
elif address_type == 'p2sh-segwit':
scriptpubkey = key_to_p2sh_p2wpkh_script(pubkey)
address = key_to_p2sh_p2wpkh(pubkey)
elif address_type == 'bech32':
scriptpubkey = key_to_p2wpkh_script(pubkey)
address = key_to_p2wpkh(pubkey)
# TODO: also support bech32m (need to generate x-only-pubkey)
else:
assert False
return pubkey, scriptpubkey, address
def address_to_scriptpubkey(address):
"""Converts a given address to the corresponding output script (scriptPubKey)."""
payload, version = base58_to_byte(address)
if version == 111: # testnet pubkey hash
return keyhash_to_p2pkh_script(payload)
elif version == 196: # testnet script hash
return scripthash_to_p2sh_script(payload)
# TODO: also support other address formats
else:
assert False
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):