From 8dcd4d3d11368c7c599cb4c675ea172a816b52c9 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 12 Mar 2019 21:30:28 +0000 Subject: [PATCH 01/18] 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 | 40 ++++- .../functional_tests/test_framework/wallet.py | 16 +- 6 files changed, 295 insertions(+), 72 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) From 98e280fcb801ac751b4ce750679479ce08a5bb51 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 14 Mar 2019 23:41:11 +0000 Subject: [PATCH 02/18] functional_tests: add wallet address/subaddress RPC tests --- .../functional_tests/test_framework/wallet.py | 107 ++++++++++++ tests/functional_tests/wallet_address.py | 152 ++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100755 tests/functional_tests/wallet_address.py diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 2ea2e4b68..215228e0e 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -118,3 +118,110 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(sweep_all) + + def get_address(self, account_index = 0, subaddresses = []): + get_address = { + 'method': 'get_address', + 'params' : { + 'account_index' : account_index, + 'address_index': subaddresses + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_address) + + def create_account(self, label = ""): + create_account = { + 'method': 'create_account', + 'params' : { + 'label': label + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(create_account) + + def create_address(self, account_index = 0, label = ""): + create_address = { + 'method': 'create_address', + 'params' : { + 'account_index': account_index, + 'label': label + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(create_address) + + def label_address(self, subaddress_index, label): + label_address = { + 'method': 'label_address', + 'params' : { + 'index': { 'major': subaddress_index[0], 'minor': subaddress_index[1]}, + 'label': label + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(label_address) + + def label_account(self, account_index, label): + label_account = { + 'method': 'label_account', + 'params' : { + 'account_index': account_index, + 'label': label + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(label_account) + + def get_address_index(self, address): + get_address_index = { + 'method': 'get_address_index', + 'params' : { + 'address': address + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_address_index) + + def query_key(self, key_type): + query_key = { + 'method': 'query_key', + 'params' : { + 'key_type': key_type + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(query_key) + + def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = ''): + restore_deterministic_wallet = { + 'method': 'restore_deterministic_wallet', + 'params' : { + 'restore_height': restore_height, + 'filename': filename, + 'seed': seed, + 'seed_offset': seed_offset, + 'password': password, + 'language': language + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(restore_deterministic_wallet) + + def close_wallet(self): + close_wallet = { + 'method': 'close_wallet', + 'params' : { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(close_wallet) diff --git a/tests/functional_tests/wallet_address.py b/tests/functional_tests/wallet_address.py new file mode 100755 index 000000000..acd7b6821 --- /dev/null +++ b/tests/functional_tests/wallet_address.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test transaction creation RPC calls + +Test the following RPCs: + - [TODO: many tests still need to be written] + +""" + +from test_framework.wallet import Wallet + +class WalletAddressTest(): + def run_test(self): + self.create() + self.check_main_address() + self.check_keys() + self.create_subaddresses() + + def create(self): + print 'Creating wallet' + wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: wallet.close_wallet() + except: pass + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = wallet.restore_deterministic_wallet(seed = seed) + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res.seed == seed + + def check_main_address(self): + print 'Getting address' + wallet = Wallet() + res = wallet.get_address() + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res + assert len(res.addresses) == 1 + assert res.addresses[0].address == res.address + assert res.addresses[0].address_index == 0 + assert res.addresses[0].used == False + + def check_keys(self): + print 'Checking keys' + wallet = Wallet() + res = wallet.query_key('view_key') + assert res.key == '49774391fa5e8d249fc2c5b45dadef13534bf2483dede880dac88f061e809100' + res = wallet.query_key('spend_key') + assert res.key == '148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e' + res = wallet.query_key('mnemonic') + assert res.key == 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + + def create_subaddresses(self): + print 'Creating subaddresses' + wallet = Wallet() + res = wallet.create_account("idx1") + assert res.account_index == 1, res + assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res + res = wallet.create_account("idx2") + assert res.account_index == 2, res + assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res + + res = wallet.get_address(0, 0) + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res + assert len(res.addresses) == 1 + assert res.addresses[0].address_index == 0, res + res = wallet.get_address(1, 0) + assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res + assert len(res.addresses) == 1 + assert res.addresses[0].label == 'idx1', res + assert res.addresses[0].address_index == 0, res + res = wallet.get_address(2, 0) + assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res + assert len(res.addresses) == 1 + assert res.addresses[0].label == 'idx2', res + assert res.addresses[0].address_index == 0, res + + res = wallet.create_address(0, "sub_0_1") + res = wallet.create_address(1, "sub_1_1") + res = wallet.create_address(1, "sub_1_2") + + res = wallet.get_address(0, [1]) + assert len(res.addresses) == 1 + assert res.addresses[0].address == '84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF' + assert res.addresses[0].label == 'sub_0_1' + res = wallet.get_address(1, [1]) + assert len(res.addresses) == 1 + assert res.addresses[0].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4' + assert res.addresses[0].label == 'sub_1_1' + res = wallet.get_address(1, [2]) + assert len(res.addresses) == 1 + assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB' + assert res.addresses[0].label == 'sub_1_2' + res = wallet.get_address(1, [0, 1, 2]) + assert len(res.addresses) == 3 + assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf' + assert res.addresses[0].label == 'idx1' + assert res.addresses[1].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4' + assert res.addresses[1].label == 'sub_1_1' + assert res.addresses[2].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB' + assert res.addresses[2].label == 'sub_1_2' + + res = wallet.label_address((1, 2), "sub_1_2_new") + res = wallet.get_address(1, [2]) + assert len(res.addresses) == 1 + assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB' + assert res.addresses[0].label == 'sub_1_2_new' + + res = wallet.label_account(1, "idx1_new") + res = wallet.get_address(1, [0]) + assert len(res.addresses) == 1 + assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf' + assert res.addresses[0].label == 'idx1_new' + + res = wallet.get_address_index('87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB') + assert res.index == {'major': 1, 'minor': 2} + res = wallet.get_address_index('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm') + assert res.index == {'major': 0, 'minor': 0} + res = wallet.get_address_index('84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF') + assert res.index == {'major': 0, 'minor': 1} + res = wallet.get_address_index('82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf') + assert res.index == {'major': 1, 'minor': 0} + +if __name__ == '__main__': + WalletAddressTest().run_test() From 18a2ed4518da8962ad30d0175ddeb7a6bc28c0f8 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 15 Mar 2019 17:39:07 +0000 Subject: [PATCH 03/18] functional_tests: add basic mining tests --- tests/functional_tests/mining.py | 123 ++++++++++++++++++ .../functional_tests/test_framework/daemon.py | 19 +++ .../functional_tests/test_framework/wallet.py | 10 ++ 3 files changed, 152 insertions(+) create mode 100755 tests/functional_tests/mining.py diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py new file mode 100755 index 000000000..a94d59cba --- /dev/null +++ b/tests/functional_tests/mining.py @@ -0,0 +1,123 @@ +#!/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. + +import time + +"""Test daemon mining RPC calls + +Test the following RPCs: + - start_mining + - stop_mining + - mining_status +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class MiningTest(): + def run_test(self): + self.create() + self.mine() + + def create(self): + print 'Creating wallet' + wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: wallet.close_wallet() + except: pass + res = wallet.restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted') + + def mine(self): + print "Test mining" + + daemon = Daemon() + wallet = Wallet() + + # check info/height/balance before generating blocks + res_info = daemon.get_info() + prev_height = res_info.height + res_getbalance = wallet.get_balance() + prev_balance = res_getbalance.balance + + res_status = daemon.mining_status() + + res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1) + + res_status = daemon.mining_status() + assert res_status.active == True + assert res_status.threads_count == 1 + assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res_status.is_background_mining_enabled == False + assert res_status.block_reward >= 600000000000 + + # wait till we mined a few of them + timeout = 5 + timeout_height = prev_height + while True: + time.sleep(1) + res_info = daemon.get_info() + height = res_info.height + if height >= prev_height + 5: + break + if height > timeout_height: + timeout = 5 + timeout_height = height + else: + timeout -= 1 + assert timeout >= 0 + + res = daemon.stop_mining() + res_status = daemon.mining_status() + assert res_status.active == False + + res_info = daemon.get_info() + new_height = res_info.height + + wallet.refresh() + res_getbalance = wallet.get_balance() + balance = res_getbalance.balance + assert balance >= prev_balance + (new_height - prev_height) * 600000000000 + + res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1, do_background_mining = True) + res_status = daemon.mining_status() + assert res_status.active == True + assert res_status.threads_count == 1 + assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res_status.is_background_mining_enabled == True + assert res_status.block_reward >= 600000000000 + + # don't wait, might be a while if the machine is busy, which it probably is + res = daemon.stop_mining() + res_status = daemon.mining_status() + assert res_status.active == False + + +if __name__ == '__main__': + MiningTest().run_test() diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py index c7619a434..6088fc0df 100644 --- a/tests/functional_tests/test_framework/daemon.py +++ b/tests/functional_tests/test_framework/daemon.py @@ -149,3 +149,22 @@ class Daemon(object): 'nblocks' : nblocks, } return self.rpc.send_request("/pop_blocks", pop_blocks) + + def start_mining(self, miner_address, threads_count = 0, do_background_mining = False, ignore_battery = False): + start_mining = { + 'miner_address' : miner_address, + 'threads_count' : threads_count, + 'do_background_mining' : do_background_mining, + 'ignore_battery' : ignore_battery, + } + return self.rpc.send_request('/start_mining', start_mining) + + def stop_mining(self): + stop_mining = { + } + return self.rpc.send_request('/stop_mining', stop_mining) + + def mining_status(self): + mining_status = { + } + return self.rpc.send_request('/mining_status', mining_status) diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 215228e0e..39db08804 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -225,3 +225,13 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(close_wallet) + + def refresh(self): + refresh = { + 'method': 'refresh', + 'params' : { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(refresh) From ef7681b6991e45b4d1dc2e0831442fff3443ba55 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 15 Mar 2019 15:15:28 +0000 Subject: [PATCH 04/18] functional_tests: plug RPC tests into the cmake machinery --- tests/functional_tests/CMakeLists.txt | 7 +- .../functional_tests/functional_tests_rpc.py | 116 ++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) create mode 100755 tests/functional_tests/functional_tests_rpc.py diff --git a/tests/functional_tests/CMakeLists.txt b/tests/functional_tests/CMakeLists.txt index 2e3519994..60060f56f 100644 --- a/tests/functional_tests/CMakeLists.txt +++ b/tests/functional_tests/CMakeLists.txt @@ -49,6 +49,7 @@ target_link_libraries(functional_tests ${Boost_PROGRAM_OPTIONS_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -set_property(TARGET functional_tests - PROPERTY - FOLDER "tests") + +add_test( + NAME functional_tests_rpc + COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/functional_tests_rpc.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" all) diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py new file mode 100755 index 000000000..5e1ee34e6 --- /dev/null +++ b/tests/functional_tests/functional_tests_rpc.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python + +from __future__ import print_function +import sys +import time +import subprocess +from signal import SIGTERM +import socket +import string + +USAGE = 'usage: functional_tests_rpc.py [ | all]' +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'mining'] +try: + python = sys.argv[1] + srcdir = sys.argv[2] + builddir = sys.argv[3] +except: + print(USAGE) + sys.exit(1) + +try: + sys.argv[4] +except: + print(USAGE) + print('Available tests: ' + string.join(DEFAULT_TESTS, ', ')) + print('Or run all with "all"') + sys.exit(0) + +try: + tests = sys.argv[4:] + if tests == ['all']: + tests = DEFAULT_TESTS +except: + tests = DEFAULT_TESTS + +monerod = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"] +wallet = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "18083", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--log-level", "1"] + +monerod_output = open(builddir + '/tests/functional_tests/monerod.log', 'a+') +wallet_output = open(builddir + '/tests/functional_tests/wallet.log', 'a+') + +print('Starting servers...') +monerod_process = None +wallet_process = None +try: + #print 'Running: ' + str(monerod) + monerod_process = subprocess.Popen(monerod, stdout = monerod_output) + #print 'Running: ' + str(wallet) + wallet_process = subprocess.Popen(wallet, stdout = wallet_output) +except Exception, e: + print('Error: ' + str(e)) + sys.exit(1) + +def kill(): + try: wallet_process.send_signal(SIGTERM) + except: pass + try: monerod_process.send_signal(SIGTERM) + except: pass + +# wait for error/startup +for i in range(10): + time.sleep(1) + all_open = True + for port in [18081, 18083]: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + if s.connect_ex(('127.0.0.1', port)) != 0: + all_open = False + break + s.close() + if all_open: + break + +if not all_open: + print('Failed to start wallet or daemon') + kill() + sys.exit(1) + +PASS = [] +FAIL = [] +for test in tests: + try: + print('[TEST STARTED] ' + test) + cmd = [python, srcdir + '/' + test + ".py"] + subprocess.check_call(cmd) + PASS.append(test) + print('[TEST PASSED] ' + test) + except: + FAIL.append(test) + print('[TEST FAILED] ' + test) + pass + +print('Stopping servers...') +kill() + +# wait for exit, the poll method does not work (https://bugs.python.org/issue2475) so we wait, possibly forever if the process hangs +if True: + wallet_process.wait() + monerod_process.wait() +else: + for i in range(10): + wallet_process.poll() + monerod_process.poll() + if wallet_process.returncode and monerod_process.returncode: + print('Both done: ' + str(wallet_process.returncode) + ' and ' + str(monerod_process.returncode)) + break + time.sleep(1) + if not wallet_process.returncode: + print('Failed to stop monero-wallet-rpc') + if not monerod_process.returncode: + print('Failed to stop monerod') + +if len(FAIL) == 0: + print('Done, ' + str(len(PASS)) + '/' + str(len(tests)) + ' tests passed') +else: + print('Done, ' + str(len(FAIL)) + '/' + str(len(tests)) + ' tests failed: ' + string.join(FAIL, ', ')) From b384309e17544a57e6fd54f3eee15f94775c7140 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sat, 16 Mar 2019 14:24:43 +0000 Subject: [PATCH 05/18] functional_tests: add basic transfer tests --- .../functional_tests/functional_tests_rpc.py | 2 +- .../functional_tests/test_framework/wallet.py | 81 ++++- tests/functional_tests/transfer.py | 291 ++++++++++++++++++ 3 files changed, 360 insertions(+), 14 deletions(-) create mode 100755 tests/functional_tests/transfer.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 5e1ee34e6..4bc77fe19 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'mining'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'mining', 'transfer'] try: python = sys.argv[1] srcdir = sys.argv[2] diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 39db08804..7756e37dd 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -47,38 +47,59 @@ class Wallet(object): destinations.append({'amount':transfer_amounts[i],'address':addresses[i]}) return destinations - def transfer(self, destinations, ringsize=7, payment_id=''): + def transfer(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False): transfer = { 'method': 'transfer', 'params': { 'destinations': destinations, - 'mixin' : ringsize - 1, - 'get_tx_key' : True + 'account_index': account_index, + 'subaddr_indices': subaddr_indices, + 'priority': priority, + 'ring_size' : ring_size, + 'unlock_time' : unlock_time, + 'payment_id' : payment_id, + 'get_tx_key' : get_tx_key, + 'do_not_relay' : do_not_relay, + 'get_tx_hex' : get_tx_hex, + 'get_tx_metadata' : get_tx_metadata, }, 'jsonrpc': '2.0', 'id': '0' } - if(len(payment_id) > 0): - transfer['params'].update({'payment_id' : payment_id}) return self.rpc.send_json_rpc_request(transfer) - def transfer_split(self, destinations, ringsize=7, payment_id=''): - print(destinations) + def transfer_split(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False): transfer = { "method": "transfer_split", "params": { - "destinations": destinations, - "mixin" : ringsize - 1, - "get_tx_key" : True, - "new_algorithm" : True + 'destinations': destinations, + 'account_index': account_index, + 'subaddr_indices': subaddr_indices, + 'priority': priority, + 'ring_size' : ring_size, + 'unlock_time' : unlock_time, + 'payment_id' : payment_id, + 'get_tx_key' : get_tx_key, + 'do_not_relay' : do_not_relay, + 'get_tx_hex' : get_tx_hex, + 'get_tx_metadata' : get_tx_metadata, }, "jsonrpc": "2.0", "id": "0" } - if(len(payment_id) > 0): - transfer['params'].update({'payment_id' : payment_id}) return self.rpc.send_json_rpc_request(transfer) + def describe_transfer(self, unsigned_txset): + describe_transfer = { + 'method': 'describe_transfer', + 'params': { + 'unsigned_txset': unsigned_txset, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(describe_transfer) + def create_wallet(self, index=''): create_wallet = { 'method': 'create_wallet', @@ -235,3 +256,37 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(refresh) + + def incoming_transfers(self, transfer_type='all', account_index = 0, subaddr_indices = []): + incoming_transfers = { + 'method': 'incoming_transfers', + 'params' : { + 'transfer_type': transfer_type, + 'account_index': account_index, + 'subaddr_indices': subaddr_indices, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(incoming_transfers) + + def get_transfers(self, in_ = True, out = True, pending = True, failed = True, pool = True, min_height = None, max_height = None, account_index = 0, subaddr_indices = [], all_accounts = False): + get_transfers = { + 'method': 'get_transfers', + 'params' : { + 'in': in_, + 'out': out, + 'pending': pending, + 'failed': failed, + 'pool': pool, + 'min_height': min_height, + 'max_height': max_height, + 'filter_by_height': min_height or max_height, + 'account_index': account_index, + 'subaddr_indices': subaddr_indices, + 'all_accounts': all_accounts, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_transfers) diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py new file mode 100755 index 000000000..de86c9daf --- /dev/null +++ b/tests/functional_tests/transfer.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test simple transfers +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class TransferTest(): + def run_test(self): + self.create(0) + self.mine() + self.transfer() + + def create(self, idx): + print 'Creating wallet' + wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: wallet.close_wallet() + except: pass + seeds = [ + 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted', + 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout', + 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', + ] + res = wallet.restore_deterministic_wallet(seed = seeds[idx]) + + def mine(self): + print("Mining some blocks") + daemon = Daemon() + wallet = Wallet() + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + wallet.refresh() + + def transfer(self): + daemon = Daemon() + wallet = Wallet() + + print("Creating transfer to self") + + dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + + payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde' + + res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert len(res.tx_key) == 0 + assert res.amount > 0 + amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) == 0 + assert len(res.unsigned_txset) == 0 + unsigned_txset = res.unsigned_txset + + wallet.refresh() + + res = daemon.get_info() + height = res.height + + res = wallet.get_transfers() + assert len(res['in']) == height - 1 # coinbases + assert not 'out' in res or len(res.out) == 0 # not mined yet + assert len(res.pending) == 1 + assert not 'pool' in res or len(res.pool) == 0 + assert not 'failed' in res or len(res.failed) == 0 + for e in res['in']: + assert e.type == 'block' + e = res.pending[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'pending' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.double_spend_seen == False + assert e.confirmations == 0 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + wallet.refresh() + + res = wallet.get_transfers() + assert len(res['in']) == height # coinbases + assert len(res.out) == 1 # not mined yet + assert not 'pending' in res or len(res.pending) == 0 + assert not 'pool' in res or len(res.pool) == 0 + assert not 'failed' in res or len(res.failed) == 0 + for e in res['in']: + assert e.type == 'block' + e = res.out[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'out' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.double_spend_seen == False + assert e.confirmations == 1 + + print("Creating transfer to another") + + dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000} + res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert len(res.tx_key) == 32*2 + assert res.amount == 1000000000000 + amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) == 0 + assert len(res.unsigned_txset) == 0 + unsigned_txset = res.unsigned_txset + + self.create(1) + wallet.refresh() + + res = wallet.get_transfers() + assert not 'in' in res or len(res['in']) == 0 + assert not 'out' in res or len(res.out) == 0 + assert not 'pending' in res or len(res.pending) == 0 + assert len(res.pool) == 1 + assert not 'failed' in res or len(res.failed) == 0 + e = res.pool[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'pool' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + assert e.double_spend_seen == False + assert e.confirmations == 0 + assert e.amount == amount + assert e.fee == fee + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + wallet.refresh() + + res = wallet.get_transfers() + assert len(res['in']) == 1 + assert not 'out' in res or len(res.out) == 0 + assert not 'pending' in res or len(res.pending) == 0 + assert not 'pool' in res or len(res.pool) == 0 + assert not 'failed' in res or len(res.failed) == 0 + e = res['in'][0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'in' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + assert e.double_spend_seen == False + assert e.confirmations == 1 + assert e.amount == amount + assert e.fee == fee + + print 'Creating multi out transfer' + + self.create(0) + wallet.refresh() + + dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000} + dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000} + res = wallet.transfer([dst0, dst1, dst2], ring_size = 11, payment_id = payment_id, get_tx_key = True) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert len(res.tx_key) == 32*2 + assert res.amount == 1000000000000 + 1100000000000 + 1200000000000 + amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) == 0 + assert len(res.unsigned_txset) == 0 + unsigned_txset = res.unsigned_txset + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + wallet.refresh() + + res = wallet.get_transfers() + assert len(res['in']) == height + 2 + assert len(res.out) == 3 + assert not 'pending' in res or len(res.pending) == 0 + assert not 'pool' in res or len(res.pool) == 1 + assert not 'failed' in res or len(res.failed) == 0 + e = [o for o in res.out if o.txid == txid] + assert len(e) == 1 + e = e[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'out' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert e.double_spend_seen == False + assert e.confirmations == 1 + + assert e.amount == amount + assert e.fee == fee + + self.create(1) + wallet.refresh() + res = wallet.get_transfers() + assert len(res['in']) == 2 + assert not 'out' in res or len(res.out) == 0 + assert not 'pending' in res or len(res.pending) == 0 + assert not 'pool' in res or len(res.pool) == 0 + assert not 'failed' in res or len(res.failed) == 0 + e = [o for o in res['in'] if o.txid == txid] + assert len(e) == 1 + e = e[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'in' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + assert e.double_spend_seen == False + assert e.confirmations == 1 + assert e.amount == 1100000000000 + assert e.fee == fee + + self.create(2) + wallet.refresh() + res = wallet.get_transfers() + assert len(res['in']) == 1 + assert not 'out' in res or len(res.out) == 0 + assert not 'pending' in res or len(res.pending) == 0 + assert not 'pool' in res or len(res.pool) == 0 + assert not 'failed' in res or len(res.failed) == 0 + e = [o for o in res['in'] if o.txid == txid] + assert len(e) == 1 + e = e[0] + assert e.txid == txid + assert e.payment_id == payment_id + assert e.type == 'in' + assert e.unlock_time == 0 + assert e.subaddr_index.major == 0 + assert e.subaddr_indices == [{'major': 0, 'minor': 0}] + assert e.address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK' + assert e.double_spend_seen == False + assert e.confirmations == 1 + assert e.amount == 1200000000000 + assert e.fee == fee + +if __name__ == '__main__': + TransferTest().run_test() From 3e93c157bd8e3e8113cce97cffd736e5e63589ea Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 17 Mar 2019 11:24:33 +0000 Subject: [PATCH 06/18] functional_tests: add integrated address tests --- .../functional_tests/functional_tests_rpc.py | 2 +- tests/functional_tests/integrated_address.py | 101 ++++++++++++++++++ .../functional_tests/test_framework/wallet.py | 23 ++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100755 tests/functional_tests/integrated_address.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 4bc77fe19..67c80bfa5 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'mining', 'transfer'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer'] try: python = sys.argv[1] srcdir = sys.argv[2] diff --git a/tests/functional_tests/integrated_address.py b/tests/functional_tests/integrated_address.py new file mode 100755 index 000000000..d6976a116 --- /dev/null +++ b/tests/functional_tests/integrated_address.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test integrated address RPC calls + +Test the following RPCs: + - make_integrated_address + - split_integrated_address + +""" + +from test_framework.wallet import Wallet + +class IntegratedAddressTest(): + def run_test(self): + self.create() + self.check() + + def create(self): + print 'Creating wallet' + wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: wallet.close_wallet() + except: pass + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = wallet.restore_deterministic_wallet(seed = seed) + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res.seed == seed + + def check(self): + wallet = Wallet() + + print 'Checking local address' + res = wallet.make_integrated_address(payment_id = '0123456789abcdef') + assert res.integrated_address == '4CMe2PUhs4J4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfSbLRB61BQVATzerHGj' + assert res.payment_id == '0123456789abcdef' + res = wallet.split_integrated_address(res.integrated_address) + assert res.standard_address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res.payment_id == '0123456789abcdef' + + print 'Checking different address' + res = wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788') + assert res.integrated_address == '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo' + assert res.payment_id == '1122334455667788' + res = wallet.split_integrated_address(res.integrated_address) + assert res.standard_address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK' + assert res.payment_id == '1122334455667788' + + print 'Checking bad payment id' + fails = 0 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '11223344556677880') + except: fails += 1 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778') + except: fails += 1 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '') + except: fails += 1 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778g') + except: fails += 1 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788112233445566778811223344556677881122334455667788') + except: fails += 1 + assert fails == 5 + + print 'Checking bad standard address' + fails = 0 + try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerr', payment_id = '1122334455667788') + except: fails += 1 + try: wallet.make_integrated_address(standard_address = '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo', payment_id = '1122334455667788') + except: fails += 1 + assert fails == 2 + +if __name__ == '__main__': + IntegratedAddressTest().run_test() diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 7756e37dd..e4f352e1d 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -290,3 +290,26 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(get_transfers) + + def make_integrated_address(self, standard_address = '', payment_id = ''): + make_integrated_address = { + 'method': 'make_integrated_address', + 'params' : { + 'standard_address': standard_address, + 'payment_id': payment_id, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(make_integrated_address) + + def split_integrated_address(self, integrated_address): + split_integrated_address = { + 'method': 'split_integrated_address', + 'params' : { + 'integrated_address': integrated_address, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(split_integrated_address) From 9e979ffa22b20bc3c11f508ae09647922bf18952 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Sun, 17 Mar 2019 14:15:41 +0000 Subject: [PATCH 07/18] functional_tests: add txpool RPC tests --- .../functional_tests/functional_tests_rpc.py | 2 +- .../functional_tests/test_framework/daemon.py | 21 +++ tests/functional_tests/txpool.py | 156 ++++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100755 tests/functional_tests/txpool.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 67c80bfa5..845781a49 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool'] try: python = sys.argv[1] srcdir = sys.argv[2] diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py index 6088fc0df..ee1fcc0fc 100644 --- a/tests/functional_tests/test_framework/daemon.py +++ b/tests/functional_tests/test_framework/daemon.py @@ -168,3 +168,24 @@ class Daemon(object): mining_status = { } return self.rpc.send_request('/mining_status', mining_status) + + def get_transaction_pool(self): + get_transaction_pool = { + } + return self.rpc.send_request('/get_transaction_pool', get_transaction_pool) + + def get_transaction_pool_hashes(self): + get_transaction_pool_hashes = { + } + return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes) + + def flush_txpool(self, txids = []): + flush_txpool = { + 'method': 'flush_txpool', + 'params': { + 'txids': txids + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(flush_txpool) diff --git a/tests/functional_tests/txpool.py b/tests/functional_tests/txpool.py new file mode 100755 index 000000000..419bd925c --- /dev/null +++ b/tests/functional_tests/txpool.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test txpool +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class TransferTest(): + def run_test(self): + self.create() + self.mine() + self.check_txpool() + + def create(self): + print 'Creating wallet' + wallet = Wallet() + # close the wallet if any, will throw if none is loaded + try: wallet.close_wallet() + except: pass + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = wallet.restore_deterministic_wallet(seed = seed) + + def mine(self): + print("Mining some blocks") + daemon = Daemon() + wallet = Wallet() + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + wallet.refresh() + + def create_txes(self, address, ntxes): + print('Creating ' + str(ntxes) + ' transactions') + + daemon = Daemon() + wallet = Wallet() + + dst = {'address': address, 'amount': 1000000000000} + + txes = {} + for i in range(ntxes): + res = wallet.transfer([dst], get_tx_hex = True) + txes[res.tx_hash] = res + + return txes + + def check_txpool(self): + daemon = Daemon() + wallet = Wallet() + + res = daemon.get_info() + height = res.height + txpool_size = res.tx_pool_size + + txes = self.create_txes('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 5) + + res = daemon.get_info() + assert res.tx_pool_size == txpool_size + 5 + txpool_size = res.tx_pool_size + + res = daemon.get_transaction_pool() + assert len(res.transactions) == txpool_size + for txid in txes.keys(): + x = [x for x in res.transactions if x.id_hash == txid] + assert len(x) == 1 + x = x[0] + assert x.kept_by_block == False + assert x.last_failed_id_hash == '0'*64 + assert x.double_spend_seen == False + assert x.weight >= x.blob_size + + assert x.blob_size * 2 == len(txes[txid].tx_blob) + assert x.fee == txes[txid].fee + assert x.tx_blob == txes[txid].tx_blob + + res = daemon.get_transaction_pool_hashes() + assert sorted(res.tx_hashes) == sorted(txes.keys()) + + print('Flushing 2 transactions') + daemon.flush_txpool([txes.keys()[1], txes.keys()[3]]) + res = daemon.get_transaction_pool() + assert len(res.transactions) == txpool_size - 2 + assert len([x for x in res.transactions if x.id_hash == txes.keys()[1]]) == 0 + assert len([x for x in res.transactions if x.id_hash == txes.keys()[3]]) == 0 + + new_keys = txes.keys() + new_keys.remove(txes.keys()[1]) + new_keys.remove(txes.keys()[3]) + res = daemon.get_transaction_pool_hashes() + assert sorted(res.tx_hashes) == sorted(new_keys) + + print('Flushing unknown transactions') + unknown_txids = ['1'*64, '2'*64, '3'*64] + daemon.flush_txpool(unknown_txids) + res = daemon.get_transaction_pool() + assert len(res.transactions) == txpool_size - 2 + + print('Mining transactions') + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + res = daemon.get_transaction_pool() + assert not 'transactions' in res or len(res.transactions) == txpool_size - 5 + res = daemon.get_transaction_pool_hashes() + assert not 'tx_hashes' in res or len(res.tx_hashes) == 0 + + print('Popping block') + daemon.pop_blocks(1) + res = daemon.get_transaction_pool_hashes() + assert sorted(res.tx_hashes) == sorted(new_keys) + res = daemon.get_transaction_pool() + assert len(res.transactions) == txpool_size - 2 + for txid in new_keys: + x = [x for x in res.transactions if x.id_hash == txid] + assert len(x) == 1 + x = x[0] + assert x.kept_by_block == True + assert x.last_failed_id_hash == '0'*64 + assert x.double_spend_seen == False + assert x.weight >= x.blob_size + + assert x.blob_size * 2 == len(txes[txid].tx_blob) + assert x.fee == txes[txid].fee + assert x.tx_blob == txes[txid].tx_blob + + +if __name__ == '__main__': + TransferTest().run_test() From b2fc57194392eef601287f77147c679acb2f63c9 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Mon, 18 Mar 2019 11:53:47 +0000 Subject: [PATCH 08/18] functional_tests: support several daemons/wallets --- .../functional_tests/functional_tests_rpc.py | 63 +++++++++++-------- .../functional_tests/test_framework/daemon.py | 4 +- .../functional_tests/test_framework/wallet.py | 4 +- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 845781a49..db48844f3 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -33,35 +33,46 @@ try: except: tests = DEFAULT_TESTS -monerod = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"] -wallet = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "18083", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--log-level", "1"] +N_MONERODS = 1 +N_WALLETS = 3 -monerod_output = open(builddir + '/tests/functional_tests/monerod.log', 'a+') -wallet_output = open(builddir + '/tests/functional_tests/wallet.log', 'a+') +monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"] +wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"] + +command_lines = [] +processes = [] +outputs = [] +ports = [] + +for i in range(N_MONERODS): + command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else x for x in monerod_base]) + outputs.append(open(builddir + '/tests/functional_tests/monerod' + str(i) + '.log', 'a+')) + ports.append(18180+i) + +for i in range(N_WALLETS): + command_lines.append([str(18090+i) if x == "wallet_port" else x for x in wallet_base]) + outputs.append(open(builddir + '/tests/functional_tests/wallet' + str(i) + '.log', 'a+')) + ports.append(18090+i) print('Starting servers...') -monerod_process = None -wallet_process = None try: - #print 'Running: ' + str(monerod) - monerod_process = subprocess.Popen(monerod, stdout = monerod_output) - #print 'Running: ' + str(wallet) - wallet_process = subprocess.Popen(wallet, stdout = wallet_output) + for i in range(len(command_lines)): + #print('Running: ' + str(command_lines[i])) + processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i])) except Exception, e: print('Error: ' + str(e)) sys.exit(1) def kill(): - try: wallet_process.send_signal(SIGTERM) - except: pass - try: monerod_process.send_signal(SIGTERM) - except: pass + for i in range(len(processes)): + try: processes[i].send_signal(SIGTERM) + except: pass # wait for error/startup for i in range(10): time.sleep(1) all_open = True - for port in [18081, 18083]: + for port in ports: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(1) if s.connect_ex(('127.0.0.1', port)) != 0: @@ -95,20 +106,22 @@ kill() # wait for exit, the poll method does not work (https://bugs.python.org/issue2475) so we wait, possibly forever if the process hangs if True: - wallet_process.wait() - monerod_process.wait() + for p in processes: + p.wait() else: for i in range(10): - wallet_process.poll() - monerod_process.poll() - if wallet_process.returncode and monerod_process.returncode: - print('Both done: ' + str(wallet_process.returncode) + ' and ' + str(monerod_process.returncode)) + n_returncode = 0 + for p in processes: + p.poll() + if p.returncode: + n_returncode += 1 + if n_returncode == len(processes): + print('All done: ' + string.join([x.returncode for x in processes], ', ')) break time.sleep(1) - if not wallet_process.returncode: - print('Failed to stop monero-wallet-rpc') - if not monerod_process.returncode: - print('Failed to stop monerod') + for p in processes: + if not p.returncode: + print('Failed to stop process') if len(FAIL) == 0: print('Done, ' + str(len(PASS)) + '/' + str(len(tests)) + ' tests passed') diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py index ee1fcc0fc..0d0eda7dc 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): - self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port)) + def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx)) def getblocktemplate(self, address): getblocktemplate = { diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index e4f352e1d..4f2997cc4 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): - self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port)) + def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18090+idx)) def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1): destinations = [] From a5dbf7f5faf9013b3cc73e9b6d1fed93ed9c6805 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 19 Mar 2019 01:11:35 +0000 Subject: [PATCH 09/18] functional_tests: add multisig and cold signing tests --- tests/functional_tests/cold_signing.py | 146 +++++++++++ .../functional_tests/functional_tests_rpc.py | 4 +- tests/functional_tests/multisig.py | 227 ++++++++++++++++++ .../functional_tests/test_framework/wallet.py | 169 +++++++++++++ 4 files changed, 544 insertions(+), 2 deletions(-) create mode 100755 tests/functional_tests/cold_signing.py create mode 100755 tests/functional_tests/multisig.py diff --git a/tests/functional_tests/cold_signing.py b/tests/functional_tests/cold_signing.py new file mode 100755 index 000000000..2acdcb137 --- /dev/null +++ b/tests/functional_tests/cold_signing.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test cold tx signing +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class ColdSigningTest(): + def run_test(self): + self.create(0) + self.mine() + self.transfer() + + def create(self, idx): + print 'Creating hot and cold wallet' + + self.hot_wallet = Wallet(idx = 0) + # close the wallet if any, will throw if none is loaded + try: self.hot_wallet.close_wallet() + except: pass + + self.cold_wallet = Wallet(idx = 1) + # close the wallet if any, will throw if none is loaded + try: self.cold_wallet.close_wallet() + except: pass + + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = self.cold_wallet.restore_deterministic_wallet(seed = seed) + self.cold_wallet.set_daemon('127.0.0.1:11111', ssl_support = "disabled") + spend_key = self.cold_wallet.query_key("spend_key").key + view_key = self.cold_wallet.query_key("view_key").key + res = self.hot_wallet.generate_from_keys(viewkey = view_key, address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm') + + ok = False + try: res = self.hot_wallet.query_key("spend_key") + except: ok = True + assert ok + ok = False + try: self.hot_wallet.query_key("mnemonic") + except: ok = True + assert ok + assert self.cold_wallet.query_key("view_key").key == view_key + assert self.cold_wallet.get_address().address == self.hot_wallet.get_address().address + + def mine(self): + print("Mining some blocks") + daemon = Daemon() + wallet = Wallet() + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + wallet.refresh() + + def transfer(self): + daemon = Daemon() + + print("Creating transaction in hot wallet") + + dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde' + + res = self.hot_wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert len(res.tx_key) == 0 + assert res.amount > 0 + amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) == 0 + assert len(res.unsigned_txset) > 0 + unsigned_txset = res.unsigned_txset + + print 'Signing transaction with cold wallet' + res = self.cold_wallet.sign_transfer(unsigned_txset) + assert len(res.signed_txset) > 0 + signed_txset = res.signed_txset + assert len(res.tx_hash_list) == 1 + txid = res.tx_hash_list[0] + assert len(txid) == 64 + + print 'Submitting transaction with hot wallet' + res = self.hot_wallet.submit_transfer(signed_txset) + assert len(res.tx_hash_list) > 0 + assert res.tx_hash_list[0] == txid + + res = self.hot_wallet.get_transfers() + assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 1 + assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + self.hot_wallet.refresh() + + res = self.hot_wallet.get_transfers() + assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0 + assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1 + + res = self.hot_wallet.get_tx_key(txid) + assert len(res.tx_key) == 0 or res.tx_key == '01' + '0' * 62 # identity is used as placeholder + res = self.cold_wallet.get_tx_key(txid) + assert len(res.tx_key) == 64 + + +class Guard: + def __enter__(self): + for i in range(2): + Wallet(idx = i).auto_refresh(False) + def __exit__(self, exc_type, exc_value, traceback): + for i in range(2): + Wallet(idx = i).auto_refresh(True) + +if __name__ == '__main__': + with Guard() as guard: + cs = ColdSigningTest().run_test() diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index db48844f3..abec0eef0 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing'] try: python = sys.argv[1] srcdir = sys.argv[2] @@ -34,7 +34,7 @@ except: tests = DEFAULT_TESTS N_MONERODS = 1 -N_WALLETS = 3 +N_WALLETS = 4 monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"] wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"] diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py new file mode 100755 index 000000000..7cdddd009 --- /dev/null +++ b/tests/functional_tests/multisig.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test multisig transfers +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class MultisigTest(): + def run_test(self): + self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5) + self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5) + self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5) + self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5) + self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60) + + self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk') + self.import_multisig_info([1, 0], 5) + txid = self.transfer([1, 0]) + self.import_multisig_info([0, 1], 6) + self.check_transaction(txid) + + self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y') + self.import_multisig_info([0, 2], 5) + txid = self.transfer([0, 2]) + self.import_multisig_info([0, 1, 2], 6) + self.check_transaction(txid) + + self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53') + self.import_multisig_info([0, 2, 3], 5) + txid = self.transfer([0, 2, 3]) + self.import_multisig_info([0, 1, 2, 3], 6) + self.check_transaction(txid) + + self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR') + self.import_multisig_info([1, 2], 5) + txid = self.transfer([1, 2]) + self.import_multisig_info([0, 1, 2, 3], 6) + self.check_transaction(txid) + + def mine(self, address, blocks): + print("Mining some blocks") + daemon = Daemon() + daemon.generateblocks(address, blocks) + + def create_multisig_wallets(self, M_threshold, N_total, expected_address): + print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet') + seeds = [ + 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted', + 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout', + 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', + 'waking gown buffet negative reorder speedy baffles hotel pliers dewdrop actress diplomat lymph emit ajar mailed kennel cynical jaunt justice weavers height teardrop toyed lymph', + ] + assert M_threshold <= N_total + assert N_total <= len(seeds) + self.wallet = [None] * N_total + info = [] + for i in range(N_total): + self.wallet[i] = Wallet(idx = i) + try: self.wallet[i].close_wallet() + except: pass + res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) + res = self.wallet[i].prepare_multisig() + assert len(res.multisig_info) > 0 + info.append(res.multisig_info) + + for i in range(N_total): + res = self.wallet[i].is_multisig() + assert res.multisig == False + + addresses = [] + next_stage = [] + for i in range(N_total): + res = self.wallet[i].make_multisig(info, M_threshold) + addresses.append(res.address) + next_stage.append(res.multisig_info) + + for i in range(N_total): + res = self.wallet[i].is_multisig() + assert res.multisig == True + assert res.ready == (M_threshold == N_total) + assert res.threshold == M_threshold + assert res.total == N_total + + while True: + n_empty = 0 + for i in range(len(next_stage)): + if len(next_stage[i]) == 0: + n_empty += 1 + assert n_empty == 0 or n_empty == len(next_stage) + if n_empty == len(next_stage): + break + info = next_stage + next_stage = [] + addresses = [] + for i in range(N_total): + res = self.wallet[i].exchange_multisig_keys(info) + next_stage.append(res.multisig_info) + addresses.append(res.address) + for i in range(N_total): + assert addresses[i] == expected_address + + for i in range(N_total): + res = self.wallet[i].is_multisig() + assert res.multisig == True + assert res.ready == True + assert res.threshold == M_threshold + assert res.total == N_total + + + def import_multisig_info(self, signers, expected_outputs): + assert len(signers) >= 2 + + print('Importing multisig info from ' + str(signers)) + + info = [] + for i in signers: + self.wallet[i].refresh() + res = self.wallet[i].export_multisig_info() + assert len(res.info) > 0 + info.append(res.info) + for i in signers: + res = self.wallet[i].import_multisig_info(info) + assert res.n_outputs == expected_outputs + + def transfer(self, signers): + assert len(signers) >= 2 + + daemon = Daemon() + + print("Creating multisig transaction from wallet " + str(signers[0])) + + dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + res = self.wallet[signers[0]].transfer([dst]) + assert len(res.tx_hash) == 0 # not known yet + txid = res.tx_hash + assert len(res.tx_key) == 32*2 + assert res.amount > 0 + amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) > 0 + assert len(res.unsigned_txset) == 0 + multisig_txset = res.multisig_txset + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + for i in range(len(self.wallet)): + self.wallet[i].refresh() + + for i in range(len(signers[1:])): + print('Signing multisig transaction with wallet ' + str(signers[i+1])) + res = self.wallet[signers[i+1]].sign_multisig(multisig_txset) + multisig_txset = res.tx_data_hex + assert len(res.tx_hash_list if 'tx_hash_list' in res else []) == (i == len(signers[1:]) - 1) + + if i < len(signers[1:]) - 1: + print('Submitting multisig transaction prematurely with wallet ' + str(signers[-1])) + ok = False + try: self.wallet[signers[-1]].submit_multisig(multisig_txset) + except: ok = True + assert ok + + print('Submitting multisig transaction with wallet ' + str(signers[-1])) + res = self.wallet[signers[-1]].submit_multisig(multisig_txset) + assert len(res.tx_hash_list) == 1 + txid = res.tx_hash_list[0] + + for i in range(len(self.wallet)): + self.wallet[i].refresh() + res = self.wallet[i].get_transfers() + assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == (1 if i == signers[-1] else 0) + assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + return txid + + def check_transaction(self, txid): + for i in range(len(self.wallet)): + self.wallet[i].refresh() + res = self.wallet[i].get_transfers() + assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0 + assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1 + + +class Guard: + def __enter__(self): + for i in range(4): + Wallet(idx = i).auto_refresh(False) + def __exit__(self, exc_type, exc_value, traceback): + for i in range(4): + Wallet(idx = i).auto_refresh(True) + +if __name__ == '__main__': + with Guard() as guard: + MultisigTest().run_test() diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 4f2997cc4..270c95f82 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -237,6 +237,22 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(restore_deterministic_wallet) + def generate_from_keys(self, restore_height = 0, filename = "", password = "", address = "", spendkey = "", viewkey = ""): + generate_from_keys = { + 'method': 'generate_from_keys', + 'params' : { + 'restore_height': restore_height, + 'filename': filename, + 'address': address, + 'spendkey': spendkey, + 'viewkey': viewkey, + 'password': password, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(generate_from_keys) + def close_wallet(self): close_wallet = { 'method': 'close_wallet', @@ -313,3 +329,156 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(split_integrated_address) + + def auto_refresh(self, enable, period = 0): + auto_refresh = { + 'method': 'auto_refresh', + 'params' : { + 'enable': enable, + 'period': period + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(auto_refresh) + + def set_daemon(self, address, trusted = False, ssl_support = "autodetect", ssl_private_key_path = "", ssl_certificate_path = "", ssl_allowed_certificates = [], ssl_allowed_fingerprints = [], ssl_allow_any_cert = False): + set_daemon = { + 'method': 'set_daemon', + 'params' : { + 'address': address, + 'trusted': trusted, + 'ssl_support': ssl_support, + 'ssl_private_key_path': ssl_private_key_path, + 'ssl_certificate_path': ssl_certificate_path, + 'ssl_allowed_certificates': ssl_allowed_certificates, + 'ssl_allowed_fingerprints': ssl_allowed_fingerprints, + 'ssl_allow_any_cert': ssl_allow_any_cert, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(set_daemon) + + def is_multisig(self): + is_multisig = { + 'method': 'is_multisig', + 'params' : { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(is_multisig) + + def prepare_multisig(self): + prepare_multisig = { + 'method': 'prepare_multisig', + 'params' : { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(prepare_multisig) + + def make_multisig(self, multisig_info, threshold, password = ''): + make_multisig = { + 'method': 'make_multisig', + 'params' : { + 'multisig_info': multisig_info, + 'threshold': threshold, + 'password': password, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(make_multisig) + + def exchange_multisig_keys(self, multisig_info, password = ''): + exchange_multisig_keys = { + 'method': 'exchange_multisig_keys', + 'params' : { + 'multisig_info': multisig_info, + 'password': password, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(exchange_multisig_keys) + + def export_multisig_info(self): + export_multisig_info = { + 'method': 'export_multisig_info', + 'params' : { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(export_multisig_info) + + def import_multisig_info(self, info = []): + import_multisig_info = { + 'method': 'import_multisig_info', + 'params' : { + 'info': info + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(import_multisig_info) + + def sign_multisig(self, tx_data_hex): + sign_multisig = { + 'method': 'sign_multisig', + 'params' : { + 'tx_data_hex': tx_data_hex + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(sign_multisig) + + def submit_multisig(self, tx_data_hex): + submit_multisig = { + 'method': 'submit_multisig', + 'params' : { + 'tx_data_hex': tx_data_hex + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(submit_multisig) + + def sign_transfer(self, unsigned_txset, export_raw = False, get_tx_keys = False): + sign_transfer = { + 'method': 'sign_transfer', + 'params' : { + 'unsigned_txset': unsigned_txset, + 'export_raw': export_raw, + 'get_tx_keys': get_tx_keys, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(sign_transfer) + + def submit_transfer(self, tx_data_hex): + submit_transfer = { + 'method': 'submit_transfer', + 'params' : { + 'tx_data_hex': tx_data_hex, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(submit_transfer) + + def get_tx_key(self, txid): + get_tx_key = { + 'method': 'get_tx_key', + 'params' : { + 'txid': txid, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_tx_key) From 0becbd16123f05607fe6b759591c13fb05ac9d3d Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 19 Mar 2019 10:39:26 +0000 Subject: [PATCH 10/18] functional_tests: add message signing/verification tests --- .../functional_tests/functional_tests_rpc.py | 2 +- tests/functional_tests/sign_message.py | 85 +++++++++++++++++++ .../functional_tests/test_framework/wallet.py | 24 ++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100755 tests/functional_tests/sign_message.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index abec0eef0..d7f4da2d7 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message'] try: python = sys.argv[1] srcdir = sys.argv[2] diff --git a/tests/functional_tests/sign_message.py b/tests/functional_tests/sign_message.py new file mode 100755 index 000000000..4a62fd733 --- /dev/null +++ b/tests/functional_tests/sign_message.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test message signing/verification RPC calls + +Test the following RPCs: + - sign + - verify + +""" + +from test_framework.wallet import Wallet + +class MessageSigningTest(): + def run_test(self): + self.create() + self.check_signing() + + def create(self): + print 'Creating wallets' + seeds = [ + 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted', + 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout', + ] + self.address = [ + '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', + '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', + ] + self.wallet = [None, None] + for i in range(2): + self.wallet[i] = Wallet(idx = i) + # close the wallet if any, will throw if none is loaded + try: self.wallet[i].close_wallet() + except: pass + res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) + assert res.address == self.address[i] + assert res.seed == seeds[i] + + def check_signing(self): + print 'Signing/verifing messages' + messages = ['foo', ''] + for message in messages: + res = self.wallet[0].sign(message) + signature = res.signature + for i in range(2): + res = self.wallet[i].verify(message, self.address[0], signature) + assert res.good + res = self.wallet[i].verify('different', self.address[0], signature) + assert not res.good + res = self.wallet[i].verify(message, self.address[1], signature) + assert not res.good + res = self.wallet[i].verify(message, self.address[0], signature + 'x') + assert not res.good + +if __name__ == '__main__': + MessageSigningTest().run_test() diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 270c95f82..340eed179 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -482,3 +482,27 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(get_tx_key) + + def sign(self, data): + sign = { + 'method': 'sign', + 'params' : { + 'data': data, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(sign) + + def verify(self, data, address, signature): + verify = { + 'method': 'verify', + 'params' : { + 'data': data, + 'address': address, + 'signature': signature, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(verify) From 5d580bfa9c55690e056ce3148cbeb27cd1635a8f Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 19 Mar 2019 13:20:42 +0000 Subject: [PATCH 11/18] functional_tests: add get_bulk_transfer tests --- .../functional_tests/test_framework/wallet.py | 12 ++++++ tests/functional_tests/transfer.py | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 340eed179..7b83944e4 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -89,6 +89,18 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(transfer) + def get_bulk_payments(self, payment_ids = [], min_block_height = 0): + get_bulk_payments = { + 'method': 'get_bulk_payments', + 'params': { + 'payment_ids': payment_ids, + 'min_block_height': min_block_height, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_bulk_payments) + def describe_transfer(self, unsigned_txset): describe_transfer = { 'method': 'describe_transfer', diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index de86c9daf..4599a7f49 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -41,6 +41,7 @@ class TransferTest(): self.create(0) self.mine() self.transfer() + self.check_get_bulk_payments() def create(self, idx): print 'Creating wallet' @@ -287,5 +288,41 @@ class TransferTest(): assert e.amount == 1200000000000 assert e.fee == fee + def check_get_bulk_payments(self): + print('Checking get_bulk_payments') + + daemon = Daemon() + res = daemon.get_info() + height = res.height + + wallet = Wallet() + + self.create(0) + wallet.refresh() + res = wallet.get_bulk_payments() + assert len(res.payments) >= 83 # at least 83 coinbases + res = wallet.get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + assert 'payments' not in res or len(res.payments) == 0 + res = wallet.get_bulk_payments(min_block_height = height) + assert 'payments' not in res or len(res.payments) == 0 + res = wallet.get_bulk_payments(min_block_height = height - 40) + assert len(res.payments) >= 39 # coinbases + + self.create(1) + wallet.refresh() + res = wallet.get_bulk_payments() + assert len(res.payments) >= 2 # two txes were sent + res = wallet.get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + assert len(res.payments) >= 2 # two txes were sent with that payment id + res = wallet.get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff']) + assert 'payments' not in res or len(res.payments) == 0 # none with that payment id + + self.create(2) + wallet.refresh() + res = wallet.get_bulk_payments() + assert len(res.payments) >= 1 # one tx was sent + res = wallet.get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64]) + assert len(res.payments) >= 1 # one tx was sent + if __name__ == '__main__': TransferTest().run_test() From a3144bd7c8dd428c523fcd0c4db521dc7f4cf3c2 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Tue, 19 Mar 2019 16:21:06 +0000 Subject: [PATCH 12/18] functional_tests: add more transfer tests manual relay, transfer to integrated address, and a few negative transfer tests --- .../functional_tests/test_framework/daemon.py | 7 +++ tests/functional_tests/transfer.py | 59 +++++++++++++++++-- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py index 0d0eda7dc..11d5f5845 100644 --- a/tests/functional_tests/test_framework/daemon.py +++ b/tests/functional_tests/test_framework/daemon.py @@ -47,6 +47,13 @@ class Daemon(object): } return self.rpc.send_json_rpc_request(getblocktemplate) + def send_raw_transaction(self, tx_as_hex, do_not_relay = False): + send_raw_transaction = { + 'tx_as_hex': tx_as_hex, + 'do_not_relay': do_not_relay, + } + return self.rpc.send_request("/send_raw_transaction", send_raw_transaction) + def submitblock(self, block): submitblock = { 'method': 'submitblock', diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index 4599a7f49..9c38f0bd0 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -71,9 +71,20 @@ class TransferTest(): print("Creating transfer to self") dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} - payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde' + print ('Checking short payment IDs cannot be used when not in an integrated address') + ok = False + try: wallet.transfer([dst], ring_size = 11, payment_id = '1234567812345678', get_tx_key = False) + except: ok = True + assert ok + + print ('Checking empty destination is rejected') + ok = False + try: wallet.transfer([], ring_size = 11, get_tx_key = False) + except: ok = True + assert ok + res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) assert len(res.tx_hash) == 32*2 txid = res.tx_hash @@ -134,10 +145,10 @@ class TransferTest(): assert e.double_spend_seen == False assert e.confirmations == 1 - print("Creating transfer to another") + print("Creating transfer to another, manual relay") dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000} - res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True) + res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True, do_not_relay = True, get_tx_hex = True) assert len(res.tx_hash) == 32*2 txid = res.tx_hash assert len(res.tx_key) == 32*2 @@ -145,11 +156,22 @@ class TransferTest(): amount = res.amount assert res.fee > 0 fee = res.fee - assert len(res.tx_blob) == 0 + assert len(res.tx_blob) > 0 assert len(res.tx_metadata) == 0 assert len(res.multisig_txset) == 0 assert len(res.unsigned_txset) == 0 - unsigned_txset = res.unsigned_txset + tx_blob = res.tx_blob + + res = daemon.send_raw_transaction(tx_blob) + assert res.not_relayed == False + assert res.low_mixin == False + assert res.double_spend == False + assert res.invalid_input == False + assert res.invalid_output == False + assert res.too_big == False + assert res.overspend == False + assert res.fee_too_low == False + assert res.not_rct == False self.create(1) wallet.refresh() @@ -288,6 +310,29 @@ class TransferTest(): assert e.amount == 1200000000000 assert e.fee == fee + print('Sending to integrated address') + self.create(0) + wallet.refresh() + res = wallet.get_balance() + i_pid = '1111111122222222' + res = wallet.make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid) + i_address = res.integrated_address + res = wallet.transfer([{'address': i_address, 'amount': 200000000}]) + assert len(res.tx_hash) == 32*2 + i_txid = res.tx_hash + assert len(res.tx_key) == 32*2 + assert res.amount == 200000000 + i_amount = res.amount + assert res.fee > 0 + fee = res.fee + assert len(res.tx_blob) == 0 + assert len(res.tx_metadata) == 0 + assert len(res.multisig_txset) == 0 + assert len(res.unsigned_txset) == 0 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + + def check_get_bulk_payments(self): print('Checking get_bulk_payments') @@ -311,11 +356,13 @@ class TransferTest(): self.create(1) wallet.refresh() res = wallet.get_bulk_payments() - assert len(res.payments) >= 2 # two txes were sent + assert len(res.payments) >= 3 # two txes to standard address were sent, plus one to integrated address res = wallet.get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) assert len(res.payments) >= 2 # two txes were sent with that payment id res = wallet.get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff']) assert 'payments' not in res or len(res.payments) == 0 # none with that payment id + res = wallet.get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48]) + assert len(res.payments) >= 1 # one tx to integrated address self.create(2) wallet.refresh() From cf6d77596459da029814e880015e3ae316bc2b93 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 21 Mar 2019 00:21:28 +0000 Subject: [PATCH 13/18] functional_tests: add proofs tests (tx key, in/out tx, reserve) --- .../functional_tests/functional_tests_rpc.py | 2 +- tests/functional_tests/proofs.py | 282 ++++++++++++++++++ .../functional_tests/test_framework/wallet.py | 67 +++++ 3 files changed, 350 insertions(+), 1 deletion(-) create mode 100755 tests/functional_tests/proofs.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index d7f4da2d7..aae546a6b 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -9,7 +9,7 @@ import socket import string USAGE = 'usage: functional_tests_rpc.py [ | all]' -DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message'] +DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message', 'proofs'] try: python = sys.argv[1] srcdir = sys.argv[2] diff --git a/tests/functional_tests/proofs.py b/tests/functional_tests/proofs.py new file mode 100755 index 000000000..03d59b765 --- /dev/null +++ b/tests/functional_tests/proofs.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 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. + +import time + +"""Test misc proofs (tx key, send, receive, reserve) +""" + +from test_framework.daemon import Daemon +from test_framework.wallet import Wallet + +class ProofsTest(): + def run_test(self): + self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + self.create_wallets() + txid, tx_key, amount = self.transfer() + self.check_tx_key(txid, tx_key, amount) + self.check_tx_proof(txid, amount) + self.check_reserve_proof() + + def mine(self, address, blocks): + print("Mining some blocks") + daemon = Daemon() + daemon.generateblocks(address, blocks) + + def transfer(self): + print('Creating transaction') + self.wallet[0].refresh() + dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount':123456789000} + res = self.wallet[0].transfer([dst], get_tx_key = True) + assert len(res.tx_hash) == 64 + assert len(res.tx_key) == 64 + daemon = Daemon() + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + return (res.tx_hash, res.tx_key, 123456789000) + + def create_wallets(self): + print('Creating wallets') + seeds = [ + 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted', + 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout', + ] + self.wallet = [None, None] + for i in range(2): + self.wallet[i] = Wallet(idx = i) + try: self.wallet[i].close_wallet() + except: pass + res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) + + def check_tx_key(self, txid, tx_key, amount): + daemon = Daemon() + + print('Checking tx key') + + self.wallet[0].refresh() + self.wallet[1].refresh() + + sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + res = self.wallet[0].get_tx_key(txid) + assert res.tx_key == tx_key + res = self.wallet[0].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address) + assert res.received == amount + assert not res.in_pool + assert res.confirmations == 1 + res = self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address) + assert res.received == amount + assert not res.in_pool + assert res.confirmations == 1 + + self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = sending_address) + assert res.received >= 0 # might be change + assert not res.in_pool + assert res.confirmations == 1 + + ok = False + try: self.wallet[1].check_tx_key(txid = '0' * 64, tx_key = tx_key, address = receiving_address) + except: ok = True + assert ok + + res = self.wallet[1].check_tx_key(txid = txid, tx_key = '0' * 64, address = receiving_address) + assert res.received == 0 + assert not res.in_pool + assert res.confirmations == 1 + + def check_tx_proof(self, txid, amount): + daemon = Daemon() + + print('Checking tx proof') + + self.wallet[0].refresh() + self.wallet[1].refresh() + + sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo'); + assert res.signature.startswith('InProof'); + signature0i = res.signature + res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar'); + assert res.signature.startswith('OutProof'); + signature0o = res.signature + res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz'); + assert res.signature.startswith('InProof'); + signature1 = res.signature + + res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i); + assert res.good + assert res.received > 0 # likely change + assert not res.in_pool + assert res.confirmations == 1 + + ok = False + try: res = self.wallet[0].check_tx_proof('0' * 64, sending_address, 'foo', signature0i); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'foo', signature0i); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, sending_address, '', signature0i); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature1); + except: ok = True + assert ok or not res.good + + + res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o); + assert res.good + assert res.received == amount + assert not res.in_pool + assert res.confirmations == 1 + + ok = False + try: res = self.wallet[0].check_tx_proof('0' * 64, receiving_address, 'bar', signature0o); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'bar', signature0o); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, receiving_address, '', signature0o); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0i); + except: ok = True + assert ok or not res.good + + + res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1); + assert res.good + assert res.received == amount + assert not res.in_pool + assert res.confirmations == 1 + + ok = False + try: res = self.wallet[1].check_tx_proof('0' * 64, receiving_address, 'baz', signature1); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[1].check_tx_proof(txid, sending_address, 'baz', signature1); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[1].check_tx_proof(txid, receiving_address, '', signature1); + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature0o); + except: ok = True + assert ok or not res.good + + + def check_reserve_proof(self): + daemon = Daemon() + + print('Checking reserve proof') + + address0 = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + address1 = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + + self.wallet[0].refresh() + res = self.wallet[0].get_balance() + balance0 = res.balance + self.wallet[1].refresh() + res = self.wallet[1].get_balance() + balance1 = res.balance + + res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo') + assert res.signature.startswith('ReserveProof') + signature = res.signature + for i in range(2): + res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) + assert res.good + assert res.total == balance0 + + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature) + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature) + except: ok = True + assert ok or not res.good + + amount = int(balance0 / 10) + res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo') + assert res.signature.startswith('ReserveProof') + signature = res.signature + for i in range(2): + res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) + assert res.good + assert res.total >= amount and res.total <= balance0 + + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature) + except: ok = True + assert ok or not res.good + + ok = False + try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature) + except: ok = True + assert ok or not res.good + + ok = False + try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo') + except: ok = True + assert ok + + +class Guard: + def __enter__(self): + for i in range(4): + Wallet(idx = i).auto_refresh(False) + def __exit__(self, exc_type, exc_value, traceback): + for i in range(4): + Wallet(idx = i).auto_refresh(True) + +if __name__ == '__main__': + with Guard() as guard: + ProofsTest().run_test() diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 7b83944e4..7b19c4d55 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -495,6 +495,73 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(get_tx_key) + def check_tx_key(self, txid = '', tx_key = '', address = ''): + check_tx_key = { + 'method': 'check_tx_key', + 'params' : { + 'txid': txid, + 'tx_key': tx_key, + 'address': address, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(check_tx_key) + + def get_tx_proof(self, txid = '', address = '', message = ''): + get_tx_proof = { + 'method': 'get_tx_proof', + 'params' : { + 'txid': txid, + 'address': address, + 'message': message, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_tx_proof) + + def check_tx_proof(self, txid = '', address = '', message = '', signature = ''): + check_tx_proof = { + 'method': 'check_tx_proof', + 'params' : { + 'txid': txid, + 'address': address, + 'message': message, + 'signature': signature, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(check_tx_proof) + + def get_reserve_proof(self, all_ = True, account_index = 0, amount = 0, message = ''): + get_reserve_proof = { + 'method': 'get_reserve_proof', + 'params' : { + 'all': all_, + 'account_index': account_index, + 'amount': amount, + 'message': message, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_reserve_proof) + + def check_reserve_proof(self, address = '', message = '', signature = ''): + check_reserve_proof = { + 'method': 'check_reserve_proof', + 'params' : { + 'address': address, + 'message': message, + 'signature': signature, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(check_reserve_proof) + def sign(self, data): sign = { 'method': 'sign', From fdfa832f0004278d74b4ec479a743acd0bc2582c Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Thu, 21 Mar 2019 23:21:12 +0000 Subject: [PATCH 14/18] functional_tests: add missing parameters to get_balance --- tests/functional_tests/test_framework/wallet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py index 7b19c4d55..e9f13667b 100644 --- a/tests/functional_tests/test_framework/wallet.py +++ b/tests/functional_tests/test_framework/wallet.py @@ -125,9 +125,14 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(create_wallet) - def get_balance(self): + def get_balance(self, account_index = 0, address_indices = [], all_accounts = False): get_balance = { 'method': 'get_balance', + 'params': { + 'account_index': account_index, + 'address_indices': address_indices, + 'all_accounts': all_accounts, + }, 'jsonrpc': '2.0', 'id': '0' } From 30c865f0f85db67c09497072ebd387b966c8c62d Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 22 Mar 2019 11:33:42 +0000 Subject: [PATCH 15/18] functional_tests: add balance tests --- tests/functional_tests/transfer.py | 230 +++++++++++++++++++++-------- 1 file changed, 171 insertions(+), 59 deletions(-) diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index 9c38f0bd0..fc582e618 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -38,54 +38,67 @@ from test_framework.wallet import Wallet class TransferTest(): def run_test(self): - self.create(0) + self.create() self.mine() self.transfer() self.check_get_bulk_payments() - def create(self, idx): - print 'Creating wallet' - wallet = Wallet() - # close the wallet if any, will throw if none is loaded - try: wallet.close_wallet() - except: pass + def create(self): + print 'Creating wallets' seeds = [ 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted', 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout', 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', ] - res = wallet.restore_deterministic_wallet(seed = seeds[idx]) + self.wallet = [None] * len(seeds) + for i in range(len(seeds)): + self.wallet[i] = Wallet(idx = i) + # close the wallet if any, will throw if none is loaded + try: self.wallet[i].close_wallet() + except: pass + res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) def mine(self): print("Mining some blocks") daemon = Daemon() - wallet = Wallet() daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) - wallet.refresh() + for i in range(len(self.wallet)): + self.wallet[i].refresh() def transfer(self): daemon = Daemon() - wallet = Wallet() print("Creating transfer to self") dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde' + start_balances = [0] * len(self.wallet) + running_balances = [0] * len(self.wallet) + for i in range(len(self.wallet)): + res = self.wallet[i].get_balance() + start_balances[i] = res.balance + running_balances[i] = res.balance + assert res.unlocked_balance <= res.balance + if i == 0: + assert res.blocks_to_unlock == 59 # we've been mining to it + else: + assert res.blocks_to_unlock == 0 + print ('Checking short payment IDs cannot be used when not in an integrated address') ok = False - try: wallet.transfer([dst], ring_size = 11, payment_id = '1234567812345678', get_tx_key = False) + try: self.wallet[0].transfer([dst], ring_size = 11, payment_id = '1234567812345678', get_tx_key = False) except: ok = True assert ok print ('Checking empty destination is rejected') ok = False - try: wallet.transfer([], ring_size = 11, get_tx_key = False) + try: self.wallet[0].transfer([], ring_size = 11, get_tx_key = False) except: ok = True assert ok - res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) + res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) assert len(res.tx_hash) == 32*2 txid = res.tx_hash assert len(res.tx_key) == 0 @@ -99,12 +112,12 @@ class TransferTest(): assert len(res.unsigned_txset) == 0 unsigned_txset = res.unsigned_txset - wallet.refresh() + self.wallet[0].refresh() res = daemon.get_info() height = res.height - res = wallet.get_transfers() + res = self.wallet[0].get_transfers() assert len(res['in']) == height - 1 # coinbases assert not 'out' in res or len(res.out) == 0 # not mined yet assert len(res.pending) == 1 @@ -123,10 +136,21 @@ class TransferTest(): assert e.double_spend_seen == False assert e.confirmations == 0 + running_balances[0] -= 1000000000000 + fee + + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) - wallet.refresh() + res = daemon.getlastblockheader() + running_balances[0] += res.block_header.reward + self.wallet[0].refresh() - res = wallet.get_transfers() + running_balances[0] += 1000000000000 + + res = self.wallet[0].get_transfers() assert len(res['in']) == height # coinbases assert len(res.out) == 1 # not mined yet assert not 'pending' in res or len(res.pending) == 0 @@ -145,10 +169,15 @@ class TransferTest(): assert e.double_spend_seen == False assert e.confirmations == 1 + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + print("Creating transfer to another, manual relay") dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000} - res = wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True, do_not_relay = True, get_tx_hex = True) + res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True, do_not_relay = True, get_tx_hex = True) assert len(res.tx_hash) == 32*2 txid = res.tx_hash assert len(res.tx_key) == 32*2 @@ -173,10 +202,16 @@ class TransferTest(): assert res.fee_too_low == False assert res.not_rct == False - self.create(1) - wallet.refresh() + self.wallet[0].refresh() + + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 - res = wallet.get_transfers() + self.wallet[1].refresh() + + res = self.wallet[1].get_transfers() assert not 'in' in res or len(res['in']) == 0 assert not 'out' in res or len(res.out) == 0 assert not 'pending' in res or len(res.pending) == 0 @@ -196,9 +231,13 @@ class TransferTest(): assert e.fee == fee daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) - wallet.refresh() + res = daemon.getlastblockheader() + running_balances[0] -= 1000000000000 + fee + running_balances[0] += res.block_header.reward + self.wallet[1].refresh() + running_balances[1] += 1000000000000 - res = wallet.get_transfers() + res = self.wallet[1].get_transfers() assert len(res['in']) == 1 assert not 'out' in res or len(res.out) == 0 assert not 'pending' in res or len(res.pending) == 0 @@ -217,15 +256,19 @@ class TransferTest(): assert e.amount == amount assert e.fee == fee + res = self.wallet[1].get_balance() + assert res.balance == running_balances[1] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 9 + print 'Creating multi out transfer' - self.create(0) - wallet.refresh() + self.wallet[0].refresh() dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000} dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000} - res = wallet.transfer([dst0, dst1, dst2], ring_size = 11, payment_id = payment_id, get_tx_key = True) + res = self.wallet[0].transfer([dst0, dst1, dst2], ring_size = 11, payment_id = payment_id, get_tx_key = True) assert len(res.tx_hash) == 32*2 txid = res.tx_hash assert len(res.tx_key) == 32*2 @@ -239,10 +282,22 @@ class TransferTest(): assert len(res.unsigned_txset) == 0 unsigned_txset = res.unsigned_txset - daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) - wallet.refresh() + running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + fee - res = wallet.get_transfers() + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + res = daemon.getlastblockheader() + running_balances[0] += res.block_header.reward + running_balances[0] += 1000000000000 + running_balances[1] += 1100000000000 + running_balances[2] += 1200000000000 + self.wallet[0].refresh() + + res = self.wallet[0].get_transfers() assert len(res['in']) == height + 2 assert len(res.out) == 3 assert not 'pending' in res or len(res.pending) == 0 @@ -264,9 +319,13 @@ class TransferTest(): assert e.amount == amount assert e.fee == fee - self.create(1) - wallet.refresh() - res = wallet.get_transfers() + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + + self.wallet[1].refresh() + res = self.wallet[1].get_transfers() assert len(res['in']) == 2 assert not 'out' in res or len(res.out) == 0 assert not 'pending' in res or len(res.pending) == 0 @@ -287,9 +346,13 @@ class TransferTest(): assert e.amount == 1100000000000 assert e.fee == fee - self.create(2) - wallet.refresh() - res = wallet.get_transfers() + res = self.wallet[1].get_balance() + assert res.balance == running_balances[1] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 9 + + self.wallet[2].refresh() + res = self.wallet[2].get_transfers() assert len(res['in']) == 1 assert not 'out' in res or len(res.out) == 0 assert not 'pending' in res or len(res.pending) == 0 @@ -310,14 +373,18 @@ class TransferTest(): assert e.amount == 1200000000000 assert e.fee == fee + res = self.wallet[2].get_balance() + assert res.balance == running_balances[2] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 9 + print('Sending to integrated address') - self.create(0) - wallet.refresh() - res = wallet.get_balance() + self.wallet[0].refresh() + res = self.wallet[0].get_balance() i_pid = '1111111122222222' - res = wallet.make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid) + res = self.wallet[0].make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid) i_address = res.integrated_address - res = wallet.transfer([{'address': i_address, 'amount': 200000000}]) + res = self.wallet[0].transfer([{'address': i_address, 'amount': 200000000}]) assert len(res.tx_hash) == 32*2 i_txid = res.tx_hash assert len(res.tx_key) == 32*2 @@ -330,7 +397,57 @@ class TransferTest(): assert len(res.multisig_txset) == 0 assert len(res.unsigned_txset) == 0 + running_balances[0] -= 200000000 + fee + + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + res = daemon.getlastblockheader() + running_balances[0] += res.block_header.reward + running_balances[1] += 200000000 + + self.wallet[0].refresh() + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + + self.wallet[1].refresh() + res = self.wallet[1].get_balance() + assert res.balance == running_balances[1] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 9 + + self.wallet[2].refresh() + res = self.wallet[2].get_balance() + assert res.balance == running_balances[2] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 8 + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + res = daemon.getlastblockheader() + running_balances[0] += res.block_header.reward + + self.wallet[0].refresh() + res = self.wallet[0].get_balance() + assert res.balance == running_balances[0] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 59 + + self.wallet[1].refresh() + res = self.wallet[1].get_balance() + assert res.balance == running_balances[1] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 8 + + self.wallet[2].refresh() + res = self.wallet[2].get_balance() + assert res.balance == running_balances[2] + assert res.unlocked_balance <= res.balance + assert res.blocks_to_unlock == 7 def check_get_bulk_payments(self): @@ -340,35 +457,30 @@ class TransferTest(): res = daemon.get_info() height = res.height - wallet = Wallet() - - self.create(0) - wallet.refresh() - res = wallet.get_bulk_payments() + self.wallet[0].refresh() + res = self.wallet[0].get_bulk_payments() assert len(res.payments) >= 83 # at least 83 coinbases - res = wallet.get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + res = self.wallet[0].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) assert 'payments' not in res or len(res.payments) == 0 - res = wallet.get_bulk_payments(min_block_height = height) + res = self.wallet[0].get_bulk_payments(min_block_height = height) assert 'payments' not in res or len(res.payments) == 0 - res = wallet.get_bulk_payments(min_block_height = height - 40) + res = self.wallet[0].get_bulk_payments(min_block_height = height - 40) assert len(res.payments) >= 39 # coinbases - self.create(1) - wallet.refresh() - res = wallet.get_bulk_payments() + self.wallet[1].refresh() + res = self.wallet[1].get_bulk_payments() assert len(res.payments) >= 3 # two txes to standard address were sent, plus one to integrated address - res = wallet.get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + res = self.wallet[1].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) assert len(res.payments) >= 2 # two txes were sent with that payment id - res = wallet.get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff']) + res = self.wallet[1].get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff']) assert 'payments' not in res or len(res.payments) == 0 # none with that payment id - res = wallet.get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48]) + res = self.wallet[1].get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48]) assert len(res.payments) >= 1 # one tx to integrated address - self.create(2) - wallet.refresh() - res = wallet.get_bulk_payments() + self.wallet[2].refresh() + res = self.wallet[2].get_bulk_payments() assert len(res.payments) >= 1 # one tx was sent - res = wallet.get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64]) + res = self.wallet[2].get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64]) assert len(res.payments) >= 1 # one tx was sent if __name__ == '__main__': From 22b644f47ee2de8a962d00588579076e7a55455a Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 22 Mar 2019 14:35:23 +0000 Subject: [PATCH 16/18] functional_tests: move RPC API to utils, it is not test specific --- tests/functional_tests/blockchain.py | 2 +- tests/functional_tests/cold_signing.py | 4 ++-- tests/functional_tests/daemon_info.py | 2 +- tests/functional_tests/functional_tests_rpc.py | 6 ++++++ tests/functional_tests/integrated_address.py | 2 +- tests/functional_tests/mining.py | 4 ++-- tests/functional_tests/multisig.py | 4 ++-- tests/functional_tests/proofs.py | 4 ++-- tests/functional_tests/sign_message.py | 2 +- tests/functional_tests/speed.py | 4 ++-- tests/functional_tests/transfer.py | 4 ++-- tests/functional_tests/txpool.py | 4 ++-- tests/functional_tests/wallet_address.py | 2 +- .../python-rpc/framework}/__init__.py | 0 .../test_framework => utils/python-rpc/framework}/daemon.py | 0 .../test_framework => utils/python-rpc/framework}/rpc.py | 0 .../test_framework => utils/python-rpc/framework}/wallet.py | 0 17 files changed, 25 insertions(+), 19 deletions(-) rename {tests/functional_tests/test_framework => utils/python-rpc/framework}/__init__.py (100%) rename {tests/functional_tests/test_framework => utils/python-rpc/framework}/daemon.py (100%) rename {tests/functional_tests/test_framework => utils/python-rpc/framework}/rpc.py (100%) rename {tests/functional_tests/test_framework => utils/python-rpc/framework}/wallet.py (100%) diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py index bdd08680f..d805fccda 100755 --- a/tests/functional_tests/blockchain.py +++ b/tests/functional_tests/blockchain.py @@ -41,7 +41,7 @@ Test the following RPCs: """ -from test_framework.daemon import Daemon +from framework.daemon import Daemon class BlockchainTest(): def run_test(self): diff --git a/tests/functional_tests/cold_signing.py b/tests/functional_tests/cold_signing.py index 2acdcb137..6895aec60 100755 --- a/tests/functional_tests/cold_signing.py +++ b/tests/functional_tests/cold_signing.py @@ -33,8 +33,8 @@ import time """Test cold tx signing """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class ColdSigningTest(): def run_test(self): diff --git a/tests/functional_tests/daemon_info.py b/tests/functional_tests/daemon_info.py index 94c3fc3ac..bd3528c3f 100755 --- a/tests/functional_tests/daemon_info.py +++ b/tests/functional_tests/daemon_info.py @@ -36,7 +36,7 @@ Test the following RPCs: """ -from test_framework.daemon import Daemon +from framework.daemon import Daemon class DaemonGetInfoTest(): def run_test(self): diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index aae546a6b..606b19e45 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -7,6 +7,7 @@ import subprocess from signal import SIGTERM import socket import string +import os USAGE = 'usage: functional_tests_rpc.py [ | all]' DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message', 'proofs'] @@ -56,6 +57,11 @@ for i in range(N_WALLETS): print('Starting servers...') try: + PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else '' + if len(PYTHONPATH) > 0: + PYTHONPATH += ':' + PYTHONPATH += '../../utils/python-rpc' + os.environ['PYTHONPATH'] = PYTHONPATH for i in range(len(command_lines)): #print('Running: ' + str(command_lines[i])) processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i])) diff --git a/tests/functional_tests/integrated_address.py b/tests/functional_tests/integrated_address.py index d6976a116..338dd14ae 100755 --- a/tests/functional_tests/integrated_address.py +++ b/tests/functional_tests/integrated_address.py @@ -38,7 +38,7 @@ Test the following RPCs: """ -from test_framework.wallet import Wallet +from framework.wallet import Wallet class IntegratedAddressTest(): def run_test(self): diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index a94d59cba..1b189beb2 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -38,8 +38,8 @@ Test the following RPCs: - mining_status """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class MiningTest(): def run_test(self): diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 7cdddd009..a0e8551cd 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -33,8 +33,8 @@ import time """Test multisig transfers """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class MultisigTest(): def run_test(self): diff --git a/tests/functional_tests/proofs.py b/tests/functional_tests/proofs.py index 03d59b765..0a0b6304d 100755 --- a/tests/functional_tests/proofs.py +++ b/tests/functional_tests/proofs.py @@ -33,8 +33,8 @@ import time """Test misc proofs (tx key, send, receive, reserve) """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class ProofsTest(): def run_test(self): diff --git a/tests/functional_tests/sign_message.py b/tests/functional_tests/sign_message.py index 4a62fd733..4c3ec3588 100755 --- a/tests/functional_tests/sign_message.py +++ b/tests/functional_tests/sign_message.py @@ -38,7 +38,7 @@ Test the following RPCs: """ -from test_framework.wallet import Wallet +from framework.wallet import Wallet class MessageSigningTest(): def run_test(self): diff --git a/tests/functional_tests/speed.py b/tests/functional_tests/speed.py index 0ce90f939..bd8892df8 100755 --- a/tests/functional_tests/speed.py +++ b/tests/functional_tests/speed.py @@ -42,8 +42,8 @@ import time from time import sleep from decimal import Decimal -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class SpeedTest(): diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index fc582e618..b7a85f1d6 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -33,8 +33,8 @@ import time """Test simple transfers """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class TransferTest(): def run_test(self): diff --git a/tests/functional_tests/txpool.py b/tests/functional_tests/txpool.py index 419bd925c..71109c9e5 100755 --- a/tests/functional_tests/txpool.py +++ b/tests/functional_tests/txpool.py @@ -33,8 +33,8 @@ import time """Test txpool """ -from test_framework.daemon import Daemon -from test_framework.wallet import Wallet +from framework.daemon import Daemon +from framework.wallet import Wallet class TransferTest(): def run_test(self): diff --git a/tests/functional_tests/wallet_address.py b/tests/functional_tests/wallet_address.py index acd7b6821..66a1633ca 100755 --- a/tests/functional_tests/wallet_address.py +++ b/tests/functional_tests/wallet_address.py @@ -37,7 +37,7 @@ Test the following RPCs: """ -from test_framework.wallet import Wallet +from framework.wallet import Wallet class WalletAddressTest(): def run_test(self): diff --git a/tests/functional_tests/test_framework/__init__.py b/utils/python-rpc/framework/__init__.py similarity index 100% rename from tests/functional_tests/test_framework/__init__.py rename to utils/python-rpc/framework/__init__.py diff --git a/tests/functional_tests/test_framework/daemon.py b/utils/python-rpc/framework/daemon.py similarity index 100% rename from tests/functional_tests/test_framework/daemon.py rename to utils/python-rpc/framework/daemon.py diff --git a/tests/functional_tests/test_framework/rpc.py b/utils/python-rpc/framework/rpc.py similarity index 100% rename from tests/functional_tests/test_framework/rpc.py rename to utils/python-rpc/framework/rpc.py diff --git a/tests/functional_tests/test_framework/wallet.py b/utils/python-rpc/framework/wallet.py similarity index 100% rename from tests/functional_tests/test_framework/wallet.py rename to utils/python-rpc/framework/wallet.py From c7bfdc356618fd1ccbe0f87fd5009e944cd12e50 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 22 Mar 2019 15:25:33 +0000 Subject: [PATCH 17/18] python-rpc: add console.py It allows one to connect to a running daemon or wallet, and use its RPC API from python. Usage: python -i console.py It will detect whether it's talking to a daemon or wallet and initialize itself accordingly. --- .../functional_tests/functional_tests_rpc.py | 2 +- utils/python-rpc/console.py | 49 +++++++++++++++++++ utils/python-rpc/framework/daemon.py | 8 +++ utils/python-rpc/framework/wallet.py | 8 +++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100755 utils/python-rpc/console.py diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 606b19e45..f2fef7e95 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -60,7 +60,7 @@ try: PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else '' if len(PYTHONPATH) > 0: PYTHONPATH += ':' - PYTHONPATH += '../../utils/python-rpc' + PYTHONPATH += srcdir + '/../../utils/python-rpc' os.environ['PYTHONPATH'] = PYTHONPATH for i in range(len(command_lines)): #print('Running: ' + str(command_lines[i])) diff --git a/utils/python-rpc/console.py b/utils/python-rpc/console.py new file mode 100755 index 000000000..ab0d9f27f --- /dev/null +++ b/utils/python-rpc/console.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +from __future__ import print_function +import sys +import subprocess +import socket +from framework import rpc +from framework import wallet +from framework import daemon + +USAGE = 'usage: python -i console.py ' +try: + port = int(sys.argv[1]) +except: + print(USAGE) + sys.exit(1) + +# check for open port +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.settimeout(1) +if s.connect_ex(('127.0.0.1', port)) != 0: + print('No wallet or daemon RPC on port ' + str(port)) + sys.exit(1) +s.close() + +# both wallet and daemon have a get_version JSON RPC +rpc = rpc.JSONRPC('{protocol}://{host}:{port}'.format(protocol='http', host='127.0.0.1', port=port)) +get_version = { + 'method': 'get_version', + 'jsonrpc': '2.0', + 'id': '0' +} +try: + res = rpc.send_json_rpc_request(get_version) +except Exception, e: + print('Failed to call version RPC: ' + str(e)) + sys.exit(1) + +if 'version' not in res: + print('Server is not a monero process') + sys.exit(1) + +if 'status' in res: + rpc = daemon.Daemon(port=port) +else: + rpc = wallet.Wallet(port=port) + +print('Connected to %s RPC on port %u' % ('daemon' if 'status' in res else 'wallet', port)) +print('The \'rpc\' object may now be used to use the API') diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 11d5f5845..83c8eea67 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -196,3 +196,11 @@ class Daemon(object): 'id': '0' } return self.rpc.send_json_rpc_request(flush_txpool) + + def get_version(self): + get_version = { + 'method': 'get_version', + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_version) diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index e9f13667b..ea683b8c5 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -590,3 +590,11 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(verify) + + def get_version(self): + get_version = { + 'method': 'get_version', + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_version) From 32973434bd6aaba159b2bd805c3fc24fad7deb52 Mon Sep 17 00:00:00 2001 From: moneromooo-monero Date: Fri, 22 Mar 2019 16:25:07 +0000 Subject: [PATCH 18/18] python-rpc: add getblockheadersrange daemon RPC --- utils/python-rpc/framework/daemon.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 83c8eea67..f60fe62db 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -106,6 +106,19 @@ class Daemon(object): } return self.rpc.send_json_rpc_request(getblockheaderbyheight) + def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False): + getblockheadersrange = { + 'method': 'getblockheadersrange', + 'params': { + 'start_height': start_height, + 'end_height': end_height, + 'fill_pow_hash': fill_pow_hash, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(getblockheadersrange) + def get_connections(self): get_connections = { 'method': 'get_connections',