Skip to content

Commit 7e1fc03

Browse files
author
MarcoFalke
committed
Merge #18334: test: Add basic test for BIP 37
fa15699 test: Add basic test for BIP 37 (MarcoFalke) Pull request description: This does not add full coverage, but should be a good start and can be extended in the future. Currently, none of the BIP 37 p2p code has test coverage. ACKs for top commit: practicalswift: Code review ACK fa15699 -- more testing coverage is better than less testing coverage Tree-SHA512: d52e8be79240dffb769105c087ae0ae9305d599282546e4ca7379c4c7add2dbcd668265b46670aa07c357638044cf0f61a6fab7dba8971dd0f80c8f99768686e
2 parents 6a11d9e + fa15699 commit 7e1fc03

File tree

4 files changed

+161
-4
lines changed

4 files changed

+161
-4
lines changed

test/functional/p2p_filter.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2020 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""
6+
Test BIP 37
7+
"""
8+
9+
from test_framework.messages import (
10+
MSG_BLOCK,
11+
MSG_FILTERED_BLOCK,
12+
msg_getdata,
13+
msg_filterload,
14+
)
15+
from test_framework.mininode import (
16+
P2PInterface,
17+
mininode_lock,
18+
)
19+
from test_framework.test_framework import BitcoinTestFramework
20+
21+
22+
class FilterNode(P2PInterface):
23+
# This is a P2SH watch-only wallet
24+
watch_script_pubkey = 'a914ffffffffffffffffffffffffffffffffffffffff87'
25+
# The initial filter (n=10, fp=0.000001) with just the above scriptPubKey added
26+
watch_filter_init = msg_filterload(
27+
data=
28+
b'@\x00\x08\x00\x80\x00\x00 \x00\xc0\x00 \x04\x00\x08$\x00\x04\x80\x00\x00 \x00\x00\x00\x00\x80\x00\x00@\x00\x02@ \x00',
29+
nHashFuncs=19,
30+
nTweak=0,
31+
nFlags=1,
32+
)
33+
34+
def on_inv(self, message):
35+
want = msg_getdata()
36+
for i in message.inv:
37+
# inv messages can only contain TX or BLOCK, so translate BLOCK to FILTERED_BLOCK
38+
if i.type == MSG_BLOCK:
39+
i.type = MSG_FILTERED_BLOCK
40+
want.inv.append(i)
41+
if len(want.inv):
42+
self.send_message(want)
43+
44+
def on_merkleblock(self, message):
45+
self.merkleblock_received = True
46+
47+
def on_tx(self, message):
48+
self.tx_received = True
49+
50+
51+
class FilterTest(BitcoinTestFramework):
52+
def set_test_params(self):
53+
self.setup_clean_chain = False
54+
self.num_nodes = 1
55+
self.extra_args = [[
56+
'-peerbloomfilters',
57+
'[email protected]', # immediate tx relay
58+
]]
59+
60+
def skip_test_if_missing_module(self):
61+
self.skip_if_no_wallet()
62+
63+
def run_test(self):
64+
self.log.info('Add filtered P2P connection to the node')
65+
filter_node = self.nodes[0].add_p2p_connection(FilterNode())
66+
filter_node.send_message(filter_node.watch_filter_init)
67+
filter_node.sync_with_ping()
68+
filter_address = self.nodes[0].decodescript(filter_node.watch_script_pubkey)['addresses'][0]
69+
70+
self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
71+
filter_node.merkleblock_received = False
72+
block_hash = self.nodes[0].generatetoaddress(1, filter_address)[0]
73+
txid = self.nodes[0].getblock(block_hash)['tx'][0]
74+
filter_node.wait_for_tx(txid)
75+
assert filter_node.merkleblock_received
76+
77+
self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
78+
with mininode_lock:
79+
filter_node.last_message.pop("merkleblock", None)
80+
filter_node.tx_received = False
81+
self.nodes[0].generatetoaddress(1, self.nodes[0].getnewaddress())
82+
filter_node.wait_for_merkleblock()
83+
assert not filter_node.tx_received
84+
85+
self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
86+
filter_node.merkleblock_received = False
87+
filter_node.tx_received = False
88+
self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 90)
89+
filter_node.sync_with_ping()
90+
filter_node.sync_with_ping()
91+
assert not filter_node.merkleblock_received
92+
assert not filter_node.tx_received
93+
94+
self.log.info('Check that we receive a tx in reply to a mempool msg if the filter matches a mempool tx')
95+
filter_node.merkleblock_received = False
96+
txid = self.nodes[0].sendtoaddress(filter_address, 90)
97+
filter_node.wait_for_tx(txid)
98+
assert not filter_node.merkleblock_received
99+
100+
101+
if __name__ == '__main__':
102+
FilterTest().main()

test/functional/test_framework/messages.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151

5252
MSG_TX = 1
5353
MSG_BLOCK = 2
54+
MSG_FILTERED_BLOCK = 3
5455
MSG_WITNESS_FLAG = 1 << 30
5556
MSG_TYPE_MASK = 0xffffffff >> 2
5657

@@ -225,10 +226,11 @@ class CInv:
225226

226227
typemap = {
227228
0: "Error",
228-
1: "TX",
229-
2: "Block",
230-
1|MSG_WITNESS_FLAG: "WitnessTx",
231-
2|MSG_WITNESS_FLAG : "WitnessBlock",
229+
MSG_TX: "TX",
230+
MSG_BLOCK: "Block",
231+
MSG_TX | MSG_WITNESS_FLAG: "WitnessTx",
232+
MSG_BLOCK | MSG_WITNESS_FLAG: "WitnessBlock",
233+
MSG_FILTERED_BLOCK: "filtered Block",
232234
4: "CompactBlock"
233235
}
234236

@@ -1318,6 +1320,41 @@ def __repr__(self):
13181320
return "msg_headers(headers=%s)" % repr(self.headers)
13191321

13201322

1323+
class msg_merkleblock:
1324+
command = b"merkleblock"
1325+
1326+
def deserialize(self, f):
1327+
pass # Placeholder for now
1328+
1329+
1330+
class msg_filterload:
1331+
__slots__ = ("data", "nHashFuncs", "nTweak", "nFlags")
1332+
command = b"filterload"
1333+
1334+
def __init__(self, data=b'00', nHashFuncs=0, nTweak=0, nFlags=0):
1335+
self.data = data
1336+
self.nHashFuncs = nHashFuncs
1337+
self.nTweak = nTweak
1338+
self.nFlags = nFlags
1339+
1340+
def deserialize(self, f):
1341+
self.data = deser_string(f)
1342+
self.nHashFuncs = struct.unpack("<I", f.read(4))[0]
1343+
self.nTweak = struct.unpack("<I", f.read(4))[0]
1344+
self.nFlags = struct.unpack("<B", f.read(1))[0]
1345+
1346+
def serialize(self):
1347+
r = b""
1348+
r += ser_string(self.data)
1349+
r += struct.pack("<I", self.nHashFuncs)
1350+
r += struct.pack("<I", self.nTweak)
1351+
r += struct.pack("<B", self.nFlags)
1352+
return r
1353+
1354+
def __repr__(self):
1355+
return "msg_filterload(data={}, nHashFuncs={}, nTweak={}, nFlags={})".format(
1356+
self.data, self.nHashFuncs, self.nTweak, self.nFlags)
1357+
13211358

13221359
class msg_feefilter:
13231360
__slots__ = ("feerate",)

test/functional/test_framework/mininode.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
msg_blocktxn,
3131
msg_cmpctblock,
3232
msg_feefilter,
33+
msg_filterload,
3334
msg_getaddr,
3435
msg_getblocks,
3536
msg_getblocktxn,
@@ -38,6 +39,7 @@
3839
msg_headers,
3940
msg_inv,
4041
msg_mempool,
42+
msg_merkleblock,
4143
msg_notfound,
4244
msg_ping,
4345
msg_pong,
@@ -62,6 +64,7 @@
6264
b"blocktxn": msg_blocktxn,
6365
b"cmpctblock": msg_cmpctblock,
6466
b"feefilter": msg_feefilter,
67+
b"filterload": msg_filterload,
6568
b"getaddr": msg_getaddr,
6669
b"getblocks": msg_getblocks,
6770
b"getblocktxn": msg_getblocktxn,
@@ -70,6 +73,7 @@
7073
b"headers": msg_headers,
7174
b"inv": msg_inv,
7275
b"mempool": msg_mempool,
76+
b"merkleblock": msg_merkleblock,
7377
b"notfound": msg_notfound,
7478
b"ping": msg_ping,
7579
b"pong": msg_pong,
@@ -318,13 +322,15 @@ def on_block(self, message): pass
318322
def on_blocktxn(self, message): pass
319323
def on_cmpctblock(self, message): pass
320324
def on_feefilter(self, message): pass
325+
def on_filterload(self, message): pass
321326
def on_getaddr(self, message): pass
322327
def on_getblocks(self, message): pass
323328
def on_getblocktxn(self, message): pass
324329
def on_getdata(self, message): pass
325330
def on_getheaders(self, message): pass
326331
def on_headers(self, message): pass
327332
def on_mempool(self, message): pass
333+
def on_merkleblock(self, message): pass
328334
def on_notfound(self, message): pass
329335
def on_pong(self, message): pass
330336
def on_reject(self, message): pass
@@ -385,6 +391,17 @@ def test_function():
385391

386392
wait_until(test_function, timeout=timeout, lock=mininode_lock)
387393

394+
def wait_for_merkleblock(self, timeout=60):
395+
def test_function():
396+
assert self.is_connected
397+
last_filtered_block = self.last_message.get('merkleblock')
398+
if not last_filtered_block:
399+
return False
400+
# TODO change this method to take a hash value and only return true if the correct block has been received
401+
return True
402+
403+
wait_until(test_function, timeout=timeout, lock=mininode_lock)
404+
388405
def wait_for_getdata(self, timeout=60):
389406
"""Waits for a getdata message.
390407

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
'rpc_net.py',
148148
'wallet_keypool.py',
149149
'p2p_mempool.py',
150+
'p2p_filter.py',
150151
'rpc_setban.py',
151152
'p2p_blocksonly.py',
152153
'mining_prioritisetransaction.py',

0 commit comments

Comments
 (0)