diff options
author | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-04-02 17:00:17 +0100 |
---|---|---|
committer | Antonio Juarez <antonio.maria.juarez@live.com> | 2014-04-02 17:00:17 +0100 |
commit | 29c2859a3e8a935ef605534c6c36333894980d50 (patch) | |
tree | 1d31daa09741cc82ecc24c0c89f15b84e74cb12d /src/simplewallet | |
parent | some fixes (diff) | |
download | monero-29c2859a3e8a935ef605534c6c36333894980d50.tar.xz |
json rpc for wallet and bugfix
Diffstat (limited to 'src/simplewallet')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 665 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 66 |
2 files changed, 564 insertions, 167 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 2ba031325..a8aa7dc00 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -15,6 +15,7 @@ #include "cryptonote_core/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "wallet/wallet_rpc_server.h" #include "version.h" #if defined(WIN32) @@ -37,27 +38,109 @@ namespace const command_line::arg_descriptor<std::string> arg_daemon_address = {"daemon-address", "Use daemon instance at <host>:<port>", ""}; const command_line::arg_descriptor<std::string> arg_daemon_host = {"daemon-host", "Use daemon instance at host <arg> instead of localhost", ""}; const command_line::arg_descriptor<std::string> arg_password = {"password", "Wallet password", "", true}; - const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of 8080", 0}; + const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of 8081", 0}; const command_line::arg_descriptor<uint32_t> arg_log_level = {"set_log", "", 0, true}; const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; - void print_success_msg(const std::string& msg, bool color = false) + inline std::string interpret_rpc_response(bool ok, const std::string& status) { - LOG_PRINT_L4(msg); - if (color) epee::log_space::set_console_color(epee::log_space::console_color_green, false); - std::cout << msg; - if (color) epee::log_space::reset_console_color(); - std::cout << std::endl; + std::string err; + if (ok) + { + if (status == CORE_RPC_STATUS_BUSY) + { + err = "daemon is busy. Please try later"; + } + else if (status != CORE_RPC_STATUS_OK) + { + err = status; + } + } + else + { + err = "possible lost connection to daemon"; + } + return err; + } + + class message_writer + { + public: + message_writer(epee::log_space::console_colors color = epee::log_space::console_color_default, bool bright = false, + std::string&& prefix = std::string(), int log_level = LOG_LEVEL_2) + : m_flush(true) + , m_color(color) + , m_bright(bright) + , m_log_level(log_level) + { + m_oss << prefix; + } + + message_writer(message_writer&& rhs) + : m_flush(std::move(rhs.m_flush)) +#if defined(_MSC_VER) + , m_oss(std::move(rhs.m_oss)) +#else + // GCC bug: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 + , m_oss(rhs.m_oss.str(), ios_base::out | ios_base::ate) +#endif + , m_color(std::move(rhs.m_color)) + , m_log_level(std::move(rhs.m_log_level)) + { + rhs.m_flush = false; + } + + template<typename T> + message_writer& operator<<(const T& val) + { + m_oss << val; + return *this; + } + + ~message_writer() + { + if (m_flush) + { + m_flush = false; + + LOG_PRINT(m_oss.str(), m_log_level) + + if (epee::log_space::console_color_default == m_color) + { + std::cout << m_oss.str(); + } + else + { + epee::log_space::set_console_color(m_color, m_bright); + std::cout << m_oss.str(); + epee::log_space::reset_console_color(); + } + std::cout << std::endl; + } + } + + private: + message_writer(message_writer& rhs); + message_writer& operator=(message_writer& rhs); + message_writer& operator=(message_writer&& rhs); + + private: + bool m_flush; + std::stringstream m_oss; + epee::log_space::console_colors m_color; + bool m_bright; + int m_log_level; + }; + + message_writer success_msg_writer(bool color = false) + { + return message_writer(color ? epee::log_space::console_color_green : epee::log_space::console_color_default, false, std::string(), LOG_LEVEL_2); } - void print_fail_msg(const std::string& msg) + message_writer fail_msg_writer() { - LOG_PRINT_L1("Error:" << msg); - epee::log_space::set_console_color(epee::log_space::console_color_red, true); - std::cout << "Error: " << msg; - epee::log_space::reset_console_color(); - std::cout << std::endl; + return message_writer(epee::log_space::console_color_red, true, "Error: ", LOG_LEVEL_0); } } @@ -75,21 +158,22 @@ std::string simple_wallet::get_commands_str() bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - std::cout << get_commands_str(); + success_msg_writer() << get_commands_str(); return true; } simple_wallet::simple_wallet() : m_daemon_port(0) + , m_refresh_progress_reporter(*this) { - m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "Start mining in daemon, start_mining <threads_count>"); + m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining <threads_count> - Start mining in daemon"); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon"); m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), "Resynchronize transactions and balance"); - m_cmd_binder.set_handler("show_balance", boost::bind(&simple_wallet::show_balance, this, _1), "Show current wallet balance"); - m_cmd_binder.set_handler("show_incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "Show incoming transfers"); - m_cmd_binder.set_handler("show_bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer <mixin_count> {<addr> <amount>} Transfer <amount> to <address>. <mixin_count> is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "Change current log detalization level, <level> is a number 0-4"); + m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), "Show current wallet balance"); + m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "incoming_transfers [available|unavailable] - Show incoming transfers - all of them or filter them by availability"); + m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer <mixin_count> <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); + m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log <level> - Change current log detalization level, <level> is a number 0-4"); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address"); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data"); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), "Show this help"); @@ -99,18 +183,18 @@ bool simple_wallet::set_log(const std::vector<std::string> &args) { if(args.size() != 1) { - std::cout << "use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "use: set_log <log_level_number_0-4>"; return true; } uint16_t l = 0; if(!string_tools::get_xtype_from_string(l, args[0])) { - std::cout << "wrong number format, use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "wrong number format, use: set_log <log_level_number_0-4>"; return true; } if(LOG_LEVEL_4 < l) { - std::cout << "wrong number range, use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "wrong number range, use: set_log <log_level_number_0-4>"; return true; } @@ -122,19 +206,27 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { handle_command_line(vm); - CHECK_AND_ASSERT_MES(m_daemon_address.empty() || (m_daemon_host.empty() && !m_daemon_port), false, "you can't specify daemon host or port several times"); + if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port) + { + fail_msg_writer() << "you can't specify daemon host or port several times"; + return false; + } size_t c = 0; if(!m_generate_new.empty()) ++c; if(!m_wallet_file.empty()) ++c; - CHECK_AND_ASSERT_MES(c == 1, false, "you must specify --wallet-file or --generate-new-wallet params"); + if (1 != c) + { + fail_msg_writer() << "you must specify --wallet-file or --generate-new-wallet params"; + return false; + } if (m_daemon_host.empty()) m_daemon_host = "localhost"; if (!m_daemon_port) m_daemon_port = RPC_DEFAULT_PORT; if (m_daemon_address.empty()) - m_daemon_address = string("http://") + m_daemon_host + ":" + lexical_cast<string>(m_daemon_port); + m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port); tools::password_container pwd_container; if (command_line::has_arg(vm, arg_password)) @@ -144,7 +236,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else { bool r = pwd_container.read_password(); - CHECK_AND_ASSERT_MES(r, false, "failed to read wallet password"); + if (!r) + { + fail_msg_writer() << "failed to read wallet password"; + return false; + } } if (!m_generate_new.empty()) @@ -169,25 +265,22 @@ bool simple_wallet::deinit() return close_wallet(); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) +void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) { m_wallet_file = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_port = command_line::get_arg(vm, arg_daemon_port); - - return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::try_connect_to_daemon() { if (!m_wallet->check_connection()) { - std::string msg = "wallet failed to connect to daemon (" + m_daemon_address + "). " + - "Daemon either is not started or passed wrong port. " + + fail_msg_writer() << "wallet failed to connect to daemon (" << m_daemon_address << "). " << + "Daemon either is not started or passed wrong port. " << "Please, make sure that daemon is running or restart the wallet with correct daemon address."; - print_fail_msg(msg); return false; } return true; @@ -196,29 +289,31 @@ bool simple_wallet::try_connect_to_daemon() bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password) { m_wallet_file = wallet_file; - if(boost::filesystem::exists(wallet_file)) - { - std::cout << "wallet creation failed, file " << wallet_file << " already exists" << std::endl; - return false; - } m_wallet.reset(new tools::wallet2()); - bool r = m_wallet->generate(wallet_file, password); - if(!r) + m_wallet->callback(this); + try + { + m_wallet->generate(wallet_file, password); + message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: " << m_wallet->get_account().get_public_address_str(); + } + catch (const std::exception& e) + { + fail_msg_writer() << "failed to generate new wallet: " << e.what(); return false; + } - cout << "Generated new wallet" << ENDL; - print_address(std::vector<std::string>()); - r = m_wallet->init(m_daemon_address); - CHECK_AND_ASSERT_MES(r, false, "failed to init wallet"); - std::cout << "**********************************************************************" << ENDL - << "Your wallet has been generated. " << ENDL - << "To start synchronizing with the daemon use \"refresh\" command." << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL - << "Always use \"exit\" command when closing simplewallet to save " - << "current session's state. Otherwise, you will possibly need to synchronize " << ENDL - << "your wallet again. Your wallet key is NOT under risk anyway." << ENDL - << "**********************************************************************" << ENDL; + m_wallet->init(m_daemon_address); + + success_msg_writer() << + "**********************************************************************\n" << + "Your wallet has been generated.\n" << + "To start synchronizing with the daemon use \"refresh\" command.\n" << + "Use \"help\" command to see the list of available commands.\n" << + "Always use \"exit\" command when closing simplewallet to save\n" << + "current session's state. Otherwise, you will possibly need to synchronize \n" << + "your wallet again. Your wallet key is NOT under risk anyway.\n" << + "**********************************************************************"; return true; } //---------------------------------------------------------------------------------------------------- @@ -226,35 +321,63 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa { m_wallet_file = wallet_file; m_wallet.reset(new tools::wallet2()); + m_wallet->callback(this); - bool r = m_wallet->load(m_wallet_file, password); - CHECK_AND_ASSERT_MES(r, false, "failed to load wallet " + m_wallet_file); - r = m_wallet->init(m_daemon_address); - CHECK_AND_ASSERT_MES(r, false, "failed to init wallet"); + try + { + m_wallet->load(m_wallet_file, password); + message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); + } + catch (const std::exception& e) + { + fail_msg_writer() << "failed to load wallet: " << e.what(); + return false; + } + + m_wallet->init(m_daemon_address); refresh(std::vector<std::string>()); - std::cout << "**********************************************************************" << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL - << "**********************************************************************" << ENDL ; + success_msg_writer() << + "**********************************************************************\n" << + "Use \"help\" command to see the list of available commands.\n" << + "**********************************************************************"; return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::close_wallet() { bool r = m_wallet->deinit(); - CHECK_AND_ASSERT_MES(r, false, "failed to deinit wallet"); - r = m_wallet->store(); - CHECK_AND_ASSERT_MES(r, false, "failed to store wallet " + m_wallet_file); + if (!r) + { + fail_msg_writer() << "failed to deinit wallet"; + return false; + } + + try + { + m_wallet->store(); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + return false; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::save(const std::vector<std::string> &args) { - bool r = m_wallet->store(); - if (r) - print_success_msg("Wallet data saved"); - else - print_fail_msg("failed to store wallet " + m_wallet_file); + try + { + m_wallet->store(); + success_msg_writer() << "Wallet data saved"; + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -276,24 +399,24 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) bool ok = string_tools::get_xtype_from_string(num, args[0]); if(!ok || 0 == num) { - print_fail_msg("wrong number of mining threads: \"" + args[0] + "\""); + fail_msg_writer() << "wrong number of mining threads: \"" << args[0] << "\""; return true; } req.threads_count = num; } else { - print_fail_msg("wrong number of arguments, expected the number of mining threads"); + fail_msg_writer() << "wrong number of arguments, expected the number of mining threads"; return true; } COMMAND_RPC_START_MINING::response res; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/start_mining", req, res, m_http_client); - std::string err = tools::interpret_rpc_response(r, res.status); + std::string err = interpret_rpc_response(r, res.status); if (err.empty()) - print_success_msg("Mining started in daemon"); + success_msg_writer() << "Mining started in daemon"; else - print_fail_msg("mining has NOT been started: " + err); + fail_msg_writer() << "mining has NOT been started: " << err; return true; } //---------------------------------------------------------------------------------------------------- @@ -305,82 +428,158 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) COMMAND_RPC_STOP_MINING::request req; COMMAND_RPC_STOP_MINING::response res; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/stop_mining", req, res, m_http_client); - std::string err = tools::interpret_rpc_response(r, res.status); + std::string err = interpret_rpc_response(r, res.status); if (err.empty()) - print_success_msg("Mining stopped in daemon"); + success_msg_writer() << "Mining stopped in daemon"; else - print_fail_msg("mining has NOT been stopped: " + err); + fail_msg_writer() << "mining has NOT been stopped: " << err; return true; } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block) +{ + m_refresh_progress_reporter.update(height, false); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) +{ + message_writer(epee::log_space::console_color_green, false) << + "Height " << height << + ", transaction " << get_transaction_hash(tx) << + ", received " << print_money(tx.vout[out_index].amount); + m_refresh_progress_reporter.update(height, true); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) +{ + message_writer(epee::log_space::console_color_magenta, false) << + "Height " << height << + ", transaction " << get_transaction_hash(spend_tx) << + ", spent " << print_money(in_tx.vout[out_index].amount); + m_refresh_progress_reporter.update(height, true); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector<std::string>& args) { if (!try_connect_to_daemon()) return true; - std::cout << "Starting refresh..." << endl; - std::atomic<bool> refresh_is_done(false); - std::thread th([&]() - { - epee::misc_utils::sleep_no_w(1000); - while(!refresh_is_done) - { - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (err.empty()) - cout << "Height " << m_wallet->get_blockchain_current_height() << " of " << bc_height << endl; - epee::misc_utils::sleep_no_w(1000); - } - }); - uint64_t initial_height = m_wallet->get_blockchain_current_height(); + message_writer() << "Starting refresh..."; uint64_t fetched_blocks = 0; - bool money_received = false; - tools::wallet2::fail_details fd; - bool ok = m_wallet->refresh(fetched_blocks, money_received, fd); - refresh_is_done = true; - th.join(); - if (ok) + bool ok = false; + std::ostringstream ss; + try { - std::stringstream ss; - ss << "Refresh done, blocks received: " << fetched_blocks; - print_success_msg(ss.str(), true); - + m_wallet->refresh(fetched_blocks); + ok = true; + // Clear line "Height xxx of xxx" + std::cout << "\r \r"; + success_msg_writer(true) << "Refresh done, blocks received: " << fetched_blocks; show_balance(); } - else + catch (const tools::error::daemon_busy&) + { + ss << "daemon is busy. Please try later"; + } + catch (const tools::error::no_connection_to_daemon&) + { + ss << "no connection to daemon. Please, make sure daemon is running"; + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("Unknown RPC error: " << e.to_string()); + ss << "RPC error \"" << e.what() << '"'; + } + catch (const tools::error::refresh_error& e) + { + LOG_ERROR("refresh error: " << e.to_string()); + ss << e.what(); + } + catch (const tools::error::wallet_internal_error& e) { - fetched_blocks = m_wallet->get_blockchain_current_height() - initial_height; - std::stringstream ss; - ss << "refresh failed: " << fd.what() << ". Blocks received: " << fetched_blocks; - print_fail_msg(ss.str()); + LOG_ERROR("internal error: " << e.to_string()); + ss << "internal error: " << e.what(); } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + ss << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + ss << "unknown error"; + } + + if (!ok) + { + fail_msg_writer() << "refresh failed: " << ss.str() << ". Blocks received: " << fetched_blocks; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) { - std::stringstream ss; - ss << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance()); - print_success_msg(ss.str()); + success_msg_writer() << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance()); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args) { - std::cout << " amount \tspent\tglobal index\t tx id" << std::endl; - bool ok = m_wallet->enum_incoming_transfers([](const cryptonote::transaction& tx, uint64_t global_out_index, uint64_t amount, bool spent) { - epee::log_space::set_console_color(spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, true); - std::cout << std::setw(21) << print_money(amount) << '\t' - << std::setw(3) << (spent ? 'T' : 'F') << " \t" - << std::setw(12) << global_out_index << '\t' - << get_transaction_hash(tx) - << '\n'; - }); - epee::log_space::reset_console_color(); - if (ok) - std::cout.flush(); - else - print_fail_msg("No incoming transfers"); + bool filter = false; + bool available = false; + if (!args.empty()) + { + if (args[0] == "available") + { + filter = true; + available = true; + } + else if (args[0] == "unavailable") + { + filter = true; + available = false; + } + } + + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + + bool transfers_found = false; + for (const auto& td : transfers) + { + if (!filter || available != td.m_spent) + { + if (!transfers_found) + { + message_writer() << " amount \tspent\tglobal index\t tx id"; + transfers_found = true; + } + message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << + std::setw(21) << print_money(td.amount()) << '\t' << + std::setw(3) << (td.m_spent ? 'T' : 'F') << " \t" << + std::setw(12) << td.m_global_output_index << '\t' << + get_transaction_hash(td.m_tx); + } + } + + if (!transfers_found) + { + if (!filter) + { + success_msg_writer() << "No incoming transfers"; + } + else if (available) + { + success_msg_writer() << "No incoming available transfers"; + } + else + { + success_msg_writer() << "No incoming unavailable transfers"; + } + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -389,7 +588,7 @@ uint64_t simple_wallet::get_daemon_blockchain_height(std::string& err) COMMAND_RPC_GET_HEIGHT::request req; COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>(); bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); - err = tools::interpret_rpc_response(r, res.status); + err = interpret_rpc_response(r, res.status); return res.height; } //---------------------------------------------------------------------------------------------------- @@ -401,9 +600,9 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - print_success_msg(boost::lexical_cast<std::string>(bc_height)); + success_msg_writer() << bc_height; else - print_fail_msg("failed to get blockchain height: " + err); + fail_msg_writer() << "failed to get blockchain height: " << err; return true; } //---------------------------------------------------------------------------------------------------- @@ -415,65 +614,132 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() < 3) { - print_fail_msg("wrong number of arguments, expected at least 3, got " + boost::lexical_cast<std::string>(local_args.size())); + fail_msg_writer() << "wrong number of arguments, expected at least 3, got " << local_args.size(); return true; } size_t fake_outs_count; if(!string_tools::get_xtype_from_string(fake_outs_count, local_args[0])) { - print_fail_msg("mixin_count should be non-negative integer, got " + local_args[0]); + fail_msg_writer() << "mixin_count should be non-negative integer, got " << local_args[0]; return true; } - local_args.erase(local_args.begin()); vector<cryptonote::tx_destination_entry> dsts; - uint64_t summary_amount = 0; - for (size_t i = 0; i < local_args.size(); i += 2) + for (size_t i = 1; i < local_args.size(); i += 2) { cryptonote::tx_destination_entry de; if(!get_account_address_from_str(de.addr, local_args[i])) { - print_fail_msg("wrong address: " + local_args[i]); + fail_msg_writer() << "wrong address: " << local_args[i]; return true; } if (local_args.size() <= i + 1) { - print_fail_msg("amount for the last address " + local_args[i] + " is not specified"); + fail_msg_writer() << "amount for the last address " << local_args[i] << " is not specified"; return true; } bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); if(!ok || 0 == de.amount) { - print_fail_msg("amount is wrong: " + local_args[i] + " " + local_args[i + 1]); + fail_msg_writer() << "amount is wrong: " << local_args[i] << ' ' << local_args[i + 1] << + ", expected number from 0 to " << print_money(std::numeric_limits<uint64_t>::max()); return true; } - summary_amount += de.amount; dsts.push_back(de); } - if(summary_amount > m_wallet->unlocked_balance()) + try { - print_fail_msg("not enough money to transfer " + print_money(summary_amount) + ", available (unlocked) only " + print_money(m_wallet->unlocked_balance())); - return true; + cryptonote::transaction tx; + m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, tx); + success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << "daemon is busy. Please try later"; + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << "no connection to daemon. Please, make sure daemon is running."; + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("Unknown RPC error: " << e.to_string()); + fail_msg_writer() << "RPC error \"" << e.what() << '"'; + } + catch (const tools::error::get_random_outs_error&) + { + fail_msg_writer() << "failed to get random outputs to mix"; + } + catch (const tools::error::not_enough_money& e) + { + fail_msg_writer() << "not enough money to transfer, available only " << print_money(e.available()) << + ", transaction amount " << print_money(e.tx_amount() + e.fee()) << " = " << print_money(e.tx_amount()) << + " + " << print_money(e.fee()) << " (fee)"; + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << "not enough outputs for specified mixin_count = " << e.mixin_count() << ":"; + for (const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs_for_amount : e.scanty_outs()) + { + writer << "\noutput amount = " << print_money(outs_for_amount.amount) << ", fount outputs to mix = " << outs_for_amount.outs.size(); + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << "transaction was not constructed"; + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " was rejected by daemon with status \"" << e.status() << '"'; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::tx_too_big& e) + { + cryptonote::transaction tx = e.tx(); + fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " is too big. Transaction size: " << + get_object_blobsize(e.tx()) << " bytes, transaction size limit: " << e.tx_size_limit() << " bytes"; + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << "one of destinations is zero"; + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << "unknown transfer error: " << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << "internal error: " << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << "unknown error"; } - cryptonote::transaction tx; - tools::wallet2::fail_details tfd; - bool ok = m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, tx, tfd); - if (ok) - print_success_msg("Money successfully sent", true); - else - print_fail_msg("failed to transfer money: " + tfd.what()); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::run() { - return m_cmd_binder.run_handling("[wallet]# ", ""); + std::string addr_start = m_wallet->get_account().get_public_address_str().substr(0, 6); + return m_cmd_binder.run_handling("[wallet " + addr_start + "]: ", ""); } //---------------------------------------------------------------------------------------------------- void simple_wallet::stop() @@ -482,9 +748,9 @@ void simple_wallet::stop() m_wallet->stop(); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::print_address(const std::vector<std::string> &args) +bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - print_success_msg(m_wallet->get_account().get_public_address_str()); + success_msg_writer() << m_wallet->get_account().get_public_address_str(); return true; } //---------------------------------------------------------------------------------------------------- @@ -495,7 +761,6 @@ bool simple_wallet::process_command(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { - #ifdef WIN32 _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif @@ -517,6 +782,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_daemon_port); command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_log_level); + tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); @@ -531,13 +797,13 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, command_line::arg_help)) { - std::cout << "Usage: simplewallet [--wallet-file=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]\n"; - std::cout << desc_all << '\n' << w.get_commands_str() << std::endl; + success_msg_writer() << "Usage: simplewallet [--wallet-file=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]"; + success_msg_writer() << desc_all << '\n' << w.get_commands_str(); return false; } else if (command_line::get_arg(vm, command_line::arg_version)) { - std::cout << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG << ENDL; + success_msg_writer() << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG; return false; } @@ -551,33 +817,104 @@ int main(int argc, char* argv[]) //set up logging options log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); + //log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); log_space::log_singletone::add_logger(LOGGER_FILE, log_space::log_singletone::get_default_log_file().c_str(), log_space::log_singletone::get_default_log_folder().c_str(), LOG_LEVEL_4); - LOG_PRINT_L0(CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG); + message_writer(epee::log_space::console_color_white, true) << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG; if(command_line::has_arg(vm, arg_log_level)) { LOG_PRINT_L0("Setting log level = " << command_line::get_arg(vm, arg_log_level)); log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, arg_log_level)); } + + if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) + { + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); + //runs wallet with rpc interface + if(!command_line::has_arg(vm, arg_wallet_file) ) + { + LOG_ERROR("Wallet file not set."); + return 1; + } + if(!command_line::has_arg(vm, arg_daemon_address) ) + { + LOG_ERROR("Daemon address not set."); + return 1; + } + if(!command_line::has_arg(vm, arg_password) ) + { + LOG_ERROR("Wallet password not set."); + return 1; + } - r = w.init(vm); - CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); - - std::vector<std::string> command = command_line::get_arg(vm, arg_command); - if (!command.empty()) - w.process_command(command); + std::string wallet_file = command_line::get_arg(vm, arg_wallet_file); + std::string wallet_password = command_line::get_arg(vm, arg_password); + std::string daemon_address = command_line::get_arg(vm, arg_daemon_address); + std::string daemon_host = command_line::get_arg(vm, arg_daemon_host); + int daemon_port = command_line::get_arg(vm, arg_daemon_port); + if (daemon_host.empty()) + daemon_host = "localhost"; + if (!daemon_port) + daemon_port = RPC_DEFAULT_PORT; + if (daemon_address.empty()) + daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); + + tools::wallet2 wal; + try + { + LOG_PRINT_L0("Loading wallet..."); + wal.load(wallet_file, wallet_password); + wal.init(daemon_address); + wal.refresh(); + LOG_PRINT_GREEN("Loaded ok", LOG_LEVEL_0); + } + catch (const std::exception& e) + { + LOG_ERROR("Wallet initialize failed: " << e.what()); + return 1; + } + tools::wallet_rpc_server wrpc(wal); + bool r = wrpc.init(vm); + CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet rpc server"); + + tools::signal_handler::install([&wrpc, &wal] { + wrpc.send_stop_signal(); + wal.store(); + }); + LOG_PRINT_L0("Starting wallet rpc server"); + wrpc.run(); + LOG_PRINT_L0("Stopped wallet rpc server"); + try + { + LOG_PRINT_L0("Storing wallet..."); + wal.store(); + LOG_PRINT_GREEN("Stored ok", LOG_LEVEL_0); + } + catch (const std::exception& e) + { + LOG_ERROR("Failed to store wallet: " << e.what()); + return 1; + } + }else + { + //runs wallet with console interface + r = w.init(vm); + CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); - tools::signal_handler::install([&w] { - w.stop(); - }); - w.run(); + std::vector<std::string> command = command_line::get_arg(vm, arg_command); + if (!command.empty()) + w.process_command(command); - w.deinit(); + tools::signal_handler::install([&w] { + w.stop(); + }); + w.run(); + w.deinit(); + } return 1; //CATCH_ENTRY_L0("main", 1); } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c448467f8..002490e89 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -20,7 +20,7 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ - class simple_wallet + class simple_wallet : public tools::i_wallet2_callback { public: typedef std::vector<std::string> command_type; @@ -35,7 +35,7 @@ namespace cryptonote bool process_command(const std::vector<std::string> &args); std::string get_commands_str(); private: - bool handle_command_line(const boost::program_options::variables_map& vm); + void handle_command_line(const boost::program_options::variables_map& vm); bool run_console_handler(); @@ -51,13 +51,72 @@ namespace cryptonote bool show_incoming_transfers(const std::vector<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); - bool print_address(const std::vector<std::string> &args); + bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool save(const std::vector<std::string> &args); bool set_log(const std::vector<std::string> &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(); + //----------------- i_wallet2_callback --------------------- + virtual void on_new_block(uint64_t height, const cryptonote::block& block); + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index); + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx); + //---------------------------------------------------------- + + friend class refresh_progress_reporter_t; + + class refresh_progress_reporter_t + { + public: + refresh_progress_reporter_t(cryptonote::simple_wallet& simple_wallet) + : m_simple_wallet(simple_wallet) + , m_blockchain_height(0) + , m_blockchain_height_update_time() + , m_print_time() + { + } + + void update(uint64_t height, bool force = false) + { + auto current_time = std::chrono::system_clock::now(); + if (std::chrono::seconds(DIFFICULTY_TARGET / 2) < current_time - m_blockchain_height_update_time || m_blockchain_height <= height) + { + update_blockchain_height(); + m_blockchain_height = (std::max)(m_blockchain_height, height); + } + + if (std::chrono::milliseconds(1) < current_time - m_print_time || force) + { + std::cout << "Height " << height << " of " << m_blockchain_height << '\r'; + m_print_time = current_time; + } + } + + private: + void update_blockchain_height() + { + std::string err; + uint64_t blockchain_height = m_simple_wallet.get_daemon_blockchain_height(err); + if (err.empty()) + { + m_blockchain_height = blockchain_height; + m_blockchain_height_update_time = std::chrono::system_clock::now(); + } + else + { + LOG_ERROR("Failed to get current blockchain height: " << err); + } + } + + private: + cryptonote::simple_wallet& m_simple_wallet; + uint64_t m_blockchain_height; + std::chrono::system_clock::time_point m_blockchain_height_update_time; + std::chrono::system_clock::time_point m_print_time; + }; + + private: std::string m_wallet_file; std::string m_generate_new; std::string m_import_path; @@ -70,5 +129,6 @@ namespace cryptonote std::unique_ptr<tools::wallet2> m_wallet; net_utils::http::http_simple_client m_http_client; + refresh_progress_reporter_t m_refresh_progress_reporter; }; } |