feature_versionbits_warning.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2016-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 version bits warning system.
   6  
   7  Generate chains with block versions that appear to be signalling unknown
   8  soft-forks, and test that warning alerts are generated.
   9  """
  10  import os
  11  import re
  12  
  13  from test_framework.blocktools import create_block
  14  from test_framework.messages import msg_block
  15  from test_framework.p2p import P2PInterface
  16  from test_framework.test_framework import BitcoinTestFramework
  17  
  18  VB_PERIOD = 144           # versionbits period length for regtest
  19  VB_THRESHOLD = 108        # versionbits activation threshold for regtest
  20  VB_TOP_BITS = 0x20000000
  21  
  22  # Choose a bit unassigned to any deployment, or start the
  23  # node with the deployment matching this bit disabled.
  24  VB_UNKNOWN_BIT = 3
  25  VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT)
  26  VB_IGNORED_BIT = 5
  27  VB_IGNORED_VERSION = VB_TOP_BITS | (1 << VB_IGNORED_BIT)
  28  
  29  WARN_UNKNOWN_RULES_ACTIVE = f"Unknown new rules activated (versionbit {VB_UNKNOWN_BIT})"
  30  VB_PATTERN = re.compile("Unknown new rules activated.*versionbit")
  31  
  32  class VersionBitsWarningTest(BitcoinTestFramework):
  33      def set_test_params(self):
  34          self.setup_clean_chain = True
  35          self.num_nodes = 1
  36  
  37      def setup_network(self):
  38          self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
  39          # Open and close to create zero-length file
  40          with open(self.alert_filename, 'w'):
  41              pass
  42          self.extra_args = [[f"-alertnotify=echo %s >> \"{self.alert_filename}\""]]
  43          self.setup_nodes()
  44  
  45      def send_blocks_with_version(self, peer, numblocks, version):
  46          """Send numblocks blocks to peer with version set"""
  47          tip = self.nodes[0].getbestblockhash()
  48          height = self.nodes[0].getblockcount()
  49          block_time = self.nodes[0].getblockheader(tip)["time"] + 1
  50          tip = int(tip, 16)
  51  
  52          for _ in range(numblocks):
  53              block = create_block(tip, height=height + 1, ntime=block_time, version=version)
  54              block.solve()
  55              peer.send_without_ping(msg_block(block))
  56              block_time += 1
  57              height += 1
  58              tip = block.hash_int
  59          peer.sync_with_ping()
  60  
  61      def versionbits_in_alert_file(self):
  62          """Test that the versionbits warning has been written to the alert file."""
  63          with open(self.alert_filename, 'r') as f:
  64              alert_text = f.read()
  65          return VB_PATTERN.search(alert_text) is not None
  66  
  67      def run_test(self):
  68          node = self.nodes[0]
  69          peer = node.add_p2p_connection(P2PInterface())
  70  
  71          node_deterministic_address = node.get_deterministic_priv_key().address
  72          # Mine one period worth of blocks
  73          self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
  74  
  75          self.log.info("Check that there is no warning if previous VB_BLOCKS have <VB_THRESHOLD blocks with unknown versionbits version.")
  76          # Build one period of blocks with < VB_THRESHOLD blocks signaling some unknown bit
  77          self.send_blocks_with_version(peer, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION)
  78          self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD + 1, node_deterministic_address)
  79  
  80          # Check that we're not getting any versionbit-related errors in get*info()
  81          assert not VB_PATTERN.match(",".join(node.getmininginfo()["warnings"]))
  82          assert not VB_PATTERN.match(",".join(node.getnetworkinfo()["warnings"]))
  83  
  84          self.log.info("Check that there is no warning if previous VB_BLOCKS have VB_PERIOD blocks with ignored versionbits version.")
  85          # Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit
  86          self.send_blocks_with_version(peer, VB_THRESHOLD, VB_IGNORED_VERSION)
  87          self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD, node_deterministic_address)
  88  
  89          # Move the ignored deployment state to ACTIVE and make sure we're out of IBD.
  90          self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
  91          self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
  92  
  93          # Check that we're not getting any versionbit-related warnings in get*info()
  94          assert not VB_PATTERN.match(", ".join(node.getmininginfo()["warnings"]))
  95          assert not VB_PATTERN.match(", ".join(node.getnetworkinfo()["warnings"]))
  96  
  97          self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.")
  98          # Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit
  99          self.send_blocks_with_version(peer, VB_THRESHOLD, VB_UNKNOWN_VERSION)
 100          self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD, node_deterministic_address)
 101  
 102          # Mine a period worth of expected blocks so the generic block-version warning
 103          # is cleared. This will move the versionbit state to ACTIVE.
 104          self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
 105  
 106          # Stop-start the node. This is required because bitcoind will only warn once about unknown versions or unknown rules activating.
 107          self.restart_node(0)
 108  
 109          # Generating one block guarantees that we'll get out of IBD
 110          self.generatetoaddress(node, 1, node_deterministic_address)
 111          self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
 112          # Generating one more block will be enough to generate an error.
 113          self.generatetoaddress(node, 1, node_deterministic_address)
 114          # Check that get*info() shows the versionbits unknown rules warning
 115          assert WARN_UNKNOWN_RULES_ACTIVE in ",".join(node.getmininginfo()["warnings"])
 116          assert WARN_UNKNOWN_RULES_ACTIVE in ",".join(node.getnetworkinfo()["warnings"])
 117          # Check that the alert file shows the versionbits unknown rules warning
 118          self.wait_until(lambda: self.versionbits_in_alert_file())
 119  
 120  if __name__ == '__main__':
 121      VersionBitsWarningTest(__file__).main()
 122