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