1#!/usr/bin/env python3
2# Copyright (c) 2015-2020 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 behavior of -maxuploadtarget.
6
7* Verify that getdata requests for old blocks (>1week) are dropped
8if uploadtarget has been reached.
9* Verify that getdata requests for recent blocks are respected even
10if uploadtarget has been reached.
11* Verify that the upload counters are reset after 24 hours.
12"""
13from collections import defaultdict
14import time
15
16from test_framework.messages import CInv, MSG_BLOCK, msg_getdata
17from test_framework.p2p import P2PInterface
18from test_framework.test_framework import BitcoinTestFramework
19from test_framework.util import assert_equal, mine_large_block
20
21class TestP2PConn(P2PInterface):
22    def __init__(self):
23        super().__init__()
24        self.block_receive_map = defaultdict(int)
25
26    def on_inv(self, message):
27        pass
28
29    def on_block(self, message):
30        message.block.calc_sha256()
31        self.block_receive_map[message.block.sha256] += 1
32
33class MaxUploadTest(BitcoinTestFramework):
34
35    def set_test_params(self):
36        self.setup_clean_chain = True
37        self.num_nodes = 1
38        self.extra_args = [[
39            "-maxuploadtarget=800",
40            "-acceptnonstdtxn=1",
41            "-peertimeout=9999",  # bump because mocktime might cause a disconnect otherwise
42        ]]
43        self.supports_cli = False
44
45        # Cache for utxos, as the listunspent may take a long time later in the test
46        self.utxo_cache = []
47
48    def skip_test_if_missing_module(self):
49        self.skip_if_no_wallet()
50
51    def run_test(self):
52        # Before we connect anything, we first set the time on the node
53        # to be in the past, otherwise things break because the CNode
54        # time counters can't be reset backward after initialization
55        old_time = int(time.time() - 2*60*60*24*7)
56        self.nodes[0].setmocktime(old_time)
57
58        # Generate some old blocks
59        self.nodes[0].generate(130)
60
61        # p2p_conns[0] will only request old blocks
62        # p2p_conns[1] will only request new blocks
63        # p2p_conns[2] will test resetting the counters
64        p2p_conns = []
65
66        for _ in range(3):
67            p2p_conns.append(self.nodes[0].add_p2p_connection(TestP2PConn()))
68
69        # Now mine a big block
70        mine_large_block(self.nodes[0], self.utxo_cache)
71
72        # Store the hash; we'll request this later
73        big_old_block = self.nodes[0].getbestblockhash()
74        old_block_size = self.nodes[0].getblock(big_old_block, True)['size']
75        big_old_block = int(big_old_block, 16)
76
77        # Advance to two days ago
78        self.nodes[0].setmocktime(int(time.time()) - 2*60*60*24)
79
80        # Mine one more block, so that the prior block looks old
81        mine_large_block(self.nodes[0], self.utxo_cache)
82
83        # We'll be requesting this new block too
84        big_new_block = self.nodes[0].getbestblockhash()
85        big_new_block = int(big_new_block, 16)
86
87        # p2p_conns[0] will test what happens if we just keep requesting the
88        # the same big old block too many times (expect: disconnect)
89
90        getdata_request = msg_getdata()
91        getdata_request.inv.append(CInv(MSG_BLOCK, big_old_block))
92
93        max_bytes_per_day = 800*1024*1024
94        daily_buffer = 144 * 4000000
95        max_bytes_available = max_bytes_per_day - daily_buffer
96        success_count = max_bytes_available // old_block_size
97
98        # 576MB will be reserved for relaying new blocks, so expect this to
99        # succeed for ~235 tries.
100        for i in range(success_count):
101            p2p_conns[0].send_and_ping(getdata_request)
102            assert_equal(p2p_conns[0].block_receive_map[big_old_block], i+1)
103
104        assert_equal(len(self.nodes[0].getpeerinfo()), 3)
105        # At most a couple more tries should succeed (depending on how long
106        # the test has been running so far).
107        for _ in range(3):
108            p2p_conns[0].send_message(getdata_request)
109        p2p_conns[0].wait_for_disconnect()
110        assert_equal(len(self.nodes[0].getpeerinfo()), 2)
111        self.log.info("Peer 0 disconnected after downloading old block too many times")
112
113        # Requesting the current block on p2p_conns[1] should succeed indefinitely,
114        # even when over the max upload target.
115        # We'll try 800 times
116        getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]
117        for i in range(800):
118            p2p_conns[1].send_and_ping(getdata_request)
119            assert_equal(p2p_conns[1].block_receive_map[big_new_block], i+1)
120
121        self.log.info("Peer 1 able to repeatedly download new block")
122
123        # But if p2p_conns[1] tries for an old block, it gets disconnected too.
124        getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]
125        p2p_conns[1].send_message(getdata_request)
126        p2p_conns[1].wait_for_disconnect()
127        assert_equal(len(self.nodes[0].getpeerinfo()), 1)
128
129        self.log.info("Peer 1 disconnected after trying to download old block")
130
131        self.log.info("Advancing system time on node to clear counters...")
132
133        # If we advance the time by 24 hours, then the counters should reset,
134        # and p2p_conns[2] should be able to retrieve the old block.
135        self.nodes[0].setmocktime(int(time.time()))
136        p2p_conns[2].sync_with_ping()
137        p2p_conns[2].send_and_ping(getdata_request)
138        assert_equal(p2p_conns[2].block_receive_map[big_old_block], 1)
139
140        self.log.info("Peer 2 able to download old block")
141
142        self.nodes[0].disconnect_p2ps()
143
144        self.log.info("Restarting node 0 with download permission and 1MB maxuploadtarget")
145        self.restart_node(0, ["-whitelist=download@127.0.0.1", "-maxuploadtarget=1"])
146
147        # Reconnect to self.nodes[0]
148        peer = self.nodes[0].add_p2p_connection(TestP2PConn())
149
150        #retrieve 20 blocks which should be enough to break the 1MB limit
151        getdata_request.inv = [CInv(MSG_BLOCK, big_new_block)]
152        for i in range(20):
153            peer.send_and_ping(getdata_request)
154            assert_equal(peer.block_receive_map[big_new_block], i+1)
155
156        getdata_request.inv = [CInv(MSG_BLOCK, big_old_block)]
157        peer.send_and_ping(getdata_request)
158
159        self.log.info("Peer still connected after trying to download old block (download permission)")
160        peer_info = self.nodes[0].getpeerinfo()
161        assert_equal(len(peer_info), 1)  # node is still connected
162        assert_equal(peer_info[0]['permissions'], ['download'])
163
164
165if __name__ == '__main__':
166    MaxUploadTest().main()
167