diff options
Diffstat (limited to 'tests')
39 files changed, 2484 insertions, 324 deletions
diff --git a/tests/README.md b/tests/README.md index 001ab6154..053dd2244 100644 --- a/tests/README.md +++ b/tests/README.md @@ -18,7 +18,7 @@ Tests are located in `tests/core_tests/`, and follow a straightforward naming co To run only Monero's core tests (after building): ``` -cd build/debug/tests/core +cd build/debug/tests/core_tests ctest ``` @@ -103,6 +103,8 @@ cd build/debug/tests/performance_tests ./performance_tests ``` +The path may be build/Linux/master/debug (adapt as necessary for your platform). + If the `performance_tests` binary does not exist, try running `make` in the `build/debug/tests/performance_tests` directory. To run the same tests on a release build, replace `debug` with `release`. diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 1e8974b78..7b3fdfe57 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -84,6 +84,11 @@ public: while (count-- && start_height < blocks.size()) ret.push_back(blocks[start_height++].long_term_weight); return ret; } + virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override { + crypto::hash hash = crypto::null_hash; + *(uint64_t*)&hash = height; + return hash; + } virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override { uint64_t h = height(); crypto::hash top = crypto::null_hash; diff --git a/tests/block_weight/block_weight.py b/tests/block_weight/block_weight.py index d6fd494e3..ba533c53c 100755 --- a/tests/block_weight/block_weight.py +++ b/tests/block_weight/block_weight.py @@ -3,6 +3,7 @@ # This uses the scheme proposed by ArticMine # Written by Sarang Nother # Copyright (c) 2019 The Monero Project +from __future__ import print_function import sys import math @@ -67,7 +68,7 @@ def run(t, blocks): lt_weights.append(min(max_weight,int(ltembw + int(ltembw * 2 / 5)))) #print "H %u, r %u, BW %u, EMBW %u, LTBW %u, LTEMBW %u, ltmedian %u" % (block, r, max_weight, embw, lt_weights[-1], ltembw, ltmedian) - print "H %u, BW %u, EMBW %u, LTBW %u" % (block, max_weight, embw, lt_weights[-1]) + print("H %u, BW %u, EMBW %u, LTBW %u" % (block, max_weight, embw, lt_weights[-1])) run(0, 2 * MEDIAN_WINDOW_BIG) run(1, 9 * MEDIAN_WINDOW_BIG) diff --git a/tests/block_weight/compare.py b/tests/block_weight/compare.py index e058a7079..e046e1640 100755 --- a/tests/block_weight/compare.py +++ b/tests/block_weight/compare.py @@ -1,5 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python +from __future__ import print_function import sys import subprocess @@ -10,11 +11,11 @@ else: first = [sys.argv[1]] second = [sys.argv[2]] -print 'running: ', first +print('running: ', first) S0 = subprocess.check_output(first, stderr=subprocess.STDOUT) -print 'running: ', second +print('running: ', second) S1 = subprocess.check_output(second, stderr=subprocess.STDOUT) -print 'comparing' +print('comparing') if S0 != S1: sys.exit(1) sys.exit(0) diff --git a/tests/difficulty/CMakeLists.txt b/tests/difficulty/CMakeLists.txt index 2ed495806..fb0dd6b9e 100644 --- a/tests/difficulty/CMakeLists.txt +++ b/tests/difficulty/CMakeLists.txt @@ -45,3 +45,6 @@ set_property(TARGET difficulty-tests add_test( NAME difficulty COMMAND difficulty-tests "${CMAKE_CURRENT_SOURCE_DIR}/data.txt") +add_test( + NAME wide_difficulty + COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/wide_difficulty.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_wide_data.py" "${CMAKE_CURRENT_BINARY_DIR}/difficulty-tests" "${CMAKE_CURRENT_BINARY_DIR}/wide_data.txt") diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp index ee20e27e4..9985b8710 100644 --- a/tests/difficulty/difficulty.cpp +++ b/tests/difficulty/difficulty.cpp @@ -43,16 +43,62 @@ using namespace std; #define DEFAULT_TEST_DIFFICULTY_TARGET 120 +static int test_wide_difficulty(const char *filename) +{ + std::vector<uint64_t> timestamps; + std::vector<cryptonote::difficulty_type> cumulative_difficulties; + fstream data(filename, fstream::in); + data.exceptions(fstream::badbit); + data.clear(data.rdstate()); + uint64_t timestamp; + cryptonote::difficulty_type difficulty, cumulative_difficulty = 0; + size_t n = 0; + while (data >> timestamp >> difficulty) { + size_t begin, end; + if (n < DIFFICULTY_WINDOW + DIFFICULTY_LAG) { + begin = 0; + end = min(n, (size_t) DIFFICULTY_WINDOW); + } else { + end = n - DIFFICULTY_LAG; + begin = end - DIFFICULTY_WINDOW; + } + cryptonote::difficulty_type res = cryptonote::next_difficulty( + std::vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end), + std::vector<cryptonote::difficulty_type>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET); + if (res != difficulty) { + cerr << "Wrong wide difficulty for block " << n << endl + << "Expected: " << difficulty << endl + << "Found: " << res << endl; + return 1; + } + timestamps.push_back(timestamp); + cumulative_difficulties.push_back(cumulative_difficulty += difficulty); + ++n; + } + if (!data.eof()) { + data.clear(fstream::badbit); + } + return 0; +} + int main(int argc, char *argv[]) { - if (argc != 2) { + if (argc < 2) { cerr << "Wrong arguments" << endl; return 1; } + if (argc == 3 && strcmp(argv[1], "--wide") == 0) + { + return test_wide_difficulty(argv[2]); + } + vector<uint64_t> timestamps, cumulative_difficulties; + std::vector<cryptonote::difficulty_type> wide_cumulative_difficulties; fstream data(argv[1], fstream::in); data.exceptions(fstream::badbit); data.clear(data.rdstate()); - uint64_t timestamp, difficulty, cumulative_difficulty = 0; + uint64_t timestamp; + uint64_t difficulty, cumulative_difficulty = 0; + cryptonote::difficulty_type wide_cumulative_difficulty = 0; size_t n = 0; while (data >> timestamp >> difficulty) { size_t begin, end; @@ -63,17 +109,27 @@ int main(int argc, char *argv[]) { end = n - DIFFICULTY_LAG; begin = end - DIFFICULTY_WINDOW; } - uint64_t res = cryptonote::next_difficulty( + uint64_t res = cryptonote::next_difficulty_64( vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end), - vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET); + std::vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET); if (res != difficulty) { cerr << "Wrong difficulty for block " << n << endl << "Expected: " << difficulty << endl << "Found: " << res << endl; return 1; } + cryptonote::difficulty_type wide_res = cryptonote::next_difficulty( + std::vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end), + std::vector<cryptonote::difficulty_type>(wide_cumulative_difficulties.begin() + begin, wide_cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET); + if (wide_res.convert_to<uint64_t>() != res) { + cerr << "Wrong wide difficulty for block " << n << endl + << "Expected: " << res << endl + << "Found: " << wide_res << endl; + return 1; + } timestamps.push_back(timestamp); cumulative_difficulties.push_back(cumulative_difficulty += difficulty); + wide_cumulative_difficulties.push_back(wide_cumulative_difficulty += difficulty); ++n; } if (!data.eof()) { diff --git a/tests/difficulty/gen_wide_data.py b/tests/difficulty/gen_wide_data.py new file mode 100644 index 000000000..64af4e208 --- /dev/null +++ b/tests/difficulty/gen_wide_data.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +from __future__ import print_function +import random + +DIFFICULTY_TARGET = 120 +DIFFICULTY_WINDOW = 720 +DIFFICULTY_LAG = 15 +DIFFICULTY_CUT = 60 + +def difficulty(): + times = [] + diffs = [] + while True: + if len(times) <= 1: + diff = 1 + else: + begin = max(len(times) - DIFFICULTY_WINDOW - DIFFICULTY_LAG, 0) + end = min(begin + DIFFICULTY_WINDOW, len(times)) + length = end - begin + assert length >= 2 + if length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT: + cut_begin = 0 + cut_end = length + else: + excess = length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + cut_begin = (excess + 1) // 2 + cut_end = length - excess // 2 + assert cut_begin + 2 <= cut_end + wnd = times[begin:end] + wnd.sort() + dtime = wnd[cut_end - 1] - wnd[cut_begin] + dtime = max(dtime, 1) + ddiff = sum(diffs[begin + cut_begin + 1:begin + cut_end]) + diff = (ddiff * DIFFICULTY_TARGET + dtime - 1) // dtime + times.append((yield diff)) + diffs.append(diff) + +random.seed(1) +time = 1000 +gen = difficulty() +diff = next(gen) +for i in range(100000): + power = 100 if i < 10000 else 100000000 if i < 500 else 1000000000000 if i < 1000 else 1000000000000000 if i < 2000 else 10000000000000000000 if i < 4000 else 1000000000000000000000000 + time += random.randint(-diff // power - 10, 3 * diff // power + 10) + print(time, diff) + diff = gen.send(time) diff --git a/tests/difficulty/wide_difficulty.py b/tests/difficulty/wide_difficulty.py new file mode 100755 index 000000000..41a2a632d --- /dev/null +++ b/tests/difficulty/wide_difficulty.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +from __future__ import print_function +import sys +import subprocess + +python = sys.argv[1] +py = sys.argv[2] +c = sys.argv[3] +data = sys.argv[4] + +first = python + " " + py + " > " + data +second = [c, '--wide', data] + +try: + print('running: ', first) + subprocess.check_call(first, shell=True) + print('running: ', second) + subprocess.check_call(second) +except: + sys.exit(1) + 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/blockchain.py b/tests/functional_tests/blockchain.py index 983658a7c..d805fccda 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 +from framework.daemon import Daemon 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/cold_signing.py b/tests/functional_tests/cold_signing.py new file mode 100755 index 000000000..6895aec60 --- /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 framework.daemon import Daemon +from 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/test_framework/rpc.py b/tests/functional_tests/daemon_info.py index b21df7b93..bd3528c3f 100644..100755 --- a/tests/functional_tests/test_framework/rpc.py +++ b/tests/functional_tests/daemon_info.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # Copyright (c) 2018 The Monero Project # # All rights reserved. @@ -26,24 +28,62 @@ # 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 requests -import json +"""Test daemon RPC calls + +Test the following RPCs: + - get_info + - hard_fork_info + +""" + +from 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; -class JSONRPC(object): - def __init__(self, url): - self.url = url + # nettype should not be TESTNET + assert 'testnet' in res.keys() + assert res.testnet == False; - def send_request(self, inputs): - res = requests.post( - self.url, - data=json.dumps(inputs), - headers={'content-type': 'application/json'}) - res = res.json() - - assert 'error' not in res, res + # nettype should not be STAGENET + assert 'stagenet' in res.keys() + assert res.stagenet == False; - return res['result'] + # 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/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py new file mode 100755 index 000000000..f2fef7e95 --- /dev/null +++ b/tests/functional_tests/functional_tests_rpc.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python + +from __future__ import print_function +import sys +import time +import subprocess +from signal import SIGTERM +import socket +import string +import os + +USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]' +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] + 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 + +N_MONERODS = 1 +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"] + +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...') +try: + PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else '' + if len(PYTHONPATH) > 0: + PYTHONPATH += ':' + PYTHONPATH += srcdir + '/../../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])) +except Exception, e: + print('Error: ' + str(e)) + sys.exit(1) + +def kill(): + 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 ports: + 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: + for p in processes: + p.wait() +else: + for i in range(10): + 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) + 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') +else: + print('Done, ' + str(len(FAIL)) + '/' + str(len(tests)) + ' tests failed: ' + string.join(FAIL, ', ')) diff --git a/tests/functional_tests/integrated_address.py b/tests/functional_tests/integrated_address.py new file mode 100755 index 000000000..338dd14ae --- /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 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/mining.py b/tests/functional_tests/mining.py new file mode 100755 index 000000000..1b189beb2 --- /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 framework.daemon import Daemon +from 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/multisig.py b/tests/functional_tests/multisig.py new file mode 100755 index 000000000..a0e8551cd --- /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 framework.daemon import Daemon +from 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/proofs.py b/tests/functional_tests/proofs.py new file mode 100755 index 000000000..0a0b6304d --- /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 framework.daemon import Daemon +from 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/sign_message.py b/tests/functional_tests/sign_message.py new file mode 100755 index 000000000..4c3ec3588 --- /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 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/speed.py b/tests/functional_tests/speed.py index 3d2af9a10..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(): @@ -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/__init__.py b/tests/functional_tests/test_framework/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/tests/functional_tests/test_framework/__init__.py +++ /dev/null diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py deleted file mode 100644 index f3490b232..000000000 --- a/tests/functional_tests/test_framework/daemon.py +++ /dev/null @@ -1,105 +0,0 @@ -# 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. - -"""Daemon class to make rpc calls and store state.""" - -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 getblocktemplate(self, address): - getblocktemplate = { - 'method': 'getblocktemplate', - 'params': { - 'wallet_address': address, - 'reserve_size' : 1 - }, - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(getblocktemplate) - - def submitblock(self, block): - submitblock = { - 'method': 'submitblock', - 'params': [ block ], - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(submitblock) - - def getblock(self, height=0): - getblock = { - 'method': 'getblock', - 'params': { - 'height': height - }, - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(getblock) - - def get_connections(self): - get_connections = { - 'method': 'get_connections', - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(get_connections) - - def get_info(self): - get_info = { - 'method': 'get_info', - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(get_info) - - def hard_fork_info(self): - hard_fork_info = { - 'method': 'hard_fork_info', - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(hard_fork_info) - - def generateblocks(self, address, blocks=1): - generateblocks = { - 'method': 'generateblocks', - 'params': { - 'amount_of_blocks' : blocks, - 'reserve_size' : 20, - 'wallet_address': address - }, - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(generateblocks) diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py deleted file mode 100644 index 357eab5b2..000000000 --- a/tests/functional_tests/test_framework/wallet.py +++ /dev/null @@ -1,120 +0,0 @@ -# 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. - -"""Daemon class to make rpc calls and store state.""" - -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 make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1): - destinations = [] - for i in range(transfer_number_of_destinations): - destinations.append({"amount":transfer_amount,"address":address}) - return destinations - - def make_destinations(self, addresses, transfer_amounts): - destinations = [] - for i in range(len(addresses)): - destinations.append({'amount':transfer_amounts[i],'address':addresses[i]}) - return destinations - - def transfer(self, destinations, ringsize=7, payment_id=''): - transfer = { - 'method': 'transfer', - 'params': { - 'destinations': destinations, - 'mixin' : ringsize - 1, - 'get_tx_key' : True - }, - 'jsonrpc': '2.0', - 'id': '0' - } - if(len(payment_id) > 0): - transfer['params'].update({'payment_id' : payment_id}) - return self.rpc.send_request(transfer) - - def transfer_split(self, destinations, ringsize=7, payment_id=''): - print(destinations) - transfer = { - "method": "transfer_split", - "params": { - "destinations": destinations, - "mixin" : ringsize - 1, - "get_tx_key" : True, - "new_algorithm" : True - }, - "jsonrpc": "2.0", - "id": "0" - } - if(len(payment_id) > 0): - transfer['params'].update({'payment_id' : payment_id}) - return self.rpc.send_request(transfer) - - def create_wallet(self, index=''): - create_wallet = { - 'method': 'create_wallet', - 'params': { - 'filename': 'testWallet' + index, - 'password' : '', - 'language' : 'English' - }, - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(create_wallet) - - def get_balance(self): - get_balance = { - 'method': 'get_balance', - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(get_balance) - - def sweep_dust(self): - sweep_dust = { - 'method': 'sweep_dust', - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(sweep_dust) - - def sweep_all(self, address): - sweep_all = { - 'method': 'sweep_all', - 'params' : { - 'address' : '' - }, - 'jsonrpc': '2.0', - 'id': '0' - } - return self.rpc.send_request(sweep_all) diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py new file mode 100755 index 000000000..b7a85f1d6 --- /dev/null +++ b/tests/functional_tests/transfer.py @@ -0,0 +1,487 @@ +#!/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 framework.daemon import Daemon +from framework.wallet import Wallet + +class TransferTest(): + def run_test(self): + self.create() + self.mine() + self.transfer() + self.check_get_bulk_payments() + + 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', + ] + 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() + + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + for i in range(len(self.wallet)): + self.wallet[i].refresh() + + def transfer(self): + daemon = Daemon() + + 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: 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: self.wallet[0].transfer([], ring_size = 11, get_tx_key = False) + except: ok = True + assert ok + + 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 + 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 + + self.wallet[0].refresh() + + res = daemon.get_info() + height = res.height + + 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 + 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 + + 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) + res = daemon.getlastblockheader() + running_balances[0] += res.block_header.reward + self.wallet[0].refresh() + + 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 + 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 + + 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 = 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 + 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 + 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.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_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) + res = daemon.getlastblockheader() + running_balances[0] -= 1000000000000 + fee + running_balances[0] += res.block_header.reward + self.wallet[1].refresh() + running_balances[1] += 1000000000000 + + 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 + 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 + + 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.wallet[0].refresh() + + dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000} + dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000} + 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 + 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 + + running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + 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[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 + 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 + + 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 + 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 + + 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 + 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 + + 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.wallet[0].refresh() + res = self.wallet[0].get_balance() + i_pid = '1111111122222222' + res = self.wallet[0].make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid) + i_address = res.integrated_address + 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 + 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 + + 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): + print('Checking get_bulk_payments') + + daemon = Daemon() + res = daemon.get_info() + height = res.height + + self.wallet[0].refresh() + res = self.wallet[0].get_bulk_payments() + assert len(res.payments) >= 83 # at least 83 coinbases + res = self.wallet[0].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + assert 'payments' not in res or len(res.payments) == 0 + res = self.wallet[0].get_bulk_payments(min_block_height = height) + assert 'payments' not in res or len(res.payments) == 0 + res = self.wallet[0].get_bulk_payments(min_block_height = height - 40) + assert len(res.payments) >= 39 # coinbases + + 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 = self.wallet[1].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde']) + assert len(res.payments) >= 2 # two txes were sent with that payment id + 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 = self.wallet[1].get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48]) + assert len(res.payments) >= 1 # one tx to integrated address + + self.wallet[2].refresh() + res = self.wallet[2].get_bulk_payments() + assert len(res.payments) >= 1 # one tx was sent + 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__': + TransferTest().run_test() diff --git a/tests/functional_tests/txpool.py b/tests/functional_tests/txpool.py new file mode 100755 index 000000000..71109c9e5 --- /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 framework.daemon import Daemon +from 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() diff --git a/tests/functional_tests/wallet_address.py b/tests/functional_tests/wallet_address.py new file mode 100755 index 000000000..66a1633ca --- /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 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() diff --git a/tests/fuzz/cold-outputs.cpp b/tests/fuzz/cold-outputs.cpp index 0a034bcd5..f4050c948 100644 --- a/tests/fuzz/cold-outputs.cpp +++ b/tests/fuzz/cold-outputs.cpp @@ -53,7 +53,7 @@ int ColdOutputsFuzzer::init() try { - wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); } diff --git a/tests/fuzz/cold-transaction.cpp b/tests/fuzz/cold-transaction.cpp index fc0cfa2bd..08117281b 100644 --- a/tests/fuzz/cold-transaction.cpp +++ b/tests/fuzz/cold-transaction.cpp @@ -54,7 +54,7 @@ int ColdTransactionFuzzer::init() try { - wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); } diff --git a/tests/fuzz/signature.cpp b/tests/fuzz/signature.cpp index 3f0ada0c9..038378ae2 100644 --- a/tests/fuzz/signature.cpp +++ b/tests/fuzz/signature.cpp @@ -54,7 +54,7 @@ int SignatureFuzzer::init() try { - wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); diff --git a/tests/hash-target.cpp b/tests/hash-target.cpp index 70368ce24..12acc5a67 100644 --- a/tests/hash-target.cpp +++ b/tests/hash-target.cpp @@ -40,7 +40,7 @@ using cryptonote::check_hash; int main(int argc, char *argv[]) { crypto::hash h; - for (uint64_t diff = 1;; diff += 1 + (diff >> 8)) { + for (cryptonote::difficulty_type diff = 1;; diff += 1 + (diff >> 8)) { for (uint16_t b = 0; b < 256; b++) { memset(&h, b, sizeof(crypto::hash)); if (check_hash(h, diff) != (b == 0 || diff <= 255 / b)) { @@ -50,7 +50,7 @@ int main(int argc, char *argv[]) { memset(&h, 0, sizeof(crypto::hash)); ((char *) &h)[31] = b; if (check_hash(h, diff) != (diff <= 255 / b)) { - return 1; + return 2; } } } @@ -58,11 +58,11 @@ int main(int argc, char *argv[]) { uint64_t val = 0; for (int i = 31; i >= 0; i--) { val = val * 256 + 255; - ((char *) &h)[i] = static_cast<char>(val / diff); - val %= diff; + ((char *) &h)[i] = static_cast<char>(static_cast<uint64_t>(val / diff)); + val %= diff.convert_to<uint64_t>(); } if (check_hash(h, diff) != true) { - return 1; + return 3; } if (diff > 1) { for (int i = 0;; i++) { @@ -74,7 +74,7 @@ int main(int argc, char *argv[]) { } } if (check_hash(h, diff) != false) { - return 1; + return 4; } } } diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt index aa424da80..b36df10dc 100644 --- a/tests/performance_tests/CMakeLists.txt +++ b/tests/performance_tests/CMakeLists.txt @@ -31,6 +31,7 @@ set(performance_tests_sources set(performance_tests_headers check_tx_signature.h + check_hash.h cn_slow_hash.h construct_tx.h derive_public_key.h diff --git a/tests/performance_tests/check_hash.h b/tests/performance_tests/check_hash.h new file mode 100644 index 000000000..d24001903 --- /dev/null +++ b/tests/performance_tests/check_hash.h @@ -0,0 +1,66 @@ +// 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. + +#pragma once + +#include "string_tools.h" +#include "cryptonote_basic/difficulty.h" + +template<uint64_t hash_target_high, uint64_t hash_target_low, uint64_t difficulty_high, uint64_t difficulty_low> +class test_check_hash +{ +public: + static const size_t loop_count = 100000; + + bool init() + { + cryptonote::difficulty_type hash_target = hash_target_high; + hash_target = (hash_target << 64) | hash_target_low; + difficulty = difficulty_high; + difficulty = (difficulty << 64) | difficulty_low; + boost::multiprecision::uint256_t hash_value = std::numeric_limits<boost::multiprecision::uint256_t>::max() / hash_target; + ((uint64_t*)&hash)[0] = (hash_value << 64 >> 64).convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&hash)[1] = (hash_value << 64 >> 64).convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&hash)[2] = (hash_value << 64 >> 64).convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&hash)[3] = (hash_value << 64 >> 64).convert_to<uint64_t>(); + return true; + } + + bool test() + { + cryptonote::check_hash_128(hash, difficulty); + return true; + } + +private: + crypto::hash hash; + cryptonote::difficulty_type difficulty; +}; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index e6558a364..c32e0df20 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -38,6 +38,7 @@ // tests #include "construct_tx.h" #include "check_tx_signature.h" +#include "check_hash.h" #include "cn_slow_hash.h" #include "derive_public_key.h" #include "derive_secret_key.h" @@ -181,6 +182,14 @@ int main(int argc, char** argv) TEST_PERFORMANCE4(filter, p, test_check_tx_signature_aggregated_bulletproofs, 2, 2, 56, 16); TEST_PERFORMANCE4(filter, p, test_check_tx_signature_aggregated_bulletproofs, 10, 2, 56, 16); + TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 1, 0, 1); + TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 0xffffffffffffffff, 0, 0xffffffffffffffff); + TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 0xffffffffffffffff, 0, 1); + TEST_PERFORMANCE4(filter, p, test_check_hash, 1, 0, 1, 0); + TEST_PERFORMANCE4(filter, p, test_check_hash, 1, 0, 0, 1); + TEST_PERFORMANCE4(filter, p, test_check_hash, 0xffffffffffffffff, 0xffffffffffffffff, 0, 1); + TEST_PERFORMANCE4(filter, p, test_check_hash, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff); + TEST_PERFORMANCE0(filter, p, test_is_out_to_acc); TEST_PERFORMANCE0(filter, p, test_is_out_to_acc_precomp); TEST_PERFORMANCE0(filter, p, test_generate_key_image_helper); diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index 310fa45f1..8d5540328 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -1406,7 +1406,7 @@ tsx_builder * tsx_builder::construct_pending_tx(tools::wallet2::pending_tx &ptx, ptx.construction_data.extra = tx.extra; ptx.construction_data.unlock_time = 0; ptx.construction_data.use_rct = true; - ptx.construction_data.use_bulletproofs = true; + ptx.construction_data.rct_config = m_rct_config; ptx.construction_data.dests = m_destinations_orig; ptx.construction_data.subaddr_account = 0; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index bfaea6add..56a1f8c4d 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -43,6 +43,7 @@ set(unit_tests_sources crypto.cpp decompose_amount_into_digits.cpp device.cpp + difficulty.cpp dns_resolver.cpp epee_boosted_tcp_server.cpp epee_levin_protocol_handler_async.cpp diff --git a/tests/unit_tests/difficulty.cpp b/tests/unit_tests/difficulty.cpp new file mode 100644 index 000000000..090fecc84 --- /dev/null +++ b/tests/unit_tests/difficulty.cpp @@ -0,0 +1,68 @@ +// 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. + +#include "gtest/gtest.h" +#include "cryptonote_basic/difficulty.h" + +static cryptonote::difficulty_type MKDIFF(uint64_t high, uint64_t low) +{ + cryptonote::difficulty_type d = high; + d = (d << 64) | low; + return d; +} + +static crypto::hash MKHASH(uint64_t high, uint64_t low) +{ + cryptonote::difficulty_type hash_target = high; + hash_target = (hash_target << 64) | low; + boost::multiprecision::uint256_t hash_value = std::numeric_limits<boost::multiprecision::uint256_t>::max() / hash_target; + crypto::hash h; + ((uint64_t*)&h)[0] = hash_value.convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&h)[1] = hash_value.convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&h)[2] = hash_value.convert_to<uint64_t>(); + hash_value >>= 64; + ((uint64_t*)&h)[3] = hash_value.convert_to<uint64_t>(); + return h; +} + +TEST(difficulty, check_hash) +{ + ASSERT_TRUE(cryptonote::check_hash(MKHASH(0, 1), MKDIFF(0, 1))); + ASSERT_FALSE(cryptonote::check_hash(MKHASH(0, 1), MKDIFF(0, 2))); + + ASSERT_TRUE(cryptonote::check_hash(MKHASH(0, 0xffffffffffffffff), MKDIFF(0, 0xffffffffffffffff))); + ASSERT_FALSE(cryptonote::check_hash(MKHASH(0, 0xffffffffffffffff), MKDIFF(1, 0))); + + ASSERT_TRUE(cryptonote::check_hash(MKHASH(1, 1), MKDIFF(1, 1))); + ASSERT_FALSE(cryptonote::check_hash(MKHASH(1, 1), MKDIFF(1, 2))); + + ASSERT_TRUE(cryptonote::check_hash(MKHASH(0xffffffffffffffff, 1), MKDIFF(0xffffffffffffffff, 1))); + ASSERT_FALSE(cryptonote::check_hash(MKHASH(0xffffffffffffffff, 1), MKDIFF(0xffffffffffffffff, 2))); +} diff --git a/tests/unit_tests/hmac_keccak.cpp b/tests/unit_tests/hmac_keccak.cpp index cb35d272a..3898d4a7a 100644 --- a/tests/unit_tests/hmac_keccak.cpp +++ b/tests/unit_tests/hmac_keccak.cpp @@ -88,7 +88,8 @@ static void test_keccak_hmac(const size_t * chunks) uint8_t key_buff[1024]; uint8_t res_exp[32]; uint8_t res_comp[32]; - const size_t len_chunks = chunks ? sizeof(chunks) / sizeof(*chunks) : 0; + size_t len_chunks = 0; + for(; chunks && chunks[len_chunks] > 0; ++len_chunks); for (size_t i = 0; i < (sizeof(keccak_hmac_vectors) / sizeof(*keccak_hmac_vectors)); i++) { @@ -124,29 +125,29 @@ static void test_keccak_hmac(const size_t * chunks) TEST(keccak_hmac, ) { - test_keccak_hmac({}); + test_keccak_hmac(nullptr); } TEST(keccak_hmac, 1) { - static const size_t chunks[] = {1}; + static const size_t chunks[] = {1, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 1_20) { - static const size_t chunks[] = {1, 20}; + static const size_t chunks[] = {1, 20, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 136_1) { - static const size_t chunks[] = {136, 1}; + static const size_t chunks[] = {136, 1, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 137_1) { - static const size_t chunks[] = {137, 1}; + static const size_t chunks[] = {137, 1, 0}; test_keccak_hmac(chunks); } diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index c8e60200c..c5917200e 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -71,7 +71,7 @@ static void make_wallet(unsigned int idx, tools::wallet2 &wallet) try { - wallet.init("", boost::none, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + wallet.init("", boost::none, boost::asio::ip::tcp::endpoint{}, 0, true, epee::net_utils::ssl_support_t::e_ssl_support_disabled); wallet.set_subaddress_lookahead(1, 1); wallet.generate("", "", spendkey, true, false); ASSERT_TRUE(test_addresses[idx].address == wallet.get_account().get_public_address_str(cryptonote::TESTNET)); diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index a38ecfe81..77fb71d96 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -33,6 +33,7 @@ #include <boost/asio/io_service.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/read.hpp> +#include <boost/asio/steady_timer.hpp> #include <boost/asio/write.hpp> #include <boost/endian/conversion.hpp> #include <boost/system/error_code.hpp> @@ -45,6 +46,7 @@ #include "net/error.h" #include "net/net_utils_base.h" #include "net/socks.h" +#include "net/socks_connect.h" #include "net/parse.h" #include "net/tor_address.h" #include "p2p/net_peerlist_boost_serialization.h" @@ -742,4 +744,92 @@ TEST(socks_client, resolve_command) while (test_client->called_ == 1); } +TEST(socks_connector, host) +{ + io_thread io{}; + boost::asio::steady_timer timeout{io.io_service}; + timeout.expires_from_now(std::chrono::seconds{5}); + + boost::unique_future<boost::asio::ip::tcp::socket> sock = + net::socks::connector{io.acceptor.local_endpoint()}("example.com", "8080", timeout); + + while (!io.connected); + const std::uint8_t expected_bytes[] = { + 4, 1, 0x1f, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00, + 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x00 + }; + + std::uint8_t actual_bytes[sizeof(expected_bytes)]; + boost::asio::read(io.server, boost::asio::buffer(actual_bytes)); + EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0); + + const std::uint8_t reply_bytes[] = {0, 90, 0, 0, 0, 0, 0, 0}; + boost::asio::write(io.server, boost::asio::buffer(reply_bytes)); + + ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3})); + EXPECT_TRUE(sock.get().is_open()); +} + +TEST(socks_connector, ipv4) +{ + io_thread io{}; + boost::asio::steady_timer timeout{io.io_service}; + timeout.expires_from_now(std::chrono::seconds{5}); + + boost::unique_future<boost::asio::ip::tcp::socket> sock = + net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout); + + while (!io.connected); + const std::uint8_t expected_bytes[] = { + 4, 1, 0x1f, 0x90, 0xfa, 0x58, 0x7d, 0x63, 0x00 + }; + + std::uint8_t actual_bytes[sizeof(expected_bytes)]; + boost::asio::read(io.server, boost::asio::buffer(actual_bytes)); + EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0); + + const std::uint8_t reply_bytes[] = {0, 90, 0, 0, 0, 0, 0, 0}; + boost::asio::write(io.server, boost::asio::buffer(reply_bytes)); + + ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3})); + EXPECT_TRUE(sock.get().is_open()); +} + +TEST(socks_connector, error) +{ + io_thread io{}; + boost::asio::steady_timer timeout{io.io_service}; + timeout.expires_from_now(std::chrono::seconds{5}); + + boost::unique_future<boost::asio::ip::tcp::socket> sock = + net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout); + + while (!io.connected); + const std::uint8_t expected_bytes[] = { + 4, 1, 0x1f, 0x90, 0xfa, 0x58, 0x7d, 0x63, 0x00 + }; + + std::uint8_t actual_bytes[sizeof(expected_bytes)]; + boost::asio::read(io.server, boost::asio::buffer(actual_bytes)); + EXPECT_TRUE(std::memcmp(expected_bytes, actual_bytes, sizeof(actual_bytes)) == 0); + + const std::uint8_t reply_bytes[] = {0, 91, 0, 0, 0, 0, 0, 0}; + boost::asio::write(io.server, boost::asio::buffer(reply_bytes)); + + ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3})); + EXPECT_THROW(sock.get().is_open(), boost::system::system_error); +} + +TEST(socks_connector, timeout) +{ + io_thread io{}; + boost::asio::steady_timer timeout{io.io_service}; + timeout.expires_from_now(std::chrono::milliseconds{10}); + + boost::unique_future<boost::asio::ip::tcp::socket> sock = + net::socks::connector{io.acceptor.local_endpoint()}("250.88.125.99", "8080", timeout); + + ASSERT_EQ(boost::future_status::ready, sock.wait_for(boost::chrono::seconds{3})); + EXPECT_THROW(sock.get().is_open(), boost::system::system_error); +} diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index eb70caefc..27b14ffff 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -1187,3 +1187,26 @@ TEST(Serialization, portability_signed_tx) ASSERT_TRUE(epee::string_tools::pod_to_hex(ki1) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0"); ASSERT_TRUE(epee::string_tools::pod_to_hex(ki2) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76"); } + +TEST(Serialization, difficulty_type) +{ + std::vector<cryptonote::difficulty_type> v_original; + + for(int i = 0; i != 100; i++) + { + v_original.push_back(cryptonote::difficulty_type("117868131154734361989189100")); + if(v_original.size() > 1) + v_original.back() *= v_original[v_original.size()-2]; + } + + std::stringstream ss; + boost::archive::portable_binary_oarchive a(ss); + a << v_original; + + std::vector<cryptonote::difficulty_type> v_unserialized; + + boost::archive::portable_binary_iarchive a2(ss); + a2 >> v_unserialized; + + ASSERT_EQ(v_original, v_unserialized); +} |