diff options
Diffstat (limited to 'src/simplewallet')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 430 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 22 |
2 files changed, 444 insertions, 8 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6a54c24fb..5c01e99fb 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -58,6 +58,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/rpc_payment_signature.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" @@ -99,12 +100,17 @@ typedef cryptonote::simple_wallet sw; #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ - /* stop any background refresh, and take over */ \ + /* stop any background refresh and other processes, and take over */ \ + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); \ m_wallet->stop(); \ boost::unique_lock<boost::mutex> lock(m_idle_mutex); \ m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ + /* m_idle_mutex is still locked here */ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ + m_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed);; \ + m_rpc_payment_checker.trigger(); \ + m_idle_cond.notify_one(); \ }) #define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \ @@ -125,15 +131,24 @@ typedef cryptonote::simple_wallet sw; return true; \ } while(0) +#define REFRESH_PERIOD 90 // seconds + +#define CREDITS_TARGET 50000 +#define MAX_PAYMENT_DIFF 10000 +#define MIN_PAYMENT_RATE 0.01f // per hash + enum TransferType { Transfer, TransferLocked, }; +static std::string get_human_readable_timespan(std::chrono::seconds seconds); + namespace { const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; const auto arg_wallet_file = wallet_args::arg_wallet_file(); + const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; @@ -247,7 +262,11 @@ namespace const char* USAGE_FROZEN("frozen <key_image>"); const char* USAGE_LOCK("lock"); const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); + const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); + const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); + const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command>]"); @@ -490,22 +509,28 @@ namespace fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>"); return r; } +} - void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) - { +void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) +{ bool warn_of_possible_attack = !trusted_daemon; try { std::rethrow_exception(e); } - catch (const tools::error::daemon_busy&) + catch (const tools::error::payment_required&) { - fail_msg_writer() << sw::tr("daemon is busy. Please try again later."); + fail_msg_writer() << tr("Payment required, see the 'rpc_payment_info' command"); + m_need_payment = true; } catch (const tools::error::no_connection_to_daemon&) { fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -602,8 +627,10 @@ namespace if (warn_of_possible_attack) fail_msg_writer() << sw::tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); - } +} +namespace +{ bool check_file_overwrite(const std::string &filename) { boost::system::error_code errcode; @@ -1908,6 +1935,77 @@ bool simple_wallet::unset_ring(const std::vector<std::string> &args) return true; } +bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + try + { + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + crypto::public_key pkey; + crypto::secret_key_to_public_key(m_wallet->get_rpc_client_secret_key(), pkey); + message_writer() << tr("RPC client ID: ") << pkey; + message_writer() << tr("RPC client secret key: ") << m_wallet->get_rpc_client_secret_key(); + if (!m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (payment_required) + { + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + message_writer() << tr("Using daemon: ") << m_wallet->get_daemon_address(); + message_writer() << tr("Payments required for node use, current credits: ") << credits; + message_writer() << tr("Credits target: ") << target; + uint64_t expected, discrepancy; + m_wallet->credit_report(expected, discrepancy); + message_writer() << tr("Credits spent this session: ") << expected; + if (expected) + message_writer() << tr("Credit discrepancy this session: ") << discrepancy << " (" << 100.0f * discrepancy / expected << "%)"; + float cph = credits_per_hash_found / (float)diff; + message_writer() << tr("Difficulty: ") << diff << ", " << credits_per_hash_found << " " << tr("credits per hash found, ") << cph << " " << tr("credits/hash");; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + bool mining = (now - m_last_rpc_payment_mining_time).total_microseconds() < 1000000; + if (mining) + { + float hash_rate = m_rpc_payment_hash_rate; + if (hash_rate > 0) + { + message_writer() << (boost::format(tr("Mining for payment at %.1f H/s")) % hash_rate).str(); + if (credits < target) + { + std::chrono::seconds seconds((unsigned)((target - credits) / cph / hash_rate)); + std::string target_string = get_human_readable_timespan(seconds); + message_writer() << (boost::format(tr("Estimated time till %u credits target mined: %s")) % target % target_string).str(); + } + } + else + message_writer() << tr("Mining for payment"); + } + else + message_writer() << tr("Not mining"); + } + else + message_writer() << tr("No payment needed for node use"); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + + return true; +} + bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2150,6 +2248,45 @@ bool simple_wallet::net_stats(const std::vector<std::string> &args) return true; } +bool simple_wallet::public_nodes(const std::vector<std::string> &args) +{ + try + { + auto nodes = m_wallet->get_public_nodes(false); + m_claimed_cph.clear(); + if (nodes.empty()) + { + fail_msg_writer() << tr("No known public nodes"); + return true; + } + std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) { + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0) + return true; + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash) + return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash; + return false; + }); + + const uint64_t now = time(NULL); + message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen"); + for (const auto &node: nodes) + { + const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE; + char cphs[9]; + snprintf(cphs, sizeof(cphs), "%.3f", cph); + const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen)); + std::string host = node.host + ":" + std::to_string(node.rpc_port); + message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen; + m_claimed_cph[host] = node.rpc_credits_per_hash; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error retrieving public node list: ") << e.what(); + } + return true; +} + bool simple_wallet::welcome(const std::vector<std::string> &args) { message_writer() << tr("Welcome to Monero, the private cryptocurrency."); @@ -2214,6 +2351,50 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& return m_wallet->import_key_images(exported_txs, 0, true); } +bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (!m_wallet->get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (!payment_required) + { + fail_msg_writer() << tr("Daemon does not require payment for RPC access"); + return true; + } + + m_rpc_payment_mining_requested = true; + m_rpc_payment_checker.trigger(); + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + success_msg_writer() << (boost::format(tr("Starting mining for RPC access: diff %llu, %f credits/hash%s")) % diff % cph % (low ? " - this is low" : "")).str(); + success_msg_writer() << tr("Run stop_mining_for_rpc to stop"); + return true; +} + +bool simple_wallet::stop_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + m_rpc_payment_mining_requested = false; + m_last_rpc_payment_mining_time = boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1)); + m_rpc_payment_hash_rate = -1.0f; + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2602,6 +2783,53 @@ bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string return true; } +bool simple_wallet::set_persistent_rpc_client_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->persistent_rpc_client_id(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + +bool simple_wallet::set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + float threshold; + if (!epee::string_tools::get_xtype_from_string(threshold, args[1]) || threshold < 0.0f) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + m_wallet->auto_mine_for_rpc_payment_threshold(threshold); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_credits_target(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + uint64_t target; + if (!epee::string_tools::get_xtype_from_string(target, args[1])) + { + fail_msg_writer() << tr("Invalid target"); + return true; + } + m_wallet->credits_target(target); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + bool simple_wallet::set_key_reuse_mitigation2(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2844,6 +3072,12 @@ simple_wallet::simple_wallet() , m_last_activity_time(time(NULL)) , m_locked(false) , m_in_command(false) + , m_need_payment(false) + , m_rpc_payment_mining_requested(false) + , m_last_rpc_payment_mining_time(boost::gregorian::date(1970, 1, 1)) + , m_daemon_rpc_payment_message_displayed(false) + , m_rpc_payment_hash_rate(-1.0f) + , m_suspend_rpc_payment_mining(false) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1), @@ -3022,7 +3256,13 @@ simple_wallet::simple_wallet() "device-name <device_name[:device_spec]>\n " " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " - " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n ")); + " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " + "persistent-client-id <1|0>\n " + " Whether to keep using the same client id for RPC payment over wallet restarts.\n" + "auto-mine-for-rpc-payment-threshold <float>\n " + " Whether to automatically start mining for RPC payment if the daemon requires it.\n" + "credits-target <unsigned int>\n" + " The RPC payment credits balance to target (0 for default).")); m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1), tr("Display the encrypted Electrum-style mnemonic seed.")); @@ -3325,6 +3565,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1), tr(USAGE_NET_STATS), tr("Prints simple network stats")); + m_cmd_binder.set_handler("public_nodes", + boost::bind(&simple_wallet::public_nodes, this, _1), + tr(USAGE_PUBLIC_NODES), + tr("Lists known public nodes")); m_cmd_binder.set_handler("welcome", boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1), tr(USAGE_WELCOME), @@ -3333,6 +3577,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1), tr(USAGE_VERSION), tr("Returns version information")); + m_cmd_binder.set_handler("rpc_payment_info", + boost::bind(&simple_wallet::rpc_payment_info, this, _1), + tr(USAGE_RPC_PAYMENT_INFO), + tr("Get info about RPC payments to current node")); + m_cmd_binder.set_handler("start_mining_for_rpc", + boost::bind(&simple_wallet::start_mining_for_rpc, this, _1), + tr(USAGE_START_MINING_FOR_RPC), + tr("Start mining to pay for RPC access")); + m_cmd_binder.set_handler("stop_mining_for_rpc", + boost::bind(&simple_wallet::stop_mining_for_rpc, this, _1), + tr(USAGE_STOP_MINING_FOR_RPC), + tr("Stop mining to pay for RPC access")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1), tr(USAGE_HELP), @@ -3402,6 +3658,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) << " (disabled on Windows)" #endif ; + success_msg_writer() << "persistent-rpc-client-id = " << m_wallet->persistent_rpc_client_id(); + success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); + success_msg_writer() << "credits-target = " << m_wallet->credits_target(); return true; } else @@ -3463,6 +3722,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\"")); + CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); + CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -4227,6 +4489,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } + if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key)) + { + crypto::secret_key rpc_client_secret_key; + if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), rpc_client_secret_key)) + { + fail_msg_writer() << tr("RPC client secret key should be 32 byte in hex format"); + return false; + } + m_wallet->set_rpc_client_secret_key(rpc_client_secret_key); + } + if (!m_wallet->is_trusted_daemon()) { message_writer(console_color_red, true) << (boost::format(tr("Warning: using an untrusted daemon at %s")) % m_wallet->get_daemon_address()).str(); @@ -4742,6 +5015,7 @@ bool simple_wallet::close_wallet() if (m_idle_run.load(std::memory_order_relaxed)) { m_idle_run.store(false, std::memory_order_relaxed); + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); m_wallet->stop(); { boost::unique_lock<boost::mutex> lock(m_idle_mutex); @@ -5009,6 +5283,40 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph) +{ + try + { + auto i = m_claimed_cph.find(daemon_url); + if (i == m_claimed_cph.end()) + return false; + + claimed_cph = m_claimed_cph[daemon_url]; + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + cryptonote::blobdata hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required) + { + actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff); + return true; + } + else + { + fail_msg_writer() << tr("Error checking daemon RPC access prices"); + } + } + catch (const std::exception &e) + { + // can't check + fail_msg_writer() << tr("Error checking daemon RPC access prices: ") << e.what(); + return false; + } + // no record found for this daemon + return false; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_daemon(const std::vector<std::string>& args) { std::string daemon_url; @@ -5032,7 +5340,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) // If no port has been provided, use the default from config if (!match[3].length()) { - int daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; + uint16_t daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; daemon_url = match[1] + match[2] + std::string(":") + std::to_string(daemon_port); } else { daemon_url = args[0]; @@ -5065,7 +5373,28 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) } catch (const std::exception &e) { } } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("Failed to connect to daemon"); + return true; + } + success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); + + // check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying + uint32_t actual_cph, claimed_cph; + if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph)) + { + if (actual_cph < claimed_cph) + { + fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently."); + fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + } + } + + m_daemon_rpc_payment_message_displayed = false; } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); } @@ -5304,6 +5633,11 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo { ss << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + ss << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -5630,6 +5964,11 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + fail_msg_writer() << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::is_key_image_spent_error&) { fail_msg_writer() << tr("failed to get spent status"); @@ -5733,6 +6072,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending req.outputs[j].index = absolute_offsets[j]; } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + req.client = cryptonote::make_rpc_payment_signature(m_wallet->get_rpc_client_secret_key()); bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res); err = interpret_rpc_response(r, res.status); if (!err.empty()) @@ -8474,6 +8814,7 @@ void simple_wallet::wallet_idle_thread() #endif m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this)); m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this)); + m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this)); if (!m_idle_run.load(std::memory_order_relaxed)) break; @@ -8527,6 +8868,78 @@ bool simple_wallet::check_mms() return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_rpc_payment() +{ + if (!m_rpc_payment_mining_requested && m_wallet->auto_mine_for_rpc_payment_threshold() == 0.0f) + return true; + + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + if (m_rpc_payment_mining_requested) + target = std::numeric_limits<uint64_t>::max(); + bool need_payment = m_need_payment || m_rpc_payment_mining_requested || (m_wallet->credits() < target && m_wallet->daemon_requires_payment()); + if (need_payment) + { + const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time(); + auto startfunc = [this](uint64_t diff, uint64_t credits_per_hash_found) + { + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + if (credits_per_hash_found > 0 && cph >= m_wallet->auto_mine_for_rpc_payment_threshold()) + { + MINFO(std::to_string(cph) << " credits per hash is >= our threshold (" << m_wallet->auto_mine_for_rpc_payment_threshold() << "), starting mining"); + return true; + } + else if (m_rpc_payment_mining_requested) + { + MINFO("Mining for RPC payment was requested, starting mining"); + return true; + } + else + { + if (!m_daemon_rpc_payment_message_displayed) + { + success_msg_writer() << boost::format(tr("Daemon requests payment at diff %llu, with %f credits/hash%s. Run start_mining_for_rpc to start mining to pay for RPC access, or use another daemon")) % + diff % cph % (low ? " - this is low" : ""); + m_cmd_binder.print_prompt(); + m_daemon_rpc_payment_message_displayed = true; + } + return false; + } + }; + auto contfunc = [&,this](unsigned n_hashes) + { + if (m_suspend_rpc_payment_mining.load(std::memory_order_relaxed)) + return false; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + m_last_rpc_payment_mining_time = now; + if ((now - start_time).total_microseconds() >= 2 * 1000000) + m_rpc_payment_hash_rate = n_hashes / (float)((now - start_time).total_seconds()); + if ((now - start_time).total_microseconds() >= REFRESH_PERIOD * 1000000) + return false; + return true; + }; + auto foundfunc = [this, target](uint64_t credits) + { + m_need_payment = false; + return credits < target; + }; + auto errorfunc = [this](const std::string &error) + { + fail_msg_writer() << tr("Error mining to daemon: ") << error; + m_cmd_binder.print_prompt(); + }; + bool ret = m_wallet->search_for_rpc_payment(target, startfunc, contfunc, foundfunc, errorfunc); + if (!ret) + { + fail_msg_writer() << tr("Failed to start mining for RPC payment"); + m_cmd_binder.print_prompt(); + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- std::string simple_wallet::get_prompt() const { if (m_locked) @@ -9728,6 +10141,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_create_address_file); command_line::add_arg(desc_params, arg_subaddress_lookahead); command_line::add_arg(desc_params, arg_use_english_language_names); + command_line::add_arg(desc_params, arg_rpc_client_secret_key); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 47e08ca87..e8f96ad54 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -151,6 +151,9 @@ namespace cryptonote bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); @@ -250,7 +253,11 @@ namespace cryptonote bool thaw(const std::vector<std::string>& args); bool frozen(const std::vector<std::string>& args); bool lock(const std::vector<std::string>& args); + bool rpc_payment_info(const std::vector<std::string> &args); + bool start_mining_for_rpc(const std::vector<std::string> &args); + bool stop_mining_for_rpc(const std::vector<std::string> &args); bool net_stats(const std::vector<std::string>& args); + bool public_nodes(const std::vector<std::string>& args); bool welcome(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args); bool on_unknown_command(const std::vector<std::string>& args); @@ -325,6 +332,11 @@ namespace cryptonote bool check_inactivity(); bool check_refresh(); bool check_mms(); + bool check_rpc_payment(); + + void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon); + + bool check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph); //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); @@ -439,7 +451,17 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<1> m_inactivity_checker; epee::math_helper::once_a_time_seconds<90> m_refresh_checker; epee::math_helper::once_a_time_seconds<90> m_mms_checker; + epee::math_helper::once_a_time_seconds<90> m_rpc_payment_checker; + std::atomic<bool> m_need_payment; + boost::posix_time::ptime m_last_rpc_payment_mining_time; + bool m_rpc_payment_mining_requested; + bool m_daemon_rpc_payment_message_displayed; + float m_rpc_payment_hash_rate; + std::atomic<bool> m_suspend_rpc_payment_mining; + + std::unordered_map<std::string, uint32_t> m_claimed_cph; + // MMS mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); }; |