1#!/usr/bin/env python3 2# Copyright (c) 2017-2019 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 6# 7# Test getblockstats rpc call 8# 9 10from test_framework.blocktools import COINBASE_MATURITY 11from test_framework.test_framework import BitcoinTestFramework 12from test_framework.util import ( 13 assert_equal, 14 assert_raises_rpc_error, 15) 16import json 17import os 18 19TESTSDIR = os.path.dirname(os.path.realpath(__file__)) 20 21class GetblockstatsTest(BitcoinTestFramework): 22 23 start_height = 101 24 max_stat_pos = 2 25 26 def add_options(self, parser): 27 parser.add_argument('--gen-test-data', dest='gen_test_data', 28 default=False, action='store_true', 29 help='Generate test data') 30 parser.add_argument('--test-data', dest='test_data', 31 default='data/rpc_getblockstats.json', 32 action='store', metavar='FILE', 33 help='Test data file') 34 35 def set_test_params(self): 36 self.num_nodes = 1 37 self.setup_clean_chain = True 38 self.supports_cli = False 39 40 def get_stats(self): 41 return [self.nodes[0].getblockstats(hash_or_height=self.start_height + i) for i in range(self.max_stat_pos+1)] 42 43 def generate_test_data(self, filename): 44 mocktime = 1525107225 45 self.nodes[0].setmocktime(mocktime) 46 self.nodes[0].generate(COINBASE_MATURITY + 1) 47 48 address = self.nodes[0].get_deterministic_priv_key().address 49 self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=True) 50 self.nodes[0].generate(1) 51 self.sync_all() 52 53 self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=True) 54 self.nodes[0].sendtoaddress(address=address, amount=10, subtractfeefromamount=False) 55 self.nodes[0].settxfee(amount=0.003) 56 self.nodes[0].sendtoaddress(address=address, amount=1, subtractfeefromamount=True) 57 self.sync_all() 58 self.nodes[0].generate(1) 59 60 self.expected_stats = self.get_stats() 61 62 blocks = [] 63 tip = self.nodes[0].getbestblockhash() 64 blockhash = None 65 height = 0 66 while tip != blockhash: 67 blockhash = self.nodes[0].getblockhash(height) 68 blocks.append(self.nodes[0].getblock(blockhash, 0)) 69 height += 1 70 71 to_dump = { 72 'blocks': blocks, 73 'mocktime': int(mocktime), 74 'stats': self.expected_stats, 75 } 76 with open(filename, 'w', encoding="utf8") as f: 77 json.dump(to_dump, f, sort_keys=True, indent=2) 78 79 def load_test_data(self, filename): 80 with open(filename, 'r', encoding="utf8") as f: 81 d = json.load(f) 82 blocks = d['blocks'] 83 mocktime = d['mocktime'] 84 self.expected_stats = d['stats'] 85 86 # Set the timestamps from the file so that the nodes can get out of Initial Block Download 87 self.nodes[0].setmocktime(mocktime) 88 self.sync_all() 89 90 for b in blocks: 91 self.nodes[0].submitblock(b) 92 93 94 def run_test(self): 95 test_data = os.path.join(TESTSDIR, self.options.test_data) 96 if self.options.gen_test_data: 97 self.generate_test_data(test_data) 98 else: 99 self.load_test_data(test_data) 100 101 self.sync_all() 102 stats = self.get_stats() 103 104 # Make sure all valid statistics are included but nothing else is 105 expected_keys = self.expected_stats[0].keys() 106 assert_equal(set(stats[0].keys()), set(expected_keys)) 107 108 assert_equal(stats[0]['height'], self.start_height) 109 assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos) 110 111 for i in range(self.max_stat_pos+1): 112 self.log.info('Checking block %d\n' % (i)) 113 assert_equal(stats[i], self.expected_stats[i]) 114 115 # Check selecting block by hash too 116 blockhash = self.expected_stats[i]['blockhash'] 117 stats_by_hash = self.nodes[0].getblockstats(hash_or_height=blockhash) 118 assert_equal(stats_by_hash, self.expected_stats[i]) 119 120 # Make sure each stat can be queried on its own 121 for stat in expected_keys: 122 for i in range(self.max_stat_pos+1): 123 result = self.nodes[0].getblockstats(hash_or_height=self.start_height + i, stats=[stat]) 124 assert_equal(list(result.keys()), [stat]) 125 if result[stat] != self.expected_stats[i][stat]: 126 self.log.info('result[%s] (%d) failed, %r != %r' % ( 127 stat, i, result[stat], self.expected_stats[i][stat])) 128 assert_equal(result[stat], self.expected_stats[i][stat]) 129 130 # Make sure only the selected statistics are included (more than one) 131 some_stats = {'minfee', 'maxfee'} 132 stats = self.nodes[0].getblockstats(hash_or_height=1, stats=list(some_stats)) 133 assert_equal(set(stats.keys()), some_stats) 134 135 # Test invalid parameters raise the proper json exceptions 136 tip = self.start_height + self.max_stat_pos 137 assert_raises_rpc_error(-8, 'Target block height %d after current tip %d' % (tip+1, tip), 138 self.nodes[0].getblockstats, hash_or_height=tip+1) 139 assert_raises_rpc_error(-8, 'Target block height %d is negative' % (-1), 140 self.nodes[0].getblockstats, hash_or_height=-1) 141 142 # Make sure not valid stats aren't allowed 143 inv_sel_stat = 'asdfghjkl' 144 inv_stats = [ 145 [inv_sel_stat], 146 ['minfee' , inv_sel_stat], 147 [inv_sel_stat, 'minfee'], 148 ['minfee', inv_sel_stat, 'maxfee'], 149 ] 150 for inv_stat in inv_stats: 151 assert_raises_rpc_error(-8, 'Invalid selected statistic %s' % inv_sel_stat, 152 self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat) 153 154 # Make sure we aren't always returning inv_sel_stat as the culprit stat 155 assert_raises_rpc_error(-8, 'Invalid selected statistic aaa%s' % inv_sel_stat, 156 self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee' , 'aaa%s' % inv_sel_stat]) 157 # Mainchain's genesis block shouldn't be found on regtest 158 assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, 159 hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') 160 161 # Invalid number of args 162 assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2) 163 assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats) 164 165 166if __name__ == '__main__': 167 GetblockstatsTest().main() 168