Skip to content

Commit ca97d29

Browse files
author
MarcoFalke
committed
Merge #16551: test: Test that low difficulty chain fork is rejected
333317c test: Test that low difficulty chain fork is rejected (MarcoFalke) fa31dc1 test: Pass down correct chain name in tests (MarcoFalke) Pull request description: To prevent OOM, Bitcoin Core will reject chain forks at low difficulty by default. This is the only use-case of checkpoints, so add a test for it to make sure the feature works as expected. If it didn't work, checkpoints would have no use-case and we might as well remove them ACKs for top commit: Sjors: Thanks for adding the node 1 example. Code review ACK 333317c Tree-SHA512: 90dffa540d0904f3cffb61d2382b1a26f84fe9560b7013e4461546383add31a8757b350616a6d43217c59ef7b8b2a1b62bb3bab582c679cbb2c660a782ce7be1
2 parents 04d9939 + 333317c commit ca97d29

File tree

6 files changed

+651
-4
lines changed

6 files changed

+651
-4
lines changed

test/functional/data/blockheader_testnet3.hex

Lines changed: 548 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
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+
"""Test that we reject low difficulty headers to prevent our block tree from filling up with useless bloat"""
6+
7+
from test_framework.messages import (
8+
CBlockHeader,
9+
FromHex,
10+
)
11+
from test_framework.mininode import (
12+
P2PInterface,
13+
msg_headers,
14+
)
15+
from test_framework.test_framework import BitcoinTestFramework
16+
17+
import os
18+
19+
20+
class RejectLowDifficultyHeadersTest(BitcoinTestFramework):
21+
def set_test_params(self):
22+
self.setup_clean_chain = True
23+
self.chain = 'testnet3' # Use testnet chain because it has an early checkpoint
24+
self.num_nodes = 2
25+
26+
def add_options(self, parser):
27+
parser.add_argument(
28+
'--datafile',
29+
default='data/blockheader_testnet3.hex',
30+
help='Test data file (default: %(default)s)',
31+
)
32+
33+
def run_test(self):
34+
self.log.info("Read headers data")
35+
self.headers_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.options.datafile)
36+
with open(self.headers_file_path, encoding='utf-8') as headers_data:
37+
h_lines = [l.strip() for l in headers_data.readlines()]
38+
39+
# The headers data is taken from testnet3 for early blocks from genesis until the first checkpoint. There are
40+
# two headers with valid POW at height 1 and 2, forking off from genesis. They are indicated by the FORK_PREFIX.
41+
FORK_PREFIX = 'fork:'
42+
self.headers = [l for l in h_lines if not l.startswith(FORK_PREFIX)]
43+
self.headers_fork = [l[len(FORK_PREFIX):] for l in h_lines if l.startswith(FORK_PREFIX)]
44+
45+
self.headers = [FromHex(CBlockHeader(), h) for h in self.headers]
46+
self.headers_fork = [FromHex(CBlockHeader(), h) for h in self.headers_fork]
47+
48+
self.log.info("Feed all non-fork headers, including and up to the first checkpoint")
49+
self.nodes[0].add_p2p_connection(P2PInterface())
50+
self.nodes[0].p2p.send_message(msg_headers(self.headers))
51+
self.nodes[0].p2p.sync_with_ping()
52+
assert {
53+
'height': 546,
54+
'hash': '000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70',
55+
'branchlen': 546,
56+
'status': 'headers-only',
57+
} in self.nodes[0].getchaintips()
58+
59+
self.log.info("Feed all fork headers (fails due to checkpoint)")
60+
with self.nodes[0].assert_debug_log(['bad-fork-prior-to-checkpoint (code 67)']):
61+
self.nodes[0].p2p.send_message(msg_headers(self.headers_fork))
62+
self.nodes[0].p2p.wait_for_disconnect()
63+
64+
self.log.info("Feed all fork headers (succeeds without checkpoint)")
65+
# On node 0 it succeeds because checkpoints are disabled
66+
self.restart_node(0, extra_args=['-nocheckpoints'])
67+
self.nodes[0].add_p2p_connection(P2PInterface())
68+
self.nodes[0].p2p.send_message(msg_headers(self.headers_fork))
69+
self.nodes[0].p2p.sync_with_ping()
70+
assert {
71+
"height": 2,
72+
"hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39",
73+
"branchlen": 2,
74+
"status": "headers-only",
75+
} in self.nodes[0].getchaintips()
76+
77+
# On node 1 it succeeds because no checkpoint has been reached yet by a chain tip
78+
self.nodes[1].add_p2p_connection(P2PInterface())
79+
self.nodes[1].p2p.send_message(msg_headers(self.headers_fork))
80+
self.nodes[1].p2p.sync_with_ping()
81+
assert {
82+
"height": 2,
83+
"hash": "00000000b0494bd6c3d5ff79c497cfce40831871cbf39b1bc28bd1dac817dc39",
84+
"branchlen": 2,
85+
"status": "headers-only",
86+
} in self.nodes[1].getchaintips()
87+
88+
89+
if __name__ == '__main__':
90+
RejectLowDifficultyHeadersTest().main()

test/functional/test_framework/mininode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def __init__(self):
111111
def is_connected(self):
112112
return self._transport is not None
113113

114-
def peer_connect(self, dstaddr, dstport, net="regtest"):
114+
def peer_connect(self, dstaddr, dstport, *, net):
115115
assert not self.is_connected
116116
self.dstaddr = dstaddr
117117
self.dstport = dstport

test/functional/test_framework/test_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs):
489489
if 'dstaddr' not in kwargs:
490490
kwargs['dstaddr'] = '127.0.0.1'
491491

492-
p2p_conn.peer_connect(**kwargs)()
492+
p2p_conn.peer_connect(**kwargs, net=self.chain)()
493493
self.p2ps.append(p2p_conn)
494494
if wait_for_verack:
495495
p2p_conn.wait_for_verack()

test/functional/test_framework/util.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,22 @@ def initialize_datadir(dirname, n, chain):
287287
datadir = get_datadir_path(dirname, n)
288288
if not os.path.isdir(datadir):
289289
os.makedirs(datadir)
290+
# Translate chain name to config name
291+
if chain == 'testnet3':
292+
chain_name_conf_arg = 'testnet'
293+
chain_name_conf_section = 'test'
294+
else:
295+
chain_name_conf_arg = chain
296+
chain_name_conf_section = chain
290297
with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f:
291-
f.write("{}=1\n".format(chain))
292-
f.write("[{}]\n".format(chain))
298+
f.write("{}=1\n".format(chain_name_conf_arg))
299+
f.write("[{}]\n".format(chain_name_conf_section))
293300
f.write("port=" + str(p2p_port(n)) + "\n")
294301
f.write("rpcport=" + str(rpc_port(n)) + "\n")
295302
f.write("server=1\n")
296303
f.write("keypool=1\n")
297304
f.write("discover=0\n")
305+
f.write("dnsseed=0\n")
298306
f.write("listenonion=0\n")
299307
f.write("printtoconsole=0\n")
300308
f.write("upnp=0\n")

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
'feature_uacomment.py',
198198
'wallet_coinbase_category.py',
199199
'feature_filelock.py',
200+
'p2p_dos_header_tree.py',
200201
'p2p_unrequested_blocks.py',
201202
'feature_includeconf.py',
202203
'rpc_deriveaddresses.py',

0 commit comments

Comments
 (0)