Changeset View
Changeset View
Standalone View
Standalone View
test/functional/p2p_dos_header_tree.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. | ||||
"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" | """Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat""" | ||||
import os | import os | ||||
from test_framework.messages import CBlockHeader, FromHex | from test_framework.messages import CBlockHeader, FromHex | ||||
from test_framework.p2p import P2PInterface, msg_headers | from test_framework.p2p import P2PInterface, msg_headers | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
class RejectLowDifficultyHeadersTest(BitcoinTestFramework): | class RejectLowDifficultyHeadersTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint | self.chain = "testnet3" # Use testnet chain because it has an early checkpoint | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
parser.add_argument( | parser.add_argument( | ||||
'--datafile', | "--datafile", | ||||
default='data/blockheader_testnet3.hex', | default="data/blockheader_testnet3.hex", | ||||
help='Test data file (default: %(default)s)', | help="Test data file (default: %(default)s)", | ||||
) | ) | ||||
def run_test(self): | def run_test(self): | ||||
self.log.info("Read headers data") | self.log.info("Read headers data") | ||||
self.headers_file_path = os.path.join( | self.headers_file_path = os.path.join( | ||||
os.path.dirname( | os.path.dirname(os.path.realpath(__file__)), self.options.datafile | ||||
os.path.realpath(__file__)), | ) | ||||
self.options.datafile) | with open(self.headers_file_path, encoding="utf-8") as headers_data: | ||||
with open(self.headers_file_path, encoding='utf-8') as headers_data: | |||||
h_lines = [line.strip() for line in headers_data.readlines()] | h_lines = [line.strip() for line in headers_data.readlines()] | ||||
# The headers data is taken from testnet3 for early blocks from genesis until the first checkpoint. There are | # The headers data is taken from testnet3 for early blocks from genesis until the first checkpoint. There are | ||||
# two headers with valid POW at height 1 and 2, forking off from | # two headers with valid POW at height 1 and 2, forking off from | ||||
# genesis. They are indicated by the FORK_PREFIX. | # genesis. They are indicated by the FORK_PREFIX. | ||||
FORK_PREFIX = 'fork:' | FORK_PREFIX = "fork:" | ||||
self.headers = [ | self.headers = [line for line in h_lines if not line.startswith(FORK_PREFIX)] | ||||
line for line in h_lines if not line.startswith(FORK_PREFIX)] | self.headers_fork = [ | ||||
self.headers_fork = [line[len(FORK_PREFIX):] | line[len(FORK_PREFIX) :] for line in h_lines if line.startswith(FORK_PREFIX) | ||||
for line in h_lines if line.startswith(FORK_PREFIX)] | ] | ||||
self.headers = [FromHex(CBlockHeader(), h) for h in self.headers] | self.headers = [FromHex(CBlockHeader(), h) for h in self.headers] | ||||
self.headers_fork = [FromHex(CBlockHeader(), h) | self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork] | ||||
for h in self.headers_fork] | |||||
self.log.info( | self.log.info( | ||||
"Feed all non-fork headers, including and up to the first checkpoint") | "Feed all non-fork headers, including and up to the first checkpoint" | ||||
) | |||||
peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) | peer_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) | ||||
peer_checkpoint.send_and_ping(msg_headers(self.headers)) | peer_checkpoint.send_and_ping(msg_headers(self.headers)) | ||||
assert { | assert { | ||||
'height': 546, | "height": 546, | ||||
'hash': '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70', | "hash": "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70", | ||||
'branchlen': 546, | "branchlen": 546, | ||||
'status': 'headers-only', | "status": "headers-only", | ||||
} in self.nodes[0].getchaintips() | } in self.nodes[0].getchaintips() | ||||
self.log.info("Feed all fork headers (fails due to checkpoint)") | self.log.info("Feed all fork headers (fails due to checkpoint)") | ||||
with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint']): | with self.nodes[0].assert_debug_log(["bad-fork-prior-to-checkpoint"]): | ||||
peer_checkpoint.send_message(msg_headers(self.headers_fork)) | peer_checkpoint.send_message(msg_headers(self.headers_fork)) | ||||
peer_checkpoint.wait_for_disconnect() | peer_checkpoint.wait_for_disconnect() | ||||
self.log.info("Feed all fork headers (succeeds without checkpoint)") | self.log.info("Feed all fork headers (succeeds without checkpoint)") | ||||
# On node 0 it succeeds because checkpoints are disabled | # On node 0 it succeeds because checkpoints are disabled | ||||
self.restart_node(0, ['-nocheckpoints']) | self.restart_node(0, ["-nocheckpoints"]) | ||||
peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) | peer_no_checkpoint = self.nodes[0].add_p2p_connection(P2PInterface()) | ||||
peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) | peer_no_checkpoint.send_and_ping(msg_headers(self.headers_fork)) | ||||
assert { | assert { | ||||
"height": 2, | "height": 2, | ||||
"hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", | "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", | ||||
"branchlen": 2, | "branchlen": 2, | ||||
"status": "headers-only", | "status": "headers-only", | ||||
} in self.nodes[0].getchaintips() | } in self.nodes[0].getchaintips() | ||||
# On node 1 it succeeds because no checkpoint has been reached yet by a | # On node 1 it succeeds because no checkpoint has been reached yet by a | ||||
# chain tip | # chain tip | ||||
peer_before_checkpoint = self.nodes[1].add_p2p_connection( | peer_before_checkpoint = self.nodes[1].add_p2p_connection(P2PInterface()) | ||||
P2PInterface()) | peer_before_checkpoint.send_and_ping(msg_headers(self.headers_fork)) | ||||
peer_before_checkpoint.send_and_ping( | |||||
msg_headers(self.headers_fork)) | |||||
assert { | assert { | ||||
"height": 2, | "height": 2, | ||||
"hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", | "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", | ||||
"branchlen": 2, | "branchlen": 2, | ||||
"status": "headers-only", | "status": "headers-only", | ||||
} in self.nodes[1].getchaintips() | } in self.nodes[1].getchaintips() | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
RejectLowDifficultyHeadersTest().main() | RejectLowDifficultyHeadersTest().main() |