Changeset View
Changeset View
Standalone View
Standalone View
test/functional/rpc_getblockstats.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2017-2019 The Bitcoin Core developers | # Copyright (c) 2017-2019 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
# | # | ||||
# Test getblockstats rpc call | # Test getblockstats rpc call | ||||
# | # | ||||
import decimal | import decimal | ||||
import json | import json | ||||
import os | import os | ||||
import time | |||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
) | ) | ||||
TESTSDIR = os.path.dirname(os.path.realpath(__file__)) | TESTSDIR = os.path.dirname(os.path.realpath(__file__)) | ||||
def EncodeDecimal(o): | def EncodeDecimal(o): | ||||
if isinstance(o, decimal.Decimal): | if isinstance(o, decimal.Decimal): | ||||
# json.load will read a quoted float as a string and not convert it back | # json.load will read a quoted float as a string and not convert it back | ||||
# to decimal, so store the value as unquoted float instead. | # to decimal, so store the value as unquoted float instead. | ||||
return float(o) | return float(o) | ||||
raise TypeError(repr(o) + " is not JSON serializable") | raise TypeError(repr(o) + " is not JSON serializable") | ||||
class GetblockstatsTest(BitcoinTestFramework): | class GetblockstatsTest(BitcoinTestFramework): | ||||
start_height = 101 | start_height = 101 | ||||
max_stat_pos = 2 | max_stat_pos = 2 | ||||
STATS_NEED_TXINDEX = [ | |||||
'avgfee', | |||||
'avgfeerate', | |||||
'maxfee', | |||||
'maxfeerate', | |||||
'medianfee', | |||||
'medianfeerate', | |||||
'minfee', | |||||
'minfeerate', | |||||
'totalfee', | |||||
'utxo_size_inc', | |||||
] | |||||
def add_options(self, parser): | def add_options(self, parser): | ||||
parser.add_argument('--gen-test-data', dest='gen_test_data', | parser.add_argument('--gen-test-data', dest='gen_test_data', | ||||
default=False, action='store_true', | default=False, action='store_true', | ||||
help='Generate test data') | help='Generate test data') | ||||
parser.add_argument('--test-data', dest='test_data', | parser.add_argument('--test-data', dest='test_data', | ||||
default='data/rpc_getblockstats.json', | default='data/rpc_getblockstats.json', | ||||
action='store', metavar='FILE', | action='store', metavar='FILE', | ||||
help='Test data file') | help='Test data file') | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 1 | ||||
self.extra_args = [['-txindex'], ['-paytxfee=0.003']] | |||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
def get_stats(self): | def get_stats(self): | ||||
return [self.nodes[0].getblockstats( | return [self.nodes[0].getblockstats( | ||||
hash_or_height=self.start_height + i) for i in range(self.max_stat_pos + 1)] | hash_or_height=self.start_height + i) for i in range(self.max_stat_pos + 1)] | ||||
def generate_test_data(self, filename): | def generate_test_data(self, filename): | ||||
mocktime = time.time() | mocktime = 1525107225 | ||||
self.nodes[0].setmocktime(mocktime) | |||||
self.nodes[0].generate(101) | self.nodes[0].generate(101) | ||||
address = self.nodes[0].get_deterministic_priv_key().address | |||||
self.nodes[0].sendtoaddress( | self.nodes[0].sendtoaddress( | ||||
address=self.nodes[1].getnewaddress(), amount=10, subtractfeefromamount=True) | address=address, | ||||
amount=10, | |||||
subtractfeefromamount=True) | |||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.sync_all() | self.sync_all() | ||||
self.nodes[0].sendtoaddress( | self.nodes[0].sendtoaddress( | ||||
address=self.nodes[0].getnewaddress(), amount=10, subtractfeefromamount=True) | address=address, | ||||
amount=10, | |||||
subtractfeefromamount=True) | |||||
self.nodes[0].sendtoaddress( | self.nodes[0].sendtoaddress( | ||||
address=self.nodes[0].getnewaddress(), amount=10, subtractfeefromamount=False) | address=address, | ||||
self.nodes[1].sendtoaddress( | amount=10, | ||||
address=self.nodes[0].getnewaddress(), amount=1, subtractfeefromamount=True) | subtractfeefromamount=False) | ||||
self.nodes[0].settxfee(amount=0.003) | |||||
self.nodes[0].sendtoaddress( | |||||
address=address, | |||||
amount=1, | |||||
subtractfeefromamount=True) | |||||
self.sync_all() | self.sync_all() | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
self.expected_stats = self.get_stats() | self.expected_stats = self.get_stats() | ||||
blocks = [] | blocks = [] | ||||
tip = self.nodes[0].getbestblockhash() | tip = self.nodes[0].getbestblockhash() | ||||
blockhash = None | blockhash = None | ||||
Show All 18 Lines | def load_test_data(self, filename): | ||||
blocks = d['blocks'] | blocks = d['blocks'] | ||||
mocktime = d['mocktime'] | mocktime = d['mocktime'] | ||||
self.expected_stats = d['stats'] | self.expected_stats = d['stats'] | ||||
self.log.info(self.expected_stats) | self.log.info(self.expected_stats) | ||||
# Set the timestamps from the file so that the nodes can get out of | # Set the timestamps from the file so that the nodes can get out of | ||||
# Initial Block Download | # Initial Block Download | ||||
self.nodes[0].setmocktime(mocktime) | self.nodes[0].setmocktime(mocktime) | ||||
self.nodes[1].setmocktime(mocktime) | self.sync_all() | ||||
for i, b in enumerate(blocks): | for i, b in enumerate(blocks): | ||||
self.nodes[0].submitblock(b) | self.nodes[0].submitblock(b) | ||||
def run_test(self): | def run_test(self): | ||||
test_data = os.path.join(TESTSDIR, self.options.test_data) | test_data = os.path.join(TESTSDIR, self.options.test_data) | ||||
if self.options.gen_test_data: | if self.options.gen_test_data: | ||||
self.generate_test_data(test_data) | self.generate_test_data(test_data) | ||||
else: | else: | ||||
self.load_test_data(test_data) | self.load_test_data(test_data) | ||||
self.sync_all() | self.sync_all() | ||||
stats = self.get_stats() | stats = self.get_stats() | ||||
expected_stats_noindex = [] | |||||
for stat_row in stats: | |||||
expected_stats_noindex.append( | |||||
{k: v for k, v in stat_row.items() if k not in self.STATS_NEED_TXINDEX}) | |||||
# Make sure all valid statistics are included but nothing else is | # Make sure all valid statistics are included but nothing else is | ||||
expected_keys = self.expected_stats[0].keys() | expected_keys = self.expected_stats[0].keys() | ||||
assert_equal(set(stats[0].keys()), set(expected_keys)) | assert_equal(set(stats[0].keys()), set(expected_keys)) | ||||
assert_equal(stats[0]['height'], self.start_height) | assert_equal(stats[0]['height'], self.start_height) | ||||
assert_equal(stats[self.max_stat_pos]['height'], | assert_equal(stats[self.max_stat_pos]['height'], | ||||
self.start_height + self.max_stat_pos) | self.start_height + self.max_stat_pos) | ||||
for i in range(self.max_stat_pos + 1): | for i in range(self.max_stat_pos + 1): | ||||
self.log.info('Checking block {}\n'.format(i)) | self.log.info('Checking block {}\n'.format(i)) | ||||
assert_equal(stats[i], self.expected_stats[i]) | assert_equal(stats[i], self.expected_stats[i]) | ||||
# Check selecting block by hash too | # Check selecting block by hash too | ||||
blockhash = self.expected_stats[i]['blockhash'] | blockhash = self.expected_stats[i]['blockhash'] | ||||
stats_by_hash = self.nodes[0].getblockstats( | stats_by_hash = self.nodes[0].getblockstats( | ||||
hash_or_height=blockhash) | hash_or_height=blockhash) | ||||
assert_equal(stats_by_hash, self.expected_stats[i]) | assert_equal(stats_by_hash, self.expected_stats[i]) | ||||
# Check with the node that has no txindex | |||||
stats_no_txindex = self.nodes[1].getblockstats( | |||||
hash_or_height=blockhash, stats=list(expected_stats_noindex[i].keys())) | |||||
assert_equal(stats_no_txindex, expected_stats_noindex[i]) | |||||
# Make sure each stat can be queried on its own | # Make sure each stat can be queried on its own | ||||
for stat in expected_keys: | for stat in expected_keys: | ||||
for i in range(self.max_stat_pos + 1): | for i in range(self.max_stat_pos + 1): | ||||
result = self.nodes[0].getblockstats( | result = self.nodes[0].getblockstats( | ||||
hash_or_height=self.start_height + i, stats=[stat]) | hash_or_height=self.start_height + i, stats=[stat]) | ||||
assert_equal(list(result.keys()), [stat]) | assert_equal(list(result.keys()), [stat]) | ||||
if result[stat] != self.expected_stats[i][stat]: | if result[stat] != self.expected_stats[i][stat]: | ||||
self.log.info('result[{}] ({}) failed, {!r} != {!r}'.format( | self.log.info('result[{}] ({}) failed, {!r} != {!r}'.format( | ||||
Show All 21 Lines | def run_test(self): | ||||
[inv_sel_stat, 'minfee'], | [inv_sel_stat, 'minfee'], | ||||
['minfee', inv_sel_stat, 'maxfee'], | ['minfee', inv_sel_stat, 'maxfee'], | ||||
] | ] | ||||
for inv_stat in inv_stats: | for inv_stat in inv_stats: | ||||
assert_raises_rpc_error(-8, 'Invalid selected statistic {}'.format( | assert_raises_rpc_error(-8, 'Invalid selected statistic {}'.format( | ||||
inv_sel_stat), self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat) | inv_sel_stat), self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat) | ||||
# Make sure we aren't always returning inv_sel_stat as the culprit stat | # Make sure we aren't always returning inv_sel_stat as the culprit stat | ||||
assert_raises_rpc_error(-8, 'Invalid selected statistic aaa{}'.format(inv_sel_stat), | |||||
self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee', 'aaa{}'.format(inv_sel_stat)]) | |||||
assert_raises_rpc_error(-8, 'One or more of the selected stats requires -txindex enabled', | |||||
self.nodes[1].getblockstats, hash_or_height=1) | |||||
assert_raises_rpc_error(-8, 'One or more of the selected stats requires -txindex enabled', | |||||
self.nodes[1].getblockstats, hash_or_height=self.start_height + self.max_stat_pos) | |||||
# Mainchain's genesis block shouldn't be found on regtest | # Mainchain's genesis block shouldn't be found on regtest | ||||
assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, | assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, | ||||
hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') | hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') | ||||
# Invalid number of args | # Invalid number of args | ||||
assert_raises_rpc_error(-1, | assert_raises_rpc_error(-1, | ||||
'getblockstats hash_or_height ( stats )', | 'getblockstats hash_or_height ( stats )', | ||||
self.nodes[0].getblockstats, | self.nodes[0].getblockstats, | ||||
Show All 10 Lines |