monero/src/simplewallet/simplewallet.cpp

1091 lines
37 KiB
C++
Raw Normal View History

2014-03-03 17:07:58 -05:00
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <thread>
#include <boost/lexical_cast.hpp>
#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>
#include "include_base_utils.h"
#include "common/command_line.h"
2014-03-20 07:46:11 -04:00
#include "common/util.h"
2014-03-03 17:07:58 -05:00
#include "p2p/net_node.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "simplewallet.h"
#include "cryptonote_core/cryptonote_format_utils.h"
#include "storages/http_abstract_invoke.h"
#include "rpc/core_rpc_server_commands_defs.h"
2014-04-02 12:00:17 -04:00
#include "wallet/wallet_rpc_server.h"
2014-03-03 17:07:58 -05:00
#include "version.h"
#include "crypto/crypto.h" // for crypto::secret_key definition
#include "crypto/electrum-words.h"
2014-03-03 17:07:58 -05:00
#if defined(WIN32)
#include <crtdbg.h>
#endif
using namespace std;
using namespace epee;
using namespace cryptonote;
using boost::lexical_cast;
namespace po = boost::program_options;
#define EXTENDED_LOGS_FILE "wallet_details.log"
namespace
{
const command_line::arg_descriptor<std::string> arg_wallet_file = {"wallet-file", "Use wallet <arg>", ""};
const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", "Generate new wallet and save it to <arg> or <address>.wallet by default", ""};
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<std::string> arg_electrum_seed = {"electrum-seed", "Specify electrum seed for wallet recovery/creation", ""};
const command_line::arg_descriptor<bool> arg_recover = {"recover", "Recover wallet using mnemonic generator (e.g. electrum word list)", false};
2014-04-02 12:00:17 -04:00
const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of 8081", 0};
2014-03-03 17:07:58 -05:00
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", ""};
2014-04-02 12:00:17 -04:00
inline std::string interpret_rpc_response(bool ok, const std::string& status)
2014-03-20 07:46:11 -04:00
{
2014-04-02 12:00:17 -04:00
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
2014-05-03 12:19:43 -04:00
, m_oss(rhs.m_oss.str(), ios_base::out | ios_base::ate)
2014-04-02 12:00:17 -04:00
#endif
, m_color(std::move(rhs.m_color))
, m_log_level(std::move(rhs.m_log_level))
{
rhs.m_flush = false;
}
template<typename T>
2014-04-07 11:02:15 -04:00
std::ostream& operator<<(const T& val)
2014-04-02 12:00:17 -04:00
{
m_oss << val;
2014-04-07 11:02:15 -04:00
return m_oss;
2014-04-02 12:00:17 -04:00
}
~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);
2014-03-20 07:46:11 -04:00
}
2014-03-03 17:07:58 -05:00
2014-04-02 12:00:17 -04:00
message_writer fail_msg_writer()
2014-03-20 07:46:11 -04:00
{
2014-04-02 12:00:17 -04:00
return message_writer(epee::log_space::console_color_red, true, "Error: ", LOG_LEVEL_0);
2014-03-20 07:46:11 -04:00
}
}
2014-03-03 17:07:58 -05:00
std::string simple_wallet::get_commands_str()
{
std::stringstream ss;
ss << "Commands: " << ENDL;
std::string usage = m_cmd_binder.get_usage();
boost::replace_all(usage, "\n", "\n ");
usage.insert(0, " ");
ss << usage << ENDL;
return ss.str();
}
2014-03-20 07:46:11 -04:00
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
success_msg_writer() << get_commands_str();
2014-03-03 17:07:58 -05:00
return true;
}
simple_wallet::simple_wallet()
: m_daemon_port(0)
2014-04-02 12:00:17 -04:00
, m_refresh_progress_reporter(*this)
2014-03-03 17:07:58 -05:00
{
2014-04-07 11:02:15 -04:00
m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining [<number_of_threads>] - Start mining in daemon");
2014-03-03 17:07:58 -05:00
m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon");
m_cmd_binder.set_handler("save_bc", boost::bind(&simple_wallet::save_bc, this, _1), "Save current blockchain data");
2014-03-03 17:07:58 -05:00
m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), "Resynchronize transactions and balance");
2014-04-02 12:00:17 -04:00
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");
2014-05-03 12:19:43 -04:00
m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), "payments <payment_id_1> [<payment_id_2> ... <payment_id_N>] - Show payments <payment_id_1>, ... <payment_id_N>");
2014-04-02 12:00:17 -04:00
m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height");
2014-05-03 12:19:43 -04:00
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>] [payment_id] - 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)");
2014-04-02 12:00:17 -04:00
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");
2014-03-03 17:07:58 -05:00
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");
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::set_log(const std::vector<std::string> &args)
{
2014-05-03 12:19:43 -04:00
if(args.size() != 1)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "use: set_log <log_level_number_0-4>";
2014-03-03 17:07:58 -05:00
return true;
}
2014-03-20 07:46:11 -04:00
uint16_t l = 0;
2014-05-25 13:06:40 -04:00
if(!epee::string_tools::get_xtype_from_string(l, args[0]))
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "wrong number format, use: set_log <log_level_number_0-4>";
2014-03-03 17:07:58 -05:00
return true;
}
2014-03-20 07:46:11 -04:00
if(LOG_LEVEL_4 < l)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "wrong number range, use: set_log <log_level_number_0-4>";
2014-03-03 17:07:58 -05:00
return true;
}
log_space::log_singletone::get_set_log_detalisation_level(true, l);
return true;
}
//----------------------------------------------------------------------------------------------------
2014-05-03 12:19:43 -04:00
bool simple_wallet::ask_wallet_create_if_needed()
{
std::string wallet_path;
std::cout << "Specify wallet file name (e.g., wallet.bin). If the wallet doesn't exist, it will be created.\n";
std::cout << "Wallet file name: ";
std::getline(std::cin, wallet_path);
wallet_path = string_tools::trim(wallet_path);
bool keys_file_exists;
2014-05-25 13:06:40 -04:00
bool wallet_file_exists;
tools::wallet2::wallet_exists(wallet_path, keys_file_exists, wallet_file_exists);
2014-05-03 12:19:43 -04:00
bool r;
if(keys_file_exists)
{
m_wallet_file = wallet_path;
r = true;
}else
{
2014-05-25 13:06:40 -04:00
if(!wallet_file_exists)
2014-05-03 12:19:43 -04:00
{
std::cout << "The wallet doesn't exist, generating new one" << std::endl;
m_generate_new = wallet_path;
r = true;
}else
{
fail_msg_writer() << "failed to open wallet \"" << wallet_path << "\". Keys file wasn't found";
r = false;
}
}
return r;
}
//----------------------------------------------------------------------------------------------------
2014-03-03 17:07:58 -05:00
bool simple_wallet::init(const boost::program_options::variables_map& vm)
{
handle_command_line(vm);
2014-04-02 12:00:17 -04:00
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;
}
2014-03-03 17:07:58 -05:00
2014-05-03 12:19:43 -04:00
size_t c = 0;
2014-03-03 17:07:58 -05:00
if(!m_generate_new.empty()) ++c;
if(!m_wallet_file.empty()) ++c;
2014-04-02 12:00:17 -04:00
if (1 != c)
{
2014-05-03 12:19:43 -04:00
if(!ask_wallet_create_if_needed())
return false;
2014-04-02 12:00:17 -04:00
}
2014-03-03 17:07:58 -05:00
if (m_daemon_host.empty())
m_daemon_host = "localhost";
if (!m_daemon_port)
m_daemon_port = RPC_DEFAULT_PORT;
if (m_daemon_address.empty())
2014-04-02 12:00:17 -04:00
m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port);
2014-03-03 17:07:58 -05:00
tools::password_container pwd_container;
if (command_line::has_arg(vm, arg_password))
{
pwd_container.password(command_line::get_arg(vm, arg_password));
}
else
{
bool r = pwd_container.read_password();
2014-04-02 12:00:17 -04:00
if (!r)
{
fail_msg_writer() << "failed to read wallet password";
return false;
}
2014-03-03 17:07:58 -05:00
}
if (!m_generate_new.empty())
{
// check for recover flag. if present, require electrum word list (only recovery option for now).
if (m_recover)
{
if (m_electrum_seed.empty())
{
fail_msg_writer() << "specify a recovery parameter (e.g. electrum word list) with the recover option";
return false;
}
else // verify recovery param (electrum word list) and convert to byte representation
{
CHECK_AND_ASSERT_MES(
crypto::ElectrumWords::words_to_bytes(m_electrum_seed, m_recovery_key),
false,
"electrum-style word list failed verification"
);
}
}
bool r = new_wallet(m_generate_new, pwd_container.password(), m_recovery_key, m_recover);
2014-03-03 17:07:58 -05:00
CHECK_AND_ASSERT_MES(r, false, "account creation failed");
}
else
{
bool r = open_wallet(m_wallet_file, pwd_container.password());
CHECK_AND_ASSERT_MES(r, false, "could not open account");
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::deinit()
{
if (!m_wallet.get())
return true;
return close_wallet();
}
//----------------------------------------------------------------------------------------------------
2014-04-02 12:00:17 -04:00
void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm)
2014-03-03 17:07:58 -05:00
{
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);
m_electrum_seed = command_line::get_arg(vm, arg_electrum_seed);
m_recover = command_line::get_arg(vm, arg_recover);
2014-03-03 17:07:58 -05:00
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::try_connect_to_daemon()
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!m_wallet->check_connection())
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "wallet failed to connect to daemon (" << m_daemon_address << "). " <<
"Daemon either is not started or passed wrong port. " <<
2014-03-20 07:46:11 -04:00
"Please, make sure that daemon is running or restart the wallet with correct daemon address.";
return false;
2014-03-03 17:07:58 -05:00
}
2014-03-20 07:46:11 -04:00
return true;
2014-03-03 17:07:58 -05:00
}
bool simple_wallet::parse_electrum()
{
return false;
}
2014-03-03 17:07:58 -05:00
//----------------------------------------------------------------------------------------------------
bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key, bool recover)
2014-03-03 17:07:58 -05:00
{
m_wallet_file = wallet_file;
m_wallet.reset(new tools::wallet2());
2014-04-02 12:00:17 -04:00
m_wallet->callback(this);
try
{
m_wallet->generate(wallet_file, password, recovery_key, recover);
2014-04-07 11:02:15 -04:00
message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: " << m_wallet->get_account().get_public_address_str() << std::endl << "view key: " << string_tools::pod_to_hex(m_wallet->get_account().get_keys().m_view_secret_key);
2014-04-02 12:00:17 -04:00
}
catch (const std::exception& e)
{
fail_msg_writer() << "failed to generate new wallet: " << e.what();
2014-03-03 17:07:58 -05:00
return false;
2014-04-02 12:00:17 -04:00
}
2014-03-03 17:07:58 -05:00
2014-04-02 12:00:17 -04:00
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" <<
"**********************************************************************";
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::open_wallet(const string &wallet_file, const std::string& password)
{
m_wallet_file = wallet_file;
m_wallet.reset(new tools::wallet2());
2014-04-02 12:00:17 -04:00
m_wallet->callback(this);
2014-03-03 17:07:58 -05:00
2014-04-02 12:00:17 -04:00
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);
2014-03-03 17:07:58 -05:00
2014-03-20 07:46:11 -04:00
refresh(std::vector<std::string>());
2014-04-02 12:00:17 -04:00
success_msg_writer() <<
"**********************************************************************\n" <<
"Use \"help\" command to see the list of available commands.\n" <<
"**********************************************************************";
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::close_wallet()
{
bool r = m_wallet->deinit();
2014-04-02 12:00:17 -04:00
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;
}
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::save(const std::vector<std::string> &args)
{
2014-04-02 12:00:17 -04:00
try
{
m_wallet->store();
success_msg_writer() << "Wallet data saved";
}
catch (const std::exception& e)
{
fail_msg_writer() << e.what();
}
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::start_mining(const std::vector<std::string>& args)
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!try_connect_to_daemon())
return true;
2014-03-03 17:07:58 -05:00
COMMAND_RPC_START_MINING::request req;
req.miner_address = m_wallet->get_account().get_public_address_str();
2014-03-20 07:46:11 -04:00
2014-04-07 11:02:15 -04:00
bool ok = true;
size_t max_mining_threads_count = (std::max)(std::thread::hardware_concurrency(), static_cast<unsigned>(2));
2014-03-20 07:46:11 -04:00
if (0 == args.size())
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
req.threads_count = 1;
}
else if (1 == args.size())
{
uint16_t num;
2014-04-07 11:02:15 -04:00
ok = string_tools::get_xtype_from_string(num, args[0]);
ok &= (1 <= num && num <= max_mining_threads_count);
2014-03-20 07:46:11 -04:00
req.threads_count = num;
}
else
{
2014-04-07 11:02:15 -04:00
ok = false;
}
if (!ok)
{
fail_msg_writer() << "invalid arguments. Please use start_mining [<number_of_threads>], " <<
"<number_of_threads> should be from 1 to " << max_mining_threads_count;
2014-03-20 07:46:11 -04:00
return true;
2014-03-03 17:07:58 -05:00
}
2014-03-20 07:46:11 -04:00
2014-03-03 17:07:58 -05:00
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);
2014-04-02 12:00:17 -04:00
std::string err = interpret_rpc_response(r, res.status);
2014-03-20 07:46:11 -04:00
if (err.empty())
2014-04-02 12:00:17 -04:00
success_msg_writer() << "Mining started in daemon";
2014-03-20 07:46:11 -04:00
else
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "mining has NOT been started: " << err;
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::stop_mining(const std::vector<std::string>& args)
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!try_connect_to_daemon())
return true;
2014-03-03 17:07:58 -05:00
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);
2014-04-02 12:00:17 -04:00
std::string err = interpret_rpc_response(r, res.status);
2014-03-20 07:46:11 -04:00
if (err.empty())
2014-04-02 12:00:17 -04:00
success_msg_writer() << "Mining stopped in daemon";
2014-03-20 07:46:11 -04:00
else
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "mining has NOT been stopped: " << err;
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::save_bc(const std::vector<std::string>& args)
{
if (!try_connect_to_daemon())
return true;
COMMAND_RPC_SAVE_BC::request req;
COMMAND_RPC_SAVE_BC::response res;
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/save_bc", req, res, m_http_client);
std::string err = interpret_rpc_response(r, res.status);
if (err.empty())
success_msg_writer() << "Blockchain saved";
else
fail_msg_writer() << "Blockchain can't be saved: " << err;
return true;
}
//----------------------------------------------------------------------------------------------------
2014-04-02 12:00:17 -04:00
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);
}
//----------------------------------------------------------------------------------------------------
2014-05-03 12:19:43 -04:00
void simple_wallet::on_skip_transaction(uint64_t height, const cryptonote::transaction& tx)
{
message_writer(epee::log_space::console_color_red, true) <<
"Height " << height <<
", transaction " << get_transaction_hash(tx) <<
", unsupported transaction format";
m_refresh_progress_reporter.update(height, true);
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::refresh(const std::vector<std::string>& args)
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!try_connect_to_daemon())
return true;
2014-03-03 17:07:58 -05:00
2014-04-02 12:00:17 -04:00
message_writer() << "Starting refresh...";
size_t fetched_blocks = 0;
2014-04-02 12:00:17 -04:00
bool ok = false;
std::ostringstream ss;
try
2014-03-20 07:46:11 -04:00
{
2014-04-02 12:00:17 -04:00
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;
2014-03-20 07:46:11 -04:00
show_balance();
}
2014-04-02 12:00:17 -04:00
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)
2014-03-20 07:46:11 -04:00
{
2014-04-02 12:00:17 -04:00
LOG_ERROR("internal error: " << e.to_string());
ss << "internal error: " << e.what();
2014-03-20 07:46:11 -04:00
}
2014-04-02 12:00:17 -04:00
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;
}
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
success_msg_writer() << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance());
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
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";
}
}
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-05-03 12:19:43 -04:00
bool simple_wallet::show_payments(const std::vector<std::string> &args)
{
if(args.empty())
{
fail_msg_writer() << "expected at least one payment_id";
return true;
}
message_writer() << " payment \t" <<
" transaction \t" <<
" height\t amount \tunlock time";
bool payments_found = false;
for(std::string arg : args)
{
crypto::hash payment_id;
if(tools::wallet2::parse_payment_id(arg, payment_id))
2014-05-03 12:19:43 -04:00
{
std::list<tools::wallet2::payment_details> payments;
m_wallet->get_payments(payment_id, payments);
if(payments.empty())
{
success_msg_writer() << "No payments with id " << payment_id;
continue;
}
for (const tools::wallet2::payment_details& pd : payments)
{
if(!payments_found)
{
payments_found = true;
}
success_msg_writer(true) <<
payment_id << '\t' <<
pd.m_tx_hash << '\t' <<
std::setw(8) << pd.m_block_height << '\t' <<
std::setw(21) << print_money(pd.m_amount) << '\t' <<
pd.m_unlock_time;
}
}
else
{
fail_msg_writer() << "payment id has invalid format: \"" << arg << "\", expected 64-character string";
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
uint64_t simple_wallet::get_daemon_blockchain_height(std::string& err)
2014-03-03 17:07:58 -05:00
{
COMMAND_RPC_GET_HEIGHT::request req;
COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>();
2014-03-20 07:46:11 -04:00
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client);
2014-04-02 12:00:17 -04:00
err = interpret_rpc_response(r, res.status);
2014-03-03 17:07:58 -05:00
return res.height;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!try_connect_to_daemon())
return true;
2014-03-03 17:07:58 -05:00
2014-03-20 07:46:11 -04:00
std::string err;
uint64_t bc_height = get_daemon_blockchain_height(err);
if (err.empty())
2014-04-02 12:00:17 -04:00
success_msg_writer() << bc_height;
2014-03-03 17:07:58 -05:00
else
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "failed to get blockchain height: " << err;
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
2014-03-20 07:46:11 -04:00
bool simple_wallet::transfer(const std::vector<std::string> &args_)
2014-03-03 17:07:58 -05:00
{
2014-03-20 07:46:11 -04:00
if (!try_connect_to_daemon())
return true;
2014-03-03 17:07:58 -05:00
2014-03-20 07:46:11 -04:00
std::vector<std::string> local_args = args_;
2014-03-03 17:07:58 -05:00
if(local_args.size() < 3)
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "wrong number of arguments, expected at least 3, got " << local_args.size();
2014-03-03 17:07:58 -05:00
return true;
}
2014-03-20 07:46:11 -04:00
2014-03-03 17:07:58 -05:00
size_t fake_outs_count;
2014-05-25 13:06:40 -04:00
if(!epee::string_tools::get_xtype_from_string(fake_outs_count, local_args[0]))
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
fail_msg_writer() << "mixin_count should be non-negative integer, got " << local_args[0];
2014-03-03 17:07:58 -05:00
return true;
}
2014-05-03 12:19:43 -04:00
local_args.erase(local_args.begin());
2014-03-03 17:07:58 -05:00
2014-05-03 12:19:43 -04:00
std::vector<uint8_t> extra;
if (1 == local_args.size() % 2)
2014-03-03 17:07:58 -05:00
{
2014-05-03 12:19:43 -04:00
std::string payment_id_str = local_args.back();
local_args.pop_back();
crypto::hash payment_id;
bool r = tools::wallet2::parse_payment_id(payment_id_str, payment_id);
2014-05-03 12:19:43 -04:00
if(r)
2014-03-03 17:07:58 -05:00
{
2014-05-03 12:19:43 -04:00
std::string extra_nonce;
set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
}
if(!r)
{
fail_msg_writer() << "payment id has invalid format: \"" << payment_id_str << "\", expected 64-character string";
2014-03-03 17:07:58 -05:00
return true;
}
2014-05-03 12:19:43 -04:00
}
2014-03-20 07:46:11 -04:00
2014-05-03 12:19:43 -04:00
vector<cryptonote::tx_destination_entry> dsts;
for (size_t i = 0; i < local_args.size(); i += 2)
{
cryptonote::tx_destination_entry de;
if(!get_account_address_from_str(de.addr, local_args[i]))
2014-03-03 17:07:58 -05:00
{
2014-05-03 12:19:43 -04:00
fail_msg_writer() << "wrong address: " << local_args[i];
2014-03-03 17:07:58 -05:00
return true;
}
2014-03-20 07:46:11 -04:00
bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]);
if(!ok || 0 == de.amount)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
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());
2014-03-03 17:07:58 -05:00
return true;
}
2014-03-20 07:46:11 -04:00
2014-03-03 17:07:58 -05:00
dsts.push_back(de);
}
2014-04-02 12:00:17 -04:00
try
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
cryptonote::transaction tx;
2014-05-03 12:19:43 -04:00
m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, extra, tx);
2014-04-02 12:00:17 -04:00
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";
2014-03-03 17:07:58 -05:00
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::run()
{
2014-04-02 12:00:17 -04:00
std::string addr_start = m_wallet->get_account().get_public_address_str().substr(0, 6);
return m_cmd_binder.run_handling("[wallet " + addr_start + "]: ", "");
2014-03-20 07:46:11 -04:00
}
//----------------------------------------------------------------------------------------------------
void simple_wallet::stop()
{
m_cmd_binder.stop_handling();
m_wallet->stop();
2014-03-03 17:07:58 -05:00
}
//----------------------------------------------------------------------------------------------------
2014-04-02 12:00:17 -04:00
bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
2014-03-03 17:07:58 -05:00
{
2014-04-02 12:00:17 -04:00
success_msg_writer() << m_wallet->get_account().get_public_address_str();
2014-03-03 17:07:58 -05:00
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::process_command(const std::vector<std::string> &args)
{
return m_cmd_binder.process_command_vec(args);
}
//----------------------------------------------------------------------------------------------------
int main(int argc, char* argv[])
{
#ifdef WIN32
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif
//TRY_ENTRY();
string_tools::set_module_name_and_folder(argv[0]);
po::options_description desc_general("General options");
command_line::add_arg(desc_general, command_line::arg_help);
command_line::add_arg(desc_general, command_line::arg_version);
po::options_description desc_params("Wallet options");
command_line::add_arg(desc_params, arg_wallet_file);
command_line::add_arg(desc_params, arg_generate_new_wallet);
command_line::add_arg(desc_params, arg_password);
command_line::add_arg(desc_params, arg_daemon_address);
command_line::add_arg(desc_params, arg_daemon_host);
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);
2014-04-02 12:00:17 -04:00
tools::wallet_rpc_server::init_options(desc_params);
2014-03-03 17:07:58 -05:00
po::positional_options_description positional_options;
positional_options.add(arg_command.name, -1);
po::options_description desc_all;
desc_all.add(desc_general).add(desc_params);
cryptonote::simple_wallet w;
po::variables_map vm;
bool r = command_line::handle_error_helper(desc_all, [&]()
{
po::store(command_line::parse_command_line(argc, argv, desc_general, true), vm);
if (command_line::get_arg(vm, command_line::arg_help))
{
success_msg_writer() << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG;
2014-04-02 12:00:17 -04:00
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();
2014-03-03 17:07:58 -05:00
return false;
}
else if (command_line::get_arg(vm, command_line::arg_version))
{
2014-04-02 12:00:17 -04:00
success_msg_writer() << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG;
2014-03-03 17:07:58 -05:00
return false;
}
auto parser = po::command_line_parser(argc, argv).options(desc_params).positional(positional_options);
po::store(parser.run(), vm);
po::notify(vm);
return true;
});
if (!r)
return 0;
2014-03-03 17:07:58 -05:00
//set up logging options
2014-03-20 07:46:11 -04:00
log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2);
2014-04-02 12:00:17 -04:00
//log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0);
2014-03-03 17:07:58 -05:00
log_space::log_singletone::add_logger(LOGGER_FILE,
log_space::log_singletone::get_default_log_file().c_str(),
2014-03-20 07:46:11 -04:00
log_space::log_singletone::get_default_log_folder().c_str(), LOG_LEVEL_4);
2014-04-02 12:00:17 -04:00
message_writer(epee::log_space::console_color_white, true) << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG;
2014-03-03 17:07:58 -05:00
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));
}
2014-05-03 12:19:43 -04:00
2014-04-02 12:00:17 -04:00
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);
2014-05-03 12:19:43 -04:00
//runs wallet with rpc interface
2014-04-02 12:00:17 -04:00
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;
}
2014-03-03 17:07:58 -05:00
2014-05-03 12:19:43 -04:00
std::string wallet_file = command_line::get_arg(vm, arg_wallet_file);
2014-04-02 12:00:17 -04:00
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
{
2014-05-03 12:19:43 -04:00
//runs wallet with console interface
2014-04-02 12:00:17 -04:00
r = w.init(vm);
CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet");
2014-03-20 07:46:11 -04:00
2014-04-02 12:00:17 -04:00
std::vector<std::string> command = command_line::get_arg(vm, arg_command);
if (!command.empty())
{
2014-04-02 12:00:17 -04:00
w.process_command(command);
w.stop();
w.deinit();
}
else
{
tools::signal_handler::install([&w] {
w.stop();
});
w.run();
2014-03-03 17:07:58 -05:00
w.deinit();
}
2014-04-02 12:00:17 -04:00
}
2014-05-28 16:27:59 -04:00
return 0;
2014-03-03 17:07:58 -05:00
//CATCH_ENTRY_L0("main", 1);
}