Merge pull request #5357

b3a9a4d add a quick early out to get_blocks.bin when up to date (moneromooo-monero)
2899379 daemon, wallet: new pay for RPC use system (moneromooo-monero)
ffa4602 simplewallet: add public_nodes command (moneromooo-monero)
pull/326/head
luigi1111 5 years ago
commit 960c215801
No known key found for this signature in database
GPG Key ID: F4ACA0183641E010

@ -132,6 +132,23 @@ static inline uint32_t div128_32(uint64_t dividend_hi, uint64_t dividend_lo, uin
// Long divisor with 2^64 base
void div128_64(uint64_t dividend_hi, uint64_t dividend_lo, uint64_t divisor, uint64_t* quotient_hi, uint64_t *quotient_lo, uint64_t *remainder_hi, uint64_t *remainder_lo);
static inline void add64clamp(uint64_t *value, uint64_t add)
{
static const uint64_t maxval = (uint64_t)-1;
if (*value > maxval - add)
*value = maxval;
else
*value += add;
}
static inline void sub64clamp(uint64_t *value, uint64_t sub)
{
if (*value < sub)
*value = 0;
else
*value -= sub;
}
#define IDENT16(x) ((uint16_t) (x))
#define IDENT32(x) ((uint32_t) (x))
#define IDENT64(x) ((uint64_t) (x))

@ -259,6 +259,11 @@ namespace math_helper
m_last_worked_time = get_time();
}
void trigger()
{
m_last_worked_time = 0;
}
template<class functor_t>
bool do_call(functor_t functr)
{

@ -26,6 +26,7 @@
#pragma once
#include <type_traits>
#include <boost/utility/value_init.hpp>
#include <boost/foreach.hpp>
#include "misc_log_ex.h"
@ -45,18 +46,20 @@ public: \
template<class t_storage> \
bool store( t_storage& st, typename t_storage::hsection hparent_section = nullptr) const\
{\
return serialize_map<true>(*this, st, hparent_section);\
using type = typename std::remove_const<typename std::remove_reference<decltype(*this)>::type>::type; \
auto &self = const_cast<type&>(*this); \
return self.template serialize_map<true>(st, hparent_section); \
}\
template<class t_storage> \
bool _load( t_storage& stg, typename t_storage::hsection hparent_section = nullptr)\
{\
return serialize_map<false>(*this, stg, hparent_section);\
return serialize_map<false>(stg, hparent_section);\
}\
template<class t_storage> \
bool load( t_storage& stg, typename t_storage::hsection hparent_section = nullptr)\
{\
try{\
return serialize_map<false>(*this, stg, hparent_section);\
return serialize_map<false>(stg, hparent_section);\
}\
catch(const std::exception& err) \
{ \
@ -65,13 +68,22 @@ public: \
return false; \
}\
}\
template<bool is_store, class this_type, class t_storage> \
static bool serialize_map(this_type& this_ref, t_storage& stg, typename t_storage::hsection hparent_section) \
{
/*template<typename T> T& this_type_resolver() { return *this; }*/ \
/*using this_type = std::result_of<decltype(this_type_resolver)>::type;*/ \
template<bool is_store, class t_storage> \
bool serialize_map(t_storage& stg, typename t_storage::hsection hparent_section) \
{ \
decltype(*this) &this_ref = *this;
#define KV_SERIALIZE_N(varialble, val_name) \
epee::serialization::selector<is_store>::serialize(this_ref.varialble, stg, hparent_section, val_name);
#define KV_SERIALIZE_PARENT(type) \
do { \
if (!((type*)this)->serialize_map<is_store, t_storage>(stg, hparent_section)) \
return false; \
} while(0);
template<typename T> inline void serialize_default(const T &t, T v) { }
template<typename T> inline void serialize_default(T &t, T v) { t = v; }

@ -101,7 +101,7 @@ namespace epee
}
template<class t_request, class t_response, class t_transport>
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, epee::json_rpc::error &error_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
{
epee::json_rpc::request<t_request> req_t = AUTO_VAL_INIT(req_t);
req_t.jsonrpc = "2.0";
@ -111,10 +111,12 @@ namespace epee
epee::json_rpc::response<t_response, epee::json_rpc::error> resp_t = AUTO_VAL_INIT(resp_t);
if(!epee::net_utils::invoke_http_json(uri, req_t, resp_t, transport, timeout, http_method))
{
error_struct = {};
return false;
}
if(resp_t.error.code || resp_t.error.message.size())
{
error_struct = resp_t.error;
LOG_ERROR("RPC call of \"" << req_t.method << "\" returned error: " << resp_t.error.code << ", message: " << resp_t.error.message);
return false;
}
@ -122,6 +124,13 @@ namespace epee
return true;
}
template<class t_request, class t_response, class t_transport>
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
{
epee::json_rpc::error error_struct;
return invoke_http_json_rpc(uri, method_name, out_struct, result_struct, error_struct, transport, timeout, http_method, req_id);
}
template<class t_command, class t_transport>
bool invoke_http_json_rpc(const boost::string_ref uri, typename t_command::request& out_struct, typename t_command::response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
{

@ -100,7 +100,7 @@ static const char *get_default_categories(int level)
switch (level)
{
case 0:
categories = "*:WARNING,net:FATAL,net.http:FATAL,net.ssl:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,serialization:FATAL,stacktrace:INFO,logging:INFO,msgwriter:INFO";
categories = "*:WARNING,net:FATAL,net.http:FATAL,net.ssl:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,serialization:FATAL,daemon.rpc.payment:ERROR,stacktrace:INFO,logging:INFO,msgwriter:INFO";
break;
case 1:
categories = "*:INFO,global:INFO,stacktrace:INFO,logging:INFO,msgwriter:INFO,perf.*:DEBUG";

@ -76,14 +76,15 @@ private:
void set_performance_timer_log_level(el::Level level);
#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level)
#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l)
#define PERF_TIMER_NAME(name) pt_##name
#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level)
#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)t_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l)
#define PERF_TIMER(name) PERF_TIMER_UNIT(name, 1000000)
#define PERF_TIMER_L(name, l) PERF_TIMER_UNIT_L(name, 1000000, l)
#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> pt_##name(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info))
#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> PERF_TIMER_NAME(name)(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info))
#define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000000)
#define PERF_TIMER_STOP(name) do { pt_##name.reset(NULL); } while(0)
#define PERF_TIMER_PAUSE(name) pt_##name->pause()
#define PERF_TIMER_RESUME(name) pt_##name->resume()
#define PERF_TIMER_STOP(name) do { PERF_TIMER_NAME(name).reset(NULL); } while(0)
#define PERF_TIMER_PAUSE(name) PERF_TIMER_NAME(name)->pause()
#define PERF_TIMER_RESUME(name) PERF_TIMER_NAME(name)->resume()
}

@ -43,7 +43,7 @@ namespace cryptonote
{
cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0),
m_last_request_time(boost::date_time::not_a_date_time), m_callback_request_count(0),
m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_anchor(false) {}
m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_rpc_credits_per_hash(0), m_anchor(false) {}
enum state
{
@ -64,6 +64,7 @@ namespace cryptonote
crypto::hash m_last_known_hash;
uint32_t m_pruning_seed;
uint16_t m_rpc_port;
uint32_t m_rpc_credits_per_hash;
bool m_anchor;
//size_t m_score; TODO: add score calculations
};

@ -148,6 +148,7 @@
#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb"
#define CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME "lock.mdb"
#define P2P_NET_DATA_FILENAME "p2pstate.bin"
#define RPC_PAYMENTS_DATA_FILENAME "rpcpayments.bin"
#define MINER_CONFIG_FILE_NAME "miner_conf.json"
#define THREAD_STACK_SIZE 5 * 1024 * 1024
@ -180,6 +181,8 @@
#define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved
//#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
#define RPC_CREDITS_PER_HASH_SCALE ((float)(1<<24))
// New constants are intended to go here
namespace config
{

@ -56,6 +56,7 @@ namespace cryptonote
std::string ip;
std::string port;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
std::string peer_id;
@ -94,6 +95,7 @@ namespace cryptonote
KV_SERIALIZE(ip)
KV_SERIALIZE(port)
KV_SERIALIZE(rpc_port)
KV_SERIALIZE(rpc_credits_per_hash)
KV_SERIALIZE(peer_id)
KV_SERIALIZE(recv_count)
KV_SERIALIZE(recv_idle_time)

@ -246,6 +246,7 @@ namespace cryptonote
cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port());
}
cnx.rpc_port = cntxt.m_rpc_port;
cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash;
std::stringstream peer_id_str;
peer_id_str << std::hex << std::setw(16) << peer_id;

@ -794,6 +794,13 @@ bool t_command_parser_executor::pop_blocks(const std::vector<std::string>& args)
return false;
}
bool t_command_parser_executor::rpc_payments(const std::vector<std::string>& args)
{
if (args.size() != 0) return false;
return m_executor.rpc_payments();
}
bool t_command_parser_executor::version(const std::vector<std::string>& args)
{
std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << std::endl;

@ -143,6 +143,8 @@ public:
bool pop_blocks(const std::vector<std::string>& args);
bool rpc_payments(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args);
bool prune_blockchain(const std::vector<std::string>& args);

@ -295,6 +295,11 @@ t_command_server::t_command_server(
, "pop_blocks <nblocks>"
, "Remove blocks from end of blockchain"
);
m_command_lookup.set_handler(
"rpc_payments"
, std::bind(&t_command_parser_executor::rpc_payments, &m_parser, p::_1)
, "Print information about RPC payments."
);
m_command_lookup.set_handler(
"version"
, std::bind(&t_command_parser_executor::version, &m_parser, p::_1)

@ -58,16 +58,6 @@ public:
)
: m_core{nullptr}
, m_vm_HACK{vm}
{
}
// TODO - get rid of circular dependencies in internals
void set_protocol(t_protocol_raw & protocol)
{
m_core.set_cryptonote_protocol(&protocol);
}
bool run()
{
//initialize core here
MGINFO("Initializing core...");
@ -78,9 +68,19 @@ public:
#endif
if (!m_core.init(m_vm_HACK, nullptr, get_checkpoints))
{
return false;
throw std::runtime_error("Failed to initialize core");
}
MGINFO("Core initialized OK");
}
// TODO - get rid of circular dependencies in internals
void set_protocol(t_protocol_raw & protocol)
{
m_core.set_cryptonote_protocol(&protocol);
}
bool run()
{
return true;
}

@ -37,6 +37,7 @@
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_basic/difficulty.h"
#include "cryptonote_basic/hardfork.h"
#include "rpc/rpc_payment_signature.h"
#include <boost/format.hpp>
#include <ctime>
#include <string>
@ -60,6 +61,13 @@ namespace {
}
}
std::string print_float(float f, int prec)
{
char buf[16];
snprintf(buf, sizeof(buf), "%*.*f", prec, prec, f);
return buf;
}
void print_peer(std::string const & prefix, cryptonote::peer const & peer, bool pruned_only, bool publicrpc_only)
{
if (pruned_only && peer.pruning_seed == 0)
@ -77,8 +85,9 @@ namespace {
epee::string_tools::xtype_to_string(peer.port, port_str);
std::string addr_str = peer.host + ":" + port_str;
std::string rpc_port = peer.rpc_port ? std::to_string(peer.rpc_port) : "-";
std::string rpc_credits_per_hash = peer.rpc_credits_per_hash ? print_float(peer.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE, 2) : "-";
std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed);
tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % pruning_seed % elapsed;
tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % rpc_credits_per_hash % pruning_seed % elapsed;
}
void print_block_header(cryptonote::block_header_response const & header)
@ -2364,4 +2373,45 @@ bool t_rpc_command_executor::set_bootstrap_daemon(
return true;
}
bool t_rpc_command_executor::rpc_payments()
{
cryptonote::COMMAND_RPC_ACCESS_DATA::request req;
cryptonote::COMMAND_RPC_ACCESS_DATA::response res;
std::string fail_message = "Unsuccessful";
epee::json_rpc::error error_resp;
if (m_is_rpc)
{
if (!m_rpc_client->json_rpc_request(req, res, "rpc_access_data", fail_message.c_str()))
{
return true;
}
}
else
{
if (!m_rpc_server->on_rpc_access_data(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
{
tools::fail_msg_writer() << make_error(fail_message, res.status);
return true;
}
}
const uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t balance = 0;
tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s")
% "Client ID" % "Balance" % "Total mined" % "Good" % "Stale" % "Bad" % "Dupes" % "Last update";
for (const auto &entry: res.entries)
{
tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s")
% entry.client % entry.balance % entry.credits_total
% entry.nonces_good % entry.nonces_stale % entry.nonces_bad % entry.nonces_dupe
% (entry.last_update_time == 0 ? "never" : get_human_time_ago(entry.last_update_time, now).c_str());
balance += entry.balance;
}
tools::msg_writer() << res.entries.size() << " clients with a total of " << balance << " credits";
tools::msg_writer() << "Aggregated client hash rate: " << get_mining_speed(res.hashrate);
return true;
}
}// namespace daemonize

@ -167,6 +167,8 @@ public:
const std::string &address,
const std::string &username,
const std::string &password);
bool rpc_payments();
};
} // namespace daemonize

@ -231,6 +231,7 @@ namespace nodetool
: m_payload_handler(payload_handler),
m_external_port(0),
m_rpc_port(0),
m_rpc_credits_per_hash(0),
m_allow_local_ip(false),
m_hide_my_port(false),
m_igd(no_igd),
@ -431,6 +432,11 @@ namespace nodetool
m_rpc_port = rpc_port;
}
void set_rpc_credits_per_hash(uint32_t rpc_credits_per_hash)
{
m_rpc_credits_per_hash = rpc_credits_per_hash;
}
private:
std::string m_config_folder;
@ -440,6 +446,7 @@ namespace nodetool
uint32_t m_listening_port_ipv6;
uint32_t m_external_port;
uint16_t m_rpc_port;
uint32_t m_rpc_credits_per_hash;
bool m_allow_local_ip;
bool m_hide_my_port;
igd_t m_igd;

@ -1057,7 +1057,8 @@ namespace nodetool
pi = context.peer_id = rsp.node_data.peer_id;
context.m_rpc_port = rsp.node_data.rpc_port;
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash;
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
// move
for (auto const& zone : m_network_zones)
@ -1123,7 +1124,7 @@ namespace nodetool
add_host_fail(context.m_remote_address);
}
if(!context.m_is_income)
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false))
{
m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id );
@ -1292,6 +1293,7 @@ namespace nodetool
pe_local.last_seen = static_cast<int64_t>(last_seen);
pe_local.pruning_seed = con->m_pruning_seed;
pe_local.rpc_port = con->m_rpc_port;
pe_local.rpc_credits_per_hash = con->m_rpc_credits_per_hash;
zone.m_peerlist.append_with_peer_white(pe_local);
//update last seen and push it to peerlist manager
@ -1922,6 +1924,7 @@ namespace nodetool
else
node_data.my_port = 0;
node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0;
node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0;
node_data.network_id = m_network_id;
return true;
}
@ -2366,6 +2369,7 @@ namespace nodetool
context.peer_id = arg.node_data.peer_id;
context.m_in_timedsync = false;
context.m_rpc_port = arg.node_data.rpc_port;
context.m_rpc_credits_per_hash = arg.node_data.rpc_credits_per_hash;
if(arg.node_data.my_port && zone.m_can_pingback)
{
@ -2393,6 +2397,7 @@ namespace nodetool
pe.id = peer_id_l;
pe.pruning_seed = context.m_pruning_seed;
pe.rpc_port = context.m_rpc_port;
pe.rpc_credits_per_hash = context.m_rpc_credits_per_hash;
this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe);
LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l);
});
@ -2710,7 +2715,7 @@ namespace nodetool
}
else
{
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port);
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port, pe.rpc_credits_per_hash);
LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id));
}
}

@ -111,7 +111,7 @@ namespace nodetool
bool append_with_peer_white(const peerlist_entry& pr);
bool append_with_peer_gray(const peerlist_entry& pr);
bool append_with_peer_anchor(const anchor_peerlist_entry& ple);
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port);
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash);
bool set_peer_unreachable(const peerlist_entry& pr);
bool is_host_allowed(const epee::net_utils::network_address &address);
bool get_random_gray_peer(peerlist_entry& pe);
@ -315,7 +315,7 @@ namespace nodetool
}
//--------------------------------------------------------------------------------------------------
inline
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port)
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash)
{
TRY_ENTRY();
CRITICAL_REGION_LOCAL(m_peerlist_lock);
@ -326,6 +326,7 @@ namespace nodetool
ple.last_seen = time(NULL);
ple.pruning_seed = pruning_seed;
ple.rpc_port = rpc_port;
ple.rpc_credits_per_hash = rpc_credits_per_hash;
return append_with_peer_white(ple);
CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false);
}

@ -42,7 +42,7 @@
#include "common/pruning.h"
#endif
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2)
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3)
namespace boost
{
@ -241,6 +241,13 @@ namespace boost
return;
}
a & pl.rpc_port;
if (ver < 3)
{
if (!typename Archive::is_saving())
pl.rpc_credits_per_hash = 0;
return;
}
a & pl.rpc_credits_per_hash;
}
template <class Archive, class ver_type>

@ -77,6 +77,7 @@ namespace nodetool
int64_t last_seen;
uint32_t pruning_seed;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(adr)
@ -85,6 +86,7 @@ namespace nodetool
KV_SERIALIZE_OPT(last_seen, (int64_t)0)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
END_KV_SERIALIZE_MAP()
};
typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry;
@ -132,6 +134,7 @@ namespace nodetool
{
ss << pe.id << "\t" << pe.adr.str()
<< " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-")
<< " \trpc credits per hash " << (pe.rpc_credits_per_hash > 0 ? std::to_string(pe.rpc_credits_per_hash) : "-")
<< " \tpruning seed " << pe.pruning_seed
<< " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen))
<< std::endl;
@ -166,6 +169,7 @@ namespace nodetool
uint64_t local_time;
uint32_t my_port;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
peerid_type peer_id;
BEGIN_KV_SERIALIZE_MAP()
@ -174,6 +178,7 @@ namespace nodetool
KV_SERIALIZE(local_time)
KV_SERIALIZE(my_port)
KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0))
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
END_KV_SERIALIZE_MAP()
};
@ -220,7 +225,7 @@ namespace nodetool
{
const epee::net_utils::network_address &na = p.adr;
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
}
else
MDEBUG("Not including in legacy peer list: " << p.adr.str());
@ -235,7 +240,7 @@ namespace nodetool
std::vector<peerlist_entry_base<network_address_old>> local_peerlist;
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
for (const auto &p: local_peerlist)
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
}
}
END_KV_SERIALIZE_MAP()

@ -29,12 +29,14 @@
include_directories(SYSTEM ${ZMQ_INCLUDE_PATH})
set(rpc_base_sources
rpc_args.cpp)
rpc_args.cpp
rpc_payment_signature.cpp
rpc_handler.cpp)
set(rpc_sources
bootstrap_daemon.cpp
core_rpc_server.cpp
rpc_handler.cpp
rpc_payment.cpp
instanciations)
set(daemon_messages_sources
@ -47,7 +49,9 @@ set(daemon_rpc_server_sources
set(rpc_base_headers
rpc_args.h)
rpc_args.h
rpc_payment_signature.h
rpc_handler.h)
set(rpc_headers
rpc_handler.h)
@ -58,6 +62,7 @@ set(daemon_rpc_server_headers)
set(rpc_daemon_private_headers
bootstrap_daemon.h
core_rpc_server.h
rpc_payment.h
core_rpc_server_commands_defs.h
core_rpc_server_error_codes.h)

File diff suppressed because it is too large Load Diff

@ -42,6 +42,7 @@
#include "cryptonote_core/cryptonote_core.h"
#include "p2p/net_node.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "rpc_payment.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc"
@ -71,6 +72,9 @@ namespace cryptonote
static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address;
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login;
static const command_line::arg_descriptor<std::string> arg_rpc_payment_address;
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty;
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits;
typedef epee::net_utils::connection_context_base connection_context;
@ -78,6 +82,7 @@ namespace cryptonote
core& cr
, nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p
);
~core_rpc_server();
static void init_options(boost::program_options::options_description& desc);
bool init(
@ -111,7 +116,7 @@ namespace cryptonote
MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted)
MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted)
MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted)
MAP_URI_AUTO_JON2_IF("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES, !m_restricted)
MAP_URI_AUTO_JON2("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES)
MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted)
MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted)
MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted)
@ -169,6 +174,12 @@ namespace cryptonote
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted)
MAP_JON_RPC_WE("rpc_access_info", on_rpc_access_info, COMMAND_RPC_ACCESS_INFO)
MAP_JON_RPC_WE("rpc_access_submit_nonce",on_rpc_access_submit_nonce, COMMAND_RPC_ACCESS_SUBMIT_NONCE)
MAP_JON_RPC_WE("rpc_access_pay", on_rpc_access_pay, COMMAND_RPC_ACCESS_PAY)
MAP_JON_RPC_WE_IF("rpc_access_tracking", on_rpc_access_tracking, COMMAND_RPC_ACCESS_TRACKING, !m_restricted)
MAP_JON_RPC_WE_IF("rpc_access_data", on_rpc_access_data, COMMAND_RPC_ACCESS_DATA, !m_restricted)
MAP_JON_RPC_WE_IF("rpc_access_account", on_rpc_access_account, COMMAND_RPC_ACCESS_ACCOUNT, !m_restricted)
END_JSON_RPC_MAP()
END_URI_MAP2()
@ -236,6 +247,12 @@ namespace cryptonote
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_tracking(const COMMAND_RPC_ACCESS_TRACKING::request& req, COMMAND_RPC_ACCESS_TRACKING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_data(const COMMAND_RPC_ACCESS_DATA::request& req, COMMAND_RPC_ACCESS_DATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
bool on_rpc_access_account(const COMMAND_RPC_ACCESS_ACCOUNT::request& req, COMMAND_RPC_ACCESS_ACCOUNT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
//-----------------------
private:
@ -252,6 +269,8 @@ private:
enum invoke_http_mode { JON, BIN, JON_RPC };
template <typename COMMAND_TYPE>
bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r);
bool get_block_template(const account_public_address &address, const crypto::hash *prev_block, const cryptonote::blobdata &extra_nonce, size_t &reserved_offset, cryptonote::difficulty_type &difficulty, uint64_t &height, uint64_t &expected_reward, block &b, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, epee::json_rpc::error &error_resp);
bool check_payment(const std::string &client, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash);
core& m_core;
nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p;
@ -260,10 +279,10 @@ private:
bool m_should_use_bootstrap_daemon;
std::chrono::system_clock::time_point m_bootstrap_height_check_time;
bool m_was_bootstrap_ever_used;
network_type m_nettype;
bool m_restricted;
epee::critical_section m_host_fails_score_lock;
std::map<std::string, uint64_t> m_host_fails_score;
std::unique_ptr<rpc_payment> m_rpc_payment;
};
}

File diff suppressed because it is too large Load Diff

@ -43,5 +43,34 @@
#define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11
#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12
#define CORE_RPC_ERROR_CODE_REGTEST_REQUIRED -13
#define CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED -14
#define CORE_RPC_ERROR_CODE_INVALID_CLIENT -15
#define CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW -16
#define CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT -17
#define CORE_RPC_ERROR_CODE_STALE_PAYMENT -18
static inline const char *get_rpc_server_error_message(int64_t code)
{
switch (code)
{
case CORE_RPC_ERROR_CODE_WRONG_PARAM: return "Invalid parameter";
case CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT: return "Height is too large";
case CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE: return "Reserve size is too large";
case CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS: return "Wrong wallet address";
case CORE_RPC_ERROR_CODE_INTERNAL_ERROR: return "Internal error";
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB: return "Wrong block blob";
case CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED: return "Block not accepted";
case CORE_RPC_ERROR_CODE_CORE_BUSY: return "Core is busy";
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE: return "Wrong block blob size";
case CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC: return "Unsupported RPC";
case CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS: return "Mining to subaddress is not supported";
case CORE_RPC_ERROR_CODE_REGTEST_REQUIRED: return "Regtest mode required";
case CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED: return "Payment required";
case CORE_RPC_ERROR_CODE_INVALID_CLIENT: return "Invalid client";
case CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW: return "Payment too low";
case CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT: return "Duplicate payment";
case CORE_RPC_ERROR_CODE_STALE_PAYMENT: return "Stale payment";
default: MERROR("Unknown error: " << code); return "Unknown error";
}
}

@ -80,6 +80,7 @@ namespace rpc
uint32_t ip;
uint16_t port;
uint16_t rpc_port;
uint32_t rpc_credits_per_hash;
uint64_t last_seen;
uint32_t pruning_seed;
};

@ -0,0 +1,402 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/archive/portable_binary_iarchive.hpp>
#include <boost/archive/portable_binary_oarchive.hpp>
#include "cryptonote_config.h"
#include "include_base_utils.h"
#include "string_tools.h"
#include "file_io_utils.h"
#include "int-util.h"
#include "common/util.h"
#include "serialization/crypto.h"
#include "common/unordered_containers_boost_serialization.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/difficulty.h"
#include "core_rpc_server_error_codes.h"
#include "rpc_payment.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment"
#define STALE_THRESHOLD 15 /* seconds */
#define PENALTY_FOR_STALE 0
#define PENALTY_FOR_BAD_HASH 20
#define PENALTY_FOR_DUPLICATE 20
#define DEFAULT_FLUSH_AGE (3600 * 24 * 180) // half a year
#define DEFAULT_ZERO_FLUSH_AGE (60 * 2) // 2 minutes
#define RPC_PAYMENT_NONCE_TAIL 0x58
namespace cryptonote
{
rpc_payment::client_info::client_info():
cookie(0),
top(crypto::null_hash),
previous_top(crypto::null_hash),
credits(0),
update_time(time(NULL)),
last_request_timestamp(0),
block_template_update_time(0),
credits_total(0),
credits_used(0),
nonces_good(0),
nonces_stale(0),
nonces_bad(0),
nonces_dupe(0)
{
}
rpc_payment::rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found):
m_address(address),
m_diff(diff),
m_credits_per_hash_found(credits_per_hash_found),
m_credits_total(0),
m_credits_used(0),
m_nonces_good(0),
m_nonces_stale(0),
m_nonces_bad(0),
m_nonces_dupe(0)
{
}
uint64_t rpc_payment::balance(const crypto::public_key &client, int64_t delta)
{
client_info &info = m_client_info[client]; // creates if not found
uint64_t credits = info.credits;
if (delta > 0 && credits > std::numeric_limits<uint64_t>::max() - delta)
credits = std::numeric_limits<uint64_t>::max();
else if (delta < 0 && credits < (uint64_t)-delta)
credits = 0;
else
credits += delta;
if (delta)
MINFO("Client " << client << ": balance change from " << info.credits << " to " << credits);
return info.credits = credits;
}
bool rpc_payment::pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits)
{
client_info &info = m_client_info[client]; // creates if not found
if (ts < info.last_request_timestamp || (ts == info.last_request_timestamp && !same_ts))
{
MDEBUG("Invalid ts: " << ts << " <= " << info.last_request_timestamp);
return false;
}
info.last_request_timestamp = ts;
if (info.credits < payment)
{
MDEBUG("Not enough credits: " << info.credits << " < " << payment);
credits = info.credits;
return false;
}
info.credits -= payment;
add64clamp(&info.credits_used, payment);
add64clamp(&m_credits_used, payment);
MDEBUG("client " << client << " paying " << payment << " for " << rpc << ", " << info.credits << " left");
credits = info.credits;
return true;
}
bool rpc_payment::get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie)
{
client_info &info = m_client_info[client]; // creates if not found
const uint64_t now = time(NULL);
bool need_template = top != info.top || now >= info.block_template_update_time + STALE_THRESHOLD;
if (need_template)
{
cryptonote::block new_block;
uint64_t new_seed_height;
crypto::hash new_seed_hash;
cryptonote::blobdata extra_nonce("\x42\x42\x42\x42", 4);
if (!get_block_template(extra_nonce, new_block, new_seed_height, new_seed_hash))
return false;
if(!remove_field_from_tx_extra(new_block.miner_tx.extra, typeid(cryptonote::tx_extra_nonce)))
return false;
char data[33];
memcpy(data, &client, 32);
data[32] = RPC_PAYMENT_NONCE_TAIL;
crypto::hash hash;
cn_fast_hash(data, sizeof(data), hash);
extra_nonce = cryptonote::blobdata((const char*)&hash, 4);
if(!add_extra_nonce_to_tx_extra(new_block.miner_tx.extra, extra_nonce))
return false;
info.previous_block = std::move(info.block);
info.block = std::move(new_block);
hashing_blob = get_block_hashing_blob(info.block);
info.previous_hashing_blob = info.hashing_blob;
info.hashing_blob = hashing_blob;
info.previous_top = info.top;
info.previous_seed_height = info.seed_height;
info.seed_height = new_seed_height;
info.previous_seed_hash = info.seed_hash;
info.seed_hash = new_seed_hash;
std::swap(info.previous_payments, info.payments);
info.payments.clear();
++info.cookie;
info.block_template_update_time = now;
}
info.top = top;
info.update_time = now;
hashing_blob = info.hashing_blob;
diff = m_diff;
credits_per_hash_found = m_credits_per_hash_found;
credits = info.credits;
seed_height = info.seed_height;
seed_hash = info.seed_hash;
cookie = info.cookie;
return true;
}
bool rpc_payment::submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale)
{
client_info &info = m_client_info[client]; // creates if not found
if (cookie != info.cookie && cookie != info.cookie - 1)
{
MWARNING("Very stale nonce");
++m_nonces_stale;
++info.nonces_stale;
sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found);
error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT;
error_message = "Very stale payment";
return false;
}
const bool is_current = cookie == info.cookie;
MINFO("client " << client << " sends nonce: " << nonce << ", " << (is_current ? "current" : "stale"));
std::unordered_set<uint64_t> &payments = is_current ? info.payments : info.previous_payments;
if (!payments.insert(nonce).second)
{
MWARNING("Duplicate nonce " << nonce << " from " << (is_current ? "current" : "previous"));
++m_nonces_dupe;
++info.nonces_dupe;
sub64clamp(&info.credits, PENALTY_FOR_DUPLICATE * m_credits_per_hash_found);
error_code = CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT;
error_message = "Duplicate payment";
return false;
}
const uint64_t now = time(NULL);
if (!is_current)
{
if (now > info.update_time + STALE_THRESHOLD)
{
MWARNING("Nonce is stale (top " << top << ", should be " << info.top << " or within " << STALE_THRESHOLD << " seconds");
++m_nonces_stale;
++info.nonces_stale;
sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found);
error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT;
error_message = "stale payment";
return false;
}
}
cryptonote::blobdata hashing_blob = is_current ? info.hashing_blob : info.previous_hashing_blob;
if (hashing_blob.size() < 43)
{
// not initialized ?
error_code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
error_message = "not initialized";
return false;
}
block = is_current ? info.block : info.previous_block;
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce);
if (block.major_version >= RX_BLOCK_VERSION)
{
const uint64_t seed_height = is_current ? info.seed_height : info.previous_seed_height;
const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash;
const uint64_t height = cryptonote::get_block_height(block);
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, 0, 0);
}
else
{
const int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0;
crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, cryptonote::get_block_height(block));
}
if (!check_hash(hash, m_diff))
{
MWARNING("Payment too low");
++m_nonces_bad;
++info.nonces_bad;
error_code = CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW;
error_message = "Hash does not meet difficulty (could be wrong PoW hash, or mining at lower difficulty than required, or attempt to defraud)";
sub64clamp(&info.credits, PENALTY_FOR_BAD_HASH * m_credits_per_hash_found);
return false;
}
add64clamp(&info.credits, m_credits_per_hash_found);
MINFO("client " << client << " credited for " << m_credits_per_hash_found << ", now " << info.credits << (is_current ? "" : " (close)"));
m_hashrate[now] += m_diff;
add64clamp(&m_credits_total, m_credits_per_hash_found);
add64clamp(&info.credits_total, m_credits_per_hash_found);
++m_nonces_good;
++info.nonces_good;
credits = info.credits;
block = info.block;
block.nonce = nonce;
stale = !is_current;
return true;
}
bool rpc_payment::foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const
{
for (std::unordered_map<crypto::public_key, client_info>::const_iterator i = m_client_info.begin(); i != m_client_info.end(); ++i)
{
if (!f(i->first, i->second))
return false;
}
return true;
}
bool rpc_payment::load(std::string directory)
{
TRY_ENTRY();
m_directory = std::move(directory);
std::string state_file_path = directory + "/" + RPC_PAYMENTS_DATA_FILENAME;
MINFO("loading rpc payments data from " << state_file_path);
std::ifstream data;
data.open(state_file_path, std::ios_base::binary | std::ios_base::in);
if (!data.fail())
{
try
{
boost::archive::portable_binary_iarchive a(data);
a >> *this;
}
catch (const std::exception &e)
{
MERROR("Failed to load RPC payments file: " << e.what());
m_client_info.clear();
}
}
else
{
m_client_info.clear();
}
CATCH_ENTRY_L0("rpc_payment::load", false);
return true;
}
bool rpc_payment::store(const std::string &directory_) const
{
TRY_ENTRY();
const std::string &directory = directory_.empty() ? m_directory : directory_;
MDEBUG("storing rpc payments data to " << directory);
if (!tools::create_directories_if_necessary(directory))
{
MWARNING("Failed to create data directory: " << directory);
return false;
}
const boost::filesystem::path state_file_path = (boost::filesystem::path(directory) / RPC_PAYMENTS_DATA_FILENAME);
if (boost::filesystem::exists(state_file_path))
{
std::string state_file_path_old = state_file_path.string() + ".old";
boost::system::error_code ec;
boost::filesystem::remove(state_file_path_old, ec);
std::error_code e = tools::replace_file(state_file_path.string(), state_file_path_old);
if (e)
MWARNING("Failed to rename " << state_file_path << " to " << state_file_path_old << ": " << e);
}
std::ofstream data;
data.open(state_file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
if (data.fail())
{
MWARNING("Failed to save RPC payments to file " << state_file_path);
return false;
};
boost::archive::portable_binary_oarchive a(data);
a << *this;
return true;
CATCH_ENTRY_L0("rpc_payment::store", false);
}
unsigned int rpc_payment::flush_by_age(time_t seconds)
{
unsigned int count = 0;
const time_t now = time(NULL);
time_t seconds0 = seconds;
if (seconds == 0)
{
seconds = DEFAULT_FLUSH_AGE;
seconds0 = DEFAULT_ZERO_FLUSH_AGE;
}
const time_t threshold = seconds > now ? 0 : now - seconds;
const time_t threshold0 = seconds0 > now ? 0 : now - seconds0;
for (std::unordered_map<crypto::public_key, client_info>::iterator i = m_client_info.begin(); i != m_client_info.end(); )
{
std::unordered_map<crypto::public_key, client_info>::iterator j = i++;
const time_t t = std::max(j->second.last_request_timestamp, j->second.update_time);
const bool erase = t < ((j->second.credits == 0) ? threshold0 : threshold);
if (erase)
{
MINFO("Erasing " << j->first << " with " << j->second.credits << " credits, inactive for " << (now-t)/86400 << " days");
m_client_info.erase(j);
++count;
}
}
return count;
}
uint64_t rpc_payment::get_hashes(unsigned int seconds) const
{
const uint64_t now = time(NULL);
uint64_t hashes = 0;
for (std::map<uint64_t, uint64_t>::const_reverse_iterator i = m_hashrate.crbegin(); i != m_hashrate.crend(); ++i)
{
if (now > i->first + seconds)
break;
hashes += i->second;
}
return hashes;
}
void rpc_payment::prune_hashrate(unsigned int seconds)
{
const uint64_t now = time(NULL);
std::map<uint64_t, uint64_t>::iterator i;
for (i = m_hashrate.begin(); i != m_hashrate.end(); ++i)
{
if (now <= i->first + seconds)
break;
}
m_hashrate.erase(m_hashrate.begin(), i);
}
bool rpc_payment::on_idle()
{
flush_by_age();
prune_hashrate(3600);
return true;
}
}

@ -0,0 +1,146 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <boost/serialization/version.hpp>
#include "cryptonote_basic/blobdatatype.h"
#include "cryptonote_basic/cryptonote_basic.h"
namespace cryptonote
{
class rpc_payment
{
public:
struct client_info
{
cryptonote::block block;
cryptonote::block previous_block;
cryptonote::blobdata hashing_blob;
cryptonote::blobdata previous_hashing_blob;
uint64_t previous_seed_height;
uint64_t seed_height;
crypto::hash previous_seed_hash;
crypto::hash seed_hash;
uint32_t cookie;
crypto::hash top;
crypto::hash previous_top;
uint64_t credits;
std::unordered_set<uint64_t> payments;
std::unordered_set<uint64_t> previous_payments;
uint64_t update_time;
uint64_t last_request_timestamp;
uint64_t block_template_update_time;
uint64_t credits_total;
uint64_t credits_used;
uint64_t nonces_good;
uint64_t nonces_stale;
uint64_t nonces_bad;
uint64_t nonces_dupe;
client_info();
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{
a & block;
a & previous_block;
a & hashing_blob;
a & previous_hashing_blob;
a & seed_height;
a & previous_seed_height;
a & seed_hash;
a & previous_seed_hash;
a & cookie;
a & top;
a & previous_top;
a & credits;
a & payments;
a & previous_payments;
a & update_time;
a & last_request_timestamp;
a & block_template_update_time;
a & credits_total;
a & credits_used;
a & nonces_good;
a & nonces_stale;
a & nonces_bad;
a & nonces_dupe;
}
};
public:
rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found);
uint64_t balance(const crypto::public_key &client, int64_t delta = 0);
bool pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits);
bool get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie);
bool submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale);
const cryptonote::account_public_address &get_payment_address() const { return m_address; }
bool foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const;
unsigned int flush_by_age(time_t seconds = 0);
uint64_t get_hashes(unsigned int seconds) const;
void prune_hashrate(unsigned int seconds);
bool on_idle();
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{
a & m_client_info;
a & m_hashrate;
a & m_credits_total;
a & m_credits_used;
a & m_nonces_good;
a & m_nonces_stale;
a & m_nonces_bad;
a & m_nonces_dupe;
}
bool load(std::string directory);
bool store(const std::string &directory = std::string()) const;
private:
cryptonote::account_public_address m_address;
uint64_t m_diff;
uint64_t m_credits_per_hash_found;
std::unordered_map<crypto::public_key, client_info> m_client_info;
std::string m_directory;
std::map<uint64_t, uint64_t> m_hashrate;
uint64_t m_credits_total;
uint64_t m_credits_used;
uint64_t m_nonces_good;
uint64_t m_nonces_stale;
uint64_t m_nonces_bad;
uint64_t m_nonces_dupe;
};
}
BOOST_CLASS_VERSION(cryptonote::rpc_payment, 0);
BOOST_CLASS_VERSION(cryptonote::rpc_payment::client_info, 0);

@ -0,0 +1,50 @@
// Copyright (c) 2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#define COST_PER_BLOCK 0.05
#define COST_PER_TX_RELAY 100
#define COST_PER_OUT 1
#define COST_PER_OUTPUT_INDEXES 1
#define COST_PER_TX 0.5
#define COST_PER_KEY_IMAGE 0.01
#define COST_PER_POOL_HASH 0.01
#define COST_PER_TX_POOL_STATS 0.2
#define COST_PER_BLOCK_HEADER 0.1
#define COST_PER_GET_INFO 1
#define COST_PER_OUTPUT_HISTOGRAM 25000
#define COST_PER_FULL_OUTPUT_HISTOGRAM 5000000
#define COST_PER_OUTPUT_DISTRIBUTION_0 20
#define COST_PER_OUTPUT_DISTRIBUTION 50000
#define COST_PER_COINBASE_TX_SUM_BLOCK 2
#define COST_PER_BLOCK_HASH 0.002
#define COST_PER_FEE_ESTIMATE 1
#define COST_PER_SYNC_INFO 2
#define COST_PER_HARD_FORK_INFO 1
#define COST_PER_PEER_LIST 2

@ -0,0 +1,107 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <inttypes.h>
#include <stdlib.h>
#include <chrono>
#include "include_base_utils.h"
#include "string_tools.h"
#include "rpc_payment_signature.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment"
#define TIMESTAMP_LEEWAY (60 * 1000000) /* 60 seconds, in microseconds */
namespace cryptonote
{
std::string make_rpc_payment_signature(const crypto::secret_key &skey)
{
std::string s;
crypto::public_key pkey;
crypto::secret_key_to_public_key(skey, pkey);
crypto::signature sig;
const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
char ts[17];
int ret = snprintf(ts, sizeof(ts), "%16.16" PRIx64, now);
CHECK_AND_ASSERT_MES(ret == 16, "", "snprintf failed");
ts[16] = 0;
CHECK_AND_ASSERT_MES(strlen(ts) == 16, "", "Invalid time conversion");
crypto::hash hash;
crypto::cn_fast_hash(ts, 16, hash);
crypto::generate_signature(hash, pkey, skey, sig);
s = epee::string_tools::pod_to_hex(pkey) + ts + epee::string_tools::pod_to_hex(sig);
return s;
}
bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts)
{
if (message.size() != 2 * sizeof(crypto::public_key) + 16 + 2 * sizeof(crypto::signature))
{
MDEBUG("Bad message size: " << message.size());
return false;
}
const std::string pkey_string = message.substr(0, 2 * sizeof(crypto::public_key));
const std::string ts_string = message.substr(2 * sizeof(crypto::public_key), 16);
const std::string signature_string = message.substr(2 * sizeof(crypto::public_key) + 16);
if (!epee::string_tools::hex_to_pod(pkey_string, pkey))
{
MDEBUG("Bad client id");
return false;
}
crypto::signature signature;
if (!epee::string_tools::hex_to_pod(signature_string, signature))
{
MDEBUG("Bad signature");
return false;
}
crypto::hash hash;
crypto::cn_fast_hash(ts_string.data(), 16, hash);
if (!crypto::check_signature(hash, pkey, signature))
{
MDEBUG("signature does not verify");
return false;
}
char *endptr = NULL;
errno = 0;
unsigned long long ull = strtoull(ts_string.c_str(), &endptr, 16);
if (ull == ULLONG_MAX && errno == ERANGE)
{
MDEBUG("bad timestamp");
return false;
}
ts = ull;
const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
if (ts > now + TIMESTAMP_LEEWAY)
{
MDEBUG("Timestamp is in the future");
return false;
}
return true;
}
}

@ -0,0 +1,39 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdint.h>
#include <string>
#include "crypto/crypto.h"
namespace cryptonote
{
std::string make_rpc_payment_signature(const crypto::secret_key &skey);
bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts);
}

@ -571,6 +571,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& in
INSERT_INTO_JSON_OBJECT(val, doc, ip, info.ip);
INSERT_INTO_JSON_OBJECT(val, doc, port, info.port);
INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, info.rpc_port);
INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, info.rpc_credits_per_hash);
INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id);
@ -607,6 +608,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& inf
GET_FROM_JSON_OBJECT(val, info.ip, ip);
GET_FROM_JSON_OBJECT(val, info.port, port);
GET_FROM_JSON_OBJECT(val, info.rpc_port, rpc_port);
GET_FROM_JSON_OBJECT(val, info.rpc_credits_per_hash, rpc_credits_per_hash);
GET_FROM_JSON_OBJECT(val, info.peer_id, peer_id);
@ -756,6 +758,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, ra
INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip);
INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port);
INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, peer.rpc_port);
INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, peer.rpc_credits_per_hash);
INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen);
INSERT_INTO_JSON_OBJECT(val, doc, pruning_seed, peer.pruning_seed);
}
@ -772,6 +775,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer)
GET_FROM_JSON_OBJECT(val, peer.ip, ip);
GET_FROM_JSON_OBJECT(val, peer.port, port);
GET_FROM_JSON_OBJECT(val, peer.rpc_port, rpc_port);
GET_FROM_JSON_OBJECT(val, peer.rpc_credits_per_hash, rpc_credits_per_hash);
GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen);
GET_FROM_JSON_OBJECT(val, peer.pruning_seed, pruning_seed);
}

@ -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);

@ -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,6 +451,16 @@ 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(); };

@ -37,6 +37,7 @@ set(wallet_sources
node_rpc_proxy.cpp
message_store.cpp
message_transporter.cpp
wallet_rpc_payments.cpp
)
set(wallet_private_headers
@ -49,7 +50,8 @@ set(wallet_private_headers
ringdb.h
node_rpc_proxy.h
message_store.h
message_transporter.h)
message_transporter.h
wallet_rpc_helpers.h)
monero_private_headers(wallet
${wallet_private_headers})
@ -58,6 +60,7 @@ monero_add_library(wallet
${wallet_private_headers})
target_link_libraries(wallet
PUBLIC
rpc_base
multisig
common
cryptonote_core
@ -116,6 +119,7 @@ if (BUILD_GUI_DEPS)
set(libs_to_merge
wallet_api
wallet
rpc_base
multisig
blockchain_db
cryptonote_core

@ -28,8 +28,22 @@
#include "node_rpc_proxy.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "rpc/rpc_payment_signature.h"
#include "rpc/rpc_payment_costs.h"
#include "storages/http_abstract_invoke.h"
#define RETURN_ON_RPC_RESPONSE_ERROR(r, error, res, method) \
do { \
CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); \
/* empty string -> not connection */ \
CHECK_AND_ASSERT_MES(!res.status.empty(), res.status, "No connection to daemon"); \
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, res.status, "Daemon busy"); \
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_PAYMENT_REQUIRED, res.status, "Payment required"); \
CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, res.status, "Error calling " + std::string(method) + " daemon RPC"); \
} while(0)
using namespace epee;
namespace tools
@ -37,8 +51,9 @@ namespace tools
static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex)
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex)
: m_http_client(http_client)
, m_rpc_payment_state(rpc_payment_state)
, m_daemon_rpc_mutex(mutex)
, m_offline(false)
{
@ -58,9 +73,13 @@ void NodeRPCProxy::invalidate()
m_target_height = 0;
m_block_weight_limit = 0;
m_get_info_time = 0;
m_rpc_payment_info_time = 0;
m_rpc_payment_seed_height = 0;
m_rpc_payment_seed_hash = crypto::null_hash;
m_rpc_payment_next_seed_hash = crypto::null_hash;
}
boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const
boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
{
if (m_offline)
return boost::optional<std::string>("offline");
@ -68,12 +87,11 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
{
cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version");
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
}
m_rpc_version = resp_t.version;
}
rpc_version = m_rpc_version;
@ -85,7 +103,7 @@ void NodeRPCProxy::set_height(uint64_t h)
m_height = h;
}
boost::optional<std::string> NodeRPCProxy::get_info() const
boost::optional<std::string> NodeRPCProxy::get_info()
{
if (m_offline)
return boost::optional<std::string>("offline");
@ -95,13 +113,15 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
uint64_t pre_call_credits = m_rpc_payment_state.credits;
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_info");
check_rpc_cost(m_rpc_payment_state, "get_info", resp_t.credits, pre_call_credits, COST_PER_GET_INFO);
}
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height");
m_height = resp_t.height;
m_target_height = resp_t.target_height;
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
@ -110,7 +130,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
{
auto res = get_info();
if (res)
@ -119,7 +139,7 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) const
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
{
auto res = get_info();
if (res)
@ -128,7 +148,7 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) c
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit) const
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit)
{
auto res = get_info();
if (res)
@ -137,7 +157,7 @@ boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &bloc
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) const
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height)
{
if (m_offline)
return boost::optional<std::string>("offline");
@ -145,14 +165,17 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
{
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
req_t.version = version;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
uint64_t pre_call_credits = m_rpc_payment_state.credits;
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status");
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "hard_fork_info");
check_rpc_cost(m_rpc_payment_state, "hard_fork_info", resp_t.credits, pre_call_credits, COST_PER_HARD_FORK_INFO);
}
m_earliest_height[version] = resp_t.earliest_height;
}
@ -160,7 +183,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee)
{
uint64_t height;
@ -174,14 +197,17 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
{
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
req_t.grace_blocks = grace_blocks;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
uint64_t pre_call_credits = m_rpc_payment_state.credits;
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
}
m_dynamic_base_fee_estimate = resp_t.fee;
m_dynamic_base_fee_estimate_cached_height = height;
m_dynamic_base_fee_estimate_grace_blocks = grace_blocks;
@ -192,7 +218,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) const
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask)
{
uint64_t height;
@ -206,14 +232,17 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
{
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
uint64_t pre_call_credits = m_rpc_payment_state.credits;
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
}
m_dynamic_base_fee_estimate = resp_t.fee;
m_dynamic_base_fee_estimate_cached_height = height;
m_fee_quantization_mask = resp_t.quantization_mask;
@ -228,4 +257,65 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
return boost::optional<std::string>();
}
boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
{
const time_t now = time(NULL);
if (m_rpc_payment_state.stale || now >= m_rpc_payment_info_time + 5*60 || (mining && now >= m_rpc_payment_info_time + 10)) // re-cache every 10 seconds if mining, 5 minutes otherwise
{
cryptonote::COMMAND_RPC_ACCESS_INFO::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_ACCESS_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_info", req_t, resp_t, m_http_client, rpc_timeout);
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "rpc_access_info");
m_rpc_payment_state.stale = false;
}
m_rpc_payment_diff = resp_t.diff;
m_rpc_payment_credits_per_hash_found = resp_t.credits_per_hash_found;
m_rpc_payment_height = resp_t.height;
m_rpc_payment_seed_height = resp_t.seed_height;
m_rpc_payment_cookie = resp_t.cookie;
if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43)
{
MERROR("Invalid hashing blob: " << resp_t.hashing_blob);
return std::string("Invalid hashing blob");
}
if (resp_t.seed_hash.empty())
{
m_rpc_payment_seed_hash = crypto::null_hash;
}
else if (!epee::string_tools::hex_to_pod(resp_t.seed_hash, m_rpc_payment_seed_hash))
{
MERROR("Invalid seed_hash: " << resp_t.seed_hash);
return std::string("Invalid seed hash");
}
if (resp_t.next_seed_hash.empty())
{
m_rpc_payment_next_seed_hash = crypto::null_hash;
}
else if (!epee::string_tools::hex_to_pod(resp_t.next_seed_hash, m_rpc_payment_next_seed_hash))
{
MERROR("Invalid next_seed_hash: " << resp_t.next_seed_hash);
return std::string("Invalid next seed hash");
}
m_rpc_payment_info_time = now;
}
payment_required = m_rpc_payment_diff > 0;
credits = m_rpc_payment_state.credits;
diff = m_rpc_payment_diff;
credits_per_hash_found = m_rpc_payment_credits_per_hash_found;
blob = m_rpc_payment_blob;
height = m_rpc_payment_height;
seed_height = m_rpc_payment_seed_height;
seed_hash = m_rpc_payment_seed_hash;
next_seed_hash = m_rpc_payment_next_seed_hash;
cookie = m_rpc_payment_cookie;
return boost::none;
}
}

@ -32,6 +32,8 @@
#include <boost/thread/mutex.hpp>
#include "include_base_utils.h"
#include "net/http_client.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "wallet_rpc_helpers.h"
namespace tools
{
@ -39,37 +41,62 @@ namespace tools
class NodeRPCProxy
{
public:
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex);
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex);
void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; }
void invalidate();
void set_offline(bool offline) { m_offline = offline; }
boost::optional<std::string> get_rpc_version(uint32_t &version) const;
boost::optional<std::string> get_height(uint64_t &height) const;
boost::optional<std::string> get_rpc_version(uint32_t &version);
boost::optional<std::string> get_height(uint64_t &height);
void set_height(uint64_t h);
boost::optional<std::string> get_target_height(uint64_t &height) const;
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit) const;
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height) const;
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const;
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask) const;
boost::optional<std::string> get_target_height(uint64_t &height);
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit);
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height);
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee);
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask);
boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
private:
boost::optional<std::string> get_info() const;
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
m_rpc_payment_state.credits = res.credits;
if (res.top_hash != m_rpc_payment_state.top_hash)
{
m_rpc_payment_state.top_hash = res.top_hash;
m_rpc_payment_state.stale = true;
}
}
template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
private:
boost::optional<std::string> get_info();
epee::net_utils::http::http_simple_client &m_http_client;
rpc_payment_state_t &m_rpc_payment_state;
boost::recursive_mutex &m_daemon_rpc_mutex;
crypto::secret_key m_client_id_secret_key;
bool m_offline;
mutable uint64_t m_height;
mutable uint64_t m_earliest_height[256];
mutable uint64_t m_dynamic_base_fee_estimate;
mutable uint64_t m_dynamic_base_fee_estimate_cached_height;
mutable uint64_t m_dynamic_base_fee_estimate_grace_blocks;
mutable uint64_t m_fee_quantization_mask;
mutable uint32_t m_rpc_version;
mutable uint64_t m_target_height;
mutable uint64_t m_block_weight_limit;
mutable time_t m_get_info_time;
uint64_t m_height;
uint64_t m_earliest_height[256];
uint64_t m_dynamic_base_fee_estimate;
uint64_t m_dynamic_base_fee_estimate_cached_height;
uint64_t m_dynamic_base_fee_estimate_grace_blocks;
uint64_t m_fee_quantization_mask;
uint32_t m_rpc_version;
uint64_t m_target_height;
uint64_t m_block_weight_limit;
time_t m_get_info_time;
time_t m_rpc_payment_info_time;
uint64_t m_rpc_payment_diff;
uint64_t m_rpc_payment_credits_per_hash_found;
cryptonote::blobdata m_rpc_payment_blob;
uint64_t m_rpc_payment_height;
uint64_t m_rpc_payment_seed_height;
crypto::hash m_rpc_payment_seed_hash;
crypto::hash m_rpc_payment_next_seed_hash;
uint32_t m_rpc_payment_cookie;
};
}

File diff suppressed because it is too large Load Diff

@ -64,10 +64,21 @@
#include "node_rpc_proxy.h"
#include "message_store.h"
#include "wallet_light_rpc.h"
#include "wallet_rpc_helpers.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
#define THROW_ON_RPC_RESPONSE_ERROR(r, error, res, method, ...) \
do { \
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
throw_on_rpc_response_error(r, error, res.status, method); \
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, ## __VA_ARGS__); \
} while(0)
#define THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, err, res, method) \
THROW_ON_RPC_RESPONSE_ERROR(r, err, res, method, tools::error::wallet_generic_rpc_error, method, res.status)
class Serialization_portability_wallet_Test;
class wallet_accessor_test;
@ -875,6 +886,8 @@ private:
uint64_t get_last_block_reward() const { return m_last_block_reward; }
uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
std::vector<cryptonote::public_node> get_public_nodes(bool white_only = true);
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{
@ -988,6 +1001,9 @@ private:
if(ver < 28)
return;
a & m_cold_key_images;
if(ver < 29)
return;
a & m_rpc_client_secret_key;
}
/*!
@ -1061,6 +1077,14 @@ private:
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
const ExportFormat & export_format() const { return m_export_format; }
inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; }
bool persistent_rpc_client_id() const { return m_persistent_rpc_client_id; }
void persistent_rpc_client_id(bool persistent) { m_persistent_rpc_client_id = persistent; }
void auto_mine_for_rpc_payment_threshold(float threshold) { m_auto_mine_for_rpc_payment_threshold = threshold; }
float auto_mine_for_rpc_payment_threshold() const { return m_auto_mine_for_rpc_payment_threshold; }
crypto::secret_key get_rpc_client_secret_key() const { return m_rpc_client_secret_key; }
void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); }
uint64_t credits_target() const { return m_credits_target; }
void credits_target(uint64_t threshold) { m_credits_target = threshold; }
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@ -1108,15 +1132,15 @@ private:
const transfer_details &get_transfer_details(size_t idx) const;
uint8_t get_current_hard_fork();
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const;
bool use_fork_rules(uint8_t version, int64_t early_blocks = 0) const;
int get_fee_algorithm() const;
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
bool use_fork_rules(uint8_t version, int64_t early_blocks = 0);
int get_fee_algorithm();
std::string get_wallet_file() const;
std::string get_keys_file() const;
std::string get_daemon_address() const;
const boost::optional<epee::net_utils::http::login>& get_daemon_login() const { return m_daemon_login; }
uint64_t get_daemon_blockchain_height(std::string& err) const;
uint64_t get_daemon_blockchain_height(std::string& err);
uint64_t get_daemon_blockchain_target_height(std::string& err);
/*!
* \brief Calculates the approximate blockchain height from current date/time.
@ -1211,21 +1235,37 @@ private:
uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31
bool is_synced() const;
bool is_synced();
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1) const;
uint64_t get_base_fee() const;
uint64_t get_fee_quantization_mask() const;
uint64_t get_min_ring_size() const;
uint64_t get_max_ring_size() const;
uint64_t adjust_mixin(uint64_t mixin) const;
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
uint64_t get_base_fee();
uint64_t get_fee_quantization_mask();
uint64_t get_min_ring_size();
uint64_t get_max_ring_size();
uint64_t adjust_mixin(uint64_t mixin);
uint32_t adjust_priority(uint32_t priority);
bool is_unattended() const { return m_unattended; }
bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
bool daemon_requires_payment();
bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance);
bool search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL);
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
m_rpc_payment_state.credits = res.credits;
if (res.top_hash != m_rpc_payment_state.top_hash)
{
m_rpc_payment_state.top_hash = res.top_hash;
m_rpc_payment_state.stale = true;
}
}
template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
// Light wallet specific functions
// fetch unspent outs from lw node and store in m_transfers
void light_wallet_get_unspent_outs();
@ -1338,6 +1378,9 @@ private:
void enable_dns(bool enable) { m_use_dns = enable; }
void set_offline(bool offline = true);
uint64_t credits() const { return m_rpc_payment_state.credits; }
void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; }
private:
/*!
* \brief Stores wallet information to wallet file.
@ -1363,7 +1406,7 @@ private:
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error);
void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception);
void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const;
bool prepare_file_names(const std::string& file_path);
@ -1379,9 +1422,9 @@ private:
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info) const;
void check_acc_out_precomp_once(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info, bool &already_seen) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
uint64_t get_upper_transaction_weight_limit() const;
std::vector<uint64_t> get_unspent_amounts_vector(bool strict) const;
uint64_t get_dynamic_base_fee_estimate() const;
uint64_t get_upper_transaction_weight_limit();
std::vector<uint64_t> get_unspent_amounts_vector(bool strict);
uint64_t get_dynamic_base_fee_estimate();
float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const;
std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const;
void set_spent(size_t idx, uint64_t height);
@ -1435,7 +1478,10 @@ private:
void on_device_progress(const hw::device_progress& event);
std::string get_rpc_status(const std::string &s) const;
void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
void throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const;
std::string get_client_signature() const;
void check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_credits, double expected_cost);
cryptonote::account_base m_account;
boost::optional<epee::net_utils::http::login> m_daemon_login;
@ -1516,6 +1562,8 @@ private:
bool m_track_uses;
uint32_t m_inactivity_lock_timeout;
BackgroundMiningSetupType m_setup_background_mining;
bool m_persistent_rpc_client_id;
float m_auto_mine_for_rpc_payment_threshold;
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
@ -1526,6 +1574,9 @@ private:
bool m_use_dns;
bool m_offline;
uint32_t m_rpc_version;
crypto::secret_key m_rpc_client_secret_key;
rpc_payment_state_t m_rpc_payment_state;
uint64_t m_credits_target;
// Aux transaction data from device
std::unordered_map<crypto::hash, std::string> m_tx_device;
@ -1569,7 +1620,7 @@ private:
ExportFormat m_export_format;
};
}
BOOST_CLASS_VERSION(tools::wallet2, 28)
BOOST_CLASS_VERSION(tools::wallet2, 29)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)

@ -76,6 +76,10 @@ namespace wallet_args
{
return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""};
}
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key()
{
return {"rpc-client-secret-key", wallet_args::tr("Set RPC client secret key for RPC payments"), ""};
}
const char* tr(const char* str)
{

@ -36,6 +36,7 @@ namespace wallet_args
{
command_line::arg_descriptor<std::string> arg_generate_from_json();
command_line::arg_descriptor<std::string> arg_wallet_file();
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key();
const char* tr(const char* str);

@ -90,6 +90,7 @@ namespace tools
// is_key_image_spent_error
// get_histogram_error
// get_output_distribution
// payment_required
// wallet_files_doesnt_correspond
//
// * - class with protected ctor
@ -781,6 +782,20 @@ namespace tools
const std::string m_status;
};
//----------------------------------------------------------------------------------------------------
struct wallet_coded_rpc_error : public wallet_rpc_error
{
explicit wallet_coded_rpc_error(std::string&& loc, const std::string& request, int code, const std::string& status)
: wallet_rpc_error(std::move(loc), std::string("error ") + std::to_string(code) + (" in ") + request + " RPC: " + status, request),
m_code(code), m_status(status)
{
}
int code() const { return m_code; }
const std::string& status() const { return m_status; }
private:
int m_code;
const std::string m_status;
};
//----------------------------------------------------------------------------------------------------
struct daemon_busy : public wallet_rpc_error
{
explicit daemon_busy(std::string&& loc, const std::string& request)
@ -821,6 +836,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
struct payment_required: public wallet_rpc_error
{
explicit payment_required(std::string&& loc, const std::string& request)
: wallet_rpc_error(std::move(loc), "payment required", request)
{
}
};
//----------------------------------------------------------------------------------------------------
struct wallet_files_doesnt_correspond : public wallet_logic_error
{
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)

@ -0,0 +1,94 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <type_traits>
namespace
{
// credits to yrp (https://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature
template <typename T>
struct HasCredits
{
template<typename U, uint64_t (U::*)> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::credits>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
}
namespace tools
{
struct rpc_payment_state_t
{
uint64_t credits;
uint64_t expected_spent;
uint64_t discrepancy;
std::string top_hash;
bool stale;
rpc_payment_state_t(): credits(0), expected_spent(0), discrepancy(0), stale(true) {}
};
static inline void check_rpc_cost(rpc_payment_state_t &rpc_payment_state, const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
{
uint64_t expected_credits = (uint64_t)expected_cost;
if (expected_credits == 0)
expected_credits = 1;
rpc_payment_state.credits = post_call_credits;
rpc_payment_state.expected_spent += expected_credits;
if (pre_call_credits < post_call_credits)
return;
uint64_t cost = pre_call_credits - post_call_credits;
if (cost == expected_credits)
{
MDEBUG("Call " << call << " cost " << cost << " credits");
return;
}
MWARNING("Call " << call << " cost " << cost << " credits, expected " << expected_credits);
if (cost > expected_credits)
{
uint64_t d = cost - expected_credits;
if (rpc_payment_state.discrepancy > std::numeric_limits<uint64_t>::max() - d)
{
MERROR("Integer overflow in credit discrepancy calculation, setting to max");
rpc_payment_state.discrepancy = std::numeric_limits<uint64_t>::max();
}
else
{
rpc_payment_state.discrepancy += d;
}
}
}
}

@ -0,0 +1,196 @@
// Copyright (c) 2018-2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <boost/optional/optional.hpp>
#include <boost/utility/value_init.hpp>
#include "include_base_utils.h"
#include "cryptonote_config.h"
#include "wallet_rpc_helpers.h"
#include "wallet2.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "rpc/rpc_payment_signature.h"
#include "misc_language.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "int-util.h"
#include "crypto/crypto.h"
#include "cryptonote_basic/blobdatatype.h"
#include "common/i18n.h"
#include "common/util.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2.rpc_payments"
#define RPC_PAYMENT_POLL_PERIOD 10 /* seconds*/
namespace tools
{
//----------------------------------------------------------------------------------------------------
std::string wallet2::get_client_signature() const
{
return cryptonote::make_rpc_payment_signature(m_rpc_client_secret_key);
}
//----------------------------------------------------------------------------------------------------
bool wallet2::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
{
boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_payment_info(mining, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie);
credits = m_rpc_payment_state.credits;
if (result && *result != CORE_RPC_STATUS_OK)
return false;
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::daemon_requires_payment()
{
bool payment_required = false;
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
uint32_t cookie;
cryptonote::blobdata blob;
crypto::hash seed_hash, next_seed_hash;
return get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance)
{
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::response res = AUTO_VAL_INIT(res);
req.nonce = nonce;
req.cookie = cookie;
m_daemon_rpc_mutex.lock();
uint64_t pre_call_credits = m_rpc_payment_state.credits;
req.client = get_client_signature();
epee::json_rpc::error error;
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, m_http_client, rpc_timeout);
m_daemon_rpc_mutex.unlock();
THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, error, res, "rpc_access_submit_nonce");
THROW_WALLET_EXCEPTION_IF(res.credits < pre_call_credits, error::wallet_internal_error, "RPC payment did not increase balance");
if (m_rpc_payment_state.top_hash != res.top_hash)
{
m_rpc_payment_state.top_hash = res.top_hash;
m_rpc_payment_state.stale = true;
}
m_rpc_payment_state.credits = res.credits;
balance = res.credits;
credits = balance - pre_call_credits;
return true;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc)
{
bool need_payment = false;
bool payment_required;
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
uint32_t cookie;
unsigned int n_hashes = 0;
cryptonote::blobdata hashing_blob;
crypto::hash seed_hash, next_seed_hash;
try
{
need_payment = 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 && credits < credits_target;
if (!need_payment)
return true;
if (!startfunc(diff, credits_per_hash_found))
return true;
}
catch (const std::exception &e) { return false; }
static std::atomic<uint32_t> nonce(0);
while (contfunc(n_hashes))
{
try
{
need_payment = get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target;
if (!need_payment)
return true;
}
catch (const std::exception &e) { return false; }
if (hashing_blob.empty())
{
MERROR("Bad hashing blob from daemon");
if (errorfunc)
errorfunc("Bad hashing blob from daemon, trying again");
epee::misc_utils::sleep_no_w(1000);
continue;
}
crypto::hash hash;
const uint32_t local_nonce = nonce++; // wrapping's OK
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce);
const uint8_t major_version = hashing_blob[0];
if (major_version >= RX_BLOCK_VERSION)
{
const int miners = 1;
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, miners, 0);
}
else
{
int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0;
crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, height);
}
++n_hashes;
if (cryptonote::check_hash(hash, diff))
{
uint64_t credits, balance;
try
{
make_rpc_payment(local_nonce, cookie, credits, balance);
if (credits != credits_per_hash_found)
{
MERROR("Found nonce, but daemon did not credit us with the expected amount");
if (errorfunc)
errorfunc("Found nonce, but daemon did not credit us with the expected amount");
return false;
}
MDEBUG("Found nonce " << local_nonce << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits");
if (!foundfunc(credits))
break;
}
catch (const tools::error::wallet_coded_rpc_error &e)
{
MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
if (errorfunc)
errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing");
}
catch (const std::exception &e)
{
MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
if (errorfunc)
errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing");
}
}
}
return true;
}
//----------------------------------------------------------------------------------------------------
void wallet2::check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
{
return tools::check_rpc_cost(m_rpc_payment_state, call, post_call_credits, pre_call_credits, expected_cost);
}
//----------------------------------------------------------------------------------------------------
}

@ -4323,6 +4323,7 @@ public:
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const auto arg_from_json = wallet_args::arg_generate_from_json();
const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
const auto wallet_file = command_line::get_arg(vm, arg_wallet_file);
const auto from_json = command_line::get_arg(vm, arg_from_json);
@ -4371,6 +4372,17 @@ public:
return false;
}
if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key))
{
crypto::secret_key client_secret_key;
if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), client_secret_key))
{
MERROR(arg_rpc_client_secret_key.name << ": RPC client secret key should be 32 byte in hex format");
return false;
}
wal->set_rpc_client_secret_key(client_secret_key);
}
bool quit = false;
tools::signal_handler::install([&wal, &quit](int) {
assert(wal);
@ -4469,6 +4481,7 @@ int main(int argc, char** argv) {
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const auto arg_from_json = wallet_args::arg_generate_from_json();
const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
po::options_description hidden_options("Hidden");
@ -4482,6 +4495,7 @@ int main(int argc, char** argv) {
command_line::add_arg(desc_params, arg_from_json);
command_line::add_arg(desc_params, arg_wallet_dir);
command_line::add_arg(desc_params, arg_prompt_for_password);
command_line::add_arg(desc_params, arg_rpc_client_secret_key);
daemonizer::init_options(hidden_options, desc_params);
desc_params.add(hidden_options);

@ -50,6 +50,20 @@ target_link_libraries(functional_tests
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
set(make_test_signature_sources
make_test_signature.cc)
add_executable(make_test_signature
${make_test_signature_sources})
target_link_libraries(make_test_signature
PRIVATE
rpc_base
cncrypto
epee
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
execute_process(COMMAND ${PYTHON_EXECUTABLE} "-c" "import requests; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE)
if (REQUESTS_OUTPUT STREQUAL "OK")
add_test(

@ -101,7 +101,7 @@ class BlockchainTest():
for n in range(blocks):
res_getblock.append(daemon.getblock(height = height + n))
block_header = res_getblock[n].block_header
assert abs(block_header.timestamp - time.time()) < 10 # within 10 seconds
assert abs(block_header.timestamp - time.time()) < 60 # within 60 seconds
assert block_header.height == height + n
assert block_header.orphan_status == False
assert block_header.depth == blocks - n - 1

@ -37,6 +37,7 @@ Test the following RPCs:
"""
from __future__ import print_function
import os
from framework.daemon import Daemon
class DaemonGetInfoTest():
@ -63,7 +64,7 @@ class DaemonGetInfoTest():
# difficulty should be set to 1 for this test
assert 'difficulty' in res.keys()
assert res.difficulty == 1;
assert res.difficulty == int(os.environ['DIFFICULTY'])
# nettype should not be TESTNET
assert 'testnet' in res.keys()

@ -10,7 +10,7 @@ import string
import os
USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]'
DEFAULT_TESTS = ['address_book', 'bans', 'blockchain', 'cold_signing', 'daemon_info', 'get_output_distribution', 'integrated_address', 'mining', 'multisig', 'proofs', 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet']
DEFAULT_TESTS = ['address_book', 'bans', 'blockchain', 'cold_signing', 'daemon_info', 'get_output_distribution', 'integrated_address', 'mining', 'multisig', 'proofs', 'rpc_payment', 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet']
try:
python = sys.argv[1]
srcdir = sys.argv[2]
@ -34,12 +34,19 @@ try:
except:
tests = DEFAULT_TESTS
N_MONERODS = 1
N_MONERODS = 2
N_WALLETS = 4
WALLET_DIRECTORY = builddir + "/functional-tests-directory"
DIFFICULTY = 10
monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
monerod_extra = [
[],
["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--data-dir", builddir + "/functional-tests-directory/monerod1"],
]
wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", WALLET_DIRECTORY, "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"]
wallet_extra = [
]
command_lines = []
processes = []
@ -48,11 +55,15 @@ ports = []
for i in range(N_MONERODS):
command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else x for x in monerod_base])
if i < len(monerod_extra):
command_lines[-1] += monerod_extra[i]
outputs.append(open(builddir + '/tests/functional_tests/monerod' + str(i) + '.log', 'a+'))
ports.append(18180+i)
for i in range(N_WALLETS):
command_lines.append([str(18090+i) if x == "wallet_port" else x for x in wallet_base])
if i < len(wallet_extra):
command_lines[-1] += wallet_extra[i]
outputs.append(open(builddir + '/tests/functional_tests/wallet' + str(i) + '.log', 'a+'))
ports.append(18090+i)
@ -65,6 +76,8 @@ try:
os.environ['PYTHONPATH'] = PYTHONPATH
os.environ['WALLET_DIRECTORY'] = WALLET_DIRECTORY
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['DIFFICULTY'] = str(DIFFICULTY)
os.environ['MAKE_TEST_SIGNATURE'] = builddir + '/tests/functional_tests/make_test_signature'
for i in range(len(command_lines)):
#print('Running: ' + str(command_lines[i]))
processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i]))

@ -0,0 +1,60 @@
// Copyright (c) 2019, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <stdio.h>
#include "string_tools.h"
#include "rpc/rpc_payment_signature.h"
int main(int argc, const char **argv)
{
if (argc > 2)
{
fprintf(stderr, "usage: %s <secret_key>\n", argv[0]);
return 1;
}
crypto::secret_key skey;
if (argc == 1)
{
crypto::public_key pkey;
crypto::random32_unbiased((unsigned char*)skey.data);
crypto::secret_key_to_public_key(skey, pkey);
printf("%s %s\n", epee::string_tools::pod_to_hex(skey).c_str(), epee::string_tools::pod_to_hex(pkey).c_str());
return 0;
}
if (!epee::string_tools::hex_to_pod(argv[1], skey))
{
fprintf(stderr, "invalid secret key\n");
return 1;
}
std::string signature = cryptonote::make_rpc_payment_signature(skey);
printf("%s\n", signature.c_str());
return 0;
}

@ -92,7 +92,7 @@ class MiningTest():
assert res_status.block_reward >= 600000000000
# wait till we mined a few of them
timeout = 5
timeout = 60 # randomx is slow to init
timeout_height = prev_height
while True:
time.sleep(1)

@ -0,0 +1,412 @@
#!/usr/bin/env python3
# Copyright (c) 2019 The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import print_function
import subprocess
import os
"""Test daemon RPC payment calls
"""
from framework.daemon import Daemon
from framework.wallet import Wallet
class RPCPaymentTest():
def run_test(self):
self.make_test_signature = os.environ['MAKE_TEST_SIGNATURE']
assert len(self.make_test_signature) > 0
self.secret_key, self.public_key = self.get_keys()
self.reset()
self.test_access_tracking()
self.test_access_mining()
self.test_access_payment()
self.test_access_account()
self.test_free_access()
def get_keys(self):
output = subprocess.check_output([self.make_test_signature]).rstrip()
fields = output.split()
assert len(fields) == 2
return fields
def get_signature(self):
return subprocess.check_output([self.make_test_signature, self.secret_key]).rstrip()
def reset(self):
print('Resetting blockchain')
daemon = Daemon(idx=1)
res = daemon.get_height()
daemon.pop_blocks(res.height - 1)
daemon.flush_txpool()
def test_access_tracking(self):
print('Testing access tracking')
daemon = Daemon(idx=1)
res = daemon.rpc_access_tracking(True)
res = daemon.rpc_access_tracking()
data = sorted(res.data, key = lambda k: k['rpc'])
assert len(data) == 1
entry = data[0]
assert entry.rpc == 'rpc_access_tracking'
assert entry.count == 1
assert entry.time >= 0
assert entry.credits == 0
daemon.get_connections()
res = daemon.rpc_access_tracking()
data = sorted(res.data, key = lambda k: k['rpc'])
assert len(data) == 2
entry = data[0]
assert entry.rpc == 'get_connections'
assert entry.count == 1
assert entry.time >= 0
assert entry.credits == 0
daemon.get_connections()
res = daemon.rpc_access_tracking()
data = sorted(res.data, key = lambda k: k['rpc'])
assert len(data) == 2
entry = data[0]
assert entry.rpc == 'get_connections'
assert entry.count == 2
assert entry.time >= 0
assert entry.credits == 0
daemon.get_alternate_chains()
res = daemon.rpc_access_tracking()
data = sorted(res.data, key = lambda k: k['rpc'])
assert len(data) == 3
entry = data[0]
assert entry.rpc == 'get_alternate_chains'
assert entry.count == 1
assert entry.time >= 0
assert entry.credits == 0
entry = res.data[1]
assert entry.rpc == 'get_connections'
assert entry.count == 2
assert entry.time >= 0
assert entry.credits == 0
res = daemon.rpc_access_tracking(True)
res = daemon.rpc_access_tracking()
data = sorted(res.data, key = lambda k: k['rpc'])
assert len(data) == 1
entry = data[0]
assert entry.rpc == 'rpc_access_tracking'
assert entry.count == 1
def test_access_mining(self):
print('Testing access mining')
daemon = Daemon(idx=1)
wallet = Wallet(idx=3)
res = daemon.rpc_access_info(client = self.get_signature())
assert len(res.hashing_blob) > 39
assert res.height == 1
assert res.top_hash == '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3'
assert res.credits_per_hash_found == 5000
assert res.diff == 10
assert res.credits == 0
cookie = res.cookie
# Try random nonces till we find one that's valid and one that's invalid
nonce = 0
found_valid = 0
found_invalid = 0
last_credits = 0
while found_valid == 0 or found_invalid == 0:
nonce += 1
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
found_valid += 1
assert res.credits == last_credits + 5000
except Exception as e:
found_invalid += 1
res = daemon.rpc_access_info(client = self.get_signature())
assert res.credits < last_credits or res.credits == 0
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
last_credits = res.credits
# we should now have 1 valid nonce, and a number of bad ones
res = daemon.rpc_access_info(client = self.get_signature())
assert len(res.hashing_blob) > 39
assert res.height > 1
assert res.top_hash != '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3' # here, any share matches network diff
assert res.credits_per_hash_found == 5000
assert res.diff == 10
cookie = res.cookie
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == 0
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 0
# Try random nonces till we find one that's valid so we get a load of credits
while last_credits == 0:
nonce += 1
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
found_valid += 1
last_credits = res.credits
break
except:
found_invalid += 1
assert nonce < 1000 # can't find a valid none -> the RPC probably fails
# we should now have at least 5000
res = daemon.rpc_access_info(client = self.get_signature())
assert res.credits == last_credits
assert res.credits >= 5000 # last one was a valid nonce
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == 0
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 0
assert e.balance == 5000
assert e.credits_total >= 5000
# find a valid one, then check dupes aren't allowed
res = daemon.rpc_access_info(client = self.get_signature())
cookie = res.cookie
old_cookie = cookie # we keep that so can submit a stale later
while True:
nonce += 1
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
found_valid += 1
break
except:
found_invalid += 1
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == 0
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 0
ok = False
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
except:
ok = True
assert ok
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == 0
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 1
# find stales without updating cookie, one within 5 seconds (accepted), one later (rejected)
res = daemon.rpc_access_info(client = self.get_signature())
found_close_stale = 0
found_late_stale = 0
while found_close_stale == 0 or found_late_stale == 0:
nonce += 1
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
found_close_stale += 1
found_valid += 1
except Exception as e:
if e[0]['error']['code'] == -18: # stale
found_late_stale += 1
else:
found_invalid += 1
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == found_late_stale # close stales are accepted, don't count here
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 1
# find very stale with old cookie (rejected)
res = daemon.rpc_access_info(client = self.get_signature())
nonce += 1
ok = False
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = old_cookie, client = self.get_signature())
except:
found_late_stale += 1
ok = True
assert ok
res = daemon.rpc_access_data()
assert len(res.entries) > 0
e = [x for x in res.entries if x['client'] == self.public_key]
assert len(e) == 1
e = e[0]
assert e.nonces_stale == found_late_stale
assert e.nonces_bad == found_invalid
assert e.nonces_good == found_valid
assert e.nonces_dupe == 1
def test_access_payment(self):
print('Testing access payment')
daemon = Daemon(idx=1)
wallet = Wallet(idx=3)
# Try random nonces till we find one that's valid so we get a load of credits
res = daemon.rpc_access_info(client = self.get_signature())
credits = res.credits
cookie = res.cookie
nonce = 0
while credits <= 100:
nonce += 1
try:
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
break
except:
pass
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
res = daemon.rpc_access_info(client = self.get_signature())
credits = res.credits
assert credits > 0
res = daemon.get_info(client = self.get_signature())
assert res.credits == credits - 1
credits = res.credits
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 100)
block_hashes = res.blocks
# ask for 1 block -> 1 credit
res = daemon.getblockheadersrange(0, 0, client = self.get_signature())
assert res.credits == credits - 1
credits = res.credits
# ask for 100 blocks -> >1 credit
res = daemon.getblockheadersrange(1, 100, client = self.get_signature())
assert res.credits < credits - 1
credits = res.credits
# external users
res = daemon.rpc_access_pay(payment = 1, paying_for = 'foo', client = self.get_signature())
assert res.credits == credits - 1
res = daemon.rpc_access_pay(payment = 4, paying_for = 'bar', client = self.get_signature())
assert res.credits == credits - 5
res = daemon.rpc_access_pay(payment = credits, paying_for = 'baz', client = self.get_signature())
assert "PAYMENT REQUIRED" in res.status
res = daemon.rpc_access_pay(payment = 2, paying_for = 'quux', client = self.get_signature())
assert res.credits == credits - 7
res = daemon.rpc_access_pay(payment = 3, paying_for = 'bar', client = self.get_signature())
assert res.credits == credits - 10
# that should be rejected because its cost is massive
ok = False
try: res = daemon.get_output_histogram(amounts = [], client = self.get_signature())
except Exception as e: print('e: ' + str(e)); ok = "PAYMENT REQUIRED" in e.status
assert ok or "PAYMENT REQUIRED" in res.status
def test_access_account(self):
print('Testing access account')
daemon = Daemon(idx=1)
wallet = Wallet(idx=3)
res = daemon.rpc_access_info(client = self.get_signature())
credits = res.credits
res = daemon.rpc_access_account(self.get_signature(), 0)
assert res.credits == credits
res = daemon.rpc_access_account(self.get_signature(), 50)
assert res.credits == credits + 50
res = daemon.rpc_access_account(self.get_signature(), -10)
assert res.credits == credits + 40
res = daemon.rpc_access_account(self.get_signature(), -(credits + 50))
assert res.credits == 0
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 5)
assert res.credits == 2**63 - 5
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 1)
assert res.credits == 2**64 - 6
res = daemon.rpc_access_account(self.get_signature(), 2)
assert res.credits == 2**64 - 4
res = daemon.rpc_access_account(self.get_signature(), 8)
assert res.credits == 2**64 - 1
res = daemon.rpc_access_account(self.get_signature(), -1)
assert res.credits == 2**64 - 2
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
assert res.credits == 2**64 - 2 -(2**63 - 1)
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
assert res.credits == 0
def test_free_access(self):
print('Testing free access')
daemon = Daemon(idx=0)
wallet = Wallet(idx=0)
res = daemon.rpc_access_info(client = self.get_signature())
assert res.credits_per_hash_found == 0
assert res.diff == 0
assert res.credits == 0
res = daemon.get_info(client = self.get_signature())
assert res.credits == 0
# any nonce will do here
res = daemon.rpc_access_submit_nonce(nonce = 0, cookie = 0, client = self.get_signature())
assert res.credits == 0
class Guard:
def __enter__(self):
for i in range(4):
Wallet(idx = i).auto_refresh(False)
def __exit__(self, exc_type, exc_value, traceback):
for i in range(4):
Wallet(idx = i).auto_refresh(True)
if __name__ == '__main__':
with Guard() as guard:
RPCPaymentTest().run_test()

@ -37,10 +37,11 @@ class Daemon(object):
self.port = port
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx))
def getblocktemplate(self, address, prev_block = ""):
def getblocktemplate(self, address, prev_block = "", client = ""):
getblocktemplate = {
'method': 'getblocktemplate',
'params': {
'client': client,
'wallet_address': address,
'reserve_size' : 1,
'prev_block' : prev_block,
@ -51,8 +52,9 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getblocktemplate)
get_block_template = getblocktemplate
def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True):
def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True, client = ""):
send_raw_transaction = {
'client': client,
'tx_as_hex': tx_as_hex,
'do_not_relay': do_not_relay,
'do_sanity_checks': do_sanity_checks,
@ -70,10 +72,11 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(submitblock)
submit_block = submitblock
def getblock(self, hash = '', height = 0, fill_pow_hash = False):
def getblock(self, hash = '', height = 0, fill_pow_hash = False, client = ""):
getblock = {
'method': 'getblock',
'params': {
'client': client,
'hash': hash,
'height': height,
'fill_pow_hash': fill_pow_hash,
@ -84,10 +87,11 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getblock)
get_block = getblock
def getlastblockheader(self):
def getlastblockheader(self, client = ""):
getlastblockheader = {
'method': 'getlastblockheader',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
@ -95,10 +99,11 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getlastblockheader)
get_last_block_header = getlastblockheader
def getblockheaderbyhash(self, hash = "", hashes = []):
def getblockheaderbyhash(self, hash = "", hashes = [], client = ""):
getblockheaderbyhash = {
'method': 'getblockheaderbyhash',
'params': {
'client': client,
'hash': hash,
'hashes': hashes,
},
@ -108,10 +113,11 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getblockheaderbyhash)
get_block_header_by_hash = getblockheaderbyhash
def getblockheaderbyheight(self, height):
def getblockheaderbyheight(self, height, client = ""):
getblockheaderbyheight = {
'method': 'getblockheaderbyheight',
'params': {
'client': client,
'height': height,
},
'jsonrpc': '2.0',
@ -120,10 +126,11 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getblockheaderbyheight)
get_block_header_by_height = getblockheaderbyheight
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False):
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False, client = ""):
getblockheadersrange = {
'method': 'getblockheadersrange',
'params': {
'client': client,
'start_height': start_height,
'end_height': end_height,
'fill_pow_hash': fill_pow_hash,
@ -134,26 +141,33 @@ class Daemon(object):
return self.rpc.send_json_rpc_request(getblockheadersrange)
get_block_headers_range = getblockheadersrange
def get_connections(self):
def get_connections(self, client = ""):
get_connections = {
'client': client,
'method': 'get_connections',
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_connections)
def get_info(self):
def get_info(self, client = ""):
get_info = {
'method': 'get_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(get_info)
getinfo = get_info
def hard_fork_info(self):
def hard_fork_info(self, client = ""):
hard_fork_info = {
'method': 'hard_fork_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
@ -174,7 +188,7 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(generateblocks)
def get_height(self):
def get_height(self, client = ""):
get_height = {
'method': 'get_height',
'jsonrpc': '2.0',
@ -208,18 +222,21 @@ class Daemon(object):
}
return self.rpc.send_request('/mining_status', mining_status)
def get_transaction_pool(self):
def get_transaction_pool(self, client = ""):
get_transaction_pool = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
def get_transaction_pool_hashes(self):
def get_transaction_pool_hashes(self, client = ""):
get_transaction_pool_hashes = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
def get_transaction_pool_stats(self):
def get_transaction_pool_stats(self, client = ""):
get_transaction_pool_stats = {
'client': client,
}
return self.rpc.send_request('/get_transaction_pool_stats', get_transaction_pool_stats)
@ -263,8 +280,9 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(set_bans)
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False):
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False, client = ""):
get_transactions = {
'client': client,
'txs_hashes': txs_hashes,
'decode_as_json': decode_as_json,
'prune': prune,
@ -273,17 +291,19 @@ class Daemon(object):
return self.rpc.send_request('/get_transactions', get_transactions)
gettransactions = get_transactions
def get_outs(self, outputs = [], get_txid = False):
def get_outs(self, outputs = [], get_txid = False, client = ""):
get_outs = {
'client': client,
'outputs': outputs,
'get_txid': get_txid,
}
return self.rpc.send_request('/get_outs', get_outs)
def get_coinbase_tx_sum(self, height, count):
def get_coinbase_tx_sum(self, height, count, client = ""):
get_coinbase_tx_sum = {
'method': 'get_coinbase_tx_sum',
'params': {
'client': client,
'height': height,
'count': count,
},
@ -292,10 +312,11 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(get_coinbase_tx_sum)
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False):
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False, client = ""):
get_output_distribution = {
'method': 'get_output_distribution',
'params': {
'client': client,
'amounts': amounts,
'from_height': from_height,
'to_height': to_height,
@ -308,10 +329,11 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(get_output_distribution)
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0):
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0, client = ""):
get_output_histogram = {
'method': 'get_output_histogram',
'params': {
'client': client,
'amounts': amounts,
'min_count': min_count,
'max_count': max_count,
@ -335,15 +357,17 @@ class Daemon(object):
}
return self.rpc.send_request('/set_log_categories', set_log_categories)
def get_alt_blocks_hashes(self):
def get_alt_blocks_hashes(self, client = ""):
get_alt_blocks_hashes = {
'client': client,
}
return self.rpc.send_request('/get_alt_blocks_hashes', get_alt_blocks_hashes)
def get_alternate_chains(self):
def get_alternate_chains(self, client = ""):
get_alternate_chains = {
'method': 'get_alternate_chains',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
@ -361,9 +385,10 @@ class Daemon(object):
}
return self.rpc.send_json_rpc_request(get_fee_estimate)
def is_key_image_spent(self, key_images = []):
def is_key_image_spent(self, key_images = [], client = ""):
is_key_image_spent = {
'key_images': key_images,
'client': client,
}
return self.rpc.send_request('/is_key_image_spent', is_key_image_spent)
@ -413,7 +438,7 @@ class Daemon(object):
def in_peers(self, in_peers):
in_peers = {
'in_peers': in_peers,
'client': client,
}
return self.rpc.send_request('/in_peers', in_peers)
@ -446,31 +471,34 @@ class Daemon(object):
on_get_block_hash = get_block_hash
on_getblockhash = get_block_hash
def relay_tx(self, txids = []):
def relay_tx(self, txids = [], client = ""):
relay_tx = {
'method': 'relay_tx',
'params': {
'txids': txids,
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(relay_tx)
def sync_info(self):
def sync_info(self, client = ""):
sync_info = {
'method': 'sync_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(sync_info)
def get_txpool_backlog(self):
def get_txpool_backlog(self, client = ""):
get_txpool_backlog = {
'method': 'get_txpool_backlog',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
@ -498,3 +526,73 @@ class Daemon(object):
'id': '0'
}
return self.rpc.send_json_rpc_request(get_block_rate)
def rpc_access_info(self, client):
rpc_access_info = {
'method': 'rpc_access_info',
'params': {
'client': client,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_info)
def rpc_access_submit_nonce(self, client, nonce, cookie):
rpc_access_submit_nonce = {
'method': 'rpc_access_submit_nonce',
'params': {
'client': client,
'nonce': nonce,
'cookie': cookie,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_submit_nonce)
def rpc_access_pay(self, client, paying_for, payment):
rpc_access_pay = {
'method': 'rpc_access_pay',
'params': {
'client': client,
'paying_for': paying_for,
'payment': payment,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_pay)
def rpc_access_tracking(self, clear = False):
rpc_access_tracking = {
'method': 'rpc_access_tracking',
'params': {
'clear': clear,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_tracking)
def rpc_access_data(self):
rpc_access_data = {
'method': 'rpc_access_data',
'params': {
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_data)
def rpc_access_account(self, client, delta_balance = 0):
rpc_access_account = {
'method': 'rpc_access_account',
'params': {
'client': client,
'delta_balance': delta_balance,
},
'jsonrpc': '2.0',
'id': '0'
}
return self.rpc.send_json_rpc_request(rpc_access_account)

Loading…
Cancel
Save