aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--contrib/epee/include/int-util.h17
-rw-r--r--contrib/epee/include/math_helper.h5
-rw-r--r--contrib/epee/include/serialization/keyvalue_serialization.h24
-rw-r--r--contrib/epee/include/storages/http_abstract_invoke.h11
-rw-r--r--contrib/epee/src/mlog.cpp2
-rw-r--r--src/common/perf_timer.h13
-rw-r--r--src/cryptonote_basic/connection_context.h3
-rw-r--r--src/cryptonote_config.h3
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_defs.h2
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl1
-rw-r--r--src/daemon/command_parser_executor.cpp7
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp5
-rw-r--r--src/daemon/core.h22
-rw-r--r--src/daemon/rpc_command_executor.cpp52
-rw-r--r--src/daemon/rpc_command_executor.h2
-rw-r--r--src/p2p/net_node.h7
-rw-r--r--src/p2p/net_node.inl11
-rw-r--r--src/p2p/net_peerlist.h5
-rw-r--r--src/p2p/net_peerlist_boost_serialization.h9
-rw-r--r--src/p2p/p2p_protocol_defs.h9
-rw-r--r--src/rpc/CMakeLists.txt11
-rw-r--r--src/rpc/core_rpc_server.cpp757
-rw-r--r--src/rpc/core_rpc_server.h21
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h798
-rw-r--r--src/rpc/core_rpc_server_error_codes.h29
-rw-r--r--src/rpc/message_data_structs.h1
-rw-r--r--src/rpc/rpc_payment.cpp402
-rw-r--r--src/rpc/rpc_payment.h146
-rw-r--r--src/rpc/rpc_payment_costs.h49
-rw-r--r--src/rpc/rpc_payment_signature.cpp107
-rw-r--r--src/rpc/rpc_payment_signature.h39
-rw-r--r--src/serialization/json_object.cpp4
-rw-r--r--src/simplewallet/simplewallet.cpp382
-rw-r--r--src/simplewallet/simplewallet.h17
-rw-r--r--src/wallet/CMakeLists.txt6
-rw-r--r--src/wallet/node_rpc_proxy.cpp174
-rw-r--r--src/wallet/node_rpc_proxy.h65
-rw-r--r--src/wallet/wallet2.cpp665
-rw-r--r--src/wallet/wallet2.h83
-rw-r--r--src/wallet/wallet_args.cpp4
-rw-r--r--src/wallet/wallet_args.h1
-rw-r--r--src/wallet/wallet_errors.h23
-rw-r--r--src/wallet/wallet_rpc_helpers.h94
-rw-r--r--src/wallet/wallet_rpc_payments.cpp196
-rw-r--r--src/wallet/wallet_rpc_server.cpp14
-rw-r--r--tests/functional_tests/CMakeLists.txt14
-rwxr-xr-xtests/functional_tests/blockchain.py2
-rwxr-xr-xtests/functional_tests/daemon_info.py3
-rwxr-xr-xtests/functional_tests/functional_tests_rpc.py19
-rw-r--r--tests/functional_tests/make_test_signature.cc60
-rwxr-xr-xtests/functional_tests/mining.py2
-rwxr-xr-xtests/functional_tests/rpc_payment.py412
-rw-r--r--utils/python-rpc/framework/daemon.py162
54 files changed, 4139 insertions, 835 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 bc5c8d6de..a9cffbb51 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 75c54d048..f552db2b8 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)
@@ -2362,4 +2371,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/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 bc7fe918f..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,11 +493,13 @@ 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;
+ CHECK_PAYMENT(req, res, 1);
+
// quick check for noop
if (!req.block_ids.empty())
{
@@ -377,14 +515,29 @@ namespace cryptonote
}
}
+ 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, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT))
+ 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());
@@ -436,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;
@@ -463,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;
@@ -471,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;
@@ -497,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))
{
@@ -510,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;
@@ -544,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;
@@ -588,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)
{
@@ -606,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)
{
@@ -832,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)
{
@@ -901,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))
@@ -980,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))
@@ -1031,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())
{
@@ -1051,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();
@@ -1092,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";
@@ -1104,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;
@@ -1121,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;
@@ -1147,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);
@@ -1186,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);
@@ -1201,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";
@@ -1214,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;
@@ -1223,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;
}
@@ -1307,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();
@@ -1317,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)
@@ -1333,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)
@@ -1375,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;
@@ -1427,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))
@@ -1449,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);
@@ -1507,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)
@@ -1563,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();
@@ -1738,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);
@@ -1769,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);
@@ -1829,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;
@@ -1841,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);
@@ -1881,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;
@@ -1892,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);
@@ -1915,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())
{
@@ -1977,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();
@@ -1988,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();
@@ -2009,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();
@@ -2073,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)
{
@@ -2121,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;
@@ -2176,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";
@@ -2213,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;
@@ -2226,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;
@@ -2236,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;
@@ -2249,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();
@@ -2282,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;
@@ -2295,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
@@ -2335,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();
@@ -2345,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();
@@ -2355,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())
{
@@ -2457,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);
@@ -2469,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 = "";
@@ -2515,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);
@@ -2544,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))
{
@@ -2562,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
@@ -2597,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)
@@ -2638,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()))
@@ -2655,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"
@@ -2700,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..1f12815b3 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(
@@ -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..1f8286951 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()
@@ -1228,26 +1232,26 @@ namespace cryptonote
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 +1261,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 +1283,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 +1305,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 +1380,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 +1404,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 +1426,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 +1455,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 +1523,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 +1545,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 +1567,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 +1582,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 +1623,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 +1642,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 +1661,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 +1685,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 +1714,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 +1741,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 +1767,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 +1788,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 +1799,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 +1819,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 +1856,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 +1905,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 +1927,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 +1936,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 +1965,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 +1979,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 +2003,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 +2032,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 +2059,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 +2090,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 +2104,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 +2127,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 +2141,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 +2163,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 +2201,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 +2211,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 +2225,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 +2235,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 +2288,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 +2502,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 +2527,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..066557f42
--- /dev/null
+++ b/src/rpc/rpc_payment_costs.h
@@ -0,0 +1,49 @@
+// 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
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 6a54c24fb..d203f905d 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"), ""};
@@ -248,6 +263,9 @@ namespace
const char* USAGE_LOCK("lock");
const char* USAGE_NET_STATS("net_stats");
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 +508,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 +626,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 +1934,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;
@@ -2214,6 +2311,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 +2743,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 +3032,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 +3216,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."));
@@ -3333,6 +3533,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 +3614,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 +3678,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 +4445,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 +4971,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 +5239,57 @@ 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
+ {
+ const std::string host = node.host + ":" + std::to_string(node.rpc_port);
+ if (host == daemon_url)
+ {
+ claimed_cph = node.rpc_credits_per_hash;
+ bool payment_required;
+ uint64_t credits, diff, credits_per_hash_found, height;
+ uint32_t cookie;
+ cryptonote::blobdata hashing_blob;
+ if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, 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;
@@ -5066,6 +5347,8 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args)
catch (const std::exception &e) { }
}
success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted"));
+
+ m_daemon_rpc_payment_message_displayed = false;
} else {
fail_msg_writer() << tr("This does not seem to be a valid daemon URL.");
}
@@ -5304,6 +5587,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 +5918,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 +6026,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 +8768,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 +8822,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 +10095,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..1ce135287 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,6 +253,9 @@ 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 welcome(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args);
@@ -325,6 +331,9 @@ 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);
//----------------- i_wallet2_callback ---------------------
virtual void on_new_block(uint64_t height, const cryptonote::block& block);
@@ -439,7 +448,15 @@ 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;
+
// 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..aecd01dec 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"
@@ -1133,13 +1138,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 +1169,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 +1278,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 +2523,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 +2548,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 +2721,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 +2783,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
catch(...)
{
error = true;
+ exception = std::current_exception();
}
}
@@ -2806,12 +2830,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 +2980,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 +3257,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 +3323,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 +3345,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 +3442,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)
@@ -3745,6 +3789,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 +3929,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 +4140,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 +5497,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 +5551,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 +5919,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 +6251,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 +6267,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 +7049,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 +7091,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 +7102,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 +7118,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 +7135,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 +7147,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 +7160,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 +7210,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 +7223,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 +7451,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 +7595,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 +7700,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 +7739,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 +7765,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 +8119,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 +10341,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 +10365,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 +10396,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 +10417,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 +10460,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 +10596,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 +10649,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 +10702,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 +10766,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 +10824,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 +10899,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 +11000,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 +11055,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 +11216,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 +11514,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 +11626,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 +12092,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 +12181,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 +13119,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 +13178,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 +13198,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 +13382,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);
}
//----------------------------------------------------------------------------------------------------
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 52118c426..3635cf227 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;
@@ -988,6 +999,9 @@ private:
if(ver < 28)
return;
a & m_cold_key_images;
+ if(ver < 29)
+ return;
+ a & m_rpc_client_secret_key;
}
/*!
@@ -1061,6 +1075,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 +1130,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 +1233,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 +1376,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 +1404,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 +1420,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 +1476,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 +1560,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 +1572,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 +1618,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 dbd42ab81..c128210c4 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)