diff options
Diffstat (limited to 'test/functional/p2p_dos_header_tree.py')
-rwxr-xr-x | test/functional/p2p_dos_header_tree.py | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/test/functional/p2p_dos_header_tree.py b/test/functional/p2p_dos_header_tree.py new file mode 100755 index 0000000000..7d386c47f6 --- /dev/null +++ b/test/functional/p2p_dos_header_tree.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# 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""" + +from test_framework.messages import ( + CBlockHeader, + FromHex, +) +from test_framework.mininode import ( + P2PInterface, + msg_headers, +) +from test_framework.test_framework import BitcoinTestFramework + +import os + + +class RejectLowDifficultyHeadersTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint + self.num_nodes = 2 + + def add_options(self, parser): + parser.add_argument( + '--datafile', + default='data/blockheader_testnet3.hex', + help='Test data file (default: %(default)s)', + ) + + def run_test(self): + self.log.info("Read headers data") + self.headers_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile) + with open(self.headers_file_path, encoding='utf-8') as headers_data: + h_lines = [l.strip() for l in headers_data.readlines()] + + # 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 genesis. They are indicated by the FORK_PREFIX. + FORK_PREFIX = 'fork:' + self.headers = [l for l in h_lines if not l.startswith(FORK_PREFIX)] + self.headers_fork = [l[len(FORK_PREFIX):] for l in h_lines if l.startswith(FORK_PREFIX)] + + self.headers = [FromHex(CBlockHeader(), h) for h in self.headers] + self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork] + + self.log.info("Feed all non-fork headers, including and up to the first checkpoint") + self.nodes[0].add_p2p_connection(P2PInterface()) + self.nodes[0].p2p.send_message(msg_headers(self.headers)) + self.nodes[0].p2p.sync_with_ping() + assert { + 'height': 546, + 'hash': '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70', + 'branchlen': 546, + 'status': 'headers-only', + } in self.nodes[0].getchaintips() + + self.log.info("Feed all fork headers (fails due to checkpoint)") + with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint (code 67)']): + self.nodes[0].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[0].p2p.wait_for_disconnect() + + self.log.info("Feed all fork headers (succeeds without checkpoint)") + # On node 0 it succeeds because checkpoints are disabled + self.restart_node(0, extra_args=['-nocheckpoints']) + self.nodes[0].add_p2p_connection(P2PInterface()) + self.nodes[0].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[0].p2p.sync_with_ping() + assert { + "height": 2, + "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", + "branchlen": 2, + "status": "headers-only", + } in self.nodes[0].getchaintips() + + # On node 1 it succeeds because no checkpoint has been reached yet by a chain tip + self.nodes[1].add_p2p_connection(P2PInterface()) + self.nodes[1].p2p.send_message(msg_headers(self.headers_fork)) + self.nodes[1].p2p.sync_with_ping() + assert { + "height": 2, + "hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39", + "branchlen": 2, + "status": "headers-only", + } in self.nodes[1].getchaintips() + + +if __name__ == '__main__': + RejectLowDifficultyHeadersTest().main() |