Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_blockfilters.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2019 The Bitcoin Core developers | # Copyright (c) 2019 The Bitcoin Core developers | ||||
# 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. | ||||
"""Tests NODE_COMPACT_FILTERS (BIP 157/158). | """Tests NODE_COMPACT_FILTERS (BIP 157/158). | ||||
Tests that a node configured with -blockfilterindex and -peerblockfilters can serve | Tests that a node configured with -blockfilterindex and -peerblockfilters can serve | ||||
cfheaders and cfcheckpts. | cfheaders and cfcheckpts. | ||||
""" | """ | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
FILTER_TYPE_BASIC, | FILTER_TYPE_BASIC, | ||||
hash256, | hash256, | ||||
msg_getcfcheckpt, | msg_getcfcheckpt, | ||||
msg_getcfheaders, | msg_getcfheaders, | ||||
msg_getcfilters, | |||||
ser_uint256, | ser_uint256, | ||||
uint256_from_str, | uint256_from_str, | ||||
) | ) | ||||
from test_framework.mininode import P2PInterface | from test_framework.mininode import P2PInterface | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
connect_nodes, | connect_nodes, | ||||
disconnect_nodes, | disconnect_nodes, | ||||
wait_until, | wait_until, | ||||
) | ) | ||||
class CFiltersClient(P2PInterface): | |||||
def __init__(self): | |||||
super().__init__() | |||||
# Store the cfilters received. | |||||
self.cfilters = [] | |||||
def pop_cfilters(self): | |||||
cfilters = self.cfilters | |||||
self.cfilters = [] | |||||
return cfilters | |||||
def on_cfilter(self, message): | |||||
"""Store cfilters received in a list.""" | |||||
self.cfilters.append(message) | |||||
class CompactFiltersTest(BitcoinTestFramework): | class CompactFiltersTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.rpc_timeout = 480 | self.rpc_timeout = 480 | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.extra_args = [ | self.extra_args = [ | ||||
["-blockfilterindex", "-peerblockfilters"], | ["-blockfilterindex", "-peerblockfilters"], | ||||
["-blockfilterindex"], | ["-blockfilterindex"], | ||||
] | ] | ||||
def run_test(self): | def run_test(self): | ||||
# Node 0 supports COMPACT_FILTERS, node 1 does not. | # Node 0 supports COMPACT_FILTERS, node 1 does not. | ||||
node0 = self.nodes[0].add_p2p_connection(P2PInterface()) | node0 = self.nodes[0].add_p2p_connection(CFiltersClient()) | ||||
node1 = self.nodes[1].add_p2p_connection(P2PInterface()) | node1 = self.nodes[1].add_p2p_connection(CFiltersClient()) | ||||
# Nodes 0 & 1 share the same first 999 blocks in the chain. | # Nodes 0 & 1 share the same first 999 blocks in the chain. | ||||
self.nodes[0].generate(999) | self.nodes[0].generate(999) | ||||
self.sync_blocks(timeout=600) | self.sync_blocks(timeout=600) | ||||
# Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting | # Stale blocks by disconnecting nodes 0 & 1, mining, then reconnecting | ||||
disconnect_nodes(self.nodes[0], self.nodes[1]) | disconnect_nodes(self.nodes[0], self.nodes[1]) | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
self.log.info("Check that peers can fetch cfheaders on active chain.") | self.log.info("Check that peers can fetch cfheaders on active chain.") | ||||
request = msg_getcfheaders( | request = msg_getcfheaders( | ||||
filter_type=FILTER_TYPE_BASIC, | filter_type=FILTER_TYPE_BASIC, | ||||
start_height=1, | start_height=1, | ||||
stop_hash=int(main_block_hash, 16) | stop_hash=int(main_block_hash, 16) | ||||
) | ) | ||||
node0.send_and_ping(request) | node0.send_and_ping(request) | ||||
response = node0.last_message['cfheaders'] | response = node0.last_message['cfheaders'] | ||||
assert_equal(len(response.hashes), 1000) | main_cfhashes = response.hashes | ||||
assert_equal(len(main_cfhashes), 1000) | |||||
assert_equal( | assert_equal( | ||||
compute_last_header(response.prev_header, response.hashes), | compute_last_header(response.prev_header, response.hashes), | ||||
int(main_cfcheckpt, 16) | int(main_cfcheckpt, 16) | ||||
) | ) | ||||
self.log.info("Check that peers can fetch cfheaders on stale chain.") | self.log.info("Check that peers can fetch cfheaders on stale chain.") | ||||
request = msg_getcfheaders( | request = msg_getcfheaders( | ||||
filter_type=FILTER_TYPE_BASIC, | filter_type=FILTER_TYPE_BASIC, | ||||
start_height=1, | start_height=1, | ||||
stop_hash=int(stale_block_hash, 16) | stop_hash=int(stale_block_hash, 16) | ||||
) | ) | ||||
node0.send_and_ping(request) | node0.send_and_ping(request) | ||||
response = node0.last_message['cfheaders'] | response = node0.last_message['cfheaders'] | ||||
assert_equal(len(response.hashes), 1000) | stale_cfhashes = response.hashes | ||||
assert_equal(len(stale_cfhashes), 1000) | |||||
assert_equal( | assert_equal( | ||||
compute_last_header(response.prev_header, response.hashes), | compute_last_header(response.prev_header, response.hashes), | ||||
int(stale_cfcheckpt, 16) | int(stale_cfcheckpt, 16) | ||||
) | ) | ||||
self.log.info("Check that peers can fetch cfilters.") | |||||
stop_hash = self.nodes[0].getblockhash(10) | |||||
request = msg_getcfilters( | |||||
filter_type=FILTER_TYPE_BASIC, | |||||
start_height=1, | |||||
stop_hash=int(stop_hash, 16) | |||||
) | |||||
node0.send_message(request) | |||||
node0.sync_with_ping() | |||||
response = node0.pop_cfilters() | |||||
assert_equal(len(response), 10) | |||||
self.log.info("Check that cfilter responses are correct.") | |||||
for cfilter, cfhash, height in zip( | |||||
response, main_cfhashes, range(1, 11)): | |||||
block_hash = self.nodes[0].getblockhash(height) | |||||
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) | |||||
assert_equal(cfilter.block_hash, int(block_hash, 16)) | |||||
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) | |||||
assert_equal(computed_cfhash, cfhash) | |||||
self.log.info("Check that peers can fetch cfilters for stale blocks.") | |||||
request = msg_getcfilters( | |||||
filter_type=FILTER_TYPE_BASIC, | |||||
start_height=1000, | |||||
stop_hash=int(stale_block_hash, 16) | |||||
) | |||||
node0.send_message(request) | |||||
node0.sync_with_ping() | |||||
response = node0.pop_cfilters() | |||||
assert_equal(len(response), 1) | |||||
cfilter = response[0] | |||||
assert_equal(cfilter.filter_type, FILTER_TYPE_BASIC) | |||||
assert_equal(cfilter.block_hash, int(stale_block_hash, 16)) | |||||
computed_cfhash = uint256_from_str(hash256(cfilter.filter_data)) | |||||
assert_equal(computed_cfhash, stale_cfhashes[999]) | |||||
self.log.info( | self.log.info( | ||||
"Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.") | "Requests to node 1 without NODE_COMPACT_FILTERS results in disconnection.") | ||||
requests = [ | requests = [ | ||||
msg_getcfcheckpt( | msg_getcfcheckpt( | ||||
filter_type=FILTER_TYPE_BASIC, | filter_type=FILTER_TYPE_BASIC, | ||||
stop_hash=int(main_block_hash, 16) | stop_hash=int(main_block_hash, 16) | ||||
), | ), | ||||
msg_getcfheaders( | msg_getcfheaders( | ||||
filter_type=FILTER_TYPE_BASIC, | filter_type=FILTER_TYPE_BASIC, | ||||
start_height=1000, | start_height=1000, | ||||
stop_hash=int(main_block_hash, 16) | stop_hash=int(main_block_hash, 16) | ||||
), | ), | ||||
msg_getcfilters( | |||||
filter_type=FILTER_TYPE_BASIC, | |||||
start_height=1000, | |||||
stop_hash=int(main_block_hash, 16) | |||||
), | |||||
] | ] | ||||
for request in requests: | for request in requests: | ||||
node1 = self.nodes[1].add_p2p_connection(P2PInterface()) | node1 = self.nodes[1].add_p2p_connection(P2PInterface()) | ||||
node1.send_message(request) | node1.send_message(request) | ||||
node1.wait_for_disconnect() | node1.wait_for_disconnect() | ||||
self.log.info("Check that invalid requests result in disconnection.") | self.log.info("Check that invalid requests result in disconnection.") | ||||
requests = [ | requests = [ | ||||
# Requesting too many filters results in disconnection. | |||||
msg_getcfilters( | |||||
filter_type=FILTER_TYPE_BASIC, | |||||
start_height=0, | |||||
stop_hash=int(main_block_hash, 16) | |||||
), | |||||
# Requesting too many filter headers results in disconnection. | # Requesting too many filter headers results in disconnection. | ||||
msg_getcfheaders( | msg_getcfheaders( | ||||
filter_type=FILTER_TYPE_BASIC, | filter_type=FILTER_TYPE_BASIC, | ||||
start_height=0, | start_height=0, | ||||
stop_hash=int(tip_hash, 16) | stop_hash=int(tip_hash, 16) | ||||
), | ), | ||||
# Requesting unknown filter type results in disconnection. | # Requesting unknown filter type results in disconnection. | ||||
msg_getcfcheckpt( | msg_getcfcheckpt( | ||||
Show All 25 Lines |