tests/trezor: HF9 and HF10 tests

- tests fixes for HF10, builder change, rct_config; fix_chain
- get_tx_key test
- proper testing after live refresh added
- live refresh synthetic test
- log available funds for easier test construction
- wallet::API tests with mocked daemon
This commit is contained in:
Dusan Klinec 2019-02-27 16:55:31 +01:00
parent a1fd1d499c
commit c9b13fbbc2
No known key found for this signature in database
GPG key ID: 6337E118CCBCE103
9 changed files with 1242 additions and 84 deletions

View file

@ -754,7 +754,7 @@ struct get_test_options {
};
//--------------------------------------------------------------------------
template<class t_test_class>
inline bool do_replay_events_get_core(std::vector<test_event_entry>& events, cryptonote::core **core)
inline bool do_replay_events_get_core(std::vector<test_event_entry>& events, cryptonote::core *core)
{
boost::program_options::options_description desc("Allowed options");
cryptonote::core::init_options(desc);
@ -768,8 +768,7 @@ inline bool do_replay_events_get_core(std::vector<test_event_entry>& events, cry
if (!r)
return false;
*core = new cryptonote::core(nullptr);
auto & c = **core;
auto & c = *core;
// FIXME: make sure that vm has arg_testnet_on set to true or false if
// this test needs for it to be so.
@ -825,9 +824,9 @@ inline bool replay_events_through_core_validate(std::vector<test_event_entry>& e
template<class t_test_class>
inline bool do_replay_events(std::vector<test_event_entry>& events)
{
cryptonote::core * core;
cryptonote::core core(nullptr);
bool ret = do_replay_events_get_core<t_test_class>(events, &core);
core->deinit();
core.deinit();
return ret;
}
//--------------------------------------------------------------------------

View file

@ -17,7 +17,6 @@ void wallet_accessor_test::set_account(tools::wallet2 * wallet, cryptonote::acco
{
wallet->clear();
wallet->m_account = account;
wallet->m_nettype = MAINNET;
wallet->m_key_device_type = account.get_device().get_type();
wallet->m_account_public_address = account.get_keys().m_account_address;

View file

@ -27,11 +27,15 @@
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(trezor_tests_sources
tools.cpp
daemon.cpp
trezor_tests.cpp
../core_tests/chaingen.cpp
../core_tests/wallet_tools.cpp)
set(trezor_tests_headers
tools.h
daemon.h
trezor_tests.h
../core_tests/chaingen.h
../core_tests/wallet_tools.h)
@ -50,6 +54,15 @@ target_link_libraries(trezor_tests
device
device_trezor
wallet
wallet_api
rpc
cryptonote_protocol
daemon_rpc_server
${Boost_CHRONO_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${ZMQ_LIB}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})

368
tests/trezor/daemon.cpp Normal file
View file

@ -0,0 +1,368 @@
// Copyright (c) 2014-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.
#include "daemon.h"
#include <common/command_line.h>
using namespace std;
using namespace daemonize;
namespace po = boost::program_options;
bool mock_rpc_daemon::on_send_raw_tx_2(const cryptonote::COMMAND_RPC_SEND_RAW_TX::request& req, cryptonote::COMMAND_RPC_SEND_RAW_TX::response& res, const cryptonote::core_rpc_server::connection_context *ctx)
{
cryptonote::COMMAND_RPC_SEND_RAW_TX::request req2(req);
req2.do_not_relay = true; // Do not relay in test setup, only one daemon running.
return cryptonote::core_rpc_server::on_send_raw_tx(req2, res, ctx);
}
void mock_daemon::init_options(boost::program_options::options_description & option_spec)
{
cryptonote::core::init_options(option_spec);
t_node_server::init_options(option_spec);
cryptonote::core_rpc_server::init_options(option_spec);
command_line::add_arg(option_spec, daemon_args::arg_zmq_rpc_bind_ip);
command_line::add_arg(option_spec, daemon_args::arg_zmq_rpc_bind_port);
}
void mock_daemon::default_options(boost::program_options::variables_map & vm)
{
std::vector<std::string> exclusive_nodes{"127.0.0.1:65525"};
tools::options::set_option(vm, nodetool::arg_p2p_add_exclusive_node, po::variable_value(exclusive_nodes, false));
tools::options::set_option(vm, nodetool::arg_p2p_bind_ip, po::variable_value(std::string("127.0.0.1"), false));
tools::options::set_option(vm, nodetool::arg_no_igd, po::variable_value(true, false));
tools::options::set_option(vm, cryptonote::arg_offline, po::variable_value(true, false));
tools::options::set_option(vm, "disable-dns-checkpoints", po::variable_value(true, false));
const char *test_mainnet = getenv("TEST_MAINNET");
if (!test_mainnet || atoi(test_mainnet) == 0)
{
tools::options::set_option(vm, cryptonote::arg_testnet_on, po::variable_value(true, false));
}
// By default pick non-standard ports to avoid confusion with possibly locally running daemons (mainnet/testnet)
const char *test_p2p_port = getenv("TEST_P2P_PORT");
auto p2p_port = std::string(test_p2p_port && strlen(test_p2p_port) > 0 ? test_p2p_port : "61340");
tools::options::set_option(vm, nodetool::arg_p2p_bind_port, po::variable_value(p2p_port, false));
const char *test_rpc_port = getenv("TEST_RPC_PORT");
auto rpc_port = std::string(test_rpc_port && strlen(test_rpc_port) > 0 ? test_rpc_port : "61341");
tools::options::set_option(vm, cryptonote::core_rpc_server::arg_rpc_bind_port, po::variable_value(rpc_port, false));
const char *test_zmq_port = getenv("TEST_ZMQ_PORT");
auto zmq_port = std::string(test_zmq_port && strlen(test_zmq_port) > 0 ? test_zmq_port : "61342");
tools::options::set_option(vm, daemon_args::arg_zmq_rpc_bind_port, po::variable_value(zmq_port, false));
po::notify(vm);
}
void mock_daemon::set_ports(boost::program_options::variables_map & vm, unsigned initial_port)
{
CHECK_AND_ASSERT_THROW_MES(initial_port < 65535-2, "Invalid port number");
tools::options::set_option(vm, nodetool::arg_p2p_bind_port, po::variable_value(std::to_string(initial_port), false));
tools::options::set_option(vm, cryptonote::core_rpc_server::arg_rpc_bind_port, po::variable_value(std::to_string(initial_port + 1), false));
tools::options::set_option(vm, daemon_args::arg_zmq_rpc_bind_port, po::variable_value(std::to_string(initial_port + 2), false));
po::notify(vm);
}
void mock_daemon::load_params(boost::program_options::variables_map const & vm)
{
m_p2p_bind_port = command_line::get_arg(vm, nodetool::arg_p2p_bind_port);
m_rpc_bind_port = command_line::get_arg(vm, cryptonote::core_rpc_server::arg_rpc_bind_port);
m_zmq_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_rpc_bind_port);
m_network_type = command_line::get_arg(vm, cryptonote::arg_testnet_on) ? cryptonote::TESTNET : cryptonote::MAINNET;
}
mock_daemon::~mock_daemon()
{
if (!m_terminated)
{
try
{
stop();
}
catch (...)
{
MERROR("Failed to stop");
}
}
if (!m_deinitalized)
{
deinit();
}
}
void mock_daemon::init()
{
m_deinitalized = false;
const auto main_rpc_port = command_line::get_arg(m_vm, cryptonote::core_rpc_server::arg_rpc_bind_port);
m_rpc_server.nettype(m_network_type);
CHECK_AND_ASSERT_THROW_MES(m_protocol.init(m_vm), "Failed to initialize cryptonote protocol.");
CHECK_AND_ASSERT_THROW_MES(m_rpc_server.init(m_vm, false, main_rpc_port), "Failed to initialize RPC server.");
if (m_start_p2p)
CHECK_AND_ASSERT_THROW_MES(m_server.init(m_vm), "Failed to initialize p2p server.");
if(m_http_client.is_connected())
m_http_client.disconnect();
CHECK_AND_ASSERT_THROW_MES(m_http_client.set_server(rpc_addr(), boost::none), "RPC client init fail");
}
void mock_daemon::deinit()
{
try
{
m_rpc_server.deinit();
}
catch (...)
{
MERROR("Failed to deinitialize RPC server...");
}
if (m_start_p2p)
{
try
{
m_server.deinit();
}
catch (...)
{
MERROR("Failed to deinitialize p2p...");
}
}
try
{
m_protocol.deinit();
m_protocol.set_p2p_endpoint(nullptr);
}
catch (...)
{
MERROR("Failed to stop cryptonote protocol!");
}
m_deinitalized = true;
}
void mock_daemon::init_and_run()
{
init();
run();
}
void mock_daemon::stop_and_deinit()
{
stop();
deinit();
}
void mock_daemon::try_init_and_run(boost::optional<unsigned> initial_port)
{
const unsigned max_attempts = 3;
for(unsigned attempts=0; attempts < max_attempts; ++attempts)
{
if (initial_port)
{
set_ports(m_vm, initial_port.get());
load_params(m_vm);
MDEBUG("Ports changed, RPC: " << rpc_addr());
}
try
{
init_and_run();
return;
}
catch(const std::exception &e)
{
MWARNING("Could not init and start, attempt: " << attempts << ", reason: " << e.what());
if (attempts + 1 >= max_attempts)
{
throw;
}
}
}
}
void mock_daemon::run()
{
m_run_thread = boost::thread(boost::bind(&mock_daemon::run_main, this));
}
bool mock_daemon::run_main()
{
CHECK_AND_ASSERT_THROW_MES(!m_terminated, "Can't run stopped daemon");
CHECK_AND_ASSERT_THROW_MES(!m_start_zmq || m_start_p2p, "ZMQ requires P2P");
boost::thread stop_thread = boost::thread([this] {
while (!this->m_stopped)
epee::misc_utils::sleep_no_w(100);
this->stop_p2p();
});
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){
m_stopped = true;
stop_thread.join();
});
try
{
CHECK_AND_ASSERT_THROW_MES(m_rpc_server.run(2, false), "Failed to start RPC");
cryptonote::rpc::DaemonHandler rpc_daemon_handler(*m_core, m_server);
cryptonote::rpc::ZmqServer zmq_server(rpc_daemon_handler);
if (m_start_zmq)
{
if (!zmq_server.addTCPSocket("127.0.0.1", m_zmq_bind_port))
{
MERROR("Failed to add TCP Socket (127.0.0.1:" << m_zmq_bind_port << ") to ZMQ RPC Server");
stop_rpc();
return false;
}
MINFO("Starting ZMQ server...");
zmq_server.run();
MINFO("ZMQ server started at 127.0.0.1: " << m_zmq_bind_port);
}
if (m_start_p2p)
{
m_server.run(); // blocks until p2p goes down
}
else
{
while (!this->m_stopped)
epee::misc_utils::sleep_no_w(100);
}
if (m_start_zmq)
zmq_server.stop();
stop_rpc();
return true;
}
catch (std::exception const & ex)
{
MFATAL("Uncaught exception! " << ex.what());
return false;
}
catch (...)
{
MFATAL("Uncaught exception!");
return false;
}
}
void mock_daemon::stop()
{
CHECK_AND_ASSERT_THROW_MES(!m_terminated, "Can't stop stopped daemon");
m_stopped = true;
m_terminated = true;
m_run_thread.join();
}
void mock_daemon::stop_rpc()
{
m_rpc_server.send_stop_signal();
m_rpc_server.timed_wait_server_stop(5000);
}
void mock_daemon::stop_p2p()
{
if (m_start_p2p)
m_server.send_stop_signal();
}
void mock_daemon::mine_blocks(size_t num_blocks, const std::string &miner_address)
{
bool blocks_mined = false;
const uint64_t start_height = get_height();
const auto mining_timeout = std::chrono::seconds(30);
MDEBUG("Current height before mining: " << start_height);
start_mining(miner_address);
auto mining_started = std::chrono::system_clock::now();
while(true) {
epee::misc_utils::sleep_no_w(100);
const uint64_t cur_height = get_height();
if (cur_height - start_height >= num_blocks)
{
MDEBUG("Cur blocks: " << cur_height << " start: " << start_height);
blocks_mined = true;
break;
}
auto current_time = std::chrono::system_clock::now();
if (mining_timeout < current_time - mining_started)
{
break;
}
}
stop_mining();
CHECK_AND_ASSERT_THROW_MES(blocks_mined, "Mining failed in the time limit");
}
constexpr const std::chrono::seconds mock_daemon::rpc_timeout;
void mock_daemon::start_mining(const std::string &miner_address, uint64_t threads_count, bool do_background_mining, bool ignore_battery)
{
cryptonote::COMMAND_RPC_START_MINING::request req;
req.miner_address = miner_address;
req.threads_count = threads_count;
req.do_background_mining = do_background_mining;
req.ignore_battery = ignore_battery;
cryptonote::COMMAND_RPC_START_MINING::response resp;
bool r = epee::net_utils::invoke_http_json("/start_mining", req, resp, m_http_client, rpc_timeout);
CHECK_AND_ASSERT_THROW_MES(r, "RPC error - start mining");
CHECK_AND_ASSERT_THROW_MES(resp.status != CORE_RPC_STATUS_BUSY, "Daemon busy");
CHECK_AND_ASSERT_THROW_MES(resp.status == CORE_RPC_STATUS_OK, "Daemon response invalid: " << resp.status);
}
void mock_daemon::stop_mining()
{
cryptonote::COMMAND_RPC_STOP_MINING::request req;
cryptonote::COMMAND_RPC_STOP_MINING::response resp;
bool r = epee::net_utils::invoke_http_json("/stop_mining", req, resp, m_http_client, rpc_timeout);
CHECK_AND_ASSERT_THROW_MES(r, "RPC error - stop mining");
CHECK_AND_ASSERT_THROW_MES(resp.status != CORE_RPC_STATUS_BUSY, "Daemon busy");
CHECK_AND_ASSERT_THROW_MES(resp.status == CORE_RPC_STATUS_OK, "Daemon response invalid: " << resp.status);
}
uint64_t mock_daemon::get_height()
{
return m_core->get_blockchain_storage().get_current_blockchain_height();
}

154
tests/trezor/daemon.h Normal file
View file

@ -0,0 +1,154 @@
// Copyright (c) 2014-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.
#pragma once
#include "misc_log_ex.h"
#include "daemon/daemon.h"
#include "rpc/daemon_handler.h"
#include "rpc/zmq_server.h"
#include "common/password.h"
#include "common/util.h"
#include "daemon/core.h"
#include "daemon/p2p.h"
#include "daemon/protocol.h"
#include "daemon/rpc.h"
#include "daemon/command_server.h"
#include "daemon/command_server.h"
#include "daemon/command_line_args.h"
#include "version.h"
#include "tools.h"
class mock_rpc_daemon : public cryptonote::core_rpc_server {
public:
mock_rpc_daemon(
cryptonote::core& cr
, nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p
): cryptonote::core_rpc_server(cr, p2p) {}
static void init_options(boost::program_options::options_description& desc){ cryptonote::core_rpc_server::init_options(desc); }
cryptonote::network_type nettype() const { return m_network_type; }
void nettype(cryptonote::network_type nettype) { m_network_type = nettype; }
CHAIN_HTTP_TO_MAP2(cryptonote::core_rpc_server::connection_context); //forward http requests to uri map
BEGIN_URI_MAP2()
MAP_URI_AUTO_JON2("/send_raw_transaction", on_send_raw_tx_2, cryptonote::COMMAND_RPC_SEND_RAW_TX)
MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx_2, cryptonote::COMMAND_RPC_SEND_RAW_TX)
else { // Default to parent for non-overriden callbacks
return cryptonote::core_rpc_server::handle_http_request_map(query_info, response_info, m_conn_context);
}
END_URI_MAP2()
bool on_send_raw_tx_2(const cryptonote::COMMAND_RPC_SEND_RAW_TX::request& req, cryptonote::COMMAND_RPC_SEND_RAW_TX::response& res, const cryptonote::core_rpc_server::connection_context *ctx);
protected:
cryptonote::network_type m_network_type;
};
class mock_daemon {
public:
typedef cryptonote::t_cryptonote_protocol_handler<cryptonote::core> t_protocol_raw;
typedef nodetool::node_server<t_protocol_raw> t_node_server;
static constexpr const std::chrono::seconds rpc_timeout = std::chrono::seconds(60);
cryptonote::core * m_core;
t_protocol_raw m_protocol;
mock_rpc_daemon m_rpc_server;
t_node_server m_server;
cryptonote::network_type m_network_type;
epee::net_utils::http::http_simple_client m_http_client;
bool m_start_p2p;
bool m_start_zmq;
boost::program_options::variables_map m_vm;
std::string m_p2p_bind_port;
std::string m_rpc_bind_port;
std::string m_zmq_bind_port;
std::atomic<bool> m_stopped;
std::atomic<bool> m_terminated;
std::atomic<bool> m_deinitalized;
boost::thread m_run_thread;
mock_daemon(
cryptonote::core * core,
boost::program_options::variables_map const & vm
)
: m_core(core)
, m_vm(vm)
, m_start_p2p(false)
, m_start_zmq(false)
, m_terminated(false)
, m_deinitalized(false)
, m_stopped(false)
, m_protocol{*core, nullptr, command_line::get_arg(vm, cryptonote::arg_offline)}
, m_server{m_protocol}
, m_rpc_server{*core, m_server}
{
// Handle circular dependencies
m_protocol.set_p2p_endpoint(&m_server);
m_core->set_cryptonote_protocol(&m_protocol);
load_params(vm);
}
virtual ~mock_daemon();
static void init_options(boost::program_options::options_description & option_spec);
static void default_options(boost::program_options::variables_map & vm);
static void set_ports(boost::program_options::variables_map & vm, unsigned initial_port);
mock_daemon * set_start_p2p(bool fl) { m_start_p2p = fl; return this; }
mock_daemon * set_start_zmq(bool fl) { m_start_zmq = fl; return this; }
void init();
void deinit();
void run();
bool run_main();
void stop();
void stop_p2p();
void stop_rpc();
void init_and_run();
void stop_and_deinit();
void try_init_and_run(boost::optional<unsigned> initial_port=boost::none);
void mine_blocks(size_t num_blocks, const std::string &miner_address);
void start_mining(const std::string &miner_address, uint64_t threads_count=1, bool do_background_mining=false, bool ignore_battery=true);
void stop_mining();
uint64_t get_height();
void load_params(boost::program_options::variables_map const & vm);
std::string zmq_addr() const { return std::string("127.0.0.1:") + m_zmq_bind_port; }
std::string rpc_addr() const { return std::string("127.0.0.1:") + m_rpc_bind_port; }
std::string p2p_addr() const { return std::string("127.0.0.1:") + m_p2p_bind_port; }
cryptonote::network_type nettype() const { return m_network_type; }
cryptonote::core * core() const { return m_core; }
};

56
tests/trezor/tools.cpp Normal file
View file

@ -0,0 +1,56 @@
// Copyright (c) 2014-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.
#include "tools.h"
namespace tools {
namespace po = boost::program_options;
void options::set_option(boost::program_options::variables_map &vm, const std::string & key, const po::variable_value &pv)
{
auto it = vm.find(key);
if (it == vm.end())
{
vm.insert(std::make_pair(key, pv));
}
else
{
it->second = pv;
}
}
void options::build_options(boost::program_options::variables_map & vm, const po::options_description & desc_params)
{
const char *argv[2] = {nullptr};
po::store(po::parse_command_line(1, argv, desc_params), vm);
po::notify(vm);
}
}

61
tests/trezor/tools.h Normal file
View file

@ -0,0 +1,61 @@
// Copyright (c) 2014-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.
#pragma once
#include "misc_log_ex.h"
#include "daemon/daemon.h"
#include "rpc/daemon_handler.h"
#include "rpc/zmq_server.h"
#include "common/password.h"
#include "common/util.h"
#include "daemon/core.h"
#include "daemon/p2p.h"
#include "daemon/protocol.h"
#include "daemon/rpc.h"
#include "daemon/command_server.h"
#include "daemon/command_server.h"
#include "daemon/command_line_args.h"
#include "version.h"
namespace tools {
class options {
public:
static void set_option(boost::program_options::variables_map &vm, const std::string &key, const boost::program_options::variable_value &pv);
static void build_options(boost::program_options::variables_map & vm, const boost::program_options::options_description & desc_params);
template<typename T, bool required, bool dependent, int NUM_DEPS>
static void set_option(boost::program_options::variables_map &vm, const command_line::arg_descriptor<T, required, dependent, NUM_DEPS> &arg, const boost::program_options::variable_value &pv)
{
set_option(vm, arg.name, pv);
}
};
};

View file

@ -41,6 +41,7 @@ using namespace cryptonote;
#include "common/util.h"
#include "common/command_line.h"
#include "trezor_tests.h"
#include "tools.h"
#include "device/device_cold.hpp"
#include "device_trezor/device_trezor.hpp"
@ -57,7 +58,7 @@ namespace
const command_line::arg_descriptor<bool> arg_fix_chain = {"fix_chain", "If chain_patch is given and file cannot be used, it is ignored and overwriten", false};
}
#define HW_TREZOR_NAME "Trezor"
#define TREZOR_ACCOUNT_ORDERING &m_miner_account, &m_alice_account, &m_bob_account, &m_eve_account
#define TREZOR_COMMON_TEST_CASE(genclass, CORE, BASE) \
rollback_chain(CORE, BASE.head_block()); \
@ -70,14 +71,17 @@ namespace
#define TREZOR_SETUP_CHAIN(NAME) do { \
++tests_count; \
try { \
setup_chain(&core, trezor_base, chain_path, fix_chain); \
setup_chain(core, trezor_base, chain_path, fix_chain, vm_core); \
} catch (const std::exception& ex) { \
failed_tests.emplace_back("gen_trezor_base " #NAME); \
} \
} while(0)
static device_trezor_test *trezor_device = nullptr;
static device_trezor_test *ensure_trezor_test_device();
static void rollback_chain(cryptonote::core * core, const cryptonote::block & head);
static void setup_chain(cryptonote::core ** core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain);
static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain, const po::variables_map & vm_core);
int main(int argc, char* argv[])
{
@ -123,21 +127,56 @@ int main(int argc, char* argv[])
const bool heavy_tests = command_line::get_arg(vm, arg_heavy_tests);
const bool fix_chain = command_line::get_arg(vm, arg_fix_chain);
hw::trezor::register_all();
hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device());
// hw::trezor::register_all(); // We use our shim instead.
// Bootstrapping common chain & accounts
cryptonote::core * core = nullptr;
const uint8_t initial_hf = 9;
const uint8_t max_hf = 10;
cryptonote::core core_obj(nullptr);
cryptonote::core * const core = &core_obj;
std::shared_ptr<mock_daemon> daemon = nullptr;
gen_trezor_base trezor_base;
trezor_base.setup_args(trezor_path, heavy_tests);
trezor_base.rct_config({rct::RangeProofPaddedBulletproof, 1}); // HF9 tests
trezor_base.set_hard_fork(initial_hf);
TREZOR_SETUP_CHAIN("HF9");
// Individual test cases using shared pre-generated blockchain.
TREZOR_COMMON_TEST_CASE(gen_trezor_ki_sync, core, trezor_base);
// Arguments for core & daemon
po::variables_map vm_core;
po::options_description desc_params_core("Core");
mock_daemon::init_options(desc_params_core);
tools::options::build_options(vm_core, desc_params_core);
mock_daemon::default_options(vm_core);
// Transaction tests
for(uint8_t hf=initial_hf; hf <= max_hf; ++hf)
{
MDEBUG("Transaction tests for HF " << (int)hf);
if (hf > initial_hf)
{
daemon->stop_and_deinit();
daemon = nullptr;
trezor_base.daemon(nullptr);
}
trezor_base.set_hard_fork(hf);
TREZOR_SETUP_CHAIN(std::string("HF") + std::to_string((int)hf));
daemon = std::make_shared<mock_daemon>(core, vm_core);
CHECK_AND_ASSERT_THROW_MES(daemon->nettype() == trezor_base.nettype(), "Serialized chain network type does not match");
daemon->try_init_and_run();
trezor_base.daemon(daemon);
// Hard-fork independent tests
if (hf == initial_hf)
{
TREZOR_COMMON_TEST_CASE(gen_trezor_ki_sync_without_refresh, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_live_refresh, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_ki_sync_with_refresh, core, trezor_base);
}
TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo_paymentid_short, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo_paymentid_short_integrated, core, trezor_base);
@ -149,12 +188,15 @@ int main(int argc, char* argv[])
TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_1norm_2sub, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_2utxo_sub_acc_to_1norm_2sub, core, trezor_base);
TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_7outs, core, trezor_base);
TREZOR_COMMON_TEST_CASE(wallet_api_tests, core, trezor_base);
}
if (trezor_base.heavy_tests())
{
TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo, core, trezor_base);
}
daemon->stop();
core->deinit();
el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error);
MLOG(level, "\nREPORT:");
@ -243,7 +285,36 @@ static bool serialize_chain_to_file(std::vector<test_event_entry>& events, gen_t
CATCH_ENTRY_L0("serialize_chain_to_file", false);
}
static void setup_chain(cryptonote::core ** core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain)
template<class t_test_class>
static bool init_core_replay_events(std::vector<test_event_entry>& events, cryptonote::core * core, const po::variables_map & vm_core)
{
// this test needs for it to be so.
get_test_options<t_test_class> gto;
// Hardforks can be specified in events.
v_hardforks_t hardforks;
cryptonote::test_options test_options_tmp{};
const cryptonote::test_options * test_options_ = &gto.test_options;
if (extract_hard_forks(events, hardforks)){
hardforks.push_back(std::make_pair((uint8_t)0, (uint64_t)0)); // terminator
test_options_tmp.hard_forks = hardforks.data();
test_options_ = &test_options_tmp;
}
core->deinit();
CHECK_AND_ASSERT_THROW_MES(core->init(vm_core, test_options_), "Core init failed");
core->get_blockchain_storage().get_db().set_batch_transactions(true);
// start with a clean pool
std::vector<crypto::hash> pool_txs;
CHECK_AND_ASSERT_THROW_MES(core->get_pool_transaction_hashes(pool_txs), "Failed to flush txpool");
core->get_blockchain_storage().flush_txes_from_pool(pool_txs);
t_test_class validator;
return replay_events_through_core<t_test_class>(*core, events, validator);
}
static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain, const po::variables_map & vm_core)
{
std::vector<test_event_entry> events;
const bool do_serialize = !chain_path.empty();
@ -272,6 +343,7 @@ static void setup_chain(cryptonote::core ** core, gen_trezor_base & trezor_base,
{
try
{
trezor_base.clear();
generated = trezor_base.generate(events);
if (generated && !loaded && do_serialize)
@ -287,7 +359,7 @@ static void setup_chain(cryptonote::core ** core, gen_trezor_base & trezor_base,
}
trezor_base.fix_hf(events);
if (generated && do_replay_events_get_core<gen_trezor_base>(events, core))
if (generated && init_core_replay_events<gen_trezor_base>(events, core, vm_core))
{
MGINFO_GREEN("#TEST-chain-init# Succeeded ");
}
@ -298,6 +370,14 @@ static void setup_chain(cryptonote::core ** core, gen_trezor_base & trezor_base,
}
}
static device_trezor_test *ensure_trezor_test_device(){
if (!trezor_device) {
trezor_device = new device_trezor_test();
trezor_device->set_name(HW_TREZOR_NAME);
}
return trezor_device;
}
static void add_hforks(std::vector<test_event_entry>& events, const v_hardforks_t& hard_forks)
{
event_replay_settings repl_set;
@ -483,8 +563,20 @@ static std::vector<tools::wallet2*> vct_wallets(tools::wallet2* w1=nullptr, tool
return res;
}
static uint64_t get_available_funds(tools::wallet2* wallet, uint32_t account=0)
{
tools::wallet2::transfer_container transfers;
wallet->get_transfers(transfers);
uint64_t sum = 0;
for(const auto & cur : transfers)
{
sum += !cur.m_spent && cur.m_subaddr_index.major == account ? cur.amount() : 0;
}
return sum;
}
// gen_trezor_base
const uint64_t gen_trezor_base::m_ts_start = 1338224400;
const uint64_t gen_trezor_base::m_ts_start = 1397862000; // As default wallet timestamp is 1397516400
const uint64_t gen_trezor_base::m_wallet_ts = m_ts_start - 60*60*24*4;
const std::string gen_trezor_base::m_device_name = "Trezor:udp";
const std::string gen_trezor_base::m_master_seed_str = "14821d0bc5659b24cafbc889dc4fc60785ee08b65d71c525f81eeaba4f3a570f";
@ -494,12 +586,16 @@ const std::string gen_trezor_base::m_alice_view_private = "a6ccd4ac344a295d1387f
gen_trezor_base::gen_trezor_base(){
m_rct_config = {rct::RangeProofPaddedBulletproof, 1};
m_test_get_tx_key = true;
m_network_type = cryptonote::TESTNET;
}
gen_trezor_base::gen_trezor_base(const gen_trezor_base &other):
m_generator(other.m_generator), m_bt(other.m_bt), m_miner_account(other.m_miner_account),
m_bob_account(other.m_bob_account), m_alice_account(other.m_alice_account), m_eve_account(other.m_eve_account),
m_hard_forks(other.m_hard_forks), m_trezor(other.m_trezor), m_rct_config(other.m_rct_config)
m_hard_forks(other.m_hard_forks), m_trezor(other.m_trezor), m_rct_config(other.m_rct_config),
m_heavy_tests(other.m_heavy_tests), m_test_get_tx_key(other.m_test_get_tx_key), m_live_refresh_enabled(other.m_live_refresh_enabled),
m_network_type(other.m_network_type), m_daemon(other.m_daemon)
{
}
@ -513,33 +609,27 @@ void gen_trezor_base::setup_args(const std::string & trezor_path, bool heavy_tes
void gen_trezor_base::setup_trezor()
{
hw::device &hwdev = hw::get_device(m_trezor_path);
m_trezor = dynamic_cast<hw::trezor::device_trezor *>(&hwdev);
CHECK_AND_ASSERT_THROW_MES(m_trezor, "Dynamic cast failed");
auto trezor = dynamic_cast<device_trezor_test *>(&hwdev);
CHECK_AND_ASSERT_THROW_MES(trezor, "Dynamic cast failed");
m_trezor->set_debug(true); // debugging commands on Trezor (auto-confirm transactions)
CHECK_AND_ASSERT_THROW_MES(m_trezor->set_name(m_trezor_path), "Could not set device name " << m_trezor_path);
m_trezor->set_network_type(MAINNET);
m_trezor->set_derivation_path(""); // empty derivation path
CHECK_AND_ASSERT_THROW_MES(m_trezor->init(), "Could not initialize the device " << m_trezor_path);
CHECK_AND_ASSERT_THROW_MES(m_trezor->connect(), "Could not connect to the device " << m_trezor_path);
m_trezor->wipe_device();
m_trezor->load_device(m_device_seed);
m_trezor->release();
m_trezor->disconnect();
trezor->setup_for_tests(m_trezor_path, m_device_seed, m_network_type);
m_trezor = trezor;
}
void gen_trezor_base::fork(gen_trezor_base & other)
{
other.m_generator = m_generator;
other.m_bt = m_bt;
other.m_network_type = m_network_type;
other.m_daemon = m_daemon;
other.m_events = m_events;
other.m_head = m_head;
other.m_hard_forks = m_hard_forks;
other.m_trezor_path = m_trezor_path;
other.m_heavy_tests = m_heavy_tests;
other.m_rct_config = m_rct_config;
other.m_test_get_tx_key = m_test_get_tx_key;
other.m_live_refresh_enabled = m_live_refresh_enabled;
other.m_miner_account = m_miner_account;
other.m_bob_account = m_bob_account;
@ -577,10 +667,22 @@ void gen_trezor_base::init_fields()
m_alice_account.set_createtime(m_wallet_ts);
}
void gen_trezor_base::update_client_settings()
{
auto dev_trezor = dynamic_cast<::hw::trezor::device_trezor*>(m_trezor);
CHECK_AND_ASSERT_THROW_MES(dev_trezor, "Could not cast to device_trezor");
dev_trezor->set_live_refresh_enabled(m_live_refresh_enabled);
}
bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
{
init_fields();
setup_trezor();
m_live_refresh_enabled = false;
update_client_settings();
m_alice_account.create_from_device(*m_trezor);
m_alice_account.set_createtime(m_wallet_ts);
@ -589,7 +691,7 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
cryptonote::block blk_gen;
std::vector<size_t> block_weights;
generate_genesis_block(blk_gen, get_config(MAINNET).GENESIS_TX, get_config(MAINNET).GENESIS_NONCE);
generate_genesis_block(blk_gen, get_config(m_network_type).GENESIS_TX, get_config(m_network_type).GENESIS_NONCE);
events.push_back(blk_gen);
generator.add_block(blk_gen, 0, block_weights, 0);
@ -644,8 +746,8 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
MDEBUG("Hardfork height: " << hardfork_height << " at block: " << get_block_hash(blk_4r));
// RCT transactions, wallets have to be used, wallet init
m_wl_alice.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_bob.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true));
m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true));
wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account);
wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account);
@ -659,7 +761,7 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
auto addr_alice_sub_1_2 = m_wl_alice->get_subaddress({1, 2});
// Miner -> Bob, RCT funds
MAKE_TX_LIST_START_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(5), 10, blk_4);
MAKE_TX_LIST_START_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(50), 10, blk_4);
const size_t target_rct = m_heavy_tests ? 105 : 15;
for(size_t i = 0; i < target_rct; ++i)
@ -688,9 +790,9 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
// Simple RCT transactions
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(7), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(1), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(3), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(4), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(10), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(30), 10, blk_4);
MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(40), 10, blk_4);
MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_5, blk_4r, m_miner_account, txs_blk_5, CUR_HF);
// Simple transaction check
@ -704,6 +806,8 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events)
// RCT transactions, wallets have to be used
wallet_tools::process_transactions(m_wl_alice.get(), events, blk_5r, m_bt);
wallet_tools::process_transactions(m_wl_bob.get(), events, blk_5r, m_bt);
MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get()));
MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get()));
// Send Alice -> Bob, manually constructed. Simple TX test, precondition.
cryptonote::transaction tx_1;
@ -768,18 +872,21 @@ void gen_trezor_base::load(std::vector<test_event_entry>& events)
m_eve_account.set_createtime(m_wallet_ts);
setup_trezor();
update_client_settings();
m_alice_account.create_from_device(*m_trezor);
m_alice_account.set_createtime(m_wallet_ts);
m_wl_alice.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_bob.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_eve.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true));
m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true));
m_wl_eve.reset(new tools::wallet2(m_network_type, 1, true));
wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account);
wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account);
wallet_accessor_test::set_account(m_wl_eve.get(), m_eve_account);
wallet_tools::process_transactions(m_wl_alice.get(), events, m_head, m_bt);
wallet_tools::process_transactions(m_wl_bob.get(), events, m_head, m_bt);
MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get()));
MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get()));
}
void gen_trezor_base::fix_hf(std::vector<test_event_entry>& events)
@ -804,12 +911,14 @@ void gen_trezor_base::test_setup(std::vector<test_event_entry>& events)
add_shared_events(events);
setup_trezor();
update_client_settings();
m_alice_account.create_from_device(*m_trezor);
m_alice_account.set_createtime(m_wallet_ts);
m_wl_alice.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_bob.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_eve.reset(new tools::wallet2(MAINNET, 1, true));
m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true));
m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true));
m_wl_eve.reset(new tools::wallet2(m_network_type, 1, true));
wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account);
wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account);
wallet_accessor_test::set_account(m_wl_eve.get(), m_eve_account);
@ -818,6 +927,31 @@ void gen_trezor_base::test_setup(std::vector<test_event_entry>& events)
wallet_tools::process_transactions(m_wl_eve.get(), events, m_head, m_bt);
}
void gen_trezor_base::add_transactions_to_events(
std::vector<test_event_entry>& events,
test_generator &generator,
const std::vector<cryptonote::transaction> &txs)
{
// If current test requires higher hard-fork, move it up
const auto current_hf = m_hard_forks.back().first;
const uint8_t tx_hf = m_rct_config.bp_version == 2 ? 10 : 9;
if (tx_hf > current_hf){
throw std::runtime_error("Too late for HF change");
}
std::list<cryptonote::transaction> tx_list;
for(const auto & tx : txs)
{
events.push_back(tx);
tx_list.push_back(tx);
}
MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_new, m_head, m_miner_account, tx_list, tx_hf);
MDEBUG("New tsx: " << (num_blocks(events) - 1) << " at block: " << get_block_hash(blk_new));
m_head = blk_new;
}
void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std::vector<tools::wallet2::pending_tx>& ptxs, std::vector<cryptonote::address_parse_info>& dsts_info, test_generator &generator, std::vector<tools::wallet2*> wallets, bool is_sweep)
{
// Construct pending transaction for signature in the Trezor.
@ -825,15 +959,8 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std:
cryptonote::block head_block = get_head_block(events);
const crypto::hash head_hash = get_block_hash(head_block);
// If current test requires higher hard-fork, move it up
const auto current_hf = m_hard_forks.back().first;
const uint8_t tx_hf = m_rct_config.bp_version == 2 ? 10 : 9;
if (tx_hf > current_hf){
throw std::runtime_error("Too late for HF change");
}
tools::wallet2::unsigned_tx_set txs;
std::list<cryptonote::transaction> tx_list;
std::vector<cryptonote::transaction> tx_list;
for(auto &ptx : ptxs) {
txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(ptx, *m_trezor));
@ -848,6 +975,7 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std:
hw::wallet_shim wallet_shim;
setup_shim(&wallet_shim);
aux_data.tx_recipients = dsts_info;
aux_data.bp_version = m_rct_config.bp_version;
dev_cold->tx_sign(&wallet_shim, txs, exported_txs, aux_data);
MDEBUG("Signed tx data from hw: " << exported_txs.ptx.size() << " transactions");
@ -865,13 +993,11 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std:
CHECK_AND_ASSERT_THROW_MES(resx, "Trezor tx_1 semantics failed");
CHECK_AND_ASSERT_THROW_MES(resy, "Trezor tx_1 Nonsemantics failed");
events.push_back(c_ptx.tx);
tx_list.push_back(c_ptx.tx);
MDEBUG("Transaction: " << dump_data(c_ptx.tx));
}
MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_7, m_head, m_miner_account, tx_list, tx_hf);
MDEBUG("Trezor tsx: " << (num_blocks(events) - 1) << " at block: " << get_block_hash(blk_7));
add_transactions_to_events(events, generator, tx_list);
// TX receive test
uint64_t sum_in = 0;
@ -909,7 +1035,7 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std:
const bool sender = widx == 0;
tools::wallet2 *wl = wallets[widx];
wallet_tools::process_transactions(wl, events, blk_7, m_bt, boost::make_optional(head_hash));
wallet_tools::process_transactions(wl, events, m_head, m_bt, boost::make_optional(head_hash));
tools::wallet2::transfer_container m_trans;
tools::wallet2::transfer_container m_trans_txid;
@ -972,6 +1098,120 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std:
}
CHECK_AND_ASSERT_THROW_MES(sum_in == sum_out, "Tx amount mismatch");
// Test get_tx_key feature for stored private tx keys
test_get_tx(events, wallets, exported_txs.ptx, aux_data.tx_device_aux);
}
bool gen_trezor_base::verify_tx_key(const ::crypto::secret_key & tx_priv, const ::crypto::public_key & tx_pub, const subaddresses_t & subs)
{
::crypto::public_key tx_pub_c;
::crypto::secret_key_to_public_key(tx_priv, tx_pub_c);
if (tx_pub == tx_pub_c)
return true;
for(const auto & elem : subs)
{
tx_pub_c = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(elem.first), rct::sk2rct(tx_priv)));
if (tx_pub == tx_pub_c)
return true;
}
return false;
}
void gen_trezor_base::test_get_tx(
std::vector<test_event_entry>& events,
std::vector<tools::wallet2*> wallets,
const std::vector<tools::wallet2::pending_tx> &ptxs,
const std::vector<std::string> &aux_tx_info)
{
if (!m_test_get_tx_key)
{
return;
}
auto dev_cold = dynamic_cast<::hw::device_cold*>(m_trezor);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
if (!dev_cold->is_get_tx_key_supported())
{
MERROR("Get TX key is not supported by the connected Trezor");
return;
}
subaddresses_t all_subs;
for(tools::wallet2 * wlt : wallets)
{
wlt->expand_subaddresses({10, 20});
const subaddresses_t & cur_sub = wallet_accessor_test::get_subaddresses(wlt);
all_subs.insert(cur_sub.begin(), cur_sub.end());
}
for(size_t txid = 0; txid < ptxs.size(); ++txid)
{
const auto &c_ptx = ptxs[txid];
const auto &c_tx = c_ptx.tx;
const ::crypto::hash tx_prefix_hash = cryptonote::get_transaction_prefix_hash(c_tx);
auto tx_pub = cryptonote::get_tx_pub_key_from_extra(c_tx.extra);
auto additional_pub_keys = cryptonote::get_additional_tx_pub_keys_from_extra(c_tx.extra);
hw::device_cold:: tx_key_data_t tx_key_data;
std::vector<::crypto::secret_key> tx_keys;
dev_cold->load_tx_key_data(tx_key_data, aux_tx_info[txid]);
CHECK_AND_ASSERT_THROW_MES(std::string(tx_prefix_hash.data, 32) == tx_key_data.tx_prefix_hash, "TX prefix mismatch");
dev_cold->get_tx_key(tx_keys, tx_key_data, m_alice_account.get_keys().m_view_secret_key);
CHECK_AND_ASSERT_THROW_MES(!tx_keys.empty(), "Empty TX keys");
CHECK_AND_ASSERT_THROW_MES(verify_tx_key(tx_keys[0], tx_pub, all_subs), "Tx pub mismatch");
CHECK_AND_ASSERT_THROW_MES(additional_pub_keys.size() == tx_keys.size() - 1, "Invalid additional keys count");
for(size_t i = 0; i < additional_pub_keys.size(); ++i)
{
CHECK_AND_ASSERT_THROW_MES(verify_tx_key(tx_keys[i + 1], additional_pub_keys[i], all_subs), "Tx pub mismatch");
}
}
}
void gen_trezor_base::mine_and_test(std::vector<test_event_entry>& events)
{
cryptonote::core * core = daemon()->core();
const uint64_t height_before_mining = daemon()->get_height();
const auto miner_address = cryptonote::get_account_address_as_str(FAKECHAIN, false, get_address(m_miner_account));
daemon()->mine_blocks(1, miner_address);
const uint64_t cur_height = daemon()->get_height();
CHECK_AND_ASSERT_THROW_MES(height_before_mining < cur_height, "Mining fail");
const crypto::hash top_hash = core->get_blockchain_storage().get_block_id_by_height(height_before_mining);
cryptonote::block top_block{};
CHECK_AND_ASSERT_THROW_MES(core->get_blockchain_storage().get_block_by_hash(top_hash, top_block), "Block fetch fail");
CHECK_AND_ASSERT_THROW_MES(!top_block.tx_hashes.empty(), "Mined block is empty");
std::vector<cryptonote::transaction> txs_found;
std::vector<crypto::hash> txs_missed;
bool r = core->get_blockchain_storage().get_transactions(top_block.tx_hashes, txs_found, txs_missed);
CHECK_AND_ASSERT_THROW_MES(r, "Transaction lookup fail");
CHECK_AND_ASSERT_THROW_MES(!txs_found.empty(), "Transaction lookup fail");
// Transaction is not expanded, but mining verified it.
events.push_back(txs_found[0]);
events.push_back(top_block);
}
void gen_trezor_base::set_hard_fork(uint8_t hf)
{
m_top_hard_fork = hf;
if (hf < 9){
throw std::runtime_error("Minimal supported Hardfork is 9");
} else if (hf == 9){
rct_config({rct::RangeProofPaddedBulletproof, 1});
} else {
rct_config({rct::RangeProofPaddedBulletproof, 2});
}
}
#define TREZOR_TEST_PREFIX() \
@ -1182,6 +1422,48 @@ std::vector<tools::wallet2::pending_tx> tsx_builder::build()
return m_ptxs;
}
device_trezor_test::device_trezor_test(): m_tx_sign_ctr(0), m_compute_key_image_ctr(0) {}
void device_trezor_test::clear_test_counters(){
m_tx_sign_ctr = 0;
m_compute_key_image_ctr = 0;
}
void device_trezor_test::setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type){
this->clear_test_counters();
this->set_callback(nullptr);
this->set_debug(true); // debugging commands on Trezor (auto-confirm transactions)
CHECK_AND_ASSERT_THROW_MES(this->set_name(trezor_path), "Could not set device name " << trezor_path);
this->set_network_type(network_type);
this->set_derivation_path(""); // empty derivation path
CHECK_AND_ASSERT_THROW_MES(this->init(), "Could not initialize the device " << trezor_path);
CHECK_AND_ASSERT_THROW_MES(this->connect(), "Could not connect to the device " << trezor_path);
this->wipe_device();
this->load_device(seed);
this->release();
this->disconnect();
}
bool device_trezor_test::compute_key_image(const ::cryptonote::account_keys &ack, const ::crypto::public_key &out_key,
const ::crypto::key_derivation &recv_derivation, size_t real_output_index,
const ::cryptonote::subaddress_index &received_index,
::cryptonote::keypair &in_ephemeral, ::crypto::key_image &ki) {
bool res = device_trezor::compute_key_image(ack, out_key, recv_derivation, real_output_index, received_index,
in_ephemeral, ki);
m_compute_key_image_ctr += res;
return res;
}
void
device_trezor_test::tx_sign(hw::wallet_shim *wallet, const ::tools::wallet2::unsigned_tx_set &unsigned_tx, size_t idx,
hw::tx_aux_data &aux_data, std::shared_ptr<hw::trezor::protocol::tx::Signer> &signer) {
m_tx_sign_ctr += 1;
device_trezor::tx_sign(wallet, unsigned_tx, idx, aux_data, signer);
}
bool gen_trezor_ki_sync::generate(std::vector<test_event_entry>& events)
{
test_generator generator(m_generator);
@ -1207,6 +1489,92 @@ bool gen_trezor_ki_sync::generate(std::vector<test_event_entry>& events)
uint64_t spent = 0, unspent = 0;
m_wl_alice->import_key_images(ski, 0, spent, unspent, false);
auto dev_trezor_test = dynamic_cast<device_trezor_test*>(m_trezor);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement test interface");
if (!m_live_refresh_enabled)
CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_compute_key_image_ctr == 0, "Live refresh should not happen: " << dev_trezor_test->m_compute_key_image_ctr);
else
CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_compute_key_image_ctr == ski.size(), "Live refresh counts invalid");
return true;
}
bool gen_trezor_ki_sync_with_refresh::generate(std::vector<test_event_entry>& events)
{
m_live_refresh_enabled = true;
return gen_trezor_ki_sync::generate(events);
}
bool gen_trezor_ki_sync_without_refresh::generate(std::vector<test_event_entry>& events)
{
m_live_refresh_enabled = false;
return gen_trezor_ki_sync::generate(events);
}
bool gen_trezor_live_refresh::generate(std::vector<test_event_entry>& events)
{
test_generator generator(m_generator);
test_setup(events);
auto dev_cold = dynamic_cast<::hw::device_cold*>(m_trezor);
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
if (!dev_cold->is_live_refresh_supported()){
MDEBUG("Trezor does not support live refresh");
return true;
}
hw::device & sw_device = hw::get_device("default");
dev_cold->live_refresh_start();
for(unsigned i=0; i<50; ++i)
{
cryptonote::subaddress_index subaddr = {0, i};
::crypto::secret_key r;
::crypto::public_key R;
::crypto::key_derivation D;
::crypto::public_key pub_ver;
::crypto::key_image ki;
::crypto::random32_unbiased((unsigned char*)r.data);
::crypto::secret_key_to_public_key(r, R);
memcpy(D.data, rct::scalarmultKey(rct::pk2rct(R), rct::sk2rct(m_alice_account.get_keys().m_view_secret_key)).bytes, 32);
::crypto::secret_key scalar_step1;
::crypto::secret_key scalar_step2;
::crypto::derive_secret_key(D, i, m_alice_account.get_keys().m_spend_secret_key, scalar_step1);
if (i == 0)
{
scalar_step2 = scalar_step1;
}
else
{
::crypto::secret_key subaddr_sk = sw_device.get_subaddress_secret_key(m_alice_account.get_keys().m_view_secret_key, subaddr);
sw_device.sc_secret_add(scalar_step2, scalar_step1, subaddr_sk);
}
::crypto::secret_key_to_public_key(scalar_step2, pub_ver);
::crypto::generate_key_image(pub_ver, scalar_step2, ki);
cryptonote::keypair in_ephemeral;
::crypto::key_image ki2;
dev_cold->live_refresh(
m_alice_account.get_keys().m_view_secret_key,
pub_ver,
D,
i,
subaddr,
in_ephemeral,
ki2
);
CHECK_AND_ASSERT_THROW_MES(ki == ki2, "Key image inconsistent");
}
dev_cold->live_refresh_finish();
return true;
}
@ -1407,3 +1775,62 @@ bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events)
TREZOR_TEST_SUFFIX();
}
void wallet_api_tests::init()
{
m_wallet_dir = boost::filesystem::unique_path();
boost::filesystem::create_directories(m_wallet_dir);
}
wallet_api_tests::~wallet_api_tests()
{
try
{
if (!m_wallet_dir.empty() && boost::filesystem::exists(m_wallet_dir))
{
boost::filesystem::remove_all(m_wallet_dir);
}
}
catch(...)
{
MERROR("Could not remove wallet directory");
}
}
bool wallet_api_tests::generate(std::vector<test_event_entry>& events)
{
init();
test_setup(events);
const std::string wallet_path = (m_wallet_dir / "wallet").string();
const auto api_net_type = m_network_type == TESTNET ? Monero::TESTNET : Monero::MAINNET;
Monero::WalletManager *wmgr = Monero::WalletManagerFactory::getWalletManager();
std::unique_ptr<Monero::Wallet> w{wmgr->createWalletFromDevice(wallet_path, "", api_net_type, m_trezor_path, 1)};
CHECK_AND_ASSERT_THROW_MES(w->init(daemon()->rpc_addr(), 0), "Wallet init fail");
CHECK_AND_ASSERT_THROW_MES(w->refresh(), "Refresh fail");
uint64_t balance = w->balance(0);
MINFO("Balance: " << balance);
CHECK_AND_ASSERT_THROW_MES(w->status() == Monero::PendingTransaction::Status_Ok, "Status nok");
auto addr = get_address(m_eve_account);
auto recepient_address = cryptonote::get_account_address_as_str(m_network_type, false, addr);
Monero::PendingTransaction * transaction = w->createTransaction(recepient_address,
"",
MK_COINS(10),
TREZOR_TEST_MIXIN,
Monero::PendingTransaction::Priority_Medium,
0,
std::set<uint32_t>{});
CHECK_AND_ASSERT_THROW_MES(transaction->status() == Monero::PendingTransaction::Status_Ok, "Status nok");
w->refresh();
CHECK_AND_ASSERT_THROW_MES(w->balance(0) == balance, "Err");
CHECK_AND_ASSERT_THROW_MES(transaction->amount() == MK_COINS(10), "Err");
CHECK_AND_ASSERT_THROW_MES(transaction->commit(), "Err");
CHECK_AND_ASSERT_THROW_MES(w->balance(0) != balance, "Err");
CHECK_AND_ASSERT_THROW_MES(wmgr->closeWallet(w.get()), "Err");
(void)w.release();
mine_and_test(events);
return true;
}

View file

@ -31,6 +31,8 @@
#pragma once
#include <device_trezor/device_trezor.hpp>
#include <wallet/api/wallet2_api.h>
#include "daemon.h"
#include "../core_tests/chaingen.h"
#include "../core_tests/wallet_tools.h"
@ -50,29 +52,48 @@ public:
gen_trezor_base(const gen_trezor_base &other);
virtual ~gen_trezor_base() {};
void setup_args(const std::string & trezor_path, bool heavy_tests=false);
virtual void setup_args(const std::string & trezor_path, bool heavy_tests=false);
virtual bool generate(std::vector<test_event_entry>& events);
virtual void load(std::vector<test_event_entry>& events); // load events, init test obj
void fix_hf(std::vector<test_event_entry>& events);
void update_trackers(std::vector<test_event_entry>& events);
virtual void fix_hf(std::vector<test_event_entry>& events);
virtual void update_trackers(std::vector<test_event_entry>& events);
void fork(gen_trezor_base & other); // fork generated chain to another test
void clear(); // clears m_events, bt, generator, hforks
void add_shared_events(std::vector<test_event_entry>& events); // m_events -> events
void test_setup(std::vector<test_event_entry>& events); // init setup env, wallets
virtual void fork(gen_trezor_base & other); // fork generated chain to another test
virtual void clear(); // clears m_events, bt, generator, hforks
virtual void add_shared_events(std::vector<test_event_entry>& events); // m_events -> events
virtual void test_setup(std::vector<test_event_entry>& events); // init setup env, wallets
void test_trezor_tx(std::vector<test_event_entry>& events,
virtual void add_transactions_to_events(
std::vector<test_event_entry>& events,
test_generator &generator,
const std::vector<cryptonote::transaction> &txs);
virtual void test_trezor_tx(
std::vector<test_event_entry>& events,
std::vector<tools::wallet2::pending_tx>& ptxs,
std::vector<cryptonote::address_parse_info>& dsts_info,
test_generator &generator,
std::vector<tools::wallet2*> wallets,
bool is_sweep=false);
crypto::hash head_hash() { return get_block_hash(m_head); }
cryptonote::block head_block() { return m_head; }
bool heavy_tests() { return m_heavy_tests; }
virtual void test_get_tx(
std::vector<test_event_entry>& events,
std::vector<tools::wallet2*> wallets,
const std::vector<tools::wallet2::pending_tx> &ptxs,
const std::vector<std::string> &aux_tx_info);
virtual void mine_and_test(std::vector<test_event_entry>& events);
virtual void set_hard_fork(uint8_t hf);
crypto::hash head_hash() const { return get_block_hash(m_head); }
cryptonote::block head_block() const { return m_head; }
bool heavy_tests() const { return m_heavy_tests; }
void rct_config(rct::RCTConfig rct_config) { m_rct_config = rct_config; }
uint8_t cur_hf(){ return m_hard_forks.size() > 0 ? m_hard_forks.back().first : 0; }
uint8_t cur_hf() const { return m_hard_forks.size() > 0 ? m_hard_forks.back().first : 0; }
cryptonote::network_type nettype() const { return m_network_type; }
std::shared_ptr<mock_daemon> daemon() const { return m_daemon; }
void daemon(std::shared_ptr<mock_daemon> daemon){ m_daemon = std::move(daemon); }
// Static configuration
static const uint64_t m_ts_start;
@ -84,19 +105,26 @@ public:
static const std::string m_alice_view_private;
protected:
void setup_trezor();
void init_fields();
virtual void setup_trezor();
virtual void init_fields();
virtual void update_client_settings();
virtual bool verify_tx_key(const ::crypto::secret_key & tx_priv, const ::crypto::public_key & tx_pub, const subaddresses_t & subs);
test_generator m_generator;
block_tracker m_bt;
cryptonote::network_type m_network_type;
std::shared_ptr<mock_daemon> m_daemon;
uint8_t m_top_hard_fork;
v_hardforks_t m_hard_forks;
cryptonote::block m_head;
std::vector<test_event_entry> m_events;
std::string m_trezor_path;
bool m_heavy_tests;
bool m_test_get_tx_key;
rct::RCTConfig m_rct_config;
bool m_live_refresh_enabled;
cryptonote::account_base m_miner_account;
cryptonote::account_base m_bob_account;
@ -113,6 +141,7 @@ protected:
void serialize(Archive & ar, const unsigned int /*version*/)
{
ar & m_generator;
ar & m_network_type;
}
};
@ -169,12 +198,52 @@ protected:
rct::RCTConfig m_rct_config;
};
// Trezor device ship to track actual method calls.
class device_trezor_test : public hw::trezor::device_trezor {
public:
size_t m_tx_sign_ctr;
size_t m_compute_key_image_ctr;
device_trezor_test();
void clear_test_counters();
void setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type);
bool compute_key_image(const ::cryptonote::account_keys &ack, const ::crypto::public_key &out_key,
const ::crypto::key_derivation &recv_derivation, size_t real_output_index,
const ::cryptonote::subaddress_index &received_index, ::cryptonote::keypair &in_ephemeral,
::crypto::key_image &ki) override;
protected:
void tx_sign(hw::wallet_shim *wallet, const ::tools::wallet2::unsigned_tx_set &unsigned_tx, size_t idx,
hw::tx_aux_data &aux_data, std::shared_ptr<hw::trezor::protocol::tx::Signer> &signer) override;
};
// Tests
class gen_trezor_ki_sync : public gen_trezor_base
{
public:
bool generate(std::vector<test_event_entry>& events) override;
};
class gen_trezor_ki_sync_with_refresh : public gen_trezor_ki_sync
{
public:
bool generate(std::vector<test_event_entry>& events) override;
};
class gen_trezor_ki_sync_without_refresh : public gen_trezor_ki_sync
{
public:
bool generate(std::vector<test_event_entry>& events) override;
};
class gen_trezor_live_refresh : public gen_trezor_base
{
public:
bool generate(std::vector<test_event_entry>& events) override;
};
class gen_trezor_1utxo : public gen_trezor_base
{
public:
@ -246,3 +315,15 @@ class gen_trezor_many_utxo : public gen_trezor_base
public:
bool generate(std::vector<test_event_entry>& events) override;
};
// Wallet::API tests
class wallet_api_tests : public gen_trezor_base
{
public:
virtual ~wallet_api_tests();
void init();
bool generate(std::vector<test_event_entry>& events) override;
protected:
boost::filesystem::path m_wallet_dir;
};