Skip to content

Commit 9bbf8c8

Browse files
authored
Merge pull request dogecoin#3297 from alamshafil/getblockstats
rpc: Add getblockstats
2 parents 4a47046 + 4307c56 commit 9bbf8c8

File tree

10 files changed

+997
-37
lines changed

10 files changed

+997
-37
lines changed

qa/pull-tester/rpc-tests.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
'wallet_create_tx.py',
176176
'liststucktransactions.py',
177177
'getblock.py',
178+
'getblockstats.py',
178179
'addnode.py',
179180
'getmocktime.py',
180181
]

qa/rpc-tests/data/getblockstats.json

Lines changed: 353 additions & 0 deletions
Large diffs are not rendered by default.

qa/rpc-tests/getblockstats.py

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

src/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ BITCOIN_CORE_H = \
130130
protocol.h \
131131
random.h \
132132
reverselock.h \
133+
rpc/blockchain.h \
133134
rpc/client.h \
134135
rpc/protocol.h \
135136
rpc/server.h \

0 commit comments

Comments
 (0)