p2p_fingerprint.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2017-present 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 various fingerprinting protections.
   6  
   7  If a stale block more than a month old or its header are requested by a peer,
   8  the node should pretend that it does not have it to avoid fingerprinting.
   9  """
  10  
  11  import time
  12  
  13  from test_framework.blocktools import create_block
  14  from test_framework.messages import CInv, MSG_BLOCK
  15  from test_framework.p2p import (
  16      P2PInterface,
  17      msg_headers,
  18      msg_block,
  19      msg_getdata,
  20      msg_getheaders,
  21      p2p_lock,
  22  )
  23  from test_framework.test_framework import BitcoinTestFramework
  24  from test_framework.util import (
  25      assert_equal,
  26  )
  27  
  28  
  29  class P2PFingerprintTest(BitcoinTestFramework):
  30      def set_test_params(self):
  31          self.setup_clean_chain = True
  32          self.num_nodes = 1
  33  
  34      # Build a chain of blocks on top of given one
  35      def build_chain(self, nblocks, prev_hash, prev_height, prev_median_time):
  36          blocks = []
  37          for _ in range(nblocks):
  38              block = create_block(int(prev_hash, 16), height=prev_height + 1, ntime=prev_median_time + 1)
  39              block.solve()
  40  
  41              blocks.append(block)
  42              prev_hash = block.hash_hex
  43              prev_height += 1
  44              prev_median_time += 1
  45          return blocks
  46  
  47      # Send a getdata request for a given block hash
  48      def send_block_request(self, block_hash, node):
  49          msg = msg_getdata()
  50          msg.inv.append(CInv(MSG_BLOCK, block_hash))
  51          node.send_without_ping(msg)
  52  
  53      # Send a getheaders request for a given single block hash
  54      def send_header_request(self, block_hash, node):
  55          msg = msg_getheaders()
  56          msg.hashstop = block_hash
  57          node.send_without_ping(msg)
  58  
  59      # Checks that stale blocks timestamped more than a month ago are not served
  60      # by the node while recent stale blocks and old active chain blocks are.
  61      # This does not currently test that stale blocks timestamped within the
  62      # last month but that have over a month's worth of work are also withheld.
  63      def run_test(self):
  64          node0 = self.nodes[0].add_p2p_connection(P2PInterface())
  65  
  66          # Set node time to 60 days ago
  67          self.nodes[0].setmocktime(int(time.time()) - 60 * 24 * 60 * 60)
  68  
  69          # Generating a chain of 10 blocks
  70          block_hashes = self.generatetoaddress(self.nodes[0], 10, self.nodes[0].get_deterministic_priv_key().address)
  71  
  72          # Create longer chain starting 2 blocks before current tip
  73          height = len(block_hashes) - 2
  74          block_hash = block_hashes[height - 1]
  75          block_time = self.nodes[0].getblockheader(block_hash)["mediantime"] + 1
  76          new_blocks = self.build_chain(5, block_hash, height, block_time)
  77  
  78          # Force reorg to a longer chain
  79          node0.send_without_ping(msg_headers(new_blocks))
  80          node0.wait_for_getdata([x.hash_int for x in new_blocks])
  81          for block in new_blocks:
  82              node0.send_and_ping(msg_block(block))
  83  
  84          # Check that reorg succeeded
  85          assert_equal(self.nodes[0].getblockcount(), 13)
  86  
  87          stale_hash = int(block_hashes[-1], 16)
  88  
  89          # Check that getdata request for stale block succeeds
  90          self.send_block_request(stale_hash, node0)
  91          node0.wait_for_block(stale_hash, timeout=3)
  92  
  93          # Check that getheader request for stale block header succeeds
  94          self.send_header_request(stale_hash, node0)
  95          node0.wait_for_header(hex(stale_hash), timeout=3)
  96  
  97          # Longest chain is extended so stale is much older than chain tip
  98          self.nodes[0].setmocktime(0)
  99          block_hash = int(self.generatetoaddress(self.nodes[0], 1, self.nodes[0].get_deterministic_priv_key().address)[-1], 16)
 100          assert_equal(self.nodes[0].getblockcount(), 14)
 101          node0.wait_for_block(block_hash, timeout=3)
 102  
 103          # Request for very old stale block should now fail
 104          with p2p_lock:
 105              node0.last_message.pop("block", None)
 106          self.send_block_request(stale_hash, node0)
 107          node0.sync_with_ping()
 108          assert "block" not in node0.last_message
 109  
 110          # Request for very old stale block header should now fail
 111          with p2p_lock:
 112              node0.last_message.pop("headers", None)
 113          self.send_header_request(stale_hash, node0)
 114          node0.sync_with_ping()
 115          assert "headers" not in node0.last_message
 116  
 117          # Verify we can fetch very old blocks and headers on the active chain
 118          block_hash = int(block_hashes[2], 16)
 119          self.send_block_request(block_hash, node0)
 120          self.send_header_request(block_hash, node0)
 121          node0.sync_with_ping()
 122  
 123          self.send_block_request(block_hash, node0)
 124          node0.wait_for_block(block_hash, timeout=3)
 125  
 126          self.send_header_request(block_hash, node0)
 127          node0.wait_for_header(hex(block_hash), timeout=3)
 128  
 129  
 130  if __name__ == '__main__':
 131      P2PFingerprintTest(__file__).main()
 132