diff options
author | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2018-02-11 15:15:56 +0000 |
---|---|---|
committer | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2019-10-25 09:34:38 +0000 |
commit | 2899379791b7542e4eb920b5d9d58cf232806937 (patch) | |
tree | 7e97a95103837852d5c4e86ee544815d9a7a6671 | |
parent | add a quick early out to get_blocks.bin when up to date (diff) | |
download | monero-2899379791b7542e4eb920b5d9d58cf232806937.tar.xz |
daemon, wallet: new pay for RPC use system
Daemons intended for public use can be set up to require payment
in the form of hashes in exchange for RPC service. This enables
public daemons to receive payment for their work over a large
number of calls. This system behaves similarly to a pool, so
payment takes the form of valid blocks every so often, yielding
a large one off payment, rather than constant micropayments.
This system can also be used by third parties as a "paywall"
layer, where users of a service can pay for use by mining Monero
to the service provider's address. An example of this for web
site access is Primo, a Monero mining based website "paywall":
https://github.com/selene-kovri/primo
This has some advantages:
- incentive to run a node providing RPC services, thereby promoting the availability of third party nodes for those who can't run their own
- incentive to run your own node instead of using a third party's, thereby promoting decentralization
- decentralized: payment is done between a client and server, with no third party needed
- private: since the system is "pay as you go", you don't need to identify yourself to claim a long lived balance
- no payment occurs on the blockchain, so there is no extra transactional load
- one may mine with a beefy server, and use those credits from a phone, by reusing the client ID (at the cost of some privacy)
- no barrier to entry: anyone may run a RPC node, and your expected revenue depends on how much work you do
- Sybil resistant: if you run 1000 idle RPC nodes, you don't magically get more revenue
- no large credit balance maintained on servers, so they have no incentive to exit scam
- you can use any/many node(s), since there's little cost in switching servers
- market based prices: competition between servers to lower costs
- incentive for a distributed third party node system: if some public nodes are overused/slow, traffic can move to others
- increases network security
- helps counteract mining pools' share of the network hash rate
- zero incentive for a payer to "double spend" since a reorg does not give any money back to the miner
And some disadvantages:
- low power clients will have difficulty mining (but one can optionally mine in advance and/or with a faster machine)
- payment is "random", so a server might go a long time without a block before getting one
- a public node's overall expected payment may be small
Public nodes are expected to compete to find a suitable level for
cost of service.
The daemon can be set up this way to require payment for RPC services:
monerod --rpc-payment-address 4xxxxxx \
--rpc-payment-credits 250 --rpc-payment-difficulty 1000
These values are an example only.
The --rpc-payment-difficulty switch selects how hard each "share" should
be, similar to a mining pool. The higher the difficulty, the fewer
shares a client will find.
The --rpc-payment-credits switch selects how many credits are awarded
for each share a client finds.
Considering both options, clients will be awarded credits/difficulty
credits for every hash they calculate. For example, in the command line
above, 0.25 credits per hash. A client mining at 100 H/s will therefore
get an average of 25 credits per second.
For reference, in the current implementation, a credit is enough to
sync 20 blocks, so a 100 H/s client that's just starting to use Monero
and uses this daemon will be able to sync 500 blocks per second.
The wallet can be set to automatically mine if connected to a daemon
which requires payment for RPC usage. It will try to keep a balance
of 50000 credits, stopping mining when it's at this level, and starting
again as credits are spent. With the example above, a new client will
mine this much credits in about half an hour, and this target is enough
to sync 500000 blocks (currently about a third of the monero blockchain).
There are three new settings in the wallet:
- credits-target: this is the amount of credits a wallet will try to
reach before stopping mining. The default of 0 means 50000 credits.
- auto-mine-for-rpc-payment-threshold: this controls the minimum
credit rate which the wallet considers worth mining for. If the
daemon credits less than this ratio, the wallet will consider mining
to be not worth it. In the example above, the rate is 0.25
- persistent-rpc-client-id: if set, this allows the wallet to reuse
a client id across runs. This means a public node can tell a wallet
that's connecting is the same as one that connected previously, but
allows a wallet to keep their credit balance from one run to the
other. Since the wallet only mines to keep a small credit balance,
this is not normally worth doing. However, someone may want to mine
on a fast server, and use that credit balance on a low power device
such as a phone. If left unset, a new client ID is generated at
each wallet start, for privacy reasons.
To mine and use a credit balance on two different devices, you can
use the --rpc-client-secret-key switch. A wallet's client secret key
can be found using the new rpc_payments command in the wallet.
Note: anyone knowing your RPC client secret key is able to use your
credit balance.
The wallet has a few new commands too:
- start_mining_for_rpc: start mining to acquire more credits,
regardless of the auto mining settings
- stop_mining_for_rpc: stop mining to acquire more credits
- rpc_payments: display information about current credits with
the currently selected daemon
The node has an extra command:
- rpc_payments: display information about clients and their
balances
The node will forget about any balance for clients which have
been inactive for 6 months. Balances carry over on node restart.
Diffstat (limited to '')
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) |