Skip to content

Commit 2308385

Browse files
jimpojnewbery
authored andcommitted
[test] Add test for cfcheckpt
1 parent f9e00bb commit 2308385

File tree

4 files changed

+187
-0
lines changed

4 files changed

+187
-0
lines changed

test/functional/p2p_blockfilters.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019 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+
"""Tests NODE_COMPACT_FILTERS (BIP 157/158).
6+
7+
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve
8+
cfcheckpts.
9+
"""
10+
11+
from test_framework.messages import (
12+
FILTER_TYPE_BASIC,
13+
msg_getcfcheckpt,
14+
)
15+
from test_framework.mininode import P2PInterface
16+
from test_framework.test_framework import BitcoinTestFramework
17+
from test_framework.util import (
18+
assert_equal,
19+
connect_nodes,
20+
disconnect_nodes,
21+
wait_until,
22+
)
23+
24+
class CompactFiltersTest(BitcoinTestFramework):
25+
def set_test_params(self):
26+
self.setup_clean_chain = True
27+
self.rpc_timeout = 480
28+
self.num_nodes = 2
29+
self.extra_args = [
30+
["-blockfilterindex", "-peerblockfilters"],
31+
["-blockfilterindex"],
32+
]
33+
34+
def run_test(self):
35+
# Node 0 supports COMPACT_FILTERS, node 1 does not.
36+
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
37+
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
38+
39+
# Nodes 0 & 1 share the same first 999 blocks in the chain.
40+
self.nodes[0].generate(999)
41+
self.sync_blocks(timeout=600)
42+
43+
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting
44+
disconnect_nodes(self.nodes[0], 1)
45+
46+
self.nodes[0].generate(1)
47+
wait_until(lambda: self.nodes[0].getblockcount() == 1000)
48+
stale_block_hash = self.nodes[0].getblockhash(1000)
49+
50+
self.nodes[1].generate(1001)
51+
wait_until(lambda: self.nodes[1].getblockcount() == 2000)
52+
53+
self.log.info("get cfcheckpt on chain to be re-orged out.")
54+
request = msg_getcfcheckpt(
55+
filter_type=FILTER_TYPE_BASIC,
56+
stop_hash=int(stale_block_hash, 16)
57+
)
58+
node0.send_and_ping(message=request)
59+
response = node0.last_message['cfcheckpt']
60+
assert_equal(response.filter_type, request.filter_type)
61+
assert_equal(response.stop_hash, request.stop_hash)
62+
assert_equal(len(response.headers), 1)
63+
64+
self.log.info("Reorg node 0 to a new chain.")
65+
connect_nodes(self.nodes[0], 1)
66+
self.sync_blocks(timeout=600)
67+
68+
main_block_hash = self.nodes[0].getblockhash(1000)
69+
assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize"
70+
71+
self.log.info("Check that peers can fetch cfcheckpt on active chain.")
72+
tip_hash = self.nodes[0].getbestblockhash()
73+
request = msg_getcfcheckpt(
74+
filter_type=FILTER_TYPE_BASIC,
75+
stop_hash=int(tip_hash, 16)
76+
)
77+
node0.send_and_ping(request)
78+
response = node0.last_message['cfcheckpt']
79+
assert_equal(response.filter_type, request.filter_type)
80+
assert_equal(response.stop_hash, request.stop_hash)
81+
82+
main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header']
83+
tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header']
84+
assert_equal(
85+
response.headers,
86+
[int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)]
87+
)
88+
89+
self.log.info("Check that peers can fetch cfcheckpt on stale chain.")
90+
request = msg_getcfcheckpt(
91+
filter_type=FILTER_TYPE_BASIC,
92+
stop_hash=int(stale_block_hash, 16)
93+
)
94+
node0.send_and_ping(request)
95+
response = node0.last_message['cfcheckpt']
96+
97+
stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header']
98+
assert_equal(
99+
response.headers,
100+
[int(header, 16) for header in (stale_cfcheckpt,)]
101+
)
102+
103+
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
104+
requests = [
105+
msg_getcfcheckpt(
106+
filter_type=FILTER_TYPE_BASIC,
107+
stop_hash=int(main_block_hash, 16)
108+
),
109+
]
110+
for request in requests:
111+
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
112+
node1.send_message(request)
113+
node1.wait_for_disconnect()
114+
115+
self.log.info("Check that invalid requests result in disconnection.")
116+
requests = [
117+
# Requesting unknown filter type results in disconnection.
118+
msg_getcfcheckpt(
119+
filter_type=255,
120+
stop_hash=int(main_block_hash, 16)
121+
),
122+
# Requesting unknown hash results in disconnection.
123+
msg_getcfcheckpt(
124+
filter_type=FILTER_TYPE_BASIC,
125+
stop_hash=123456789,
126+
),
127+
]
128+
for request in requests:
129+
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
130+
node0.send_message(request)
131+
node0.wait_for_disconnect()
132+
133+
if __name__ == '__main__':
134+
CompactFiltersTest().main()

test/functional/test_framework/messages.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
MSG_WITNESS_FLAG = 1 << 30
5858
MSG_TYPE_MASK = 0xffffffff >> 2
5959

60+
FILTER_TYPE_BASIC = 0
61+
6062
# Serialization/deserialization tools
6163
def sha256(s):
6264
return hashlib.new('sha256', s).digest()
@@ -1512,3 +1514,50 @@ class msg_no_witness_blocktxn(msg_blocktxn):
15121514

15131515
def serialize(self):
15141516
return self.block_transactions.serialize(with_witness=False)
1517+
1518+
class msg_getcfcheckpt:
1519+
__slots__ = ("filter_type", "stop_hash")
1520+
msgtype = b"getcfcheckpt"
1521+
1522+
def __init__(self, filter_type, stop_hash):
1523+
self.filter_type = filter_type
1524+
self.stop_hash = stop_hash
1525+
1526+
def deserialize(self, f):
1527+
self.filter_type = struct.unpack("<B", f.read(1))[0]
1528+
self.stop_hash = deser_uint256(f)
1529+
1530+
def serialize(self):
1531+
r = b""
1532+
r += struct.pack("<B", self.filter_type)
1533+
r += ser_uint256(self.stop_hash)
1534+
return r
1535+
1536+
def __repr__(self):
1537+
return "msg_getcfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
1538+
self.filter_type, self.stop_hash)
1539+
1540+
class msg_cfcheckpt:
1541+
__slots__ = ("filter_type", "stop_hash", "headers")
1542+
msgtype = b"cfcheckpt"
1543+
1544+
def __init__(self, filter_type=None, stop_hash=None, headers=None):
1545+
self.filter_type = filter_type
1546+
self.stop_hash = stop_hash
1547+
self.headers = headers
1548+
1549+
def deserialize(self, f):
1550+
self.filter_type = struct.unpack("<B", f.read(1))[0]
1551+
self.stop_hash = deser_uint256(f)
1552+
self.headers = deser_uint256_vector(f)
1553+
1554+
def serialize(self):
1555+
r = b""
1556+
r += struct.pack("<B", self.filter_type)
1557+
r += ser_uint256(self.stop_hash)
1558+
r += ser_uint256_vector(self.headers)
1559+
return r
1560+
1561+
def __repr__(self):
1562+
return "msg_cfcheckpt(filter_type={:#x}, stop_hash={:x})".format(
1563+
self.filter_type, self.stop_hash)

test/functional/test_framework/mininode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
msg_block,
3232
MSG_BLOCK,
3333
msg_blocktxn,
34+
msg_cfcheckpt,
3435
msg_cmpctblock,
3536
msg_feefilter,
3637
msg_filteradd,
@@ -67,6 +68,7 @@
6768
b"addr": msg_addr,
6869
b"block": msg_block,
6970
b"blocktxn": msg_blocktxn,
71+
b"cfcheckpt": msg_cfcheckpt,
7072
b"cmpctblock": msg_cmpctblock,
7173
b"feefilter": msg_feefilter,
7274
b"filteradd": msg_filteradd,
@@ -328,6 +330,7 @@ def on_close(self):
328330
def on_addr(self, message): pass
329331
def on_block(self, message): pass
330332
def on_blocktxn(self, message): pass
333+
def on_cfcheckpt(self, message): pass
331334
def on_cmpctblock(self, message): pass
332335
def on_feefilter(self, message): pass
333336
def on_filteradd(self, message): pass

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@
225225
'feature_loadblock.py',
226226
'p2p_dos_header_tree.py',
227227
'p2p_unrequested_blocks.py',
228+
'p2p_blockfilters.py',
228229
'feature_includeconf.py',
229230
'feature_asmap.py',
230231
'mempool_unbroadcast.py',

0 commit comments

Comments
 (0)