From 8dcd4d3d11368c7c599cb4c675ea172a816b52c9 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 12 Mar 2019 21:30:28 +0000 Subject: [PATCH] functional_tests: improve RPC blockchain tests --- tests/functional_tests/blockchain.py | 156 ++++++++++++------ tests/functional_tests/daemon_info.py | 89 ++++++++++ tests/functional_tests/speed.py | 2 +- .../functional_tests/test_framework/daemon.py | 64 ++++++- tests/functional_tests/test_framework/rpc.py | 42 ++++- .../functional_tests/test_framework/wallet.py | 16 +- 6 files changed, 296 insertions(+), 73 deletions(-) create mode 100755 tests/functional_tests/daemon_info.py diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py index 983658a7c..bdd08680f 100755 --- a/tests/functional_tests/blockchain.py +++ b/tests/functional_tests/blockchain.py @@ -28,75 +28,129 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"""Test blockchain RPC calls +import time + +"""Test daemon blockchain RPC calls Test the following RPCs: - get_info - generateblocks + - misc block retrieval + - pop_blocks - [TODO: many tests still need to be written] """ from test_framework.daemon import Daemon -from test_framework.wallet import Wallet class BlockchainTest(): def run_test(self): - self._test_get_info() - self._test_hardfork_info() self._test_generateblocks(5) - def _test_get_info(self): - print('Test get_info') - - daemon = Daemon() - res = daemon.get_info() - - # difficulty should be set to 1 for this test - assert 'difficulty' in res.keys() - assert res['difficulty'] == 1; - - # nettype should not be TESTNET - assert 'testnet' in res.keys() - assert res['testnet'] == False; - - # nettype should not be STAGENET - assert 'stagenet' in res.keys() - assert res['stagenet'] == False; - - # nettype should be FAKECHAIN - assert 'nettype' in res.keys() - assert res['nettype'] == "fakechain"; - - # free_space should be > 0 - assert 'free_space' in res.keys() - assert res['free_space'] > 0 - - # height should be greater or equal to 1 - assert 'height' in res.keys() - assert res['height'] >= 1 - - - def _test_hardfork_info(self): - print('Test hard_fork_info') - - daemon = Daemon() - res = daemon.hard_fork_info() - - # hard_fork version should be set at height 1 - assert 'earliest_height' in res.keys() - assert res['earliest_height'] == 1; - - def _test_generateblocks(self, blocks): - print("Test generating", blocks, 'blocks') + assert blocks >= 2 + + print "Test generating", blocks, 'blocks' daemon = Daemon() - res = daemon.get_info() - height = res['height'] - res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks) - assert res['height'] == height + blocks - 1 + # check info/height before generating blocks + res_info = daemon.get_info() + height = res_info.height + prev_block = res_info.top_block_hash + res_height = daemon.get_height() + assert res_height.height == height + assert int(res_info.wide_cumulative_difficulty) == (res_info.cumulative_difficulty_top64 << 64) + res_info.cumulative_difficulty + cumulative_difficulty = int(res_info.wide_cumulative_difficulty) + + # we should not see a block at height + ok = False + try: daemon.getblock(height) + except: ok = True + assert ok + + # generate blocks + res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks) + + # check info/height after generateblocks blocks + assert res_generateblocks.height == height + blocks - 1 + res_info = daemon.get_info() + assert res_info.height == height + blocks + assert res_info.top_block_hash != prev_block + res_height = daemon.get_height() + assert res_height.height == height + blocks + + # get the blocks, check they have the right height + res_getblock = [] + for n in range(blocks): + res_getblock.append(daemon.getblock(height + n)) + block_header = res_getblock[n].block_header + assert abs(block_header.timestamp - time.time()) < 10 # within 10 seconds + assert block_header.height == height + n + assert block_header.orphan_status == False + assert block_header.depth == blocks - n - 1 + assert block_header.prev_hash == prev_block, prev_block + assert int(block_header.wide_difficulty) == (block_header.difficulty_top64 << 64) + block_header.difficulty + assert int(block_header.wide_cumulative_difficulty) == (block_header.cumulative_difficulty_top64 << 64) + block_header.cumulative_difficulty + assert block_header.reward >= 600000000000 # tail emission + cumulative_difficulty += int(block_header.wide_difficulty) + assert cumulative_difficulty == int(block_header.wide_cumulative_difficulty) + assert block_header.block_size > 0 + assert block_header.block_weight >= block_header.block_size + assert block_header.long_term_weight > 0 + prev_block = block_header.hash + + # we should not see a block after that + ok = False + try: daemon.getblock(height + blocks) + except: ok = True + assert ok + + # getlastblockheader and by height/hash should return the same block + res_getlastblockheader = daemon.getlastblockheader() + assert res_getlastblockheader.block_header == block_header + res_getblockheaderbyhash = daemon.getblockheaderbyhash(prev_block) + assert res_getblockheaderbyhash.block_header == block_header + res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 1) + assert res_getblockheaderbyheight.block_header == block_header + + # getting a block template after that should have the right height, etc + res_getblocktemplate = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm') + assert res_getblocktemplate.height == height + blocks + assert res_getblocktemplate.reserved_offset > 0 + assert res_getblocktemplate.prev_hash == res_info.top_block_hash + assert res_getblocktemplate.expected_reward >= 600000000000 + assert len(res_getblocktemplate.blocktemplate_blob) > 0 + assert len(res_getblocktemplate.blockhashing_blob) > 0 + assert int(res_getblocktemplate.wide_difficulty) == (res_getblocktemplate.difficulty_top64 << 64) + res_getblocktemplate.difficulty + + # diff etc should be the same + assert res_getblocktemplate.prev_hash == res_info.top_block_hash + + res_getlastblockheader = daemon.getlastblockheader() + + # pop a block + res_popblocks = daemon.pop_blocks(1) + assert res_popblocks.height == height + blocks - 1 + + res_info = daemon.get_info() + assert res_info.height == height + blocks - 1 + + # getlastblockheader and by height/hash should return the previous block + block_header = res_getblock[blocks - 2].block_header + block_header.depth = 0 # this will be different, ignore it + res_getlastblockheader = daemon.getlastblockheader() + assert res_getlastblockheader.block_header == block_header + res_getblockheaderbyhash = daemon.getblockheaderbyhash(block_header.hash) + assert res_getblockheaderbyhash.block_header == block_header + res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 2) + assert res_getblockheaderbyheight.block_header == block_header + + # we should not see the popped block anymore + ok = False + try: daemon.getblock(height + blocks - 1) + except: ok = True + assert ok if __name__ == '__main__': diff --git a/tests/functional_tests/daemon_info.py b/tests/functional_tests/daemon_info.py new file mode 100755 index 000000000..94c3fc3ac --- /dev/null +++ b/tests/functional_tests/daemon_info.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2018 The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Test daemon RPC calls + +Test the following RPCs: + - get_info + - hard_fork_info + +""" + +from test_framework.daemon import Daemon + +class DaemonGetInfoTest(): + def run_test(self): + self._test_hardfork_info() + self._test_get_info() + + def _test_hardfork_info(self): + print('Test hard_fork_info') + + daemon = Daemon() + res = daemon.hard_fork_info() + + # hard_fork version should be set at height 1 + assert 'earliest_height' in res.keys() + #assert res['earliest_height'] == 1; + assert res.earliest_height == 1 + + def _test_get_info(self): + print('Test get_info') + + daemon = Daemon() + res = daemon.get_info() + + # difficulty should be set to 1 for this test + assert 'difficulty' in res.keys() + assert res.difficulty == 1; + + # nettype should not be TESTNET + assert 'testnet' in res.keys() + assert res.testnet == False; + + # nettype should not be STAGENET + assert 'stagenet' in res.keys() + assert res.stagenet == False; + + # nettype should be FAKECHAIN + assert 'nettype' in res.keys() + assert res.nettype == "fakechain"; + + # free_space should be > 0 + assert 'free_space' in res.keys() + assert res.free_space > 0 + + # height should be greater or equal to 1 + assert 'height' in res.keys() + assert res.height >= 1 + + +if __name__ == '__main__': + DaemonGetInfoTest().run_test() diff --git a/tests/functional_tests/speed.py b/tests/functional_tests/speed.py index 3d2af9a10..0ce90f939 100755 --- a/tests/functional_tests/speed.py +++ b/tests/functional_tests/speed.py @@ -58,7 +58,7 @@ class SpeedTest(): self._test_speed_generateblocks(daemon=daemon, blocks=70) for i in range(1, 10): - while wallet.get_balance()['unlocked_balance'] == 0: + while wallet.get_balance().unlocked_balance == 0: print('Waiting for wallet to refresh...') sleep(1) self._test_speed_transfer_split(wallet=wallet) diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py index f3490b232..c7619a434 100644 --- a/tests/functional_tests/test_framework/daemon.py +++ b/tests/functional_tests/test_framework/daemon.py @@ -32,8 +32,8 @@ from .rpc import JSONRPC class Daemon(object): - def __init__(self, protocol='http', host='127.0.0.1', port=18081, path='/json_rpc'): - self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path)) + def __init__(self, protocol='http', host='127.0.0.1', port=18081): + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port)) def getblocktemplate(self, address): getblocktemplate = { @@ -45,7 +45,7 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(getblocktemplate) + return self.rpc.send_json_rpc_request(getblocktemplate) def submitblock(self, block): submitblock = { @@ -54,7 +54,7 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(submitblock) + return self.rpc.send_json_rpc_request(submitblock) def getblock(self, height=0): getblock = { @@ -65,7 +65,39 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(getblock) + return self.rpc.send_json_rpc_request(getblock) + + def getlastblockheader(self): + getlastblockheader = { + 'method': 'getlastblockheader', + 'params': { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(getlastblockheader) + + def getblockheaderbyhash(self, hash): + getblockheaderbyhash = { + 'method': 'getblockheaderbyhash', + 'params': { + 'hash': hash, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(getblockheaderbyhash) + + def getblockheaderbyheight(self, height): + getblockheaderbyheight = { + 'method': 'getblockheaderbyheight', + 'params': { + 'height': height, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(getblockheaderbyheight) def get_connections(self): get_connections = { @@ -73,7 +105,7 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(get_connections) + return self.rpc.send_json_rpc_request(get_connections) def get_info(self): get_info = { @@ -81,7 +113,7 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(get_info) + return self.rpc.send_json_rpc_request(get_info) def hard_fork_info(self): hard_fork_info = { @@ -89,7 +121,7 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(hard_fork_info) + return self.rpc.send_json_rpc_request(hard_fork_info) def generateblocks(self, address, blocks=1): generateblocks = { @@ -102,4 +134,18 @@ class Daemon(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(generateblocks) + return self.rpc.send_json_rpc_request(generateblocks) + + def get_height(self): + get_height = { + 'method': 'get_height', + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_request("/get_height", get_height) + + def pop_blocks(self, nblocks = 1): + pop_blocks = { + 'nblocks' : nblocks, + } + return self.rpc.send_request("/pop_blocks", pop_blocks) diff --git a/tests/functional_tests/test_framework/rpc.py b/tests/functional_tests/test_framework/rpc.py index b21df7b93..b857be4d2 100644 --- a/tests/functional_tests/test_framework/rpc.py +++ b/tests/functional_tests/test_framework/rpc.py @@ -29,21 +29,55 @@ import requests import json +class Response(dict): + def __init__(self, d): + for k in d.keys(): + if type(d[k]) == dict: + self[k] = Response(d[k]) + elif type(d[k]) == list: + self[k] = [] + for i in range(len(d[k])): + if type(d[k][i]) == dict: + self[k].append(Response(d[k][i])) + else: + self[k].append(d[k][i]) + else: + self[k] = d[k] + + def __getattr__(self, key): + return self[key] + def __setattr__(self, key, value): + self[key] = value + def __eq__(self, other): + if type(other) == dict: + return self == Response(other) + if self.keys() != other.keys(): + return False + for k in self.keys(): + if self[k] != other[k]: + return False + return True + class JSONRPC(object): def __init__(self, url): self.url = url - def send_request(self, inputs): + def send_request(self, path, inputs, result_field = None): res = requests.post( - self.url, + self.url + path, data=json.dumps(inputs), headers={'content-type': 'application/json'}) res = res.json() assert 'error' not in res, res - return res['result'] - + if result_field: + res = res[result_field] + + return Response(res) + + def send_json_rpc_request(self, inputs): + return self.send_request("/json_rpc", inputs, 'result') diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 357eab5b2..2ea2e4b68 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -32,8 +32,8 @@ from .rpc import JSONRPC class Wallet(object): - def __init__(self, protocol='http', host='127.0.0.1', port=18083, path='/json_rpc'): - self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path)) + def __init__(self, protocol='http', host='127.0.0.1', port=18083): + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port)) def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1): destinations = [] @@ -60,7 +60,7 @@ class Wallet(object): } if(len(payment_id) > 0): transfer['params'].update({'payment_id' : payment_id}) - return self.rpc.send_request(transfer) + return self.rpc.send_json_rpc_request(transfer) def transfer_split(self, destinations, ringsize=7, payment_id=''): print(destinations) @@ -77,7 +77,7 @@ class Wallet(object): } if(len(payment_id) > 0): transfer['params'].update({'payment_id' : payment_id}) - return self.rpc.send_request(transfer) + return self.rpc.send_json_rpc_request(transfer) def create_wallet(self, index=''): create_wallet = { @@ -90,7 +90,7 @@ class Wallet(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(create_wallet) + return self.rpc.send_json_rpc_request(create_wallet) def get_balance(self): get_balance = { @@ -98,7 +98,7 @@ class Wallet(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(get_balance) + return self.rpc.send_json_rpc_request(get_balance) def sweep_dust(self): sweep_dust = { @@ -106,7 +106,7 @@ class Wallet(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(sweep_dust) + return self.rpc.send_json_rpc_request(sweep_dust) def sweep_all(self, address): sweep_all = { @@ -117,4 +117,4 @@ class Wallet(object): 'jsonrpc': '2.0', 'id': '0' } - return self.rpc.send_request(sweep_all) + return self.rpc.send_json_rpc_request(sweep_all)