Skip to content

Commit 2a15f99

Browse files
jimpojnewbery
authored andcommitted
[test] Add test for cfcheckpt
1 parent 5880a0a commit 2a15f99

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

test/functional/p2p_cfilters.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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 -peercfilters 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", "-peercfilters"],
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_message(request)
59+
node0.sync_with_ping(timeout=5)
60+
response = node0.last_message['cfcheckpt']
61+
assert_equal(response.filter_type, request.filter_type)
62+
assert_equal(response.stop_hash, request.stop_hash)
63+
assert_equal(len(response.headers), 1)
64+
65+
self.log.info("Reorg node 0 to a new chain.")
66+
connect_nodes(self.nodes[0], 1)
67+
self.sync_blocks(timeout=600)
68+
69+
main_block_hash = self.nodes[0].getblockhash(1000)
70+
assert main_block_hash != stale_block_hash, "node 0 chain did not reorganize"
71+
72+
self.log.info("Check that peers can fetch cfcheckpt on active chain.")
73+
tip_hash = self.nodes[0].getbestblockhash()
74+
request = msg_getcfcheckpt(
75+
filter_type=FILTER_TYPE_BASIC,
76+
stop_hash=int(tip_hash, 16)
77+
)
78+
node0.send_message(request)
79+
node0.sync_with_ping()
80+
response = node0.last_message['cfcheckpt']
81+
assert_equal(response.filter_type, request.filter_type)
82+
assert_equal(response.stop_hash, request.stop_hash)
83+
84+
main_cfcheckpt = self.nodes[0].getblockfilter(main_block_hash, 'basic')['header']
85+
tip_cfcheckpt = self.nodes[0].getblockfilter(tip_hash, 'basic')['header']
86+
assert_equal(
87+
response.headers,
88+
[int(header, 16) for header in (main_cfcheckpt, tip_cfcheckpt)]
89+
)
90+
91+
self.log.info("Check that peers can fetch cfcheckpt on stale chain.")
92+
request = msg_getcfcheckpt(
93+
filter_type=FILTER_TYPE_BASIC,
94+
stop_hash=int(stale_block_hash, 16)
95+
)
96+
node0.send_message(request)
97+
node0.sync_with_ping()
98+
response = node0.last_message['cfcheckpt']
99+
100+
stale_cfcheckpt = self.nodes[0].getblockfilter(stale_block_hash, 'basic')['header']
101+
assert_equal(
102+
response.headers,
103+
[int(header, 16) for header in (stale_cfcheckpt,)]
104+
)
105+
106+
self.log.info("Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.")
107+
requests = [
108+
msg_getcfcheckpt(
109+
filter_type=FILTER_TYPE_BASIC,
110+
stop_hash=int(main_block_hash, 16)
111+
),
112+
]
113+
for request in requests:
114+
node1 = self.nodes[1].add_p2p_connection(P2PInterface())
115+
node1.send_message(request)
116+
node1.wait_for_disconnect()
117+
118+
self.log.info("Check that invalid requests result in disconnection.")
119+
requests = [
120+
# Requesting unknown filter type results in disconnection.
121+
msg_getcfcheckpt(
122+
filter_type=255,
123+
stop_hash=int(main_block_hash, 16)
124+
),
125+
# Requesting unknown hash results in disconnection.
126+
msg_getcfcheckpt(
127+
filter_type=FILTER_TYPE_BASIC,
128+
stop_hash=123456789,
129+
),
130+
]
131+
for request in requests:
132+
node0 = self.nodes[0].add_p2p_connection(P2PInterface())
133+
node0.send_message(request)
134+
node0.wait_for_disconnect()
135+
136+
if __name__ == '__main__':
137+
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_cfilters.py',
228229
'feature_includeconf.py',
229230
'feature_asmap.py',
230231
'mempool_unbroadcast.py',

0 commit comments

Comments
 (0)