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