aboutsummaryrefslogtreecommitdiff
path: root/src/simplewallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/simplewallet')
-rw-r--r--src/simplewallet/simplewallet.cpp430
-rw-r--r--src/simplewallet/simplewallet.h22
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(); };