rpc_dumptxoutset.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2019-2022 The Limenka 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 the generation of UTXO snapshots using `dumptxoutset`.
   6  """
   7  
   8  from test_framework.blocktools import COINBASE_MATURITY
   9  from test_framework.test_framework import LimenkaTestFramework
  10  from test_framework.util import (
  11      assert_equal,
  12      assert_raises_rpc_error,
  13      sha256sum_file,
  14  )
  15  import hashlib
  16  import os
  17  
  18  
  19  class DumptxoutsetTest(LimenkaTestFramework):
  20      def set_test_params(self):
  21          self.setup_clean_chain = True
  22          self.num_nodes = 1
  23  
  24      def check_expected_network(self, node, active):
  25          rev_file = node.blocks_path / "rev00000.dat"
  26          bogus_file = node.blocks_path / "bogus.dat"
  27          rev_file.rename(bogus_file)
  28          assert_raises_rpc_error(
  29              -1, 'Could not roll back to requested height.', node.dumptxoutset, 'utxos.dat', rollback=99)
  30          assert_equal(node.getnetworkinfo()['networkactive'], active)
  31  
  32          # Cleanup
  33          bogus_file.rename(rev_file)
  34  
  35      @staticmethod
  36      def check_output_file(path, is_human_readable, expected_digest):
  37          with open(str(path), 'rb') as f:
  38              content = f.read()
  39  
  40              if is_human_readable:
  41                  # Normalise platform EOL to \n, while making sure any stray \n becomes a literal backslash+n to avoid a false positive
  42                  # This ensures the platform EOL and only the platform EOL produces the expected hash
  43                  linesep = os.linesep.encode('utf8')
  44                  content = b'\n'.join(line.replace(b'\n', b'\\n') for line in content.split(linesep))
  45  
  46              digest = hashlib.sha256(content).hexdigest()
  47              # UTXO snapshot hash should be deterministic based on mocked time.
  48              assert_equal(digest, expected_digest)
  49  
  50      def test_dump_file(self, testname, params, expected_digest):
  51          node = self.nodes[0]
  52  
  53          self.log.info(testname)
  54          filename = testname + '_txoutset.dat'
  55          is_human_readable = not params.get('format') is None
  56  
  57          out = node.dumptxoutset(path=filename, type="latest", **params)
  58          expected_path = node.chain_path / filename
  59  
  60          assert expected_path.is_file()
  61  
  62          assert_equal(out['coins_written'], 100)
  63          assert_equal(out['base_height'], 100)
  64          assert_equal(out['path'], str(expected_path))
  65          # Blockhash should be deterministic based on mocked time.
  66          assert_equal(
  67              out['base_hash'],
  68              '09abf0e7b510f61ca6cf33bab104e9ee99b3528b371d27a2d4b39abb800fba7e')
  69  
  70          self.check_output_file(expected_path, is_human_readable, expected_digest)
  71  
  72          if {'format'} == set(params) - {'show_header', 'separator'}:
  73              # Test backward compatibility
  74              def test_dump_file_compat(*a, **ka):
  75                  os.replace(expected_path, node.chain_path / (filename + ".old"))
  76                  out2 = node.dumptxoutset(filename, *a, **ka)
  77                  assert_equal(out, out2)
  78                  self.check_output_file(expected_path, is_human_readable, expected_digest)
  79              test_dump_file_compat(params.get('format'), params.get('show_header'), params.get('separator'))
  80              test_dump_file_compat(params.get('format'), params.get('show_header'), separator=params.get('separator'))
  81              test_dump_file_compat(params.get('format'), show_header=params.get('show_header'), separator=params.get('separator'))
  82  
  83          assert_equal(
  84              out['txoutset_hash'], 'a0b7baa3bf5ccbd3279728f230d7ca0c44a76e9923fca8f32dbfd08d65ea496a')
  85          assert_equal(out['nchaintx'], 101)
  86  
  87          self.log.info("Test that a path to an existing or invalid file will fail")
  88          assert_raises_rpc_error(
  89              -8, '{} already exists'.format(filename),  node.dumptxoutset, filename, "latest")
  90          invalid_path = node.datadir_path / "invalid" / "path"
  91          assert_raises_rpc_error(
  92              -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest")
  93  
  94          self.log.info("Test that dumptxoutset with unknown dump type fails")
  95          assert_raises_rpc_error(
  96              -8, 'Invalid snapshot type "bogus" specified. Please specify "rollback" or "latest"', node.dumptxoutset, 'utxos.dat', "bogus")
  97  
  98          self.log.info("Test that dumptxoutset failure does not leave the network activity suspended when it was on previously")
  99          self.check_expected_network(node, True)
 100  
 101          self.log.info("Test that dumptxoutset failure leaves the network activity suspended when it was off")
 102          node.setnetworkactive(False)
 103          self.check_expected_network(node, False)
 104          node.setnetworkactive(True)
 105  
 106          if params.get('format') == ():
 107              with open(expected_path, 'r', encoding='utf-8') as f:
 108                  content = f.readlines()
 109                  sep = params.get('separator', ',')
 110                  if params.get('show_header', True):
 111                      assert_equal(content.pop(0).rstrip(),
 112                          "#(blockhash 09abf0e7b510f61ca6cf33bab104e9ee99b3528b371d27a2d4b39abb800fba7e ) txid{s}vout{s}value{s}coinbase{s}height{s}scriptPubKey".format(s=sep))
 113                  assert_equal(content[0].rstrip(),
 114                      "b9edce02689692b1cdc3440d03011486a27c46b966248b922cc6e4315e900708{s}0{s}5000000000{s}1{s}78{s}76a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac".format(s=sep))
 115  
 116      def run_test(self):
 117          """Test a trivial usage of the dumptxoutset RPC command."""
 118          node = self.nodes[0]
 119          mocktime = node.getblockheader(node.getblockhash(0))['time'] + 1
 120          node.setmocktime(mocktime)
 121          self.generate(node, COINBASE_MATURITY)
 122  
 123          self.test_dump_file('no_option',           {},
 124                              '31fcdd0cf542a4b1dfc13c3c05106620ce48951ef62907dd8e5e8c15a0aa993b')
 125          self.test_dump_file('all_data',            {'format': ()},
 126                              '50d7bf3ecca8c5daf648aca884b91496386d8269ef001ff95a1db4381d399bfb')
 127          self.test_dump_file('partial_data_1',      {'format': ('txid',)},
 128                              'f9966db510b46d865a9412da88d17ac2c05c6bfe612ffc7c1b004aec1b508c5c')
 129          self.test_dump_file('partial_data_order',  {'format': ('height', 'vout')},
 130                              '0ef7e361fde77f5c9f3667b1d8ce4351ec8dc81826937da0dab5631e2aedc5fe')
 131          self.test_dump_file('partial_data_double', {'format': ('scriptPubKey', 'scriptPubKey')},
 132                              '8bd128d326b971ea37bd28c016aae506e29d23dac578edd849636a8ab2ee31a8')
 133          self.test_dump_file('no_header',           {'format': (), 'show_header': False},
 134                              'af1f38ee1d1b8bbdc117ab7e8353910dab5ab45f18be27aa4fa7d96ccc96a050')
 135          self.test_dump_file('separator',           {'format': (), 'separator': ':'},
 136                              '5bee81096e400d1b3bf02de432e0fd4af8f4d9244907dc1c857ec329c5ce4490')
 137          self.test_dump_file('all_options',         {'format': (), 'show_header': False, 'separator': ':'},
 138                              '5c52c2a9bdb23946eb0f6d088f25ed8f5d9ebc3a3512182287975f1041cdedb4')
 139  
 140          # Other failing tests
 141          assert_raises_rpc_error(
 142              -8, 'unable to find item \'sample\'',  node.dumptxoutset, path='xxx', type='latest', format=['sample'])
 143  
 144  if __name__ == '__main__':
 145      DumptxoutsetTest(__file__).main()
 146