/ test / functional / feature_versionbits_warning.py
feature_versionbits_warning.py
  1  #!/usr/bin/env python3
  2  # Copyright (c) 2016-2022 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, create_coinbase
 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  VB_UNKNOWN_BIT = 27       # Choose a bit unassigned to any deployment
 22  VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT)
 23  
 24  WARN_UNKNOWN_RULES_ACTIVE = f"Unknown new rules activated (versionbit {VB_UNKNOWN_BIT})"
 25  VB_PATTERN = re.compile("Unknown new rules activated.*versionbit")
 26  
 27  class VersionBitsWarningTest(BitcoinTestFramework):
 28      def set_test_params(self):
 29          self.setup_clean_chain = True
 30          self.num_nodes = 1
 31  
 32      def setup_network(self):
 33          self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
 34          # Open and close to create zero-length file
 35          with open(self.alert_filename, 'w', encoding='utf8'):
 36              pass
 37          self.extra_args = [[f"-alertnotify=echo %s >> \"{self.alert_filename}\""]]
 38          self.setup_nodes()
 39  
 40      def send_blocks_with_version(self, peer, numblocks, version):
 41          """Send numblocks blocks to peer with version set"""
 42          tip = self.nodes[0].getbestblockhash()
 43          height = self.nodes[0].getblockcount()
 44          block_time = self.nodes[0].getblockheader(tip)["time"] + 1
 45          tip = int(tip, 16)
 46  
 47          for _ in range(numblocks):
 48              block = create_block(tip, create_coinbase(height + 1), block_time, version=version)
 49              block.solve()
 50              peer.send_message(msg_block(block))
 51              block_time += 1
 52              height += 1
 53              tip = block.sha256
 54          peer.sync_with_ping()
 55  
 56      def versionbits_in_alert_file(self):
 57          """Test that the versionbits warning has been written to the alert file."""
 58          with open(self.alert_filename, 'r', encoding='utf8') as f:
 59              alert_text = f.read()
 60          return VB_PATTERN.search(alert_text) is not None
 61  
 62      def run_test(self):
 63          node = self.nodes[0]
 64          peer = node.add_p2p_connection(P2PInterface())
 65  
 66          node_deterministic_address = node.get_deterministic_priv_key().address
 67          # Mine one period worth of blocks
 68          self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
 69  
 70          self.log.info("Check that there is no warning if previous VB_BLOCKS have <VB_THRESHOLD blocks with unknown versionbits version.")
 71          # Build one period of blocks with < VB_THRESHOLD blocks signaling some unknown bit
 72          self.send_blocks_with_version(peer, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION)
 73          self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD + 1, node_deterministic_address)
 74  
 75          # Check that we're not getting any versionbit-related errors in get*info()
 76          assert not VB_PATTERN.match(node.getmininginfo()["warnings"])
 77          assert not VB_PATTERN.match(node.getnetworkinfo()["warnings"])
 78  
 79          # Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit
 80          self.send_blocks_with_version(peer, VB_THRESHOLD, VB_UNKNOWN_VERSION)
 81          self.generatetoaddress(node, VB_PERIOD - VB_THRESHOLD, node_deterministic_address)
 82  
 83          self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.")
 84          # Mine a period worth of expected blocks so the generic block-version warning
 85          # is cleared. This will move the versionbit state to ACTIVE.
 86          self.generatetoaddress(node, VB_PERIOD, node_deterministic_address)
 87  
 88          # Stop-start the node. This is required because bitcoind will only warn once about unknown versions or unknown rules activating.
 89          self.restart_node(0)
 90  
 91          # Generating one block guarantees that we'll get out of IBD
 92          self.generatetoaddress(node, 1, node_deterministic_address)
 93          self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
 94          # Generating one more block will be enough to generate an error.
 95          self.generatetoaddress(node, 1, node_deterministic_address)
 96          # Check that get*info() shows the versionbits unknown rules warning
 97          assert WARN_UNKNOWN_RULES_ACTIVE in node.getmininginfo()["warnings"]
 98          assert WARN_UNKNOWN_RULES_ACTIVE in node.getnetworkinfo()["warnings"]
 99          # Check that the alert file shows the versionbits unknown rules warning
100          self.wait_until(lambda: self.versionbits_in_alert_file())
101  
102  if __name__ == '__main__':
103      VersionBitsWarningTest().main()