Skip to content

Commit 33e31f8

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#23079: test: use MiniWallet for p2p_filter.py
cfdb6ba test: use MiniWallet for p2p_filter.py (Sebastian Falbesoner) 6fc2cd3 test: introduce helper to create random P2WPKH scriptPubKeys (Sebastian Falbesoner) aa26797 test: MiniWallet: add `send_to` method to create arbitrary txouts (Sebastian Falbesoner) Pull request description: This PR enables one more of the non-wallet functional tests (p2p_filter.py) to be run even with the Bitcoin Core wallet disabled by using the MiniWallet instead, as proposed in #20078. For this purpose, a MiniWallet method `send_to` is introduced first, which allows to create arbitrary outputs (scriptPubKey/amount). Note that the implementation for this is already present in feature_rbf.py (recently added in PR #22998), i.e. it is simply moved to the MiniWallet interface. ACKs for top commit: laanwj: Code review ACK cfdb6ba Tree-SHA512: 13b063631f0d7af065b7757cfe8b47c9be6cb9850ac5db2968a2bba4f5a18cdc9f89173a9b03971545356225082042f5fdbe49d3036027d18e8b7eb042d04f5e
2 parents 8daecf4 + cfdb6ba commit 33e31f8

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

test/functional/feature_rbf.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from test_framework.test_framework import BitcoinTestFramework
2020
from test_framework.util import (
2121
assert_equal,
22-
assert_greater_than,
2322
assert_raises_rpc_error,
2423
)
2524
from test_framework.script_util import (
@@ -96,23 +95,10 @@ def run_test(self):
9695
def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
9796
"""Create a txout with a given amount and scriptPubKey
9897
99-
Assumes that MiniWallet has enough funds to cover the amount and the fixed fee
100-
(from it's internal utxos, the one with the largest value is taken).
101-
10298
confirmed - txouts created will be confirmed in the blockchain;
10399
unconfirmed otherwise.
104100
"""
105-
# MiniWallet only supports sweeping utxos to its own internal scriptPubKey, so in
106-
# order to create an output with arbitrary amount/scriptPubKey, we have to add it
107-
# manually after calling the create_self_transfer method. The MiniWallet output's
108-
# nValue has to be adapted accordingly (amount and fee deduction). To keep things
109-
# simple, we use a fixed fee of 1000 Satoshis here.
110-
fee = 1000
111-
tx = self.wallet.create_self_transfer(from_node=node, fee_rate=0, mempool_valid=False)['tx']
112-
assert_greater_than(tx.vout[0].nValue, amount + fee)
113-
tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet
114-
tx.vout.append(CTxOut(amount, scriptPubKey)) # desired output -> to be returned
115-
txid = self.wallet.sendrawtransaction(from_node=node, tx_hex=tx.serialize().hex())
101+
txid, n = self.wallet.send_to(from_node=node, scriptPubKey=scriptPubKey, amount=amount)
116102

117103
# If requested, ensure txouts are confirmed.
118104
if confirmed:
@@ -125,7 +111,7 @@ def make_utxo(self, node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRI
125111
assert new_size < mempool_size
126112
mempool_size = new_size
127113

128-
return COutPoint(int(txid, 16), 1)
114+
return COutPoint(int(txid, 16), n)
129115

130116
def test_simple_doublespend(self):
131117
"""Simple doublespend"""

test/functional/p2p_filter.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from test_framework.messages import (
1010
CInv,
11+
COIN,
1112
MAX_BLOOM_FILTER_SIZE,
1213
MAX_BLOOM_HASH_FUNCS,
1314
MSG_BLOCK,
@@ -28,11 +29,15 @@
2829
)
2930
from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE
3031
from test_framework.test_framework import BitcoinTestFramework
32+
from test_framework.wallet import (
33+
MiniWallet,
34+
random_p2wpkh,
35+
)
3136

3237

3338
class P2PBloomFilter(P2PInterface):
3439
# This is a P2SH watch-only wallet
35-
watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87'
40+
watch_script_pubkey = bytes.fromhex('a914ffffffffffffffffffffffffffffffffffffffff87')
3641
# The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added
3742
watch_filter_init = msg_filterload(
3843
data=
@@ -93,8 +98,9 @@ def set_test_params(self):
9398
'[email protected]', # immediate tx relay
9499
]]
95100

96-
def skip_test_if_missing_module(self):
97-
self.skip_if_no_wallet()
101+
def generatetoscriptpubkey(self, scriptpubkey):
102+
"""Helper to generate a single block to the given scriptPubKey."""
103+
return self.generatetodescriptor(self.nodes[0], 1, f'raw({scriptpubkey.hex()})')[0]
98104

99105
def test_size_limits(self, filter_peer):
100106
self.log.info('Check that too large filter is rejected')
@@ -130,8 +136,7 @@ def test_msg_mempool(self):
130136
filter_peer = P2PBloomFilter()
131137

132138
self.log.debug("Create a tx relevant to the peer before connecting")
133-
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
134-
txid = self.nodes[0].sendtoaddress(filter_address, 90)
139+
txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
135140

136141
self.log.debug("Send a mempool msg after connecting and check that the tx is received")
137142
self.nodes[0].add_p2p_connection(filter_peer)
@@ -142,8 +147,7 @@ def test_msg_mempool(self):
142147
def test_frelay_false(self, filter_peer):
143148
self.log.info("Check that a node with fRelay set to false does not receive invs until the filter is set")
144149
filter_peer.tx_received = False
145-
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
146-
self.nodes[0].sendtoaddress(filter_address, 90)
150+
self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
147151
# Sync to make sure the reason filter_peer doesn't receive the tx is not p2p delays
148152
filter_peer.sync_with_ping()
149153
assert not filter_peer.tx_received
@@ -156,45 +160,44 @@ def test_filter(self, filter_peer):
156160
filter_peer.send_and_ping(filter_peer.watch_filter_init)
157161
# If fRelay is not already True, sending filterload sets it to True
158162
assert self.nodes[0].getpeerinfo()[0]['relaytxes']
159-
filter_address = self.nodes[0].decodescript(filter_peer.watch_script_pubkey)['address']
160163

161164
self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
162-
block_hash = self.generatetoaddress(self.nodes[0], 1, filter_address)[0]
165+
block_hash = self.generatetoscriptpubkey(filter_peer.watch_script_pubkey)
163166
txid = self.nodes[0].getblock(block_hash)['tx'][0]
164167
filter_peer.wait_for_merkleblock(block_hash)
165168
filter_peer.wait_for_tx(txid)
166169

167170
self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
168171
filter_peer.tx_received = False
169-
block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
172+
block_hash = self.generatetoscriptpubkey(random_p2wpkh())
170173
filter_peer.wait_for_merkleblock(block_hash)
171174
assert not filter_peer.tx_received
172175

173176
self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
174177
filter_peer.merkleblock_received = False
175178
filter_peer.tx_received = False
176-
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90)
179+
self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN)
177180
filter_peer.sync_send_with_ping()
178181
assert not filter_peer.merkleblock_received
179182
assert not filter_peer.tx_received
180183

181184
self.log.info('Check that we receive a tx if the filter matches a mempool tx')
182185
filter_peer.merkleblock_received = False
183-
txid = self.nodes[0].sendtoaddress(filter_address, 90)
186+
txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
184187
filter_peer.wait_for_tx(txid)
185188
assert not filter_peer.merkleblock_received
186189

187190
self.log.info('Check that after deleting filter all txs get relayed again')
188191
filter_peer.send_and_ping(msg_filterclear())
189192
for _ in range(5):
190-
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 7)
193+
txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN)
191194
filter_peer.wait_for_tx(txid)
192195

193196
self.log.info('Check that request for filtered blocks is ignored if no filter is set')
194197
filter_peer.merkleblock_received = False
195198
filter_peer.tx_received = False
196199
with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
197-
block_hash = self.generatetoaddress(self.nodes[0], 1, self.nodes[0].getnewaddress())[0]
200+
block_hash = self.generatetoscriptpubkey(random_p2wpkh())
198201
filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
199202
filter_peer.sync_with_ping()
200203
assert not filter_peer.merkleblock_received
@@ -210,6 +213,9 @@ def test_filter(self, filter_peer):
210213
self.nodes[0].disconnect_p2ps()
211214

212215
def run_test(self):
216+
self.wallet = MiniWallet(self.nodes[0])
217+
self.wallet.rescan_utxos()
218+
213219
filter_peer = self.nodes[0].add_p2p_connection(P2PBloomFilter())
214220
self.log.info('Test filter size limits')
215221
self.test_size_limits(filter_peer)

test/functional/test_framework/wallet.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
OP_NOP,
2929
SIGHASH_ALL,
3030
)
31+
from test_framework.script_util import key_to_p2wpkh_script
3132
from test_framework.util import (
3233
assert_equal,
3334
assert_greater_than_or_equal,
@@ -146,6 +147,25 @@ def send_self_transfer(self, **kwargs):
146147
self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
147148
return tx
148149

150+
def send_to(self, *, from_node, scriptPubKey, amount, fee=1000):
151+
"""
152+
Create and send a tx with an output to a given scriptPubKey/amount,
153+
plus a change output to our internal address. To keep things simple, a
154+
fixed fee given in Satoshi is used.
155+
156+
Note that this method fails if there is no single internal utxo
157+
available that can cover the cost for the amount and the fixed fee
158+
(the utxo with the largest value is taken).
159+
160+
Returns a tuple (txid, n) referring to the created external utxo outpoint.
161+
"""
162+
tx = self.create_self_transfer(from_node=from_node, fee_rate=0, mempool_valid=False)['tx']
163+
assert_greater_than_or_equal(tx.vout[0].nValue, amount + fee)
164+
tx.vout[0].nValue -= (amount + fee) # change output -> MiniWallet
165+
tx.vout.append(CTxOut(amount, scriptPubKey)) # arbitrary output -> to be returned
166+
txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex())
167+
return txid, 1
168+
149169
def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
150170
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
151171
self._utxos = sorted(self._utxos, key=lambda k: k['value'])
@@ -188,6 +208,14 @@ def sendrawtransaction(self, *, from_node, tx_hex):
188208
return txid
189209

190210

211+
def random_p2wpkh():
212+
"""Generate a random P2WPKH scriptPubKey. Can be used when a random destination is needed,
213+
but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC)."""
214+
key = ECKey()
215+
key.generate()
216+
return key_to_p2wpkh_script(key.get_pubkey().get_bytes())
217+
218+
191219
def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE):
192220
"""Build a transaction that spends parent_txid.vout[n] and produces one output with
193221
amount = parent_value with a fee deducted.

0 commit comments

Comments
 (0)