diff options
55 files changed, 4245 insertions, 840 deletions
diff --git a/contrib/epee/include/int-util.h b/contrib/epee/include/int-util.h index 939c018ea..dcd58a8c4 100644 --- a/contrib/epee/include/int-util.h +++ b/contrib/epee/include/int-util.h @@ -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)) diff --git a/contrib/epee/include/math_helper.h b/contrib/epee/include/math_helper.h index ddc1f7f4b..604a04680 100644 --- a/contrib/epee/include/math_helper.h +++ b/contrib/epee/include/math_helper.h @@ -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) { diff --git a/contrib/epee/include/serialization/keyvalue_serialization.h b/contrib/epee/include/serialization/keyvalue_serialization.h index 5459c8409..78d294d05 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization.h +++ b/contrib/epee/include/serialization/keyvalue_serialization.h @@ -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; } diff --git a/contrib/epee/include/storages/http_abstract_invoke.h b/contrib/epee/include/storages/http_abstract_invoke.h index 18b7f10c1..78e02c93a 100644 --- a/contrib/epee/include/storages/http_abstract_invoke.h +++ b/contrib/epee/include/storages/http_abstract_invoke.h @@ -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") { diff --git a/contrib/epee/src/mlog.cpp b/contrib/epee/src/mlog.cpp index 66dfabcdf..e96bf627f 100644 --- a/contrib/epee/src/mlog.cpp +++ b/contrib/epee/src/mlog.cpp @@ -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"; diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index 717391623..ea2237348 100644 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -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() } diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 5e8f6685d..a682bebf2 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -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 }; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 86e6c99d1..ca127c3ee 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -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 { diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index dcd108626..201001c8e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -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) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 35b2d2a39..74ceeb41d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -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; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index d47823735..8e78f3bd1 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -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; diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 25587dea8..5c94c540e 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -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); diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index e44ce9137..b00e253c7 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -296,6 +296,11 @@ t_command_server::t_command_server( , "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) , "Print version information." diff --git a/src/daemon/core.h b/src/daemon/core.h index 91dbb7a4b..9a3579e20 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -59,16 +59,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..."); #if defined(PER_BLOCK_CHECKPOINT) @@ -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; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 273d78619..552862d2f 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -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 diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index af7081ef3..783b47373 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -167,6 +167,8 @@ public: const std::string &address, const std::string &username, const std::string &password); + + bool rpc_payments(); }; } // namespace daemonize diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 41aa3e9cb..7ad09dbef 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -64,6 +64,9 @@ const hardfork_t mainnet_hard_forks[] = { // version 11 starts from block 1788720, which is on or around the 10th of March, 2019. Fork time finalised on 2019-02-15. { 11, 1788720, 0, 1550225678 }, + + // version 12 starts from block 1978433, which is on or around the 30th of November, 2019. Fork time finalised on 2019-10-18. + { 12, 1978433, 0, 1571419280 }, }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -106,5 +109,6 @@ const hardfork_t stagenet_hard_forks[] = { { 9, 177176, 0, 1537821771 }, { 10, 269000, 0, 1550153694 }, { 11, 269720, 0, 1550225678 }, + { 12, 454721, 0, 1571419280 }, }; const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 0c9c285e8..29db5403e 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -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; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 9150ebb1b..a5921b8bf 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -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)); } } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index c65b9dd82..58b704f73 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -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); } diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index c2773981c..bd5063510 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -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> diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 85774fcd5..44b278589 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -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() diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 116e7f568..ebb1e767f 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -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) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index dfc4d4cf3..8882df5d7 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -39,6 +39,7 @@ using namespace epee; #include "common/download.h" #include "common/util.h" #include "common/perf_timer.h" +#include "int-util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -49,6 +50,8 @@ using namespace epee; #include "crypto/hash.h" #include "rpc/rpc_args.h" #include "rpc/rpc_handler.h" +#include "rpc/rpc_payment_costs.h" +#include "rpc/rpc_payment_signature.h" #include "core_rpc_server_error_codes.h" #include "p2p/net_node.h" #include "version.h" @@ -61,8 +64,50 @@ using namespace epee; #define OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION (3 * 86400) // 3 days max, the wallet requests 1.8 days +#define DEFAULT_PAYMENT_DIFFICULTY 1000 +#define DEFAULT_PAYMENT_CREDITS_PER_HASH 100 + +#define RPC_TRACKER(rpc) \ + PERF_TIMER(rpc); \ + RPCTracker tracker(#rpc, PERF_TIMER_NAME(rpc)) + namespace { + class RPCTracker + { + public: + struct entry_t + { + uint64_t count; + uint64_t time; + uint64_t credits; + }; + + RPCTracker(const char *rpc, tools::LoggingPerformanceTimer &timer): rpc(rpc), timer(timer) { + } + ~RPCTracker() { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + ++e.count; + e.time += timer.value(); + } + void pay(uint64_t amount) { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + e.credits += amount; + } + const std::string &rpc_name() const { return rpc; } + static void clear() { boost::unique_lock<boost::mutex> lock(mutex); tracker.clear(); } + static std::unordered_map<std::string, entry_t> data() { boost::unique_lock<boost::mutex> lock(mutex); return tracker; } + private: + std::string rpc; + tools::LoggingPerformanceTimer &timer; + static boost::mutex mutex; + static std::unordered_map<std::string, entry_t> tracker; + }; + boost::mutex RPCTracker::mutex; + std::unordered_map<std::string, RPCTracker::entry_t> RPCTracker::tracker; + void add_reason(std::string &reasons, const char *reason) { if (!reasons.empty()) @@ -95,6 +140,9 @@ namespace cryptonote command_line::add_arg(desc, arg_bootstrap_daemon_address); command_line::add_arg(desc, arg_bootstrap_daemon_login); cryptonote::rpc_args::init_options(desc, true); + command_line::add_arg(desc, arg_rpc_payment_address); + command_line::add_arg(desc, arg_rpc_payment_difficulty); + command_line::add_arg(desc, arg_rpc_payment_credits); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -173,6 +221,11 @@ namespace cryptonote return true; } + core_rpc_server::~core_rpc_server() + { + if (m_rpc_payment) + m_rpc_payment->store(); + } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::init( const boost::program_options::variables_map& vm @@ -188,6 +241,45 @@ namespace cryptonote if (!rpc_config) return false; + std::string address = command_line::get_arg(vm, arg_rpc_payment_address); + if (!address.empty()) + { + if (!m_restricted && nettype() != FAKECHAIN) + { + MERROR("RPC payment enabled, but server is not restricted, anyone can adjust their balance to bypass payment"); + return false; + } + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, nettype(), address)) + { + MERROR("Invalid payment address: " << address); + return false; + } + if (info.is_subaddress) + { + MERROR("Payment address may not be a subaddress: " << address); + return false; + } + uint64_t diff = command_line::get_arg(vm, arg_rpc_payment_difficulty); + uint64_t credits = command_line::get_arg(vm, arg_rpc_payment_credits); + if (diff == 0 || credits == 0) + { + MERROR("Payments difficulty and/or payments credits are 0, but a payment address was given"); + return false; + } + m_rpc_payment.reset(new rpc_payment(info.address, diff, credits)); + m_rpc_payment->load(command_line::get_arg(vm, cryptonote::arg_data_dir)); + m_p2p.set_rpc_credits_per_hash(RPC_CREDITS_PER_HASH_SCALE * (credits / (float)diff)); + } + + if (!m_rpc_payment) + { + uint32_t bind_ip; + bool ok = epee::string_tools::get_ip_int32_from_string(bind_ip, rpc_config->bind_ip); + if (ok & !epee::net_utils::is_ip_loopback(bind_ip)) + MWARNING("The RPC server is accessible from the outside, but no RPC payment was setup. RPC access will be free for all."); + } + if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), command_line::get_arg(vm, arg_bootstrap_daemon_login))) { @@ -200,6 +292,9 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); + if (m_rpc_payment) + m_net_server.add_idle_handler([this](){ return m_rpc_payment->on_idle(); }, 60 * 1000); + auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( rng, std::move(port), std::move(rpc_config->bind_ip), @@ -208,6 +303,45 @@ namespace cryptonote ); } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::check_payment(const std::string &client_message, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash) + { + if (m_rpc_payment == NULL) + { + credits = 0; + return true; + } + uint64_t height; + crypto::hash hash; + m_core.get_blockchain_top(height, hash); + top_hash = epee::string_tools::pod_to_hex(hash); + crypto::public_key client; + uint64_t ts; +#ifndef NDEBUG + if (nettype() == TESTNET && client_message == "debug") + { + credits = 0; + return true; + } +#endif + if (!cryptonote::verify_rpc_payment_signature(client_message, client, ts)) + { + credits = 0; + message = "Client signature does not verify for " + rpc; + return false; + } + crypto::public_key local_client; + if (!m_rpc_payment->pay(client, ts, payment, rpc, same_ts, credits)) + { + message = CORE_RPC_STATUS_PAYMENT_REQUIRED; + return false; + } + return true; + } +#define CHECK_PAYMENT_BASE(req, res, payment, same_ts) do { if (!ctx) break; uint64_t P = (uint64_t)payment; if (P > 0 && !check_payment(req.client, P, tracker.rpc_name(), same_ts, res.status, res.credits, res.top_hash)){return true;} tracker.pay(P); } while(0) +#define CHECK_PAYMENT(req, res, payment) CHECK_PAYMENT_BASE(req, res, payment, false) +#define CHECK_PAYMENT_SAME_TS(req, res, payment) CHECK_PAYMENT_BASE(req, res, payment, true) +#define CHECK_PAYMENT_MIN1(req, res, payment, same_ts) do { if (!ctx) break; uint64_t P = (uint64_t)payment; if (P == 0) P = 1; if(!check_payment(req.client, P, tracker.rpc_name(), same_ts, res.status, res.credits, res.top_hash)){return true;} tracker.pay(P); } while(0) + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() { if(!m_p2p.get_payload_object().is_synchronized()) @@ -239,7 +373,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_height); + RPC_TRACKER(get_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HEIGHT>(invoke_http_mode::JON, "/getheight", req, res, r)) return r; @@ -254,7 +388,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_info); + RPC_TRACKER(get_info); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_INFO>(invoke_http_mode::JON, "/getinfo", req, res, r)) { @@ -272,6 +406,8 @@ namespace cryptonote return r; } + CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false); + const bool restricted = m_restricted && ctx; crypto::hash top_hash; @@ -330,7 +466,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_net_stats); + RPC_TRACKER(get_net_stats); // No bootstrap daemon check: Only ever get stats about local server res.start_time = (uint64_t)m_core.get_start_time(); { @@ -357,20 +493,51 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_blocks); + RPC_TRACKER(get_blocks); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCKS_FAST>(invoke_http_mode::BIN, "/getblocks.bin", req, res, r)) return r; - std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; + CHECK_PAYMENT(req, res, 1); + + // quick check for noop + if (!req.block_ids.empty()) + { + uint64_t last_block_height; + crypto::hash last_block_hash; + m_core.get_blockchain_top(last_block_height, last_block_hash); + if (last_block_hash == req.block_ids.front()) + { + res.start_height = 0; + res.current_height = m_core.get_current_blockchain_height(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + } - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + size_t max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT; + if (m_rpc_payment) + { + max_blocks = res.credits / COST_PER_BLOCK; + if (max_blocks > COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT) + max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT; + if (max_blocks == 0) + { + res.status = CORE_RPC_STATUS_PAYMENT_REQUIRED; + return false; + } + } + + std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, max_blocks)) { res.status = "Failed"; add_host_fail(ctx); return false; } + CHECK_PAYMENT_SAME_TS(req, res, bs.size() * COST_PER_BLOCK); + size_t pruned_size = 0, unpruned_size = 0, ntxes = 0; res.blocks.reserve(bs.size()); res.output_indices.reserve(bs.size()); @@ -422,7 +589,7 @@ namespace cryptonote } bool core_rpc_server::on_get_alt_blocks_hashes(const COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request& req, COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_alt_blocks_hashes); + RPC_TRACKER(get_alt_blocks_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_ALT_BLOCKS_HASHES>(invoke_http_mode::JON, "/get_alt_blocks_hashes", req, res, r)) return r; @@ -449,7 +616,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_blocks_by_height); + RPC_TRACKER(get_blocks_by_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCKS_BY_HEIGHT>(invoke_http_mode::BIN, "/getblocks_by_height.bin", req, res, r)) return r; @@ -457,6 +624,7 @@ namespace cryptonote res.status = "Failed"; res.blocks.clear(); res.blocks.reserve(req.heights.size()); + CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false); for (uint64_t height : req.heights) { block blk; @@ -483,11 +651,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_hashes); + RPC_TRACKER(get_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HASHES_FAST>(invoke_http_mode::BIN, "/gethashes.bin", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + res.start_height = req.start_height; if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, NULL, res.start_height, res.current_height, false)) { @@ -496,17 +666,21 @@ namespace cryptonote return false; } + CHECK_PAYMENT_SAME_TS(req, res, res.m_block_ids.size() * COST_PER_BLOCK_HASH); + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_outs_bin); + RPC_TRACKER(get_outs_bin); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUTS_BIN>(invoke_http_mode::BIN, "/get_outs.bin", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, req.outputs.size() * COST_PER_OUT, false); + res.status = "Failed"; const bool restricted = m_restricted && ctx; @@ -530,11 +704,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_outs); + RPC_TRACKER(get_outs); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUTS>(invoke_http_mode::JON, "/get_outs", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, req.outputs.size() * COST_PER_OUT, false); + res.status = "Failed"; const bool restricted = m_restricted && ctx; @@ -574,11 +750,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_indexes); + RPC_TRACKER(get_indexes); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES>(invoke_http_mode::BIN, "/get_o_indexes.bin", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, COST_PER_OUTPUT_INDEXES, false); + bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) { @@ -592,11 +770,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transactions); + RPC_TRACKER(get_transactions); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTIONS>(invoke_http_mode::JON, "/gettransactions", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, req.txs_hashes.size() * COST_PER_TX, false); + std::vector<crypto::hash> vh; for(const auto& tx_hex_str: req.txs_hashes) { @@ -818,13 +998,16 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, const connection_context *ctx) { - PERF_TIMER(on_is_key_image_spent); + RPC_TRACKER(is_key_image_spent); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_IS_KEY_IMAGE_SPENT>(invoke_http_mode::JON, "/is_key_image_spent", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, req.key_images.size() * COST_PER_KEY_IMAGE, false); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; + std::vector<crypto::key_image> key_images; for(const auto& ki_hex_str: req.key_images) { @@ -887,12 +1070,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res, const connection_context *ctx) { - PERF_TIMER(on_send_raw_tx); + RPC_TRACKER(send_raw_tx); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_SEND_RAW_TX>(invoke_http_mode::JON, "/sendrawtransaction", req, res, ok)) return ok; CHECK_CORE_READY(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_RELAY, false); std::string tx_blob; if(!string_tools::parse_hexstr_to_binbuff(req.tx_as_hex, tx_blob)) @@ -966,7 +1150,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res, const connection_context *ctx) { - PERF_TIMER(on_start_mining); + RPC_TRACKER(start_mining); CHECK_CORE_READY(); cryptonote::address_parse_info info; if(!get_account_address_from_str(info, nettype(), req.miner_address)) @@ -1017,7 +1201,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res, const connection_context *ctx) { - PERF_TIMER(on_stop_mining); + RPC_TRACKER(stop_mining); cryptonote::miner &miner= m_core.get_miner(); if(!miner.is_mining()) { @@ -1037,7 +1221,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res, const connection_context *ctx) { - PERF_TIMER(on_mining_status); + RPC_TRACKER(mining_status); const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); @@ -1078,7 +1262,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx) { - PERF_TIMER(on_save_bc); + RPC_TRACKER(save_bc); if( !m_core.get_blockchain_storage().store_blockchain() ) { res.status = "Error while storing blockchain"; @@ -1090,7 +1274,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_peer_list); + RPC_TRACKER(get_peer_list); std::vector<nodetool::peerlist_entry> white_list; std::vector<nodetool::peerlist_entry> gray_list; @@ -1107,24 +1291,24 @@ namespace cryptonote { if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), - entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), - entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else - res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); } for (auto & entry : gray_list) { if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), - entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), - entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else - res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); } res.status = CORE_RPC_STATUS_OK; @@ -1133,7 +1317,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_public_nodes); + RPC_TRACKER(get_public_nodes); COMMAND_RPC_GET_PEER_LIST::response peer_list_res; const bool success = on_get_peer_list(COMMAND_RPC_GET_PEER_LIST::request(), peer_list_res, ctx); @@ -1172,7 +1356,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_hash_rate); + RPC_TRACKER(set_log_hash_rate); if(m_core.get_miner().is_mining()) { m_core.get_miner().do_print_hashrate(req.visible); @@ -1187,7 +1371,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_level); + RPC_TRACKER(set_log_level); if (req.level < 0 || req.level > 4) { res.status = "Error: log level not valid"; @@ -1200,7 +1384,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_categories); + RPC_TRACKER(set_log_categories); mlog_set_log(req.categories.c_str()); res.categories = mlog_get_categories(); res.status = CORE_RPC_STATUS_OK; @@ -1209,62 +1393,92 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool); + RPC_TRACKER(get_transaction_pool); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL>(invoke_http_mode::JON, "/get_transaction_pool", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !restricted); - for (tx_info& txi : res.transactions) - txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_TX); + m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !restricted); + for (tx_info& txi : res.transactions) + txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_hashes_bin(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_hashes); + RPC_TRACKER(get_transaction_pool_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN>(invoke_http_mode::JON, "/get_transaction_pool_hashes.bin", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !restricted); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_POOL_HASH); + m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !restricted); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_hashes); + RPC_TRACKER(get_transaction_pool_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_HASHES>(invoke_http_mode::JON, "/get_transaction_pool_hashes", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - std::vector<crypto::hash> tx_hashes; - m_core.get_pool_transaction_hashes(tx_hashes, !request_has_rpc_origin || !restricted); - res.tx_hashes.reserve(tx_hashes.size()); - for (const crypto::hash &tx_hash: tx_hashes) - res.tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_POOL_HASH); + std::vector<crypto::hash> tx_hashes; + m_core.get_pool_transaction_hashes(tx_hashes, !request_has_rpc_origin || !restricted); + res.tx_hashes.reserve(tx_hashes.size()); + for (const crypto::hash &tx_hash: tx_hashes) + res.tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_stats); + RPC_TRACKER(get_transaction_pool_stats); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_STATS>(invoke_http_mode::JON, "/get_transaction_pool_stats", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS, false); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; m_core.get_pool_transaction_stats(res.pool_stats, !request_has_rpc_origin || !restricted); + res.status = CORE_RPC_STATUS_OK; return true; } @@ -1293,7 +1507,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res, const connection_context *ctx) { - PERF_TIMER(on_stop_daemon); + RPC_TRACKER(stop_daemon); // FIXME: replace back to original m_p2p.send_stop_signal() after // investigating why that isn't working quite right. m_p2p.send_stop_signal(); @@ -1303,7 +1517,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx) { - PERF_TIMER(on_getblockcount); + RPC_TRACKER(getblockcount); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1319,7 +1533,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_getblockhash); + RPC_TRACKER(getblockhash); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1361,9 +1575,65 @@ namespace cryptonote return 0; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + b = boost::value_initialized<cryptonote::block>(); + if(!m_core.get_block_template(b, prev_block, address, difficulty, height, expected_reward, extra_nonce)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to create block template"); + return false; + } + blobdata block_blob = t_serializable_object_to_blob(b); + crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); + if(tx_pub_key == crypto::null_pkey) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to get tx pub key in coinbase extra"); + return false; + } + + if (b.major_version >= RX_BLOCK_VERSION) + { + uint64_t next_height; + crypto::rx_seedheights(height, &seed_height, &next_height); + seed_hash = m_core.get_block_id_by_height(seed_height); + if (next_height != seed_height) + next_seed_hash = m_core.get_block_id_by_height(next_height); + else + next_seed_hash = seed_hash; + } + + if (extra_nonce.empty()) + { + reserved_offset = 0; + return true; + } + + reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key)); + if(!reserved_offset) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to find tx pub key in blockblob"); + return false; + } + reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + if(reserved_offset + extra_nonce.size() > block_blob.size()) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to calculate offset for "); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_getblocktemplate); + RPC_TRACKER(getblocktemplate); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GETBLOCKTEMPLATE>(invoke_http_mode::JON_RPC, "getblocktemplate", req, res, r)) return r; @@ -1413,6 +1683,7 @@ namespace cryptonote block b; cryptonote::blobdata blob_reserve; + size_t reserved_offset; if(!req.extra_nonce.empty()) { if(!string_tools::parse_hexstr_to_binbuff(req.extra_nonce, blob_reserve)) @@ -1435,54 +1706,20 @@ namespace cryptonote return false; } } - if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to create block template"); + uint64_t seed_height; + crypto::hash seed_hash, next_seed_hash; + if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp)) return false; - } if (b.major_version >= RX_BLOCK_VERSION) { - uint64_t seed_height, next_height; - crypto::hash seed_hash; - crypto::rx_seedheights(res.height, &seed_height, &next_height); - seed_hash = m_core.get_block_id_by_height(seed_height); res.seed_hash = string_tools::pod_to_hex(seed_hash); - if (next_height != seed_height) { - seed_hash = m_core.get_block_id_by_height(next_height); - res.next_seed_hash = string_tools::pod_to_hex(seed_hash); - } + if (seed_hash != next_seed_hash) + res.next_seed_hash = string_tools::pod_to_hex(next_seed_hash); } + + res.reserved_offset = reserved_offset; store_difficulty(wdiff, res.difficulty, res.wide_difficulty, res.difficulty_top64); blobdata block_blob = t_serializable_object_to_blob(b); - crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); - if(tx_pub_key == crypto::null_pkey) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to get tx pub key in coinbase extra"); - return false; - } - res.reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key)); - if(!res.reserved_offset) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to find tx pub key in blockblob"); - return false; - } - if (req.reserve_size) - res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) - else - res.reserved_offset = 0; - if(res.reserved_offset + req.reserve_size > block_blob.size()) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to calculate offset for "); - return false; - } blobdata hashing_blob = get_block_hashing_blob(b); res.prev_hash = string_tools::pod_to_hex(b.prev_id); res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); @@ -1493,7 +1730,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_submitblock); + RPC_TRACKER(submitblock); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1549,7 +1786,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_generateblocks); + RPC_TRACKER(generateblocks); CHECK_CORE_READY(); @@ -1724,12 +1961,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_last_block_header); + RPC_TRACKER(get_last_block_header); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_LAST_BLOCK_HEADER>(invoke_http_mode::JON_RPC, "getlastblockheader", req, res, r)) return r; CHECK_CORE_READY(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); uint64_t last_block_height; crypto::hash last_block_hash; m_core.get_blockchain_top(last_block_height, last_block_hash); @@ -1755,11 +1993,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_header_by_hash); + RPC_TRACKER(get_block_header_by_hash); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH>(invoke_http_mode::JON_RPC, "getblockheaderbyhash", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); + auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool { crypto::hash block_hash; bool hash_parsed = parse_hash256(hash, block_hash); @@ -1815,7 +2055,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_headers_range(const COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request& req, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_headers_range); + RPC_TRACKER(get_block_headers_range); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r)) return r; @@ -1827,6 +2067,7 @@ namespace cryptonote error_resp.message = "Invalid start/end heights."; return false; } + CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false); for (uint64_t h = req.start_height; h <= req.end_height; ++h) { crypto::hash block_hash = m_core.get_block_id_by_height(h); @@ -1867,7 +2108,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_header_by_height); + RPC_TRACKER(get_block_header_by_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r)) return r; @@ -1878,6 +2119,7 @@ namespace cryptonote error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); return false; } + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); crypto::hash block_hash = m_core.get_block_id_by_height(req.height); block blk; bool have_block = m_core.get_block_by_hash(block_hash, blk); @@ -1901,11 +2143,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block(const COMMAND_RPC_GET_BLOCK::request& req, COMMAND_RPC_GET_BLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block); + RPC_TRACKER(get_block); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK>(invoke_http_mode::JON_RPC, "getblock", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false); + crypto::hash block_hash; if (!req.hash.empty()) { @@ -1963,7 +2207,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_connections); + RPC_TRACKER(get_connections); res.connections = m_p2p.get_payload_object().get_connections(); @@ -1974,16 +2218,24 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - return on_get_info(req, res, ctx); + on_get_info(req, res, ctx); + if (res.status != CORE_RPC_STATUS_OK) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = res.status; + return false; + } + return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_hard_fork_info); + RPC_TRACKER(hard_fork_info); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_HARD_FORK_INFO>(invoke_http_mode::JON_RPC, "hard_fork_info", req, res, r)) return r; + CHECK_PAYMENT(req, res, COST_PER_HARD_FORK_INFO); const Blockchain &blockchain = m_core.get_blockchain_storage(); uint8_t version = req.version > 0 ? req.version : blockchain.get_next_hard_fork_version(); res.version = blockchain.get_current_hard_fork_version(); @@ -1995,7 +2247,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_bans); + RPC_TRACKER(get_bans); auto now = time(nullptr); std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); @@ -2059,7 +2311,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_set_bans); + RPC_TRACKER(set_bans); for (auto i = req.bans.begin(); i != req.bans.end(); ++i) { @@ -2107,7 +2359,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_flush_txpool); + RPC_TRACKER(flush_txpool); bool failed = false; std::vector<crypto::hash> txids; @@ -2162,12 +2414,22 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_output_histogram); + RPC_TRACKER(get_output_histogram); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_HISTOGRAM>(invoke_http_mode::JON_RPC, "get_output_histogram", req, res, r)) return r; const bool restricted = m_restricted && ctx; + size_t amounts = req.amounts.size(); + if (restricted && amounts == 0) + { + res.status = "Restricted RPC will not serve histograms on the whole blockchain. Use your own node."; + return true; + } + + uint64_t cost = req.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * amounts); + CHECK_PAYMENT_MIN1(req, res, cost, false); + if (restricted && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) { res.status = "Recent cutoff is too old"; @@ -2199,7 +2461,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_version); + RPC_TRACKER(get_version); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_VERSION>(invoke_http_mode::JON_RPC, "get_version", req, res, r)) return r; @@ -2212,7 +2474,14 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_coinbase_tx_sum); + RPC_TRACKER(get_coinbase_tx_sum); + const uint64_t bc_height = m_core.get_current_blockchain_height(); + if (req.height >= bc_height || req.count > bc_height) + { + res.status = "height or count is too large"; + return true; + } + CHECK_PAYMENT_MIN1(req, res, COST_PER_COINBASE_TX_SUM_BLOCK * req.count, false); std::pair<uint64_t, uint64_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); res.emission_amount = amounts.first; res.fee_amount = amounts.second; @@ -2222,11 +2491,12 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_base_fee_estimate(const COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_base_fee_estimate); + RPC_TRACKER(get_base_fee_estimate); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BASE_FEE_ESTIMATE>(invoke_http_mode::JON_RPC, "get_fee_estimate", req, res, r)) return r; + CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); res.quantization_mask = Blockchain::get_fee_quantization_mask(); res.status = CORE_RPC_STATUS_OK; @@ -2235,7 +2505,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_alternate_chains); + RPC_TRACKER(get_alternate_chains); try { std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); @@ -2268,7 +2538,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_limit); + RPC_TRACKER(get_limit); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_LIMIT>(invoke_http_mode::JON, "/get_limit", req, res, r)) return r; @@ -2281,7 +2551,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_limit); + RPC_TRACKER(set_limit); // -1 = reset to default // 0 = do not modify @@ -2321,7 +2591,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res, const connection_context *ctx) { - PERF_TIMER(on_out_peers); + RPC_TRACKER(out_peers); if (req.set) m_p2p.change_max_out_public_peers(req.out_peers); res.out_peers = m_p2p.get_max_out_public_peers(); @@ -2331,7 +2601,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_in_peers(const COMMAND_RPC_IN_PEERS::request& req, COMMAND_RPC_IN_PEERS::response& res, const connection_context *ctx) { - PERF_TIMER(on_in_peers); + RPC_TRACKER(in_peers); if (req.set) m_p2p.change_max_in_public_peers(req.in_peers); res.in_peers = m_p2p.get_max_in_public_peers(); @@ -2341,7 +2611,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res, const connection_context *ctx) { - PERF_TIMER(on_update); + RPC_TRACKER(update); if (m_core.offline()) { @@ -2443,7 +2713,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_pop_blocks(const COMMAND_RPC_POP_BLOCKS::request& req, COMMAND_RPC_POP_BLOCKS::response& res, const connection_context *ctx) { - PERF_TIMER(on_pop_blocks); + RPC_TRACKER(pop_blocks); m_core.get_blockchain_storage().pop_blocks(req.nblocks); @@ -2455,7 +2725,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_relay_tx); + RPC_TRACKER(relay_tx); + CHECK_PAYMENT_MIN1(req, res, req.txids.size() * COST_PER_TX_RELAY, false); bool failed = false; res.status = ""; @@ -2501,7 +2772,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_sync_info); + RPC_TRACKER(sync_info); + CHECK_PAYMENT(req, res, COST_PER_SYNC_INFO); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); @@ -2530,10 +2802,12 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::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) { - PERF_TIMER(on_get_txpool_backlog); + RPC_TRACKER(get_txpool_backlog); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r)) return r; + size_t n_txes = m_core.get_pool_transactions_count(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false); if (!m_core.get_txpool_backlog(res.backlog)) { @@ -2548,11 +2822,16 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::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) { - PERF_TIMER(on_get_output_distribution); + RPC_TRACKER(get_output_distribution); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::JON_RPC, "get_output_distribution", req, res, r)) return r; + size_t n_0 = 0, n_non0 = 0; + for (uint64_t amount: req.amounts) + if (amount) ++n_non0; else ++n_0; + CHECK_PAYMENT_MIN1(req, res, n_0 * COST_PER_OUTPUT_DISTRIBUTION_0 + n_non0 * COST_PER_OUTPUT_DISTRIBUTION, false); + try { // 0 is placeholder for the whole chain @@ -2583,12 +2862,17 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_distribution_bin(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_output_distribution_bin); + RPC_TRACKER(get_output_distribution_bin); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::BIN, "/get_output_distribution.bin", req, res, r)) return r; + size_t n_0 = 0, n_non0 = 0; + for (uint64_t amount: req.amounts) + if (amount) ++n_non0; else ++n_0; + CHECK_PAYMENT_MIN1(req, res, n_0 * COST_PER_OUTPUT_DISTRIBUTION_0 + n_non0 * COST_PER_OUTPUT_DISTRIBUTION, false); + res.status = "Failed"; if (!req.binary) @@ -2624,6 +2908,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::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) { + RPC_TRACKER(prune_blockchain); + try { if (!(req.check ? m_core.check_blockchain_pruning() : m_core.prune_blockchain())) @@ -2641,13 +2927,248 @@ namespace cryptonote error_resp.message = "Failed to prune blockchain"; return false; } + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_info); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON, "rpc_access_info", req, res, r)) + return r; + + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.diff = 0; + res.credits_per_hash_found = 0; + res.credits = 0; + res.height = 0; + res.seed_height = 0; + res.status = CORE_RPC_STATUS_OK; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + crypto::hash top_hash; + m_core.get_blockchain_top(res.height, top_hash); + ++res.height; + cryptonote::blobdata hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (!m_rpc_payment->get_info(client, [&](const cryptonote::blobdata &extra_nonce, cryptonote::block &b, uint64_t &seed_height, crypto::hash &seed_hash)->bool{ + cryptonote::difficulty_type difficulty; + uint64_t height, expected_reward; + size_t reserved_offset; + if (!get_block_template(m_rpc_payment->get_payment_address(), NULL, extra_nonce, reserved_offset, difficulty, height, expected_reward, b, seed_height, seed_hash, next_seed_hash, error_resp)) + return false; + return true; + }, hashing_blob, res.seed_height, seed_hash, top_hash, res.diff, res.credits_per_hash_found, res.credits, res.cookie)) + { + return false; + } + if (hashing_blob.empty()) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Invalid hashing blob"; + return false; + } + res.hashing_blob = epee::string_tools::buff_to_hex_nodelimer(hashing_blob); + res.top_hash = epee::string_tools::pod_to_hex(top_hash); + if (hashing_blob[0] >= RX_BLOCK_VERSION) + { + res.seed_hash = string_tools::pod_to_hex(seed_hash); + if (seed_hash != next_seed_hash) + res.next_seed_hash = string_tools::pod_to_hex(next_seed_hash); + } res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_submit_nonce); + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON, "rpc_access_submit_nonce", req, res, r)) + return r; + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.status = "Payment not necessary"; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + res.credits = 0; + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + crypto::hash hash; + cryptonote::block block; + crypto::hash top_hash; + uint64_t height; + bool stale; + m_core.get_blockchain_top(height, top_hash); + if (!m_rpc_payment->submit_nonce(client, req.nonce, top_hash, error_resp.code, error_resp.message, res.credits, hash, block, req.cookie, stale)) + { + return false; + } + + if (!stale) + { + // it might be a valid block! + const difficulty_type current_difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); + if (check_hash(hash, current_difficulty)) + { + MINFO("This payment meets the current network difficulty"); + block_verification_context bvc; + if(m_core.handle_block_found(block, bvc)) + MGINFO_GREEN("Block found by RPC user at height " << get_block_height(block) << ": " << + print_money(cryptonote::get_outs_money_amount(block.miner_tx))); + else + MERROR("Seemingly valid block was not accepted"); + } + } + + m_core.get_blockchain_top(height, top_hash); + res.top_hash = epee::string_tools::pod_to_hex(top_hash); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_pay); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON, "rpc_access_pay", req, res, r)) + return r; + + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.status = "Payment not necessary"; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + res.credits = 0; + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + RPCTracker ext_tracker(("external:" + req.paying_for).c_str(), PERF_TIMER_NAME(rpc_access_pay)); + if (!check_payment(req.client, req.payment, req.paying_for, false, res.status, res.credits, res.top_hash)) + return true; + ext_tracker.pay(req.payment); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_tracking); + + if (req.clear) + { + RPCTracker::clear(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + + auto data = RPCTracker::data(); + for (const auto &d: data) + { + res.data.resize(res.data.size() + 1); + res.data.back().rpc = d.first; + res.data.back().count = d.second.count; + res.data.back().time = d.second.time; + res.data.back().credits = d.second.credits; + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_data); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON, "rpc_access_data", req, res, r)) + return r; + + if (!m_rpc_payment) + { + res.status = "Payments not enabled"; + return false; + } + + m_rpc_payment->foreach([&](const crypto::public_key &client, const rpc_payment::client_info &info){ + res.entries.push_back({ + epee::string_tools::pod_to_hex(client), info.credits, std::max(info.last_request_timestamp / 1000000, info.update_time), + info.credits_total, info.credits_used, info.nonces_good, info.nonces_stale, info.nonces_bad, info.nonces_dupe + }); + return true; + }); + + res.hashrate = m_rpc_payment->get_hashes(600) / 600; + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::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) + { + RPC_TRACKER(rpc_access_account); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON, "rpc_access_account", req, res, r)) + return r; + + if (!m_rpc_payment) + { + res.status = "Payments not enabled"; + return false; + } + + crypto::public_key client; + if (!epee::string_tools::hex_to_pod(req.client.substr(0, 2 * sizeof(client)), client)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + res.credits = m_rpc_payment->balance(client, req.delta_balance); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = { "rpc-bind-port" , "Port for RPC server" @@ -2686,4 +3207,22 @@ namespace cryptonote , "Specify username:password for the bootstrap daemon login" , "" }; + + const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_payment_address = { + "rpc-payment-address" + , "Restrict RPC to clients sending micropayment to this address" + , "" + }; + + const command_line::arg_descriptor<uint64_t> core_rpc_server::arg_rpc_payment_difficulty = { + "rpc-payment-difficulty" + , "Restrict RPC to clients sending micropayment at this difficulty" + , DEFAULT_PAYMENT_DIFFICULTY + }; + + const command_line::arg_descriptor<uint64_t> core_rpc_server::arg_rpc_payment_credits = { + "rpc-payment-credits" + , "Restrict RPC to clients sending micropayment, yields that many credits per payment" + , DEFAULT_PAYMENT_CREDITS_PER_HASH + }; } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index fe03012b7..6655d621e 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -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; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 2760260f6..89c88a835 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -78,6 +78,7 @@ namespace cryptonote #define CORE_RPC_STATUS_OK "OK" #define CORE_RPC_STATUS_BUSY "BUSY" #define CORE_RPC_STATUS_NOT_MINING "NOT MINING" +#define CORE_RPC_STATUS_PAYMENT_REQUIRED "PAYMENT REQUIRED" // When making *any* change here, bump minor // If the change is incompatible, then bump major and set minor to 0 @@ -91,26 +92,67 @@ namespace cryptonote #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) + struct rpc_request_base + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct rpc_response_base + { + std::string status; + bool untrusted; + + rpc_response_base(): untrusted(false) {} + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) + END_KV_SERIALIZE_MAP() + }; + + struct rpc_access_request_base: public rpc_request_base + { + std::string client; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(client) + END_KV_SERIALIZE_MAP() + }; + + struct rpc_access_response_base: public rpc_response_base + { + uint64_t credits; + std::string top_hash; + + rpc_access_response_base(): credits(0) {} + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(credits) + KV_SERIALIZE(top_hash) + END_KV_SERIALIZE_MAP() + }; + struct COMMAND_RPC_GET_HEIGHT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t height; - std::string status; - bool untrusted; std::string hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) KV_SERIALIZE(hash) END_KV_SERIALIZE_MAP() }; @@ -120,13 +162,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCKS_FAST { - struct request_t + struct request_t: public rpc_access_request_base { std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; bool prune; bool no_miner_tx; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(prune) @@ -153,22 +196,19 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<block_complete_entry> blocks; uint64_t start_height; uint64_t current_height; - std::string status; std::vector<block_output_indices> output_indices; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blocks) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) - KV_SERIALIZE(status) KV_SERIALIZE(output_indices) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -176,25 +216,23 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCKS_BY_HEIGHT { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> heights; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(heights) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<block_complete_entry> blocks; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blocks) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -202,23 +240,21 @@ namespace cryptonote struct COMMAND_RPC_GET_ALT_BLOCKS_HASHES { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<std::string> blks_hashes; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blks_hashes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -226,31 +262,29 @@ namespace cryptonote struct COMMAND_RPC_GET_HASHES_FAST { - struct request_t + struct request_t: public rpc_access_request_base { std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<crypto::hash> m_block_ids; uint64_t start_height; uint64_t current_height; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -288,7 +322,7 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_TRANSACTIONS { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> txs_hashes; bool decode_as_json; @@ -296,6 +330,7 @@ namespace cryptonote bool split; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(txs_hashes) KV_SERIALIZE(decode_as_json) KV_SERIALIZE_OPT(prune, false) @@ -341,7 +376,7 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { // older compatibility stuff std::vector<std::string> txs_as_hex; //transactions blobs as hex (old compat) @@ -352,16 +387,13 @@ namespace cryptonote // new style std::vector<entry> txs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(txs_as_hex) KV_SERIALIZE(txs_as_json) KV_SERIALIZE(txs) KV_SERIALIZE(missed_tx) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -376,27 +408,25 @@ namespace cryptonote SPENT_IN_POOL = 2, }; - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> key_images; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(key_images) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<int> spent_status; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(spent_status) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -405,25 +435,24 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES { - struct request_t + struct request_t: public rpc_access_request_base { crypto::hash txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_VAL_POD_AS_BLOB(txid) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<uint64_t> o_indexes; - std::string status; - bool untrusted; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(o_indexes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -442,12 +471,13 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUTS_BIN { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<get_outputs_out> outputs; bool get_txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(outputs) KV_SERIALIZE_OPT(get_txid, true) END_KV_SERIALIZE_MAP() @@ -471,16 +501,13 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<outkey> outs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -488,12 +515,13 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_OUTPUTS { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<get_outputs_out> outputs; bool get_txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(outputs) KV_SERIALIZE(get_txid) END_KV_SERIALIZE_MAP() @@ -517,16 +545,13 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<outkey> outs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -534,13 +559,14 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_SEND_RAW_TX { - struct request_t + struct request_t: public rpc_access_request_base { std::string tx_as_hex; bool do_not_relay; bool do_sanity_checks; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(tx_as_hex) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(do_sanity_checks, true) @@ -549,9 +575,8 @@ namespace cryptonote typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::string reason; bool not_relayed; bool low_mixin; @@ -564,10 +589,9 @@ namespace cryptonote bool not_rct; bool too_few_outputs; bool sanity_check_failed; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(reason) KV_SERIALIZE(not_relayed) KV_SERIALIZE(low_mixin) @@ -580,7 +604,6 @@ namespace cryptonote KV_SERIALIZE(not_rct) KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -588,7 +611,7 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_START_MINING { - struct request_t + struct request_t: public rpc_request_base { std::string miner_address; uint64_t threads_count; @@ -596,6 +619,7 @@ namespace cryptonote bool ignore_battery; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(miner_address) KV_SERIALIZE(threads_count) KV_SERIALIZE(do_background_mining) @@ -604,12 +628,10 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -617,17 +639,16 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_INFO { - struct request_t + struct request_t: public rpc_access_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t height; uint64_t target_height; uint64_t difficulty; @@ -657,7 +678,6 @@ namespace cryptonote uint64_t start_time; uint64_t free_space; bool offline; - bool untrusted; std::string bootstrap_daemon_address; uint64_t height_without_bootstrap; bool was_bootstrap_ever_used; @@ -666,7 +686,7 @@ namespace cryptonote std::string version; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(height) KV_SERIALIZE(target_height) KV_SERIALIZE(difficulty) @@ -696,7 +716,6 @@ namespace cryptonote KV_SERIALIZE(start_time) KV_SERIALIZE(free_space) KV_SERIALIZE(offline) - KV_SERIALIZE(untrusted) KV_SERIALIZE(bootstrap_daemon_address) KV_SERIALIZE(height_without_bootstrap) KV_SERIALIZE(was_bootstrap_ever_used) @@ -712,18 +731,17 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_NET_STATS { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t start_time; uint64_t total_packets_in; uint64_t total_bytes_in; @@ -731,7 +749,7 @@ namespace cryptonote uint64_t total_bytes_out; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(start_time) KV_SERIALIZE(total_packets_in) KV_SERIALIZE(total_bytes_in) @@ -745,21 +763,19 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_STOP_MINING { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -768,18 +784,17 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_MINING_STATUS { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; bool active; uint64_t speed; uint32_t threads_count; @@ -797,7 +812,7 @@ namespace cryptonote uint64_t difficulty_top64; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(active) KV_SERIALIZE(speed) KV_SERIALIZE(threads_count) @@ -821,21 +836,19 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_SAVE_BC { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -846,14 +859,13 @@ namespace cryptonote { typedef std::list<std::string> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t count; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(count) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -869,7 +881,7 @@ namespace cryptonote struct COMMAND_RPC_GETBLOCKTEMPLATE { - struct request_t + struct request_t: public rpc_request_base { uint64_t reserve_size; //max 255 bytes std::string wallet_address; @@ -877,6 +889,7 @@ namespace cryptonote std::string extra_nonce; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(reserve_size) KV_SERIALIZE(wallet_address) KV_SERIALIZE(prev_block) @@ -885,7 +898,7 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t difficulty; std::string wide_difficulty; @@ -894,14 +907,14 @@ namespace cryptonote uint64_t reserved_offset; uint64_t expected_reward; std::string prev_hash; + uint64_t seed_height; std::string seed_hash; std::string next_seed_hash; blobdata blocktemplate_blob; blobdata blockhashing_blob; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(difficulty) KV_SERIALIZE(wide_difficulty) KV_SERIALIZE(difficulty_top64) @@ -909,10 +922,9 @@ namespace cryptonote KV_SERIALIZE(reserved_offset) KV_SERIALIZE(expected_reward) KV_SERIALIZE(prev_hash) + KV_SERIALIZE(seed_height) KV_SERIALIZE(blocktemplate_blob) KV_SERIALIZE(blockhashing_blob) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) KV_SERIALIZE(seed_hash) KV_SERIALIZE(next_seed_hash) END_KV_SERIALIZE_MAP() @@ -924,12 +936,10 @@ namespace cryptonote { typedef std::vector<std::string> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -937,7 +947,7 @@ namespace cryptonote struct COMMAND_RPC_GENERATEBLOCKS { - struct request_t + struct request_t: public rpc_request_base { uint64_t amount_of_blocks; std::string wallet_address; @@ -945,6 +955,7 @@ namespace cryptonote uint32_t starting_nonce; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(amount_of_blocks) KV_SERIALIZE(wallet_address) KV_SERIALIZE(prev_block) @@ -953,16 +964,15 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t height; std::vector<std::string> blocks; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) KV_SERIALIZE(blocks) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1021,26 +1031,24 @@ namespace cryptonote struct COMMAND_RPC_GET_LAST_BLOCK_HEADER { - struct request_t + struct request_t: public rpc_access_request_base { bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1049,13 +1057,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH { - struct request_t + struct request_t: public rpc_access_request_base { std::string hash; std::vector<std::string> hashes; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(hashes) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1063,18 +1072,15 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; std::vector<block_header_response> block_headers; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) KV_SERIALIZE(block_headers) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1082,28 +1088,26 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(height) KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1111,13 +1115,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK { - struct request_t + struct request_t: public rpc_access_request_base { std::string hash; uint64_t height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(height) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1125,24 +1130,21 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; std::string miner_tx_hash; std::vector<std::string> tx_hashes; std::string blob; std::string json; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) KV_SERIALIZE(miner_tx_hash) KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(status) KV_SERIALIZE(blob) KV_SERIALIZE(json) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1154,19 +1156,20 @@ namespace cryptonote uint32_t ip; uint16_t port; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; uint64_t last_seen; uint32_t pruning_seed; peer() = default; - peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} - peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} - peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} BEGIN_KV_SERIALIZE_MAP() @@ -1175,6 +1178,7 @@ namespace cryptonote KV_SERIALIZE(ip) KV_SERIALIZE(port) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) + KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0) KV_SERIALIZE(last_seen) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) END_KV_SERIALIZE_MAP() @@ -1182,24 +1186,24 @@ namespace cryptonote struct COMMAND_RPC_GET_PEER_LIST { - struct request_t + struct request_t: public rpc_request_base { bool public_only; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(public_only, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<peer> white_list; std::vector<peer> gray_list; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(white_list) KV_SERIALIZE(gray_list) END_KV_SERIALIZE_MAP() @@ -1212,42 +1216,44 @@ namespace cryptonote std::string host; uint64_t last_seen; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; - public_node() = delete; + public_node(): last_seen(0), rpc_port(0), rpc_credits_per_hash(0) {} public_node(const peer &peer) - : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) + : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port), rpc_credits_per_hash(peer.rpc_credits_per_hash) {} BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(host) KV_SERIALIZE(last_seen) KV_SERIALIZE(rpc_port) + KV_SERIALIZE(rpc_credits_per_hash) END_KV_SERIALIZE_MAP() }; struct COMMAND_RPC_GET_PUBLIC_NODES { - struct request_t + struct request_t: public rpc_request_base { bool gray; bool white; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(gray, false) KV_SERIALIZE_OPT(white, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<public_node> gray; std::vector<public_node> white; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(gray) KV_SERIALIZE(white) END_KV_SERIALIZE_MAP() @@ -1257,21 +1263,21 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_HASH_RATE { - struct request_t + struct request_t: public rpc_request_base { bool visible; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(visible) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1279,21 +1285,21 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_LEVEL { - struct request_t + struct request_t: public rpc_request_base { int8_t level; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(level) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1301,23 +1307,23 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_CATEGORIES { - struct request_t + struct request_t: public rpc_request_base { std::string categories; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(categories) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::string categories; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(categories) END_KV_SERIALIZE_MAP() }; @@ -1376,25 +1382,23 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<tx_info> transactions; std::vector<spent_key_image_info> spent_key_images; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(transactions) KV_SERIALIZE(spent_key_images) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1402,23 +1406,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<crypto::hash> tx_hashes; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(tx_hashes) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1426,23 +1428,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_HASHES { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<std::string> tx_hashes; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1457,23 +1457,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<tx_backlog_entry> backlog; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(backlog) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1527,23 +1525,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_STATS { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; txpool_stats pool_stats; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(pool_stats) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1551,20 +1547,20 @@ namespace cryptonote struct COMMAND_RPC_GET_CONNECTIONS { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::list<connection_info> connections; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(connections) END_KV_SERIALIZE_MAP() }; @@ -1573,13 +1569,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADERS_RANGE { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t start_height; uint64_t end_height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(start_height) KV_SERIALIZE(end_height) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1587,16 +1584,13 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<block_header_response> headers; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(headers) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1631,19 +1625,18 @@ namespace cryptonote struct COMMAND_RPC_STOP_DAEMON { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1651,19 +1644,18 @@ namespace cryptonote struct COMMAND_RPC_FAST_EXIT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1671,25 +1663,23 @@ namespace cryptonote struct COMMAND_RPC_GET_LIMIT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t limit_up; uint64_t limit_down; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(limit_up) KV_SERIALIZE(limit_down) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1697,26 +1687,26 @@ namespace cryptonote struct COMMAND_RPC_SET_LIMIT { - struct request_t + struct request_t: public rpc_request_base { int64_t limit_down; // all limits (for get and set) are kB/s int64_t limit_up; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(limit_down) KV_SERIALIZE(limit_up) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; int64_t limit_up; int64_t limit_down; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(limit_up) KV_SERIALIZE(limit_down) END_KV_SERIALIZE_MAP() @@ -1726,25 +1716,26 @@ namespace cryptonote struct COMMAND_RPC_OUT_PEERS { - struct request_t + struct request_t: public rpc_request_base { bool set; uint32_t out_peers; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(set, true) KV_SERIALIZE(out_peers) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint32_t out_peers; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(out_peers) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1752,25 +1743,25 @@ namespace cryptonote struct COMMAND_RPC_IN_PEERS { - struct request_t + struct request_t: public rpc_request_base { bool set; uint32_t in_peers; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(set, true) KV_SERIALIZE(in_peers) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint32_t in_peers; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(in_peers) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1778,17 +1769,18 @@ namespace cryptonote struct COMMAND_RPC_HARD_FORK_INFO { - struct request_t + struct request_t: public rpc_access_request_base { uint8_t version; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(version) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { uint8_t version; bool enabled; @@ -1798,10 +1790,9 @@ namespace cryptonote uint8_t voting; uint32_t state; uint64_t earliest_height; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(version) KV_SERIALIZE(enabled) KV_SERIALIZE(window) @@ -1810,8 +1801,6 @@ namespace cryptonote KV_SERIALIZE(voting) KV_SERIALIZE(state) KV_SERIALIZE(earliest_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1832,20 +1821,20 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<ban> bans; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(bans) END_KV_SERIALIZE_MAP() }; @@ -1869,22 +1858,21 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct request_t + struct request_t: public rpc_request_base { std::vector<ban> bans; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(bans) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1919,22 +1907,21 @@ namespace cryptonote struct COMMAND_RPC_FLUSH_TRANSACTION_POOL { - struct request_t + struct request_t: public rpc_request_base { std::vector<std::string> txids; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(txids) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1942,7 +1929,7 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUT_HISTOGRAM { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> amounts; uint64_t min_count; @@ -1951,6 +1938,7 @@ namespace cryptonote uint64_t recent_cutoff; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(amounts); KV_SERIALIZE(min_count); KV_SERIALIZE(max_count); @@ -1979,16 +1967,13 @@ namespace cryptonote entry() {} }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<entry> histogram; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(histogram) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1996,25 +1981,23 @@ namespace cryptonote struct COMMAND_RPC_GET_VERSION { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint32_t version; bool release; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(version) KV_SERIALIZE(release) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2022,26 +2005,26 @@ namespace cryptonote struct COMMAND_RPC_GET_COINBASE_TX_SUM { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t height; uint64_t count; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(height); KV_SERIALIZE(count); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t emission_amount; uint64_t fee_amount; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(emission_amount) KV_SERIALIZE(fee_amount) END_KV_SERIALIZE_MAP() @@ -2051,28 +2034,26 @@ namespace cryptonote struct COMMAND_RPC_GET_BASE_FEE_ESTIMATE { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t grace_blocks; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(grace_blocks) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t fee; uint64_t quantization_mask; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(fee) KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2080,9 +2061,10 @@ namespace cryptonote struct COMMAND_RPC_GET_ALTERNATE_CHAINS { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2110,13 +2092,12 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<chain_info> chains; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(chains) END_KV_SERIALIZE_MAP() }; @@ -2125,21 +2106,21 @@ namespace cryptonote struct COMMAND_RPC_UPDATE { - struct request_t + struct request_t: public rpc_request_base { std::string command; std::string path; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(command); - KV_SERIALIZE(path); + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(command) + KV_SERIALIZE(path) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; bool update; std::string version; std::string user_uri; @@ -2148,7 +2129,7 @@ namespace cryptonote std::string path; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(update) KV_SERIALIZE(version) KV_SERIALIZE(user_uri) @@ -2162,22 +2143,21 @@ namespace cryptonote struct COMMAND_RPC_RELAY_TX { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> txids; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(txids) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2185,9 +2165,10 @@ namespace cryptonote struct COMMAND_RPC_SYNC_INFO { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2222,9 +2203,8 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t height; uint64_t target_height; uint32_t next_needed_pruning_seed; @@ -2233,7 +2213,7 @@ namespace cryptonote std::string overview; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(height) KV_SERIALIZE(target_height) KV_SERIALIZE(next_needed_pruning_seed) @@ -2247,7 +2227,7 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUT_DISTRIBUTION { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> amounts; uint64_t from_height; @@ -2257,6 +2237,7 @@ namespace cryptonote bool compress; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(amounts) KV_SERIALIZE_OPT(from_height, (uint64_t)0) KV_SERIALIZE_OPT(to_height, (uint64_t)0) @@ -2309,16 +2290,213 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<distribution> distributions; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(distributions) - KV_SERIALIZE(untrusted) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_INFO + { + struct request_t: public rpc_access_request_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + std::string hashing_blob; + uint64_t seed_height; + std::string seed_hash; + std::string next_seed_hash; + uint32_t cookie; + uint64_t diff; + uint64_t credits_per_hash_found; + uint64_t height; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + KV_SERIALIZE(hashing_blob) + KV_SERIALIZE(seed_height) + KV_SERIALIZE(seed_hash) + KV_SERIALIZE(next_seed_hash) + KV_SERIALIZE(cookie) + KV_SERIALIZE(diff) + KV_SERIALIZE(credits_per_hash_found) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_SUBMIT_NONCE + { + struct request_t: public rpc_access_request_base + { + uint32_t nonce; + uint32_t cookie; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(nonce) + KV_SERIALIZE(cookie) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_PAY + { + struct request_t: public rpc_access_request_base + { + std::string paying_for; + uint64_t payment; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(paying_for) + KV_SERIALIZE(payment) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_TRACKING + { + struct request_t: public rpc_request_base + { + bool clear; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(clear) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct entry + { + std::string rpc; + uint64_t count; + uint64_t time; + uint64_t credits; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(rpc) + KV_SERIALIZE(count) + KV_SERIALIZE(time) + KV_SERIALIZE(credits) + END_KV_SERIALIZE_MAP() + }; + + struct response_t: public rpc_response_base + { + std::vector<entry> data; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(data) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_DATA + { + struct request_t: public rpc_request_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct entry + { + std::string client; + uint64_t balance; + uint64_t last_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; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(client) + KV_SERIALIZE(balance) + KV_SERIALIZE(last_update_time) + KV_SERIALIZE(credits_total) + KV_SERIALIZE(credits_used) + KV_SERIALIZE(nonces_good) + KV_SERIALIZE(nonces_stale) + KV_SERIALIZE(nonces_bad) + KV_SERIALIZE(nonces_dupe) + END_KV_SERIALIZE_MAP() + }; + + struct response_t: public rpc_response_base + { + std::list<entry> entries; + uint32_t hashrate; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(entries) + KV_SERIALIZE(hashrate) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_ACCOUNT + { + struct request_t: public rpc_request_base + { + std::string client; + int64_t delta_balance; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(client) + KV_SERIALIZE_OPT(delta_balance, (int64_t)0) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_response_base + { + uint64_t credits; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(credits) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2326,23 +2504,23 @@ namespace cryptonote struct COMMAND_RPC_POP_BLOCKS { - struct request_t + struct request_t: public rpc_request_base { uint64_t nblocks; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(nblocks); + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(nblocks) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t height; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) END_KV_SERIALIZE_MAP() }; @@ -2351,24 +2529,24 @@ namespace cryptonote struct COMMAND_RPC_PRUNE_BLOCKCHAIN { - struct request_t + struct request_t: public rpc_request_base { bool check; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(check, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { bool pruned; uint32_t pruning_seed; - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(pruned) KV_SERIALIZE(pruning_seed) END_KV_SERIALIZE_MAP() diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index b13049e61..2fd42f43f 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -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"; + } +} diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index 2a43811cf..e64f5f163 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -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; }; diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp new file mode 100644 index 000000000..0637db728 --- /dev/null +++ b/src/rpc/rpc_payment.cpp @@ -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; + } +} diff --git a/src/rpc/rpc_payment.h b/src/rpc/rpc_payment.h new file mode 100644 index 000000000..f6832fd34 --- /dev/null +++ b/src/rpc/rpc_payment.h @@ -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); diff --git a/src/rpc/rpc_payment_costs.h b/src/rpc/rpc_payment_costs.h new file mode 100644 index 000000000..3b27bf286 --- /dev/null +++ b/src/rpc/rpc_payment_costs.h @@ -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 diff --git a/src/rpc/rpc_payment_signature.cpp b/src/rpc/rpc_payment_signature.cpp new file mode 100644 index 000000000..159bb5730 --- /dev/null +++ b/src/rpc/rpc_payment_signature.cpp @@ -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; + } +} diff --git a/src/rpc/rpc_payment_signature.h b/src/rpc/rpc_payment_signature.h new file mode 100644 index 000000000..4a2fe2ea3 --- /dev/null +++ b/src/rpc/rpc_payment_signature.h @@ -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); +} diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index 42d09aa5f..ea67209dc 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -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); } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 24267ca09..03693a57c 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -58,6 +58,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/rpc_payment_signature.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" @@ -99,12 +100,17 @@ typedef cryptonote::simple_wallet sw; #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ - /* stop any background refresh, and take over */ \ + /* stop any background refresh and other processes, and take over */ \ + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); \ m_wallet->stop(); \ boost::unique_lock<boost::mutex> lock(m_idle_mutex); \ m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ + /* m_idle_mutex is still locked here */ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ + m_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed);; \ + m_rpc_payment_checker.trigger(); \ + m_idle_cond.notify_one(); \ }) #define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \ @@ -125,15 +131,24 @@ typedef cryptonote::simple_wallet sw; return true; \ } while(0) +#define REFRESH_PERIOD 90 // seconds + +#define CREDITS_TARGET 50000 +#define MAX_PAYMENT_DIFF 10000 +#define MIN_PAYMENT_RATE 0.01f // per hash + enum TransferType { Transfer, TransferLocked, }; +static std::string get_human_readable_timespan(std::chrono::seconds seconds); + namespace { const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; const auto arg_wallet_file = wallet_args::arg_wallet_file(); + const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; @@ -247,7 +262,11 @@ namespace const char* USAGE_FROZEN("frozen <key_image>"); const char* USAGE_LOCK("lock"); const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); + const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); + const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); + const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command>]"); @@ -490,22 +509,28 @@ namespace fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>"); return r; } +} - void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) - { +void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) +{ bool warn_of_possible_attack = !trusted_daemon; try { std::rethrow_exception(e); } - catch (const tools::error::daemon_busy&) + catch (const tools::error::payment_required&) { - fail_msg_writer() << sw::tr("daemon is busy. Please try again later."); + fail_msg_writer() << tr("Payment required, see the 'rpc_payment_info' command"); + m_need_payment = true; } catch (const tools::error::no_connection_to_daemon&) { fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -602,8 +627,10 @@ namespace if (warn_of_possible_attack) fail_msg_writer() << sw::tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); - } +} +namespace +{ bool check_file_overwrite(const std::string &filename) { boost::system::error_code errcode; @@ -1908,6 +1935,77 @@ bool simple_wallet::unset_ring(const std::vector<std::string> &args) return true; } +bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + try + { + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + crypto::public_key pkey; + crypto::secret_key_to_public_key(m_wallet->get_rpc_client_secret_key(), pkey); + message_writer() << tr("RPC client ID: ") << pkey; + message_writer() << tr("RPC client secret key: ") << m_wallet->get_rpc_client_secret_key(); + if (!m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (payment_required) + { + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + message_writer() << tr("Using daemon: ") << m_wallet->get_daemon_address(); + message_writer() << tr("Payments required for node use, current credits: ") << credits; + message_writer() << tr("Credits target: ") << target; + uint64_t expected, discrepancy; + m_wallet->credit_report(expected, discrepancy); + message_writer() << tr("Credits spent this session: ") << expected; + if (expected) + message_writer() << tr("Credit discrepancy this session: ") << discrepancy << " (" << 100.0f * discrepancy / expected << "%)"; + float cph = credits_per_hash_found / (float)diff; + message_writer() << tr("Difficulty: ") << diff << ", " << credits_per_hash_found << " " << tr("credits per hash found, ") << cph << " " << tr("credits/hash");; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + bool mining = (now - m_last_rpc_payment_mining_time).total_microseconds() < 1000000; + if (mining) + { + float hash_rate = m_rpc_payment_hash_rate; + if (hash_rate > 0) + { + message_writer() << (boost::format(tr("Mining for payment at %.1f H/s")) % hash_rate).str(); + if (credits < target) + { + std::chrono::seconds seconds((unsigned)((target - credits) / cph / hash_rate)); + std::string target_string = get_human_readable_timespan(seconds); + message_writer() << (boost::format(tr("Estimated time till %u credits target mined: %s")) % target % target_string).str(); + } + } + else + message_writer() << tr("Mining for payment"); + } + else + message_writer() << tr("Not mining"); + } + else + message_writer() << tr("No payment needed for node use"); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + + return true; +} + bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2150,6 +2248,45 @@ bool simple_wallet::net_stats(const std::vector<std::string> &args) return true; } +bool simple_wallet::public_nodes(const std::vector<std::string> &args) +{ + try + { + auto nodes = m_wallet->get_public_nodes(false); + m_claimed_cph.clear(); + if (nodes.empty()) + { + fail_msg_writer() << tr("No known public nodes"); + return true; + } + std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) { + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0) + return true; + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash) + return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash; + return false; + }); + + const uint64_t now = time(NULL); + message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen"); + for (const auto &node: nodes) + { + const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE; + char cphs[9]; + snprintf(cphs, sizeof(cphs), "%.3f", cph); + const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen)); + std::string host = node.host + ":" + std::to_string(node.rpc_port); + message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen; + m_claimed_cph[host] = node.rpc_credits_per_hash; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error retrieving public node list: ") << e.what(); + } + return true; +} + bool simple_wallet::welcome(const std::vector<std::string> &args) { message_writer() << tr("Welcome to Monero, the private cryptocurrency."); @@ -2214,6 +2351,50 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& return m_wallet->import_key_images(exported_txs, 0, true); } +bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (!m_wallet->get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (!payment_required) + { + fail_msg_writer() << tr("Daemon does not require payment for RPC access"); + return true; + } + + m_rpc_payment_mining_requested = true; + m_rpc_payment_checker.trigger(); + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + success_msg_writer() << (boost::format(tr("Starting mining for RPC access: diff %llu, %f credits/hash%s")) % diff % cph % (low ? " - this is low" : "")).str(); + success_msg_writer() << tr("Run stop_mining_for_rpc to stop"); + return true; +} + +bool simple_wallet::stop_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + m_rpc_payment_mining_requested = false; + m_last_rpc_payment_mining_time = boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1)); + m_rpc_payment_hash_rate = -1.0f; + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2602,6 +2783,53 @@ bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string return true; } +bool simple_wallet::set_persistent_rpc_client_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->persistent_rpc_client_id(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + +bool simple_wallet::set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + float threshold; + if (!epee::string_tools::get_xtype_from_string(threshold, args[1]) || threshold < 0.0f) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + m_wallet->auto_mine_for_rpc_payment_threshold(threshold); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_credits_target(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + uint64_t target; + if (!epee::string_tools::get_xtype_from_string(target, args[1])) + { + fail_msg_writer() << tr("Invalid target"); + return true; + } + m_wallet->credits_target(target); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + bool simple_wallet::set_key_reuse_mitigation2(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2844,6 +3072,12 @@ simple_wallet::simple_wallet() , m_last_activity_time(time(NULL)) , m_locked(false) , m_in_command(false) + , m_need_payment(false) + , m_rpc_payment_mining_requested(false) + , m_last_rpc_payment_mining_time(boost::gregorian::date(1970, 1, 1)) + , m_daemon_rpc_payment_message_displayed(false) + , m_rpc_payment_hash_rate(-1.0f) + , m_suspend_rpc_payment_mining(false) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1), @@ -3022,7 +3256,13 @@ simple_wallet::simple_wallet() "device-name <device_name[:device_spec]>\n " " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " - " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n ")); + " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " + "persistent-client-id <1|0>\n " + " Whether to keep using the same client id for RPC payment over wallet restarts.\n" + "auto-mine-for-rpc-payment-threshold <float>\n " + " Whether to automatically start mining for RPC payment if the daemon requires it.\n" + "credits-target <unsigned int>\n" + " The RPC payment credits balance to target (0 for default).")); m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1), tr("Display the encrypted Electrum-style mnemonic seed.")); @@ -3325,6 +3565,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1), tr(USAGE_NET_STATS), tr("Prints simple network stats")); + m_cmd_binder.set_handler("public_nodes", + boost::bind(&simple_wallet::public_nodes, this, _1), + tr(USAGE_PUBLIC_NODES), + tr("Lists known public nodes")); m_cmd_binder.set_handler("welcome", boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1), tr(USAGE_WELCOME), @@ -3333,6 +3577,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1), tr(USAGE_VERSION), tr("Returns version information")); + m_cmd_binder.set_handler("rpc_payment_info", + boost::bind(&simple_wallet::rpc_payment_info, this, _1), + tr(USAGE_RPC_PAYMENT_INFO), + tr("Get info about RPC payments to current node")); + m_cmd_binder.set_handler("start_mining_for_rpc", + boost::bind(&simple_wallet::start_mining_for_rpc, this, _1), + tr(USAGE_START_MINING_FOR_RPC), + tr("Start mining to pay for RPC access")); + m_cmd_binder.set_handler("stop_mining_for_rpc", + boost::bind(&simple_wallet::stop_mining_for_rpc, this, _1), + tr(USAGE_STOP_MINING_FOR_RPC), + tr("Stop mining to pay for RPC access")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1), tr(USAGE_HELP), @@ -3402,6 +3658,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) << " (disabled on Windows)" #endif ; + success_msg_writer() << "persistent-rpc-client-id = " << m_wallet->persistent_rpc_client_id(); + success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); + success_msg_writer() << "credits-target = " << m_wallet->credits_target(); return true; } else @@ -3463,6 +3722,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\"")); + CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); + CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -4227,6 +4489,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } + if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key)) + { + crypto::secret_key rpc_client_secret_key; + if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), rpc_client_secret_key)) + { + fail_msg_writer() << tr("RPC client secret key should be 32 byte in hex format"); + return false; + } + m_wallet->set_rpc_client_secret_key(rpc_client_secret_key); + } + if (!m_wallet->is_trusted_daemon()) { message_writer(console_color_red, true) << (boost::format(tr("Warning: using an untrusted daemon at %s")) % m_wallet->get_daemon_address()).str(); @@ -4742,6 +5015,7 @@ bool simple_wallet::close_wallet() if (m_idle_run.load(std::memory_order_relaxed)) { m_idle_run.store(false, std::memory_order_relaxed); + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); m_wallet->stop(); { boost::unique_lock<boost::mutex> lock(m_idle_mutex); @@ -5009,6 +5283,40 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph) +{ + try + { + auto i = m_claimed_cph.find(daemon_url); + if (i == m_claimed_cph.end()) + return false; + + claimed_cph = m_claimed_cph[daemon_url]; + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + cryptonote::blobdata hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required) + { + actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff); + return true; + } + else + { + fail_msg_writer() << tr("Error checking daemon RPC access prices"); + } + } + catch (const std::exception &e) + { + // can't check + fail_msg_writer() << tr("Error checking daemon RPC access prices: ") << e.what(); + return false; + } + // no record found for this daemon + return false; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_daemon(const std::vector<std::string>& args) { std::string daemon_url; @@ -5032,7 +5340,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) // If no port has been provided, use the default from config if (!match[3].length()) { - int daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; + uint16_t daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; daemon_url = match[1] + match[2] + std::string(":") + std::to_string(daemon_port); } else { daemon_url = args[0]; @@ -5065,7 +5373,28 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) } catch (const std::exception &e) { } } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("Failed to connect to daemon"); + return true; + } + success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); + + // check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying + uint32_t actual_cph, claimed_cph; + if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph)) + { + if (actual_cph < claimed_cph) + { + fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently."); + fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + } + } + + m_daemon_rpc_payment_message_displayed = false; } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); } @@ -5304,6 +5633,11 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo { ss << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + ss << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -5630,6 +5964,11 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + fail_msg_writer() << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::is_key_image_spent_error&) { fail_msg_writer() << tr("failed to get spent status"); @@ -5733,6 +6072,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending req.outputs[j].index = absolute_offsets[j]; } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + req.client = cryptonote::make_rpc_payment_signature(m_wallet->get_rpc_client_secret_key()); bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res); err = interpret_rpc_response(r, res.status); if (!err.empty()) @@ -8474,6 +8814,7 @@ void simple_wallet::wallet_idle_thread() #endif m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this)); m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this)); + m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this)); if (!m_idle_run.load(std::memory_order_relaxed)) break; @@ -8527,6 +8868,78 @@ bool simple_wallet::check_mms() return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_rpc_payment() +{ + if (!m_rpc_payment_mining_requested && m_wallet->auto_mine_for_rpc_payment_threshold() == 0.0f) + return true; + + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + if (m_rpc_payment_mining_requested) + target = std::numeric_limits<uint64_t>::max(); + bool need_payment = m_need_payment || m_rpc_payment_mining_requested || (m_wallet->credits() < target && m_wallet->daemon_requires_payment()); + if (need_payment) + { + const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time(); + auto startfunc = [this](uint64_t diff, uint64_t credits_per_hash_found) + { + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + if (credits_per_hash_found > 0 && cph >= m_wallet->auto_mine_for_rpc_payment_threshold()) + { + MINFO(std::to_string(cph) << " credits per hash is >= our threshold (" << m_wallet->auto_mine_for_rpc_payment_threshold() << "), starting mining"); + return true; + } + else if (m_rpc_payment_mining_requested) + { + MINFO("Mining for RPC payment was requested, starting mining"); + return true; + } + else + { + if (!m_daemon_rpc_payment_message_displayed) + { + success_msg_writer() << boost::format(tr("Daemon requests payment at diff %llu, with %f credits/hash%s. Run start_mining_for_rpc to start mining to pay for RPC access, or use another daemon")) % + diff % cph % (low ? " - this is low" : ""); + m_cmd_binder.print_prompt(); + m_daemon_rpc_payment_message_displayed = true; + } + return false; + } + }; + auto contfunc = [&,this](unsigned n_hashes) + { + if (m_suspend_rpc_payment_mining.load(std::memory_order_relaxed)) + return false; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + m_last_rpc_payment_mining_time = now; + if ((now - start_time).total_microseconds() >= 2 * 1000000) + m_rpc_payment_hash_rate = n_hashes / (float)((now - start_time).total_seconds()); + if ((now - start_time).total_microseconds() >= REFRESH_PERIOD * 1000000) + return false; + return true; + }; + auto foundfunc = [this, target](uint64_t credits) + { + m_need_payment = false; + return credits < target; + }; + auto errorfunc = [this](const std::string &error) + { + fail_msg_writer() << tr("Error mining to daemon: ") << error; + m_cmd_binder.print_prompt(); + }; + bool ret = m_wallet->search_for_rpc_payment(target, startfunc, contfunc, foundfunc, errorfunc); + if (!ret) + { + fail_msg_writer() << tr("Failed to start mining for RPC payment"); + m_cmd_binder.print_prompt(); + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- std::string simple_wallet::get_prompt() const { if (m_locked) @@ -9728,6 +10141,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_create_address_file); command_line::add_arg(desc_params, arg_subaddress_lookahead); command_line::add_arg(desc_params, arg_use_english_language_names); + command_line::add_arg(desc_params, arg_rpc_client_secret_key); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 47e08ca87..e8f96ad54 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -151,6 +151,9 @@ namespace cryptonote bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); @@ -250,7 +253,11 @@ namespace cryptonote bool thaw(const std::vector<std::string>& args); bool frozen(const std::vector<std::string>& args); bool lock(const std::vector<std::string>& args); + bool rpc_payment_info(const std::vector<std::string> &args); + bool start_mining_for_rpc(const std::vector<std::string> &args); + bool stop_mining_for_rpc(const std::vector<std::string> &args); bool net_stats(const std::vector<std::string>& args); + bool public_nodes(const std::vector<std::string>& args); bool welcome(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args); bool on_unknown_command(const std::vector<std::string>& args); @@ -325,6 +332,11 @@ namespace cryptonote bool check_inactivity(); bool check_refresh(); bool check_mms(); + bool check_rpc_payment(); + + void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon); + + bool check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph); //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); @@ -439,7 +451,17 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<1> m_inactivity_checker; epee::math_helper::once_a_time_seconds<90> m_refresh_checker; epee::math_helper::once_a_time_seconds<90> m_mms_checker; + epee::math_helper::once_a_time_seconds<90> m_rpc_payment_checker; + std::atomic<bool> m_need_payment; + boost::posix_time::ptime m_last_rpc_payment_mining_time; + bool m_rpc_payment_mining_requested; + bool m_daemon_rpc_payment_message_displayed; + float m_rpc_payment_hash_rate; + std::atomic<bool> m_suspend_rpc_payment_mining; + + std::unordered_map<std::string, uint32_t> m_claimed_cph; + // MMS mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); }; diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 553445a39..3be1a6f6b 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -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 diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 1d5078a11..731896715 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -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(); - 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"); + { + 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); + 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(); - 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(); + { + 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); + 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; - 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"); + + { + 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); + 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; - 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"); + + { + 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); + 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; - 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"); + + { + 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); + 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; +} + } diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 3b75c8b94..a9d8167ac 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -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; }; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c7374b896..9b3e7e8b4 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -44,15 +44,20 @@ using namespace epee; #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/core_rpc_server_error_codes.h" +#include "rpc/rpc_payment_signature.h" +#include "rpc/rpc_payment_costs.h" #include "misc_language.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "multisig/multisig.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" #include "common/threadpool.h" +#include "int-util.h" #include "profile_tools.h" #include "crypto/crypto.h" #include "serialization/binary_utils.h" @@ -385,6 +390,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl { auto parsed = tools::login::parse( command_line::get_arg(vm, opts.daemon_login), false, [password_prompter](bool verify) { + if (!password_prompter) + { + MERROR("Password needed without prompt function"); + return boost::optional<tools::password_container>(); + } return password_prompter("Daemon client password", verify); } ); @@ -1133,13 +1143,15 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_track_uses(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), + m_persistent_rpc_client_id(false), + m_auto_mine_for_rpc_payment_threshold(-1.0f), m_is_initialized(false), m_kdf_rounds(kdf_rounds), is_old_file_format(false), m_watch_only(false), m_multisig(false), m_multisig_threshold(0), - m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), + m_node_rpc_proxy(m_http_client, m_rpc_payment_state, m_daemon_rpc_mutex), m_account_public_address{crypto::null_pkey, crypto::null_pkey}, m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR), m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR), @@ -1162,8 +1174,10 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_use_dns(true), m_offline(false), m_rpc_version(0), - m_export_format(ExportFormat::Binary) + m_export_format(ExportFormat::Binary), + m_credits_target(0) { + set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } wallet2::~wallet2() @@ -1269,9 +1283,16 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u if(m_http_client.is_connected()) m_http_client.disconnect(); + const bool changed = m_daemon_address != daemon_address; m_daemon_address = std::move(daemon_address); m_daemon_login = std::move(daemon_login); m_trusted_daemon = trusted_daemon; + if (changed) + { + m_rpc_payment_state.expected_spent = 0; + m_rpc_payment_state.discrepancy = 0; + m_node_rpc_proxy.invalidate(); + } MINFO("setting daemon to " << get_daemon_address()); return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options)); @@ -2507,15 +2528,18 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, req.prune = true; req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_bin("/getblocks.bin", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(res.status)); - THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, - "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + - boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon"); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status)); + THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error, + "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" + + boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon"); + check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, 1 + res.blocks.size() * COST_PER_BLOCK); + } blocks_start_height = res.start_height; blocks = std::move(res.blocks); @@ -2529,12 +2553,15 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, req.block_ids = short_chain_history; req.start_height = start_height; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_bin("/gethashes.bin", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gethashes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gethashes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_hashes_error, get_rpc_status(res.status)); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + req.client = get_client_signature(); + uint64_t pre_call_credits = m_rpc_payment_state.credits; + bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "gethashes.bin", error::get_hashes_error, get_rpc_status(res.status)); + check_rpc_cost("/gethashes.bin", res.credits, pre_call_credits, 1 + res.m_block_ids.size() * COST_PER_BLOCK_HASH); + } blocks_start_height = res.start_height; hashes = std::move(res.m_block_ids); @@ -2699,9 +2726,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo refresh(trusted_daemon, start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- -void wallet2::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 wallet2::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) { error = false; + exception = NULL; try { @@ -2760,6 +2788,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks catch(...) { error = true; + exception = std::current_exception(); } } @@ -2806,12 +2835,15 @@ void wallet2::update_pool_state(bool refreshed) // get the pool state cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req; cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/get_transaction_pool_hashes.bin", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_transaction_pool_hashes.bin", error::get_tx_pool_error); + check_rpc_cost("/get_transaction_pool_hashes.bin", res.credits, pre_call_credits, 1 + res.tx_hashes.size() * COST_PER_POOL_HASH); + } MTRACE("update_pool_state got pool"); // remove any pending tx that's not in the pool @@ -2953,9 +2985,17 @@ void wallet2::update_pool_state(bool refreshed) MDEBUG("asking for " << txids.size() << " transactions"); req.decode_as_json = false; req.prune = true; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/gettransactions", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); + + bool r; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + if (r && res.status == CORE_RPC_STATUS_OK) + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); + } + MDEBUG("Got " << r << " and " << res.status); if (r && res.status == CORE_RPC_STATUS_OK) { @@ -3222,19 +3262,22 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo std::vector<cryptonote::block_complete_entry> next_blocks; std::vector<parsed_block> next_parsed_blocks; bool error; + std::exception_ptr exception; try { // pull the next set of blocks while we're processing the current one error = false; + exception = NULL; next_blocks.clear(); next_parsed_blocks.clear(); added_blocks = 0; if (!first && blocks.empty()) { - refreshed = false; + m_node_rpc_proxy.set_height(m_blockchain.size()); + refreshed = true; break; } - tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, error);}); + tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, error, exception);}); if (!first) { @@ -3285,7 +3328,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // handle error from async fetching thread if (error) { - throw std::runtime_error("proxy exception in refresh thread"); + if (exception) + std::rethrow_exception(exception); + else + throw std::runtime_error("proxy exception in refresh thread"); } // if we've got at least 10 blocks to refresh, assume we're starting @@ -3304,6 +3350,12 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo waiter.wait(&tpool); throw; } + catch (const error::payment_required&) + { + // no point in trying again, it'd just eat up credits + waiter.wait(&tpool); + throw; + } catch (const std::exception&) { blocks_fetched += added_blocks; @@ -3395,22 +3447,19 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> req.cumulative = false; req.binary = true; req.compress = true; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_bin("/get_output_distribution.bin", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - if (!r) - { - MWARNING("Failed to request output distribution: no connection to daemon"); - return false; - } - if (res.status == CORE_RPC_STATUS_BUSY) + + bool r; + try { - MWARNING("Failed to request output distribution: daemon is busy"); - return false; + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_output_distribution.bin"); + check_rpc_cost("/get_output_distribution.bin", res.credits, pre_call_credits, COST_PER_OUTPUT_DISTRIBUTION_0); } - if (res.status != CORE_RPC_STATUS_OK) + catch(...) { - MWARNING("Failed to request output distribution: " << res.status); return false; } if (res.distributions.size() != 1) @@ -3480,6 +3529,7 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); m_pub_keys.erase(it_pk); } + transfers_detached = std::distance(it, m_transfers.end()); m_transfers.erase(it, m_transfers.end()); size_t blocks_detached = m_blockchain.size() - height; @@ -3745,6 +3795,15 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable json.AddMember("original_view_secret_key", value, json.GetAllocator()); } + value2.SetInt(m_persistent_rpc_client_id ? 1 : 0); + json.AddMember("persistent_rpc_client_id", value2, json.GetAllocator()); + + value2.SetFloat(m_auto_mine_for_rpc_payment_threshold); + json.AddMember("auto_mine_for_rpc_payment", value2, json.GetAllocator()); + + value2.SetUint64(m_credits_target); + json.AddMember("credits_target", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -3876,6 +3935,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_device_derivation_path = ""; m_key_device_type = hw::device::device_type::SOFTWARE; encrypted_secret_keys = false; + m_persistent_rpc_client_id = false; + m_auto_mine_for_rpc_payment_threshold = -1.0f; + m_credits_target = 0; } else if(json.IsObject()) { @@ -4084,6 +4146,14 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ { m_original_keys_available = false; } + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, persistent_rpc_client_id, int, Int, false, false); + m_persistent_rpc_client_id = field_persistent_rpc_client_id; + // save as float, load as double, because it can happen you can't load back as float... + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_mine_for_rpc_payment, float, Double, false, FLT_MAX); + m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0); + m_credits_target = field_credits_target; } else { @@ -5433,6 +5503,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); } + if (!m_persistent_rpc_client_id) + set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); + cryptonote::block genesis; generate_genesis(genesis); crypto::hash genesis_hash = get_block_hash(genesis); @@ -5484,10 +5557,18 @@ void wallet2::trim_hashchain() MINFO("Fixing empty hashchain"); cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res); - m_daemon_rpc_mutex.lock(); - req.height = m_blockchain.size() - 1; - bool r = invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); + + bool r; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + req.height = m_blockchain.size() - 1; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, m_http_client, rpc_timeout); + if (r && res.status == CORE_RPC_STATUS_OK) + check_rpc_cost("getblockheaderbyheight", res.credits, pre_call_credits, COST_PER_BLOCK_HEADER); + } + if (r && res.status == CORE_RPC_STATUS_OK) { crypto::hash hash; @@ -5844,15 +5925,19 @@ void wallet2::rescan_spent() COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp); for (size_t n = start_offset; n < start_offset + n_outputs; ++n) req.key_images.push_back(string_tools::pod_to_hex(m_transfers[n].m_key_image)); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, get_rpc_status(daemon_resp.status)); - THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, - "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + - std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs)); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "is_key_image_spent", error::is_key_image_spent_error, get_rpc_status(daemon_resp.status)); + THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error, + "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + + std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs)); + check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, n_outputs * COST_PER_KEY_IMAGE); + } + std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status)); } @@ -6172,12 +6257,13 @@ void wallet2::commit_tx(pending_tx& ptx) oreq.address = get_account().get_public_address_str(m_nettype); oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key); oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/submit_raw_tx", oreq, ores, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx"); - // MyMonero and OpenMonero use different status strings - THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error); + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST"); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx"); + // MyMonero and OpenMonero use different status strings + THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error); + } } else { @@ -6187,12 +6273,16 @@ void wallet2::commit_tx(pending_tx& ptx) req.do_not_relay = false; req.do_sanity_checks = true; COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/sendrawtransaction", req, daemon_send_resp, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction"); - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp)); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_send_resp, "sendrawtransaction", error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp)); + check_rpc_cost("/sendrawtransaction", daemon_send_resp.credits, pre_call_credits, COST_PER_TX_RELAY); + } + // sanity checks for (size_t idx: ptx.selected_transfers) { @@ -6965,7 +7055,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto return sign_multisig_tx_to_file(exported_txs, filename, txids); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const +uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const struct { @@ -7007,7 +7097,7 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const return 1; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_dynamic_base_fee_estimate() const +uint64_t wallet2::get_dynamic_base_fee_estimate() { uint64_t fee; boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_base_fee_estimate(FEE_ESTIMATE_GRACE_BLOCKS, fee); @@ -7018,7 +7108,7 @@ uint64_t wallet2::get_dynamic_base_fee_estimate() const return base_fee; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_base_fee() const +uint64_t wallet2::get_base_fee() { if(m_light_wallet) { @@ -7034,7 +7124,7 @@ uint64_t wallet2::get_base_fee() const return get_dynamic_base_fee_estimate(); } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_fee_quantization_mask() const +uint64_t wallet2::get_fee_quantization_mask() { if(m_light_wallet) { @@ -7051,7 +7141,7 @@ uint64_t wallet2::get_fee_quantization_mask() const return fee_quantization_mask; } //---------------------------------------------------------------------------------------------------- -int wallet2::get_fee_algorithm() const +int wallet2::get_fee_algorithm() { // changes at v3, v5, v8 if (use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0)) @@ -7063,7 +7153,7 @@ int wallet2::get_fee_algorithm() const return 0; } //------------------------------------------------------------------------------------------------------------------------------ -uint64_t wallet2::get_min_ring_size() const +uint64_t wallet2::get_min_ring_size() { if (use_fork_rules(8, 10)) return 11; @@ -7076,14 +7166,14 @@ uint64_t wallet2::get_min_ring_size() const return 0; } //------------------------------------------------------------------------------------------------------------------------------ -uint64_t wallet2::get_max_ring_size() const +uint64_t wallet2::get_max_ring_size() { if (use_fork_rules(8, 10)) return 11; return 0; } //------------------------------------------------------------------------------------------------------------------------------ -uint64_t wallet2::adjust_mixin(uint64_t mixin) const +uint64_t wallet2::adjust_mixin(uint64_t mixin) { const uint64_t min_ring_size = get_min_ring_size(); if (mixin + 1 < min_ring_size) @@ -7126,7 +7216,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority) // get the current full reward zone uint64_t block_weight_limit = 0; const auto result = m_node_rpc_proxy.get_block_weight_limit(block_weight_limit); - throw_on_rpc_response_error(result, "get_info"); + if (result) + return priority; const uint64_t full_reward_zone = block_weight_limit / 2; // get the last N block headers and sum the block sizes @@ -7138,14 +7229,18 @@ uint32_t wallet2::adjust_priority(uint32_t priority) } cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request getbh_req = AUTO_VAL_INIT(getbh_req); cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response getbh_res = AUTO_VAL_INIT(getbh_res); - m_daemon_rpc_mutex.lock(); getbh_req.start_height = m_blockchain.size() - N; getbh_req.end_height = m_blockchain.size() - 1; - bool r = invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange"); - THROW_WALLET_EXCEPTION_IF(getbh_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange"); - THROW_WALLET_EXCEPTION_IF(getbh_res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(getbh_res.status)); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + getbh_req.client = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, getbh_res, "getblockheadersrange", error::get_blocks_error, get_rpc_status(getbh_res.status)); + check_rpc_cost("/sendrawtransaction", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER); + } + if (getbh_res.headers.size() != N) { MERROR("Bad blockheaders size"); @@ -7362,17 +7457,18 @@ bool wallet2::find_and_save_rings(bool force) size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; for (size_t s = slice; s < slice + ntxes; ++s) req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txs_hashes[s])); - bool r; + { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_json("/gettransactions", req, res, rpc_timeout); + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size())); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, - "daemon returned wrong response for gettransactions, wrong txs count = " + - std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size())); MDEBUG("Scanning " << res.txs.size() << " transactions"); THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size"); @@ -7505,11 +7601,15 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } oreq.count = light_wallet_requested_outputs_count; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/get_random_outs", oreq, ores, rpc_timeout, "POST"); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); - THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST"); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs"); + THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error); + size_t n_outs = 0; for (const auto &e: ores.amount_outs) n_outs += e.outputs.size(); + } // Check if we got enough outputs for each amount for(auto& out: ores.amount_outs) { @@ -7606,7 +7706,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // check whether we're shortly after the fork uint64_t height; boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); - throw_on_rpc_response_error(result, "get_info"); + THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height"); bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY; bool is_after_segregation_fork = height >= segregation_fork_height; @@ -7645,12 +7745,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); req_t.unlocked = true; req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, get_rpc_status(resp_t.status)); + + { + 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 = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, get_rpc_status(resp_t.status)); + check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); + } } // if we want to segregate fake outs pre or post fork, get distribution @@ -7668,12 +7771,17 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> req_t.to_height = segregation_fork_height + 1; req_t.cumulative = true; req_t.binary = true; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, rpc_timeout * 1000); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, get_rpc_status(resp_t.status)); + + { + 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 = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_distribution", error::get_output_distribution, get_rpc_status(resp_t.status)); + uint64_t expected_cost = 0; + for (uint64_t amount: req_t.amounts) expected_cost += (amount ? COST_PER_OUTPUT_DISTRIBUTION : COST_PER_OUTPUT_DISTRIBUTION_0); + check_rpc_cost("get_output_distribution", resp_t.credits, pre_call_credits, expected_cost); + } // check we got all data for(size_t idx: selected_transfers) @@ -8017,15 +8125,18 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // get the keys for those req.get_txid = false; - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_bin("/get_outs.bin", req, daemon_resp, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_outs_error, get_rpc_status(daemon_resp.status)); - THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error, - "daemon returned wrong response for get_outs.bin, wrong amounts count = " + - std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "get_outs.bin", error::get_outs_error, get_rpc_status(daemon_resp.status)); + THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + check_rpc_cost("/get_outs.bin", daemon_resp.credits, pre_call_credits, daemon_resp.outs.size() * COST_PER_OUT); + } std::unordered_map<uint64_t, uint64_t> scanty_outs; size_t base = 0; @@ -10236,22 +10347,21 @@ uint8_t wallet2::get_current_hard_fork() return resp_t.version; } //---------------------------------------------------------------------------------------------------- -void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const +void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) { boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); - throw_on_rpc_response_error(result, "get_hard_fork_info"); } //---------------------------------------------------------------------------------------------------- -bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const +bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) { // TODO: How to get fork rule info from light wallet node? if(m_light_wallet) return true; uint64_t height, earliest_height; boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); - throw_on_rpc_response_error(result, "get_info"); + THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height"); result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); - throw_on_rpc_response_error(result, "get_hard_fork_info"); + THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height"); bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand if (close_enough) @@ -10261,7 +10371,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const return close_enough; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_upper_transaction_weight_limit() const +uint64_t wallet2::get_upper_transaction_weight_limit() { if (m_upper_transaction_weight_limit > 0) return m_upper_transaction_weight_limit; @@ -10292,7 +10402,7 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c return outputs; } //---------------------------------------------------------------------------------------------------- -std::vector<uint64_t> wallet2::get_unspent_amounts_vector(bool strict) const +std::vector<uint64_t> wallet2::get_unspent_amounts_vector(bool strict) { std::set<uint64_t> set; for (const auto &td: m_transfers) @@ -10313,18 +10423,22 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co { cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); - m_daemon_rpc_mutex.lock(); if (is_trusted_daemon()) req_t.amounts = get_unspent_amounts_vector(false); req_t.min_count = count; req_t.max_count = 0; req_t.unlocked = unlocked; req_t.recent_cutoff = 0; - bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + + { + 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 = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); + uint64_t cost = req_t.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size()); + check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, cost); + } std::set<uint64_t> mixable; for (const auto &i: resp_t.histogram) @@ -10352,19 +10466,22 @@ uint64_t wallet2::get_num_rct_outputs() { cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); - m_daemon_rpc_mutex.lock(); req_t.amounts.push_back(0); req_t.min_count = 0; req_t.max_count = 0; req_t.unlocked = true; req_t.recent_cutoff = 0; - bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); - THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); - THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); + + { + 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 = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status); + THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); + THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); + check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM); + } return resp_t.histogram[0].total_instances; } @@ -10485,17 +10602,22 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; req.prune = true; - m_daemon_rpc_mutex.lock(); - bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), - error::wallet_internal_error, "Failed to get transaction from daemon"); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + req.client = get_client_signature(); + uint64_t pre_call_credits = m_rpc_payment_state.credits; + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX); + } cryptonote::transaction tx; crypto::hash tx_hash{}; cryptonote::blobdata tx_data; crypto::hash tx_prefix_hash{}; - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + bool ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "Failed to validate transaction from daemon"); @@ -10533,16 +10655,19 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; + uint64_t pre_call_credits; { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_json("/gettransactions", req, res, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, - "daemon returned wrong response for gettransactions, wrong txs count = " + - std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::transaction tx; crypto::hash tx_hash; THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error, @@ -10583,16 +10708,18 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; + uint64_t pre_call_credits; { const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_json("/gettransactions", req, res, rpc_timeout); + pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, - "daemon returned wrong response for gettransactions, wrong txs count = " + - std::to_string(res.txs.size()) + ", expected 1"); cryptonote::transaction tx; crypto::hash tx_hash; @@ -10645,16 +10772,18 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; + uint64_t pre_call_credits; { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, ring_size * COST_PER_OUT); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, - "daemon returned wrong response for get_outs.bin, wrong amounts count = " + - std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); // copy pubkey pointers std::vector<const crypto::public_key *> p_output_keys; @@ -10701,16 +10830,18 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes req.prune = true; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; + uint64_t pre_call_credits; { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_json("/gettransactions", req, res, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, - "daemon returned wrong response for gettransactions, wrong txs count = " + - std::to_string(res.txs.size()) + ", expected 1"); cryptonote::transaction tx; crypto::hash tx_hash; @@ -10774,16 +10905,18 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); bool r; + uint64_t pre_call_credits; { - const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; - r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout); + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, req.outputs.size() * COST_PER_OUT); } - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); - THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, - "daemon returned wrong response for get_outs.bin, wrong amounts count = " + - std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); // copy pointers std::vector<const crypto::public_key *> p_output_keys; @@ -10873,11 +11006,17 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; req.prune = true; - m_daemon_rpc_mutex.lock(); - bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), - error::wallet_internal_error, "Failed to get transaction from daemon"); + + bool ok; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); + } cryptonote::transaction tx; crypto::hash tx_hash; @@ -10922,11 +11061,17 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; req.prune = true; - m_daemon_rpc_mutex.lock(); - bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), - error::wallet_internal_error, "Failed to get transaction from daemon"); + + bool ok; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); + } cryptonote::transaction tx; crypto::hash tx_hash; @@ -11077,11 +11222,17 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; req.prune = true; - m_daemon_rpc_mutex.lock(); - bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), - error::wallet_internal_error, "Failed to get transaction from daemon"); + + bool ok; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX); + } cryptonote::transaction tx; crypto::hash tx_hash; @@ -11369,22 +11520,33 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); gettx_req.decode_as_json = false; gettx_req.prune = true; - m_daemon_rpc_mutex.lock(); - bool ok = invoke_http_json("/gettransactions", gettx_req, gettx_res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), - error::wallet_internal_error, "Failed to get transaction from daemon"); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + gettx_req.client = get_client_signature(); + bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); + THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(), + error::wallet_internal_error, "Failed to get transaction from daemon"); + check_rpc_cost("/gettransactions", gettx_res.credits, pre_call_credits, gettx_res.txs.size() * COST_PER_TX); + } // check spent status COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req; COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res; for (size_t i = 0; i < proofs.size(); ++i) kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image)); - m_daemon_rpc_mutex.lock(); - ok = invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), - error::wallet_internal_error, "Failed to get key image spent status from daemon"); + + bool ok; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + kispent_req.client = get_client_signature(); + ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout); + THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(), + error::wallet_internal_error, "Failed to get key image spent status from daemon"); + check_rpc_cost("/is_key_image_spent", kispent_res.credits, pre_call_credits, kispent_res.spent_status.size() * COST_PER_KEY_IMAGE); + } total = spent = 0; for (size_t i = 0; i < proofs.size(); ++i) @@ -11470,7 +11632,7 @@ std::string wallet2::get_daemon_address() const return m_daemon_address; } -uint64_t wallet2::get_daemon_blockchain_height(string &err) const +uint64_t wallet2::get_daemon_blockchain_height(string &err) { uint64_t height; @@ -11936,15 +12098,18 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag if(check_spent) { PERF_TIMER(import_key_images_RPC); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); - THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status); - THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error, - "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + - std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size())); + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, daemon_resp, "is_key_image_spent"); + THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error, + "daemon returned wrong response for is_key_image_spent, wrong amounts count = " + + std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size())); + check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, daemon_resp.spent_status.size() * COST_PER_KEY_IMAGE); + } + for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n) { transfer_details &td = m_transfers[n + offset]; @@ -12022,13 +12187,16 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag PERF_TIMER_START(import_key_images_E); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json("/gettransactions", gettxs_req, gettxs_res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); - THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, - "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + gettxs_req.client = get_client_signature(); + uint64_t pre_call_credits = m_rpc_payment_state.credits; + bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, gettxs_res, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size())); + check_rpc_cost("/gettransactions", gettxs_res.credits, pre_call_credits, spent_txids.size() * COST_PER_TX); + } PERF_TIMER_STOP(import_key_images_E); // process each outgoing tx @@ -12957,7 +13125,17 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui height_mid, height_max }; - bool r = invoke_http_bin("/getblocks_by_height.bin", req, res, rpc_timeout); + + bool r; + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, m_http_client, rpc_timeout); + if (r && res.status == CORE_RPC_STATUS_OK) + check_rpc_cost("/getblocks_by_height.bin", res.credits, pre_call_credits, 3 * COST_PER_BLOCK); + } + if (!r || res.status != CORE_RPC_STATUS_OK) { std::ostringstream oss; @@ -13006,7 +13184,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui } } //---------------------------------------------------------------------------------------------------- -bool wallet2::is_synced() const +bool wallet2::is_synced() { uint64_t height; boost::optional<std::string> result = m_node_rpc_proxy.get_target_height(height); @@ -13026,16 +13204,19 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: // get txpool backlog cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response res = AUTO_VAL_INIT(res); - m_daemon_rpc_mutex.lock(); - bool r = invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "Failed to connect to daemon"); - THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_txpool_backlog"); - THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + uint64_t pre_call_credits = m_rpc_payment_state.credits; + req.client = get_client_signature(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_txpool_backlog", error::get_tx_pool_error); + check_rpc_cost("get_txpool_backlog", res.credits, pre_call_credits, COST_PER_TX_POOL_STATS * res.backlog.size()); + } uint64_t block_weight_limit = 0; const auto result = m_node_rpc_proxy.get_block_weight_limit(block_weight_limit); - throw_on_rpc_response_error(result, "get_info"); + THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Invalid block weight limit from daemon"); uint64_t full_reward_zone = block_weight_limit / 2; THROW_WALLET_EXCEPTION_IF(full_reward_zone == 0, error::wallet_internal_error, "Invalid block weight limit from daemon"); @@ -13207,22 +13388,22 @@ std::string wallet2::get_rpc_status(const std::string &s) const { if (m_trusted_daemon) return s; + if (s == CORE_RPC_STATUS_OK) + return s; + if (s == CORE_RPC_STATUS_BUSY || s == CORE_RPC_STATUS_PAYMENT_REQUIRED) + return s; return "<error>"; } //---------------------------------------------------------------------------------------------------- -void wallet2::throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const +void wallet2::throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const { - // no error - if (!status) - return; - - MERROR("RPC error: " << method << ": status " << *status); - + THROW_WALLET_EXCEPTION_IF(error.code, tools::error::wallet_coded_rpc_error, method, error.code, get_rpc_server_error_message(error.code)); + THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, method); // empty string -> not connection - THROW_WALLET_EXCEPTION_IF(status->empty(), tools::error::no_connection_to_daemon, method); + THROW_WALLET_EXCEPTION_IF(status.empty(), tools::error::no_connection_to_daemon, method); - THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method); - THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error"); + THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method); + THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_PAYMENT_REQUIRED, tools::error::payment_required, method); } //---------------------------------------------------------------------------------------------------- @@ -13374,4 +13555,25 @@ uint64_t wallet2::get_bytes_received() const { return m_http_client.get_bytes_received(); } +//---------------------------------------------------------------------------------------------------- +std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only) +{ + cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::response res = AUTO_VAL_INIT(res); + + req.white = true; + req.gray = !white_only; + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + bool r = epee::net_utils::invoke_http_json("/get_public_nodes", req, res, m_http_client, rpc_timeout); + THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_public_nodes"); + } + + std::vector<cryptonote::public_node> nodes; + nodes = res.white; + nodes.reserve(nodes.size() + res.gray.size()); + std::copy(res.gray.begin(), res.gray.end(), std::back_inserter(nodes)); + return nodes; +} } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 52118c426..640565a4e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -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) diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 9da9d109c..350f016c7 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -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) { diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index c861dca11..59529662f 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -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); diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 6ebaaa395..3e94e604a 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -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) diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h new file mode 100644 index 000000000..91803ff77 --- /dev/null +++ b/src/wallet/wallet_rpc_helpers.h @@ -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; + } + } + } +} diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp new file mode 100644 index 000000000..41696d13b --- /dev/null +++ b/src/wallet/wallet_rpc_payments.cpp @@ -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); +} +//---------------------------------------------------------------------------------------------------- +} diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 185a91e77..ec21b2897 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -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); diff --git a/tests/functional_tests/CMakeLists.txt b/tests/functional_tests/CMakeLists.txt index bc55da9e3..702ccc646 100644 --- a/tests/functional_tests/CMakeLists.txt +++ b/tests/functional_tests/CMakeLists.txt @@ -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( diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py index 324af624a..78e0d8952 100755 --- a/tests/functional_tests/blockchain.py +++ b/tests/functional_tests/blockchain.py @@ -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 diff --git a/tests/functional_tests/daemon_info.py b/tests/functional_tests/daemon_info.py index 4fa768b03..14fc14062 100755 --- a/tests/functional_tests/daemon_info.py +++ b/tests/functional_tests/daemon_info.py @@ -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() diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 9043565c7..5f2a3d077 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -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])) diff --git a/tests/functional_tests/make_test_signature.cc b/tests/functional_tests/make_test_signature.cc new file mode 100644 index 000000000..8c0333233 --- /dev/null +++ b/tests/functional_tests/make_test_signature.cc @@ -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; +} diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index a08a45ad4..ad646417e 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -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) diff --git a/tests/functional_tests/rpc_payment.py b/tests/functional_tests/rpc_payment.py new file mode 100755 index 000000000..fa72f547a --- /dev/null +++ b/tests/functional_tests/rpc_payment.py @@ -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() diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 1d6c57c56..f811535b1 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -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,28 +141,35 @@ 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', - 'jsonrpc': '2.0', - 'id': '0' + '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', - 'jsonrpc': '2.0', - 'id': '0' + 'method': 'hard_fork_info', + 'params': { + 'client': client, + }, + 'jsonrpc': '2.0', + 'id': '0' } return self.rpc.send_json_rpc_request(hard_fork_info) @@ -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) |