mirror of
https://git.wownero.com/wownero/wownero.git
synced 2024-12-22 17:58:53 +00:00
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.
This commit is contained in:
parent
b3a9a4d99d
commit
2899379791
@ -132,6 +132,23 @@ static inline uint32_t div128_32(uint64_t dividend_hi, uint64_t dividend_lo, uin
|
||||
// Long divisor with 2^64 base
|
||||
void div128_64(uint64_t dividend_hi, uint64_t dividend_lo, uint64_t divisor, uint64_t* quotient_hi, uint64_t *quotient_lo, uint64_t *remainder_hi, uint64_t *remainder_lo);
|
||||
|
||||
static inline void add64clamp(uint64_t *value, uint64_t add)
|
||||
{
|
||||
static const uint64_t maxval = (uint64_t)-1;
|
||||
if (*value > maxval - add)
|
||||
*value = maxval;
|
||||
else
|
||||
*value += add;
|
||||
}
|
||||
|
||||
static inline void sub64clamp(uint64_t *value, uint64_t sub)
|
||||
{
|
||||
if (*value < sub)
|
||||
*value = 0;
|
||||
else
|
||||
*value -= sub;
|
||||
}
|
||||
|
||||
#define IDENT16(x) ((uint16_t) (x))
|
||||
#define IDENT32(x) ((uint32_t) (x))
|
||||
#define IDENT64(x) ((uint64_t) (x))
|
||||
|
@ -259,6 +259,11 @@ namespace math_helper
|
||||
m_last_worked_time = get_time();
|
||||
}
|
||||
|
||||
void trigger()
|
||||
{
|
||||
m_last_worked_time = 0;
|
||||
}
|
||||
|
||||
template<class functor_t>
|
||||
bool do_call(functor_t functr)
|
||||
{
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <boost/utility/value_init.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include "misc_log_ex.h"
|
||||
@ -45,18 +46,20 @@ public: \
|
||||
template<class t_storage> \
|
||||
bool store( t_storage& st, typename t_storage::hsection hparent_section = nullptr) const\
|
||||
{\
|
||||
return serialize_map<true>(*this, st, hparent_section);\
|
||||
using type = typename std::remove_const<typename std::remove_reference<decltype(*this)>::type>::type; \
|
||||
auto &self = const_cast<type&>(*this); \
|
||||
return self.template serialize_map<true>(st, hparent_section); \
|
||||
}\
|
||||
template<class t_storage> \
|
||||
bool _load( t_storage& stg, typename t_storage::hsection hparent_section = nullptr)\
|
||||
{\
|
||||
return serialize_map<false>(*this, stg, hparent_section);\
|
||||
return serialize_map<false>(stg, hparent_section);\
|
||||
}\
|
||||
template<class t_storage> \
|
||||
bool load( t_storage& stg, typename t_storage::hsection hparent_section = nullptr)\
|
||||
{\
|
||||
try{\
|
||||
return serialize_map<false>(*this, stg, hparent_section);\
|
||||
return serialize_map<false>(stg, hparent_section);\
|
||||
}\
|
||||
catch(const std::exception& err) \
|
||||
{ \
|
||||
@ -65,13 +68,22 @@ public: \
|
||||
return false; \
|
||||
}\
|
||||
}\
|
||||
template<bool is_store, class this_type, class t_storage> \
|
||||
static bool serialize_map(this_type& this_ref, t_storage& stg, typename t_storage::hsection hparent_section) \
|
||||
{
|
||||
/*template<typename T> T& this_type_resolver() { return *this; }*/ \
|
||||
/*using this_type = std::result_of<decltype(this_type_resolver)>::type;*/ \
|
||||
template<bool is_store, class t_storage> \
|
||||
bool serialize_map(t_storage& stg, typename t_storage::hsection hparent_section) \
|
||||
{ \
|
||||
decltype(*this) &this_ref = *this;
|
||||
|
||||
#define KV_SERIALIZE_N(varialble, val_name) \
|
||||
epee::serialization::selector<is_store>::serialize(this_ref.varialble, stg, hparent_section, val_name);
|
||||
|
||||
#define KV_SERIALIZE_PARENT(type) \
|
||||
do { \
|
||||
if (!((type*)this)->serialize_map<is_store, t_storage>(stg, hparent_section)) \
|
||||
return false; \
|
||||
} while(0);
|
||||
|
||||
template<typename T> inline void serialize_default(const T &t, T v) { }
|
||||
template<typename T> inline void serialize_default(T &t, T v) { t = v; }
|
||||
|
||||
|
@ -101,7 +101,7 @@ namespace epee
|
||||
}
|
||||
|
||||
template<class t_request, class t_response, class t_transport>
|
||||
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
|
||||
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, epee::json_rpc::error &error_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
|
||||
{
|
||||
epee::json_rpc::request<t_request> req_t = AUTO_VAL_INIT(req_t);
|
||||
req_t.jsonrpc = "2.0";
|
||||
@ -111,10 +111,12 @@ namespace epee
|
||||
epee::json_rpc::response<t_response, epee::json_rpc::error> resp_t = AUTO_VAL_INIT(resp_t);
|
||||
if(!epee::net_utils::invoke_http_json(uri, req_t, resp_t, transport, timeout, http_method))
|
||||
{
|
||||
error_struct = {};
|
||||
return false;
|
||||
}
|
||||
if(resp_t.error.code || resp_t.error.message.size())
|
||||
{
|
||||
error_struct = resp_t.error;
|
||||
LOG_ERROR("RPC call of \"" << req_t.method << "\" returned error: " << resp_t.error.code << ", message: " << resp_t.error.message);
|
||||
return false;
|
||||
}
|
||||
@ -122,6 +124,13 @@ namespace epee
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class t_request, class t_response, class t_transport>
|
||||
bool invoke_http_json_rpc(const boost::string_ref uri, std::string method_name, const t_request& out_struct, t_response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
|
||||
{
|
||||
epee::json_rpc::error error_struct;
|
||||
return invoke_http_json_rpc(uri, method_name, out_struct, result_struct, error_struct, transport, timeout, http_method, req_id);
|
||||
}
|
||||
|
||||
template<class t_command, class t_transport>
|
||||
bool invoke_http_json_rpc(const boost::string_ref uri, typename t_command::request& out_struct, typename t_command::response& result_struct, t_transport& transport, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0")
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ static const char *get_default_categories(int level)
|
||||
switch (level)
|
||||
{
|
||||
case 0:
|
||||
categories = "*:WARNING,net:FATAL,net.http:FATAL,net.ssl:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,serialization:FATAL,stacktrace:INFO,logging:INFO,msgwriter:INFO";
|
||||
categories = "*:WARNING,net:FATAL,net.http:FATAL,net.ssl:FATAL,net.p2p:FATAL,net.cn:FATAL,global:INFO,verify:FATAL,serialization:FATAL,daemon.rpc.payment:ERROR,stacktrace:INFO,logging:INFO,msgwriter:INFO";
|
||||
break;
|
||||
case 1:
|
||||
categories = "*:INFO,global:INFO,stacktrace:INFO,logging:INFO,msgwriter:INFO,perf.*:DEBUG";
|
||||
|
@ -76,14 +76,15 @@ private:
|
||||
|
||||
void set_performance_timer_log_level(el::Level level);
|
||||
|
||||
#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level)
|
||||
#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l)
|
||||
#define PERF_TIMER_NAME(name) pt_##name
|
||||
#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level)
|
||||
#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)t_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l)
|
||||
#define PERF_TIMER(name) PERF_TIMER_UNIT(name, 1000000)
|
||||
#define PERF_TIMER_L(name, l) PERF_TIMER_UNIT_L(name, 1000000, l)
|
||||
#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> pt_##name(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info))
|
||||
#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> PERF_TIMER_NAME(name)(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info))
|
||||
#define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000000)
|
||||
#define PERF_TIMER_STOP(name) do { pt_##name.reset(NULL); } while(0)
|
||||
#define PERF_TIMER_PAUSE(name) pt_##name->pause()
|
||||
#define PERF_TIMER_RESUME(name) pt_##name->resume()
|
||||
#define PERF_TIMER_STOP(name) do { PERF_TIMER_NAME(name).reset(NULL); } while(0)
|
||||
#define PERF_TIMER_PAUSE(name) PERF_TIMER_NAME(name)->pause()
|
||||
#define PERF_TIMER_RESUME(name) PERF_TIMER_NAME(name)->resume()
|
||||
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace cryptonote
|
||||
{
|
||||
cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0),
|
||||
m_last_request_time(boost::date_time::not_a_date_time), m_callback_request_count(0),
|
||||
m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_anchor(false) {}
|
||||
m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_rpc_credits_per_hash(0), m_anchor(false) {}
|
||||
|
||||
enum state
|
||||
{
|
||||
@ -64,6 +64,7 @@ namespace cryptonote
|
||||
crypto::hash m_last_known_hash;
|
||||
uint32_t m_pruning_seed;
|
||||
uint16_t m_rpc_port;
|
||||
uint32_t m_rpc_credits_per_hash;
|
||||
bool m_anchor;
|
||||
//size_t m_score; TODO: add score calculations
|
||||
};
|
||||
|
@ -148,6 +148,7 @@
|
||||
#define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb"
|
||||
#define CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME "lock.mdb"
|
||||
#define P2P_NET_DATA_FILENAME "p2pstate.bin"
|
||||
#define RPC_PAYMENTS_DATA_FILENAME "rpcpayments.bin"
|
||||
#define MINER_CONFIG_FILE_NAME "miner_conf.json"
|
||||
|
||||
#define THREAD_STACK_SIZE 5 * 1024 * 1024
|
||||
@ -180,6 +181,8 @@
|
||||
#define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved
|
||||
//#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED
|
||||
|
||||
#define RPC_CREDITS_PER_HASH_SCALE ((float)(1<<24))
|
||||
|
||||
// New constants are intended to go here
|
||||
namespace config
|
||||
{
|
||||
|
@ -56,6 +56,7 @@ namespace cryptonote
|
||||
std::string ip;
|
||||
std::string port;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
|
||||
std::string peer_id;
|
||||
|
||||
@ -94,6 +95,7 @@ namespace cryptonote
|
||||
KV_SERIALIZE(ip)
|
||||
KV_SERIALIZE(port)
|
||||
KV_SERIALIZE(rpc_port)
|
||||
KV_SERIALIZE(rpc_credits_per_hash)
|
||||
KV_SERIALIZE(peer_id)
|
||||
KV_SERIALIZE(recv_count)
|
||||
KV_SERIALIZE(recv_idle_time)
|
||||
|
@ -246,6 +246,7 @@ namespace cryptonote
|
||||
cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port());
|
||||
}
|
||||
cnx.rpc_port = cntxt.m_rpc_port;
|
||||
cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash;
|
||||
|
||||
std::stringstream peer_id_str;
|
||||
peer_id_str << std::hex << std::setw(16) << peer_id;
|
||||
|
@ -794,6 +794,13 @@ bool t_command_parser_executor::pop_blocks(const std::vector<std::string>& args)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool t_command_parser_executor::rpc_payments(const std::vector<std::string>& args)
|
||||
{
|
||||
if (args.size() != 0) return false;
|
||||
|
||||
return m_executor.rpc_payments();
|
||||
}
|
||||
|
||||
bool t_command_parser_executor::version(const std::vector<std::string>& args)
|
||||
{
|
||||
std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << std::endl;
|
||||
|
@ -143,6 +143,8 @@ public:
|
||||
|
||||
bool pop_blocks(const std::vector<std::string>& args);
|
||||
|
||||
bool rpc_payments(const std::vector<std::string>& args);
|
||||
|
||||
bool version(const std::vector<std::string>& args);
|
||||
|
||||
bool prune_blockchain(const std::vector<std::string>& args);
|
||||
|
@ -295,6 +295,11 @@ t_command_server::t_command_server(
|
||||
, "pop_blocks <nblocks>"
|
||||
, "Remove blocks from end of blockchain"
|
||||
);
|
||||
m_command_lookup.set_handler(
|
||||
"rpc_payments"
|
||||
, std::bind(&t_command_parser_executor::rpc_payments, &m_parser, p::_1)
|
||||
, "Print information about RPC payments."
|
||||
);
|
||||
m_command_lookup.set_handler(
|
||||
"version"
|
||||
, std::bind(&t_command_parser_executor::version, &m_parser, p::_1)
|
||||
|
@ -59,6 +59,18 @@ public:
|
||||
: m_core{nullptr}
|
||||
, m_vm_HACK{vm}
|
||||
{
|
||||
//initialize core here
|
||||
MGINFO("Initializing core...");
|
||||
#if defined(PER_BLOCK_CHECKPOINT)
|
||||
const cryptonote::GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData;
|
||||
#else
|
||||
const cryptonote::GetCheckpointsCallback& get_checkpoints = nullptr;
|
||||
#endif
|
||||
if (!m_core.init(m_vm_HACK, nullptr, get_checkpoints))
|
||||
{
|
||||
throw std::runtime_error("Failed to initialize core");
|
||||
}
|
||||
MGINFO("Core initialized OK");
|
||||
}
|
||||
|
||||
// TODO - get rid of circular dependencies in internals
|
||||
@ -69,18 +81,6 @@ public:
|
||||
|
||||
bool run()
|
||||
{
|
||||
//initialize core here
|
||||
MGINFO("Initializing core...");
|
||||
#if defined(PER_BLOCK_CHECKPOINT)
|
||||
const cryptonote::GetCheckpointsCallback& get_checkpoints = blocks::GetCheckpointsData;
|
||||
#else
|
||||
const cryptonote::GetCheckpointsCallback& get_checkpoints = nullptr;
|
||||
#endif
|
||||
if (!m_core.init(m_vm_HACK, nullptr, get_checkpoints))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
MGINFO("Core initialized OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "cryptonote_core/cryptonote_core.h"
|
||||
#include "cryptonote_basic/difficulty.h"
|
||||
#include "cryptonote_basic/hardfork.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
#include <boost/format.hpp>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
@ -60,6 +61,13 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
std::string print_float(float f, int prec)
|
||||
{
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%*.*f", prec, prec, f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void print_peer(std::string const & prefix, cryptonote::peer const & peer, bool pruned_only, bool publicrpc_only)
|
||||
{
|
||||
if (pruned_only && peer.pruning_seed == 0)
|
||||
@ -77,8 +85,9 @@ namespace {
|
||||
epee::string_tools::xtype_to_string(peer.port, port_str);
|
||||
std::string addr_str = peer.host + ":" + port_str;
|
||||
std::string rpc_port = peer.rpc_port ? std::to_string(peer.rpc_port) : "-";
|
||||
std::string rpc_credits_per_hash = peer.rpc_credits_per_hash ? print_float(peer.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE, 2) : "-";
|
||||
std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed);
|
||||
tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % pruning_seed % elapsed;
|
||||
tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % rpc_credits_per_hash % pruning_seed % elapsed;
|
||||
}
|
||||
|
||||
void print_block_header(cryptonote::block_header_response const & header)
|
||||
@ -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
|
||||
|
@ -167,6 +167,8 @@ public:
|
||||
const std::string &address,
|
||||
const std::string &username,
|
||||
const std::string &password);
|
||||
|
||||
bool rpc_payments();
|
||||
};
|
||||
|
||||
} // namespace daemonize
|
||||
|
@ -231,6 +231,7 @@ namespace nodetool
|
||||
: m_payload_handler(payload_handler),
|
||||
m_external_port(0),
|
||||
m_rpc_port(0),
|
||||
m_rpc_credits_per_hash(0),
|
||||
m_allow_local_ip(false),
|
||||
m_hide_my_port(false),
|
||||
m_igd(no_igd),
|
||||
@ -431,6 +432,11 @@ namespace nodetool
|
||||
m_rpc_port = rpc_port;
|
||||
}
|
||||
|
||||
void set_rpc_credits_per_hash(uint32_t rpc_credits_per_hash)
|
||||
{
|
||||
m_rpc_credits_per_hash = rpc_credits_per_hash;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_config_folder;
|
||||
|
||||
@ -440,6 +446,7 @@ namespace nodetool
|
||||
uint32_t m_listening_port_ipv6;
|
||||
uint32_t m_external_port;
|
||||
uint16_t m_rpc_port;
|
||||
uint32_t m_rpc_credits_per_hash;
|
||||
bool m_allow_local_ip;
|
||||
bool m_hide_my_port;
|
||||
igd_t m_igd;
|
||||
|
@ -1057,7 +1057,8 @@ namespace nodetool
|
||||
|
||||
pi = context.peer_id = rsp.node_data.peer_id;
|
||||
context.m_rpc_port = rsp.node_data.rpc_port;
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
|
||||
context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash;
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
|
||||
|
||||
// move
|
||||
for (auto const& zone : m_network_zones)
|
||||
@ -1123,7 +1124,7 @@ namespace nodetool
|
||||
add_host_fail(context.m_remote_address);
|
||||
}
|
||||
if(!context.m_is_income)
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
|
||||
if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false))
|
||||
{
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id );
|
||||
@ -1292,6 +1293,7 @@ namespace nodetool
|
||||
pe_local.last_seen = static_cast<int64_t>(last_seen);
|
||||
pe_local.pruning_seed = con->m_pruning_seed;
|
||||
pe_local.rpc_port = con->m_rpc_port;
|
||||
pe_local.rpc_credits_per_hash = con->m_rpc_credits_per_hash;
|
||||
zone.m_peerlist.append_with_peer_white(pe_local);
|
||||
//update last seen and push it to peerlist manager
|
||||
|
||||
@ -1922,6 +1924,7 @@ namespace nodetool
|
||||
else
|
||||
node_data.my_port = 0;
|
||||
node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0;
|
||||
node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0;
|
||||
node_data.network_id = m_network_id;
|
||||
return true;
|
||||
}
|
||||
@ -2366,6 +2369,7 @@ namespace nodetool
|
||||
context.peer_id = arg.node_data.peer_id;
|
||||
context.m_in_timedsync = false;
|
||||
context.m_rpc_port = arg.node_data.rpc_port;
|
||||
context.m_rpc_credits_per_hash = arg.node_data.rpc_credits_per_hash;
|
||||
|
||||
if(arg.node_data.my_port && zone.m_can_pingback)
|
||||
{
|
||||
@ -2393,6 +2397,7 @@ namespace nodetool
|
||||
pe.id = peer_id_l;
|
||||
pe.pruning_seed = context.m_pruning_seed;
|
||||
pe.rpc_port = context.m_rpc_port;
|
||||
pe.rpc_credits_per_hash = context.m_rpc_credits_per_hash;
|
||||
this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe);
|
||||
LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l);
|
||||
});
|
||||
@ -2710,7 +2715,7 @@ namespace nodetool
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port);
|
||||
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port, pe.rpc_credits_per_hash);
|
||||
LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id));
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ namespace nodetool
|
||||
bool append_with_peer_white(const peerlist_entry& pr);
|
||||
bool append_with_peer_gray(const peerlist_entry& pr);
|
||||
bool append_with_peer_anchor(const anchor_peerlist_entry& ple);
|
||||
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port);
|
||||
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash);
|
||||
bool set_peer_unreachable(const peerlist_entry& pr);
|
||||
bool is_host_allowed(const epee::net_utils::network_address &address);
|
||||
bool get_random_gray_peer(peerlist_entry& pe);
|
||||
@ -315,7 +315,7 @@ namespace nodetool
|
||||
}
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
inline
|
||||
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port)
|
||||
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash)
|
||||
{
|
||||
TRY_ENTRY();
|
||||
CRITICAL_REGION_LOCAL(m_peerlist_lock);
|
||||
@ -326,6 +326,7 @@ namespace nodetool
|
||||
ple.last_seen = time(NULL);
|
||||
ple.pruning_seed = pruning_seed;
|
||||
ple.rpc_port = rpc_port;
|
||||
ple.rpc_credits_per_hash = rpc_credits_per_hash;
|
||||
return append_with_peer_white(ple);
|
||||
CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include "common/pruning.h"
|
||||
#endif
|
||||
|
||||
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2)
|
||||
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3)
|
||||
|
||||
namespace boost
|
||||
{
|
||||
@ -241,6 +241,13 @@ namespace boost
|
||||
return;
|
||||
}
|
||||
a & pl.rpc_port;
|
||||
if (ver < 3)
|
||||
{
|
||||
if (!typename Archive::is_saving())
|
||||
pl.rpc_credits_per_hash = 0;
|
||||
return;
|
||||
}
|
||||
a & pl.rpc_credits_per_hash;
|
||||
}
|
||||
|
||||
template <class Archive, class ver_type>
|
||||
|
@ -77,6 +77,7 @@ namespace nodetool
|
||||
int64_t last_seen;
|
||||
uint32_t pruning_seed;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(adr)
|
||||
@ -85,6 +86,7 @@ namespace nodetool
|
||||
KV_SERIALIZE_OPT(last_seen, (int64_t)0)
|
||||
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
|
||||
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
|
||||
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry;
|
||||
@ -132,6 +134,7 @@ namespace nodetool
|
||||
{
|
||||
ss << pe.id << "\t" << pe.adr.str()
|
||||
<< " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-")
|
||||
<< " \trpc credits per hash " << (pe.rpc_credits_per_hash > 0 ? std::to_string(pe.rpc_credits_per_hash) : "-")
|
||||
<< " \tpruning seed " << pe.pruning_seed
|
||||
<< " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen))
|
||||
<< std::endl;
|
||||
@ -166,6 +169,7 @@ namespace nodetool
|
||||
uint64_t local_time;
|
||||
uint32_t my_port;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
peerid_type peer_id;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
@ -174,6 +178,7 @@ namespace nodetool
|
||||
KV_SERIALIZE(local_time)
|
||||
KV_SERIALIZE(my_port)
|
||||
KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0))
|
||||
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
@ -220,7 +225,7 @@ namespace nodetool
|
||||
{
|
||||
const epee::net_utils::network_address &na = p.adr;
|
||||
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
|
||||
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
|
||||
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
|
||||
}
|
||||
else
|
||||
MDEBUG("Not including in legacy peer list: " << p.adr.str());
|
||||
@ -235,7 +240,7 @@ namespace nodetool
|
||||
std::vector<peerlist_entry_base<network_address_old>> local_peerlist;
|
||||
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
|
||||
for (const auto &p: local_peerlist)
|
||||
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
|
||||
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
|
||||
}
|
||||
}
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
@ -29,12 +29,14 @@
|
||||
include_directories(SYSTEM ${ZMQ_INCLUDE_PATH})
|
||||
|
||||
set(rpc_base_sources
|
||||
rpc_args.cpp)
|
||||
rpc_args.cpp
|
||||
rpc_payment_signature.cpp
|
||||
rpc_handler.cpp)
|
||||
|
||||
set(rpc_sources
|
||||
bootstrap_daemon.cpp
|
||||
core_rpc_server.cpp
|
||||
rpc_handler.cpp
|
||||
rpc_payment.cpp
|
||||
instanciations)
|
||||
|
||||
set(daemon_messages_sources
|
||||
@ -47,7 +49,9 @@ set(daemon_rpc_server_sources
|
||||
|
||||
|
||||
set(rpc_base_headers
|
||||
rpc_args.h)
|
||||
rpc_args.h
|
||||
rpc_payment_signature.h
|
||||
rpc_handler.h)
|
||||
|
||||
set(rpc_headers
|
||||
rpc_handler.h)
|
||||
@ -58,6 +62,7 @@ set(daemon_rpc_server_headers)
|
||||
set(rpc_daemon_private_headers
|
||||
bootstrap_daemon.h
|
||||
core_rpc_server.h
|
||||
rpc_payment.h
|
||||
core_rpc_server_commands_defs.h
|
||||
core_rpc_server_error_codes.h)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
#include "cryptonote_core/cryptonote_core.h"
|
||||
#include "p2p/net_node.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "rpc_payment.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc"
|
||||
@ -71,6 +72,9 @@ namespace cryptonote
|
||||
static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert;
|
||||
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address;
|
||||
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login;
|
||||
static const command_line::arg_descriptor<std::string> arg_rpc_payment_address;
|
||||
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty;
|
||||
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits;
|
||||
|
||||
typedef epee::net_utils::connection_context_base connection_context;
|
||||
|
||||
@ -78,6 +82,7 @@ namespace cryptonote
|
||||
core& cr
|
||||
, nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p
|
||||
);
|
||||
~core_rpc_server();
|
||||
|
||||
static void init_options(boost::program_options::options_description& desc);
|
||||
bool init(
|
||||
@ -169,6 +174,12 @@ namespace cryptonote
|
||||
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
|
||||
MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
|
||||
MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted)
|
||||
MAP_JON_RPC_WE("rpc_access_info", on_rpc_access_info, COMMAND_RPC_ACCESS_INFO)
|
||||
MAP_JON_RPC_WE("rpc_access_submit_nonce",on_rpc_access_submit_nonce, COMMAND_RPC_ACCESS_SUBMIT_NONCE)
|
||||
MAP_JON_RPC_WE("rpc_access_pay", on_rpc_access_pay, COMMAND_RPC_ACCESS_PAY)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_tracking", on_rpc_access_tracking, COMMAND_RPC_ACCESS_TRACKING, !m_restricted)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_data", on_rpc_access_data, COMMAND_RPC_ACCESS_DATA, !m_restricted)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_account", on_rpc_access_account, COMMAND_RPC_ACCESS_ACCOUNT, !m_restricted)
|
||||
END_JSON_RPC_MAP()
|
||||
END_URI_MAP2()
|
||||
|
||||
@ -236,6 +247,12 @@ namespace cryptonote
|
||||
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_tracking(const COMMAND_RPC_ACCESS_TRACKING::request& req, COMMAND_RPC_ACCESS_TRACKING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_data(const COMMAND_RPC_ACCESS_DATA::request& req, COMMAND_RPC_ACCESS_DATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_account(const COMMAND_RPC_ACCESS_ACCOUNT::request& req, COMMAND_RPC_ACCESS_ACCOUNT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
//-----------------------
|
||||
|
||||
private:
|
||||
@ -252,6 +269,8 @@ private:
|
||||
enum invoke_http_mode { JON, BIN, JON_RPC };
|
||||
template <typename COMMAND_TYPE>
|
||||
bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r);
|
||||
bool get_block_template(const account_public_address &address, const crypto::hash *prev_block, const cryptonote::blobdata &extra_nonce, size_t &reserved_offset, cryptonote::difficulty_type &difficulty, uint64_t &height, uint64_t &expected_reward, block &b, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, epee::json_rpc::error &error_resp);
|
||||
bool check_payment(const std::string &client, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash);
|
||||
|
||||
core& m_core;
|
||||
nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p;
|
||||
@ -260,10 +279,10 @@ private:
|
||||
bool m_should_use_bootstrap_daemon;
|
||||
std::chrono::system_clock::time_point m_bootstrap_height_check_time;
|
||||
bool m_was_bootstrap_ever_used;
|
||||
network_type m_nettype;
|
||||
bool m_restricted;
|
||||
epee::critical_section m_host_fails_score_lock;
|
||||
std::map<std::string, uint64_t> m_host_fails_score;
|
||||
std::unique_ptr<rpc_payment> m_rpc_payment;
|
||||
};
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,5 +43,34 @@
|
||||
#define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11
|
||||
#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12
|
||||
#define CORE_RPC_ERROR_CODE_REGTEST_REQUIRED -13
|
||||
#define CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED -14
|
||||
#define CORE_RPC_ERROR_CODE_INVALID_CLIENT -15
|
||||
#define CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW -16
|
||||
#define CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT -17
|
||||
#define CORE_RPC_ERROR_CODE_STALE_PAYMENT -18
|
||||
|
||||
static inline const char *get_rpc_server_error_message(int64_t code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case CORE_RPC_ERROR_CODE_WRONG_PARAM: return "Invalid parameter";
|
||||
case CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT: return "Height is too large";
|
||||
case CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE: return "Reserve size is too large";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS: return "Wrong wallet address";
|
||||
case CORE_RPC_ERROR_CODE_INTERNAL_ERROR: return "Internal error";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB: return "Wrong block blob";
|
||||
case CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED: return "Block not accepted";
|
||||
case CORE_RPC_ERROR_CODE_CORE_BUSY: return "Core is busy";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE: return "Wrong block blob size";
|
||||
case CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC: return "Unsupported RPC";
|
||||
case CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS: return "Mining to subaddress is not supported";
|
||||
case CORE_RPC_ERROR_CODE_REGTEST_REQUIRED: return "Regtest mode required";
|
||||
case CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED: return "Payment required";
|
||||
case CORE_RPC_ERROR_CODE_INVALID_CLIENT: return "Invalid client";
|
||||
case CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW: return "Payment too low";
|
||||
case CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT: return "Duplicate payment";
|
||||
case CORE_RPC_ERROR_CODE_STALE_PAYMENT: return "Stale payment";
|
||||
default: MERROR("Unknown error: " << code); return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,7 @@ namespace rpc
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
uint64_t last_seen;
|
||||
uint32_t pruning_seed;
|
||||
};
|
||||
|
402
src/rpc/rpc_payment.cpp
Normal file
402
src/rpc/rpc_payment.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
146
src/rpc/rpc_payment.h
Normal file
146
src/rpc/rpc_payment.h
Normal file
@ -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);
|
49
src/rpc/rpc_payment_costs.h
Normal file
49
src/rpc/rpc_payment_costs.h
Normal file
@ -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
|
107
src/rpc/rpc_payment_signature.cpp
Normal file
107
src/rpc/rpc_payment_signature.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
39
src/rpc/rpc_payment_signature.h
Normal file
39
src/rpc/rpc_payment_signature.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
std::string make_rpc_payment_signature(const crypto::secret_key &skey);
|
||||
bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts);
|
||||
}
|
@ -571,6 +571,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& in
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, ip, info.ip);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, port, info.port);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, info.rpc_port);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, info.rpc_credits_per_hash);
|
||||
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id);
|
||||
|
||||
@ -607,6 +608,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& inf
|
||||
GET_FROM_JSON_OBJECT(val, info.ip, ip);
|
||||
GET_FROM_JSON_OBJECT(val, info.port, port);
|
||||
GET_FROM_JSON_OBJECT(val, info.rpc_port, rpc_port);
|
||||
GET_FROM_JSON_OBJECT(val, info.rpc_credits_per_hash, rpc_credits_per_hash);
|
||||
|
||||
GET_FROM_JSON_OBJECT(val, info.peer_id, peer_id);
|
||||
|
||||
@ -756,6 +758,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, ra
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, peer.rpc_port);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, peer.rpc_credits_per_hash);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen);
|
||||
INSERT_INTO_JSON_OBJECT(val, doc, pruning_seed, peer.pruning_seed);
|
||||
}
|
||||
@ -772,6 +775,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer)
|
||||
GET_FROM_JSON_OBJECT(val, peer.ip, ip);
|
||||
GET_FROM_JSON_OBJECT(val, peer.port, port);
|
||||
GET_FROM_JSON_OBJECT(val, peer.rpc_port, rpc_port);
|
||||
GET_FROM_JSON_OBJECT(val, peer.rpc_credits_per_hash, rpc_credits_per_hash);
|
||||
GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen);
|
||||
GET_FROM_JSON_OBJECT(val, peer.pruning_seed, pruning_seed);
|
||||
}
|
||||
|
@ -58,6 +58,7 @@
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "storages/http_abstract_invoke.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
#include "crypto/crypto.h" // for crypto::secret_key definition
|
||||
#include "mnemonics/electrum-words.h"
|
||||
#include "rapidjson/document.h"
|
||||
@ -99,12 +100,17 @@ typedef cryptonote::simple_wallet sw;
|
||||
#define LOCK_IDLE_SCOPE() \
|
||||
bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \
|
||||
m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \
|
||||
/* stop any background refresh, and take over */ \
|
||||
/* stop any background refresh and other processes, and take over */ \
|
||||
m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); \
|
||||
m_wallet->stop(); \
|
||||
boost::unique_lock<boost::mutex> lock(m_idle_mutex); \
|
||||
m_idle_cond.notify_all(); \
|
||||
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \
|
||||
/* m_idle_mutex is still locked here */ \
|
||||
m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \
|
||||
m_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed);; \
|
||||
m_rpc_payment_checker.trigger(); \
|
||||
m_idle_cond.notify_one(); \
|
||||
})
|
||||
|
||||
#define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \
|
||||
@ -125,15 +131,24 @@ typedef cryptonote::simple_wallet sw;
|
||||
return true; \
|
||||
} while(0)
|
||||
|
||||
#define REFRESH_PERIOD 90 // seconds
|
||||
|
||||
#define CREDITS_TARGET 50000
|
||||
#define MAX_PAYMENT_DIFF 10000
|
||||
#define MIN_PAYMENT_RATE 0.01f // per hash
|
||||
|
||||
enum TransferType {
|
||||
Transfer,
|
||||
TransferLocked,
|
||||
};
|
||||
|
||||
static std::string get_human_readable_timespan(std::chrono::seconds seconds);
|
||||
|
||||
namespace
|
||||
{
|
||||
const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}};
|
||||
const auto arg_wallet_file = wallet_args::arg_wallet_file();
|
||||
const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
|
||||
const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""};
|
||||
const command_line::arg_descriptor<std::string> arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to <arg>"), ""};
|
||||
const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""};
|
||||
@ -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);
|
||||
|
@ -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,6 +448,14 @@ 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(); };
|
||||
|
@ -37,6 +37,7 @@ set(wallet_sources
|
||||
node_rpc_proxy.cpp
|
||||
message_store.cpp
|
||||
message_transporter.cpp
|
||||
wallet_rpc_payments.cpp
|
||||
)
|
||||
|
||||
set(wallet_private_headers
|
||||
@ -49,7 +50,8 @@ set(wallet_private_headers
|
||||
ringdb.h
|
||||
node_rpc_proxy.h
|
||||
message_store.h
|
||||
message_transporter.h)
|
||||
message_transporter.h
|
||||
wallet_rpc_helpers.h)
|
||||
|
||||
monero_private_headers(wallet
|
||||
${wallet_private_headers})
|
||||
@ -58,6 +60,7 @@ monero_add_library(wallet
|
||||
${wallet_private_headers})
|
||||
target_link_libraries(wallet
|
||||
PUBLIC
|
||||
rpc_base
|
||||
multisig
|
||||
common
|
||||
cryptonote_core
|
||||
@ -116,6 +119,7 @@ if (BUILD_GUI_DEPS)
|
||||
set(libs_to_merge
|
||||
wallet_api
|
||||
wallet
|
||||
rpc_base
|
||||
multisig
|
||||
blockchain_db
|
||||
cryptonote_core
|
||||
|
@ -28,8 +28,22 @@
|
||||
|
||||
#include "node_rpc_proxy.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
#include "rpc/rpc_payment_costs.h"
|
||||
#include "storages/http_abstract_invoke.h"
|
||||
|
||||
#define RETURN_ON_RPC_RESPONSE_ERROR(r, error, res, method) \
|
||||
do { \
|
||||
CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \
|
||||
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
|
||||
CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); \
|
||||
/* empty string -> not connection */ \
|
||||
CHECK_AND_ASSERT_MES(!res.status.empty(), res.status, "No connection to daemon"); \
|
||||
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, res.status, "Daemon busy"); \
|
||||
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_PAYMENT_REQUIRED, res.status, "Payment required"); \
|
||||
CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, res.status, "Error calling " + std::string(method) + " daemon RPC"); \
|
||||
} while(0)
|
||||
|
||||
using namespace epee;
|
||||
|
||||
namespace tools
|
||||
@ -37,8 +51,9 @@ namespace tools
|
||||
|
||||
static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
|
||||
|
||||
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex)
|
||||
NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex)
|
||||
: m_http_client(http_client)
|
||||
, m_rpc_payment_state(rpc_payment_state)
|
||||
, m_daemon_rpc_mutex(mutex)
|
||||
, m_offline(false)
|
||||
{
|
||||
@ -58,9 +73,13 @@ void NodeRPCProxy::invalidate()
|
||||
m_target_height = 0;
|
||||
m_block_weight_limit = 0;
|
||||
m_get_info_time = 0;
|
||||
m_rpc_payment_info_time = 0;
|
||||
m_rpc_payment_seed_height = 0;
|
||||
m_rpc_payment_seed_hash = crypto::null_hash;
|
||||
m_rpc_payment_next_seed_hash = crypto::null_hash;
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
|
||||
{
|
||||
if (m_offline)
|
||||
return boost::optional<std::string>("offline");
|
||||
@ -68,12 +87,11 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version");
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
|
||||
}
|
||||
m_rpc_version = resp_t.version;
|
||||
}
|
||||
rpc_version = m_rpc_version;
|
||||
@ -85,7 +103,7 @@ void NodeRPCProxy::set_height(uint64_t h)
|
||||
m_height = h;
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
boost::optional<std::string> NodeRPCProxy::get_info()
|
||||
{
|
||||
if (m_offline)
|
||||
return boost::optional<std::string>("offline");
|
||||
@ -95,13 +113,15 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_info");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_info", resp_t.credits, pre_call_credits, COST_PER_GET_INFO);
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height");
|
||||
m_height = resp_t.height;
|
||||
m_target_height = resp_t.target_height;
|
||||
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
|
||||
@ -110,7 +130,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -119,7 +139,7 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -128,7 +148,7 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) c
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -137,7 +157,7 @@ boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &bloc
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height)
|
||||
{
|
||||
if (m_offline)
|
||||
return boost::optional<std::string>("offline");
|
||||
@ -145,14 +165,17 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
|
||||
{
|
||||
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.version = version;
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status");
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "hard_fork_info");
|
||||
check_rpc_cost(m_rpc_payment_state, "hard_fork_info", resp_t.credits, pre_call_credits, COST_PER_HARD_FORK_INFO);
|
||||
}
|
||||
|
||||
m_earliest_height[version] = resp_t.earliest_height;
|
||||
}
|
||||
|
||||
@ -160,7 +183,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee)
|
||||
{
|
||||
uint64_t height;
|
||||
|
||||
@ -174,14 +197,17 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.grace_blocks = grace_blocks;
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
|
||||
}
|
||||
|
||||
m_dynamic_base_fee_estimate = resp_t.fee;
|
||||
m_dynamic_base_fee_estimate_cached_height = height;
|
||||
m_dynamic_base_fee_estimate_grace_blocks = grace_blocks;
|
||||
@ -192,7 +218,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask)
|
||||
{
|
||||
uint64_t height;
|
||||
|
||||
@ -206,14 +232,17 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks;
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
|
||||
}
|
||||
|
||||
m_dynamic_base_fee_estimate = resp_t.fee;
|
||||
m_dynamic_base_fee_estimate_cached_height = height;
|
||||
m_fee_quantization_mask = resp_t.quantization_mask;
|
||||
@ -228,4 +257,65 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
|
||||
{
|
||||
const time_t now = time(NULL);
|
||||
if (m_rpc_payment_state.stale || now >= m_rpc_payment_info_time + 5*60 || (mining && now >= m_rpc_payment_info_time + 10)) // re-cache every 10 seconds if mining, 5 minutes otherwise
|
||||
{
|
||||
cryptonote::COMMAND_RPC_ACCESS_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_ACCESS_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "rpc_access_info");
|
||||
m_rpc_payment_state.stale = false;
|
||||
}
|
||||
|
||||
m_rpc_payment_diff = resp_t.diff;
|
||||
m_rpc_payment_credits_per_hash_found = resp_t.credits_per_hash_found;
|
||||
m_rpc_payment_height = resp_t.height;
|
||||
m_rpc_payment_seed_height = resp_t.seed_height;
|
||||
m_rpc_payment_cookie = resp_t.cookie;
|
||||
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43)
|
||||
{
|
||||
MERROR("Invalid hashing blob: " << resp_t.hashing_blob);
|
||||
return std::string("Invalid hashing blob");
|
||||
}
|
||||
if (resp_t.seed_hash.empty())
|
||||
{
|
||||
m_rpc_payment_seed_hash = crypto::null_hash;
|
||||
}
|
||||
else if (!epee::string_tools::hex_to_pod(resp_t.seed_hash, m_rpc_payment_seed_hash))
|
||||
{
|
||||
MERROR("Invalid seed_hash: " << resp_t.seed_hash);
|
||||
return std::string("Invalid seed hash");
|
||||
}
|
||||
if (resp_t.next_seed_hash.empty())
|
||||
{
|
||||
m_rpc_payment_next_seed_hash = crypto::null_hash;
|
||||
}
|
||||
else if (!epee::string_tools::hex_to_pod(resp_t.next_seed_hash, m_rpc_payment_next_seed_hash))
|
||||
{
|
||||
MERROR("Invalid next_seed_hash: " << resp_t.next_seed_hash);
|
||||
return std::string("Invalid next seed hash");
|
||||
}
|
||||
m_rpc_payment_info_time = now;
|
||||
}
|
||||
|
||||
payment_required = m_rpc_payment_diff > 0;
|
||||
credits = m_rpc_payment_state.credits;
|
||||
diff = m_rpc_payment_diff;
|
||||
credits_per_hash_found = m_rpc_payment_credits_per_hash_found;
|
||||
blob = m_rpc_payment_blob;
|
||||
height = m_rpc_payment_height;
|
||||
seed_height = m_rpc_payment_seed_height;
|
||||
seed_hash = m_rpc_payment_seed_hash;
|
||||
next_seed_hash = m_rpc_payment_next_seed_hash;
|
||||
cookie = m_rpc_payment_cookie;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,8 @@
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include "include_base_utils.h"
|
||||
#include "net/http_client.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "wallet_rpc_helpers.h"
|
||||
|
||||
namespace tools
|
||||
{
|
||||
@ -39,37 +41,62 @@ namespace tools
|
||||
class NodeRPCProxy
|
||||
{
|
||||
public:
|
||||
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex);
|
||||
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex);
|
||||
|
||||
void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; }
|
||||
void invalidate();
|
||||
void set_offline(bool offline) { m_offline = offline; }
|
||||
|
||||
boost::optional<std::string> get_rpc_version(uint32_t &version) const;
|
||||
boost::optional<std::string> get_height(uint64_t &height) const;
|
||||
boost::optional<std::string> get_rpc_version(uint32_t &version);
|
||||
boost::optional<std::string> get_height(uint64_t &height);
|
||||
void set_height(uint64_t h);
|
||||
boost::optional<std::string> get_target_height(uint64_t &height) const;
|
||||
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit) const;
|
||||
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height) const;
|
||||
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const;
|
||||
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask) const;
|
||||
boost::optional<std::string> get_target_height(uint64_t &height);
|
||||
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit);
|
||||
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height);
|
||||
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee);
|
||||
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask);
|
||||
boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
|
||||
|
||||
private:
|
||||
boost::optional<std::string> get_info() const;
|
||||
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
|
||||
if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
|
||||
m_rpc_payment_state.credits = res.credits;
|
||||
if (res.top_hash != m_rpc_payment_state.top_hash)
|
||||
{
|
||||
m_rpc_payment_state.top_hash = res.top_hash;
|
||||
m_rpc_payment_state.stale = true;
|
||||
}
|
||||
}
|
||||
template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
|
||||
|
||||
private:
|
||||
boost::optional<std::string> get_info();
|
||||
|
||||
epee::net_utils::http::http_simple_client &m_http_client;
|
||||
rpc_payment_state_t &m_rpc_payment_state;
|
||||
boost::recursive_mutex &m_daemon_rpc_mutex;
|
||||
crypto::secret_key m_client_id_secret_key;
|
||||
bool m_offline;
|
||||
|
||||
mutable uint64_t m_height;
|
||||
mutable uint64_t m_earliest_height[256];
|
||||
mutable uint64_t m_dynamic_base_fee_estimate;
|
||||
mutable uint64_t m_dynamic_base_fee_estimate_cached_height;
|
||||
mutable uint64_t m_dynamic_base_fee_estimate_grace_blocks;
|
||||
mutable uint64_t m_fee_quantization_mask;
|
||||
mutable uint32_t m_rpc_version;
|
||||
mutable uint64_t m_target_height;
|
||||
mutable uint64_t m_block_weight_limit;
|
||||
mutable time_t m_get_info_time;
|
||||
uint64_t m_height;
|
||||
uint64_t m_earliest_height[256];
|
||||
uint64_t m_dynamic_base_fee_estimate;
|
||||
uint64_t m_dynamic_base_fee_estimate_cached_height;
|
||||
uint64_t m_dynamic_base_fee_estimate_grace_blocks;
|
||||
uint64_t m_fee_quantization_mask;
|
||||
uint32_t m_rpc_version;
|
||||
uint64_t m_target_height;
|
||||
uint64_t m_block_weight_limit;
|
||||
time_t m_get_info_time;
|
||||
time_t m_rpc_payment_info_time;
|
||||
uint64_t m_rpc_payment_diff;
|
||||
uint64_t m_rpc_payment_credits_per_hash_found;
|
||||
cryptonote::blobdata m_rpc_payment_blob;
|
||||
uint64_t m_rpc_payment_height;
|
||||
uint64_t m_rpc_payment_seed_height;
|
||||
crypto::hash m_rpc_payment_seed_hash;
|
||||
crypto::hash m_rpc_payment_next_seed_hash;
|
||||
uint32_t m_rpc_payment_cookie;
|
||||
};
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -64,10 +64,21 @@
|
||||
#include "node_rpc_proxy.h"
|
||||
#include "message_store.h"
|
||||
#include "wallet_light_rpc.h"
|
||||
#include "wallet_rpc_helpers.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
|
||||
|
||||
#define THROW_ON_RPC_RESPONSE_ERROR(r, error, res, method, ...) \
|
||||
do { \
|
||||
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
|
||||
throw_on_rpc_response_error(r, error, res.status, method); \
|
||||
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, ## __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, err, res, method) \
|
||||
THROW_ON_RPC_RESPONSE_ERROR(r, err, res, method, tools::error::wallet_generic_rpc_error, method, res.status)
|
||||
|
||||
class Serialization_portability_wallet_Test;
|
||||
class wallet_accessor_test;
|
||||
|
||||
@ -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)
|
||||
|
@ -76,6 +76,10 @@ namespace wallet_args
|
||||
{
|
||||
return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""};
|
||||
}
|
||||
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key()
|
||||
{
|
||||
return {"rpc-client-secret-key", wallet_args::tr("Set RPC client secret key for RPC payments"), ""};
|
||||
}
|
||||
|
||||
const char* tr(const char* str)
|
||||
{
|
||||
|
@ -36,6 +36,7 @@ namespace wallet_args
|
||||
{
|
||||
command_line::arg_descriptor<std::string> arg_generate_from_json();
|
||||
command_line::arg_descriptor<std::string> arg_wallet_file();
|
||||
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key();
|
||||
|
||||
const char* tr(const char* str);
|
||||
|
||||
|
@ -90,6 +90,7 @@ namespace tools
|
||||
// is_key_image_spent_error
|
||||
// get_histogram_error
|
||||
// get_output_distribution
|
||||
// payment_required
|
||||
// wallet_files_doesnt_correspond
|
||||
//
|
||||
// * - class with protected ctor
|
||||
@ -781,6 +782,20 @@ namespace tools
|
||||
const std::string m_status;
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_coded_rpc_error : public wallet_rpc_error
|
||||
{
|
||||
explicit wallet_coded_rpc_error(std::string&& loc, const std::string& request, int code, const std::string& status)
|
||||
: wallet_rpc_error(std::move(loc), std::string("error ") + std::to_string(code) + (" in ") + request + " RPC: " + status, request),
|
||||
m_code(code), m_status(status)
|
||||
{
|
||||
}
|
||||
int code() const { return m_code; }
|
||||
const std::string& status() const { return m_status; }
|
||||
private:
|
||||
int m_code;
|
||||
const std::string m_status;
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct daemon_busy : public wallet_rpc_error
|
||||
{
|
||||
explicit daemon_busy(std::string&& loc, const std::string& request)
|
||||
@ -821,6 +836,14 @@ namespace tools
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct payment_required: public wallet_rpc_error
|
||||
{
|
||||
explicit payment_required(std::string&& loc, const std::string& request)
|
||||
: wallet_rpc_error(std::move(loc), "payment required", request)
|
||||
{
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_files_doesnt_correspond : public wallet_logic_error
|
||||
{
|
||||
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
|
||||
|
94
src/wallet/wallet_rpc_helpers.h
Normal file
94
src/wallet/wallet_rpc_helpers.h
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace
|
||||
{
|
||||
// credits to yrp (https://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature
|
||||
template <typename T>
|
||||
struct HasCredits
|
||||
{
|
||||
template<typename U, uint64_t (U::*)> struct SFINAE {};
|
||||
template<typename U> static char Test(SFINAE<U, &U::credits>*);
|
||||
template<typename U> static int Test(...);
|
||||
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
|
||||
};
|
||||
}
|
||||
|
||||
namespace tools
|
||||
{
|
||||
struct rpc_payment_state_t
|
||||
{
|
||||
uint64_t credits;
|
||||
uint64_t expected_spent;
|
||||
uint64_t discrepancy;
|
||||
std::string top_hash;
|
||||
bool stale;
|
||||
|
||||
rpc_payment_state_t(): credits(0), expected_spent(0), discrepancy(0), stale(true) {}
|
||||
};
|
||||
|
||||
static inline void check_rpc_cost(rpc_payment_state_t &rpc_payment_state, const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
|
||||
{
|
||||
uint64_t expected_credits = (uint64_t)expected_cost;
|
||||
if (expected_credits == 0)
|
||||
expected_credits = 1;
|
||||
|
||||
rpc_payment_state.credits = post_call_credits;
|
||||
rpc_payment_state.expected_spent += expected_credits;
|
||||
|
||||
if (pre_call_credits < post_call_credits)
|
||||
return;
|
||||
|
||||
uint64_t cost = pre_call_credits - post_call_credits;
|
||||
|
||||
if (cost == expected_credits)
|
||||
{
|
||||
MDEBUG("Call " << call << " cost " << cost << " credits");
|
||||
return;
|
||||
}
|
||||
MWARNING("Call " << call << " cost " << cost << " credits, expected " << expected_credits);
|
||||
|
||||
if (cost > expected_credits)
|
||||
{
|
||||
uint64_t d = cost - expected_credits;
|
||||
if (rpc_payment_state.discrepancy > std::numeric_limits<uint64_t>::max() - d)
|
||||
{
|
||||
MERROR("Integer overflow in credit discrepancy calculation, setting to max");
|
||||
rpc_payment_state.discrepancy = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
rpc_payment_state.discrepancy += d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
196
src/wallet/wallet_rpc_payments.cpp
Normal file
196
src/wallet/wallet_rpc_payments.cpp
Normal file
@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <boost/optional/optional.hpp>
|
||||
#include <boost/utility/value_init.hpp>
|
||||
#include "include_base_utils.h"
|
||||
#include "cryptonote_config.h"
|
||||
#include "wallet_rpc_helpers.h"
|
||||
#include "wallet2.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
#include "misc_language.h"
|
||||
#include "cryptonote_basic/cryptonote_basic_impl.h"
|
||||
#include "int-util.h"
|
||||
#include "crypto/crypto.h"
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
#include "common/i18n.h"
|
||||
#include "common/util.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2.rpc_payments"
|
||||
|
||||
#define RPC_PAYMENT_POLL_PERIOD 10 /* seconds*/
|
||||
|
||||
namespace tools
|
||||
{
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string wallet2::get_client_signature() const
|
||||
{
|
||||
return cryptonote::make_rpc_payment_signature(m_rpc_client_secret_key);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
|
||||
{
|
||||
boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_payment_info(mining, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie);
|
||||
credits = m_rpc_payment_state.credits;
|
||||
if (result && *result != CORE_RPC_STATUS_OK)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::daemon_requires_payment()
|
||||
{
|
||||
bool payment_required = false;
|
||||
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
|
||||
uint32_t cookie;
|
||||
cryptonote::blobdata blob;
|
||||
crypto::hash seed_hash, next_seed_hash;
|
||||
return get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance)
|
||||
{
|
||||
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::request req = AUTO_VAL_INIT(req);
|
||||
cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::response res = AUTO_VAL_INIT(res);
|
||||
req.nonce = nonce;
|
||||
req.cookie = cookie;
|
||||
m_daemon_rpc_mutex.lock();
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req.client = get_client_signature();
|
||||
epee::json_rpc::error error;
|
||||
bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, error, res, "rpc_access_submit_nonce");
|
||||
THROW_WALLET_EXCEPTION_IF(res.credits < pre_call_credits, error::wallet_internal_error, "RPC payment did not increase balance");
|
||||
if (m_rpc_payment_state.top_hash != res.top_hash)
|
||||
{
|
||||
m_rpc_payment_state.top_hash = res.top_hash;
|
||||
m_rpc_payment_state.stale = true;
|
||||
}
|
||||
|
||||
m_rpc_payment_state.credits = res.credits;
|
||||
balance = res.credits;
|
||||
credits = balance - pre_call_credits;
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool wallet2::search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc)
|
||||
{
|
||||
bool need_payment = false;
|
||||
bool payment_required;
|
||||
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
|
||||
uint32_t cookie;
|
||||
unsigned int n_hashes = 0;
|
||||
cryptonote::blobdata hashing_blob;
|
||||
crypto::hash seed_hash, next_seed_hash;
|
||||
try
|
||||
{
|
||||
need_payment = get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target;
|
||||
if (!need_payment)
|
||||
return true;
|
||||
if (!startfunc(diff, credits_per_hash_found))
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e) { return false; }
|
||||
|
||||
static std::atomic<uint32_t> nonce(0);
|
||||
while (contfunc(n_hashes))
|
||||
{
|
||||
try
|
||||
{
|
||||
need_payment = get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target;
|
||||
if (!need_payment)
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception &e) { return false; }
|
||||
if (hashing_blob.empty())
|
||||
{
|
||||
MERROR("Bad hashing blob from daemon");
|
||||
if (errorfunc)
|
||||
errorfunc("Bad hashing blob from daemon, trying again");
|
||||
epee::misc_utils::sleep_no_w(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
crypto::hash hash;
|
||||
const uint32_t local_nonce = nonce++; // wrapping's OK
|
||||
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce);
|
||||
const uint8_t major_version = hashing_blob[0];
|
||||
if (major_version >= RX_BLOCK_VERSION)
|
||||
{
|
||||
const int miners = 1;
|
||||
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, miners, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0;
|
||||
crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, height);
|
||||
}
|
||||
++n_hashes;
|
||||
if (cryptonote::check_hash(hash, diff))
|
||||
{
|
||||
uint64_t credits, balance;
|
||||
try
|
||||
{
|
||||
make_rpc_payment(local_nonce, cookie, credits, balance);
|
||||
if (credits != credits_per_hash_found)
|
||||
{
|
||||
MERROR("Found nonce, but daemon did not credit us with the expected amount");
|
||||
if (errorfunc)
|
||||
errorfunc("Found nonce, but daemon did not credit us with the expected amount");
|
||||
return false;
|
||||
}
|
||||
MDEBUG("Found nonce " << local_nonce << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits");
|
||||
if (!foundfunc(credits))
|
||||
break;
|
||||
}
|
||||
catch (const tools::error::wallet_coded_rpc_error &e)
|
||||
{
|
||||
MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
|
||||
if (errorfunc)
|
||||
errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
|
||||
if (errorfunc)
|
||||
errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing");
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
|
||||
{
|
||||
return tools::check_rpc_cost(m_rpc_payment_state, call, post_call_credits, pre_call_credits, expected_cost);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
}
|
@ -4323,6 +4323,7 @@ public:
|
||||
|
||||
const auto arg_wallet_file = wallet_args::arg_wallet_file();
|
||||
const auto arg_from_json = wallet_args::arg_generate_from_json();
|
||||
const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
|
||||
|
||||
const auto wallet_file = command_line::get_arg(vm, arg_wallet_file);
|
||||
const auto from_json = command_line::get_arg(vm, arg_from_json);
|
||||
@ -4371,6 +4372,17 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key))
|
||||
{
|
||||
crypto::secret_key client_secret_key;
|
||||
if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), client_secret_key))
|
||||
{
|
||||
MERROR(arg_rpc_client_secret_key.name << ": RPC client secret key should be 32 byte in hex format");
|
||||
return false;
|
||||
}
|
||||
wal->set_rpc_client_secret_key(client_secret_key);
|
||||
}
|
||||
|
||||
bool quit = false;
|
||||
tools::signal_handler::install([&wal, &quit](int) {
|
||||
assert(wal);
|
||||
@ -4469,6 +4481,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
const auto arg_wallet_file = wallet_args::arg_wallet_file();
|
||||
const auto arg_from_json = wallet_args::arg_generate_from_json();
|
||||
const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
|
||||
|
||||
po::options_description hidden_options("Hidden");
|
||||
|
||||
@ -4482,6 +4495,7 @@ int main(int argc, char** argv) {
|
||||
command_line::add_arg(desc_params, arg_from_json);
|
||||
command_line::add_arg(desc_params, arg_wallet_dir);
|
||||
command_line::add_arg(desc_params, arg_prompt_for_password);
|
||||
command_line::add_arg(desc_params, arg_rpc_client_secret_key);
|
||||
|
||||
daemonizer::init_options(hidden_options, desc_params);
|
||||
desc_params.add(hidden_options);
|
||||
|
@ -50,6 +50,20 @@ target_link_libraries(functional_tests
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${EXTRA_LIBRARIES})
|
||||
|
||||
set(make_test_signature_sources
|
||||
make_test_signature.cc)
|
||||
|
||||
add_executable(make_test_signature
|
||||
${make_test_signature_sources})
|
||||
|
||||
target_link_libraries(make_test_signature
|
||||
PRIVATE
|
||||
rpc_base
|
||||
cncrypto
|
||||
epee
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${EXTRA_LIBRARIES})
|
||||
|
||||
execute_process(COMMAND ${PYTHON_EXECUTABLE} "-c" "import requests; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (REQUESTS_OUTPUT STREQUAL "OK")
|
||||
add_test(
|
||||
|
@ -101,7 +101,7 @@ class BlockchainTest():
|
||||
for n in range(blocks):
|
||||
res_getblock.append(daemon.getblock(height = height + n))
|
||||
block_header = res_getblock[n].block_header
|
||||
assert abs(block_header.timestamp - time.time()) < 10 # within 10 seconds
|
||||
assert abs(block_header.timestamp - time.time()) < 60 # within 60 seconds
|
||||
assert block_header.height == height + n
|
||||
assert block_header.orphan_status == False
|
||||
assert block_header.depth == blocks - n - 1
|
||||
|
@ -37,6 +37,7 @@ Test the following RPCs:
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import os
|
||||
from framework.daemon import Daemon
|
||||
|
||||
class DaemonGetInfoTest():
|
||||
@ -63,7 +64,7 @@ class DaemonGetInfoTest():
|
||||
|
||||
# difficulty should be set to 1 for this test
|
||||
assert 'difficulty' in res.keys()
|
||||
assert res.difficulty == 1;
|
||||
assert res.difficulty == int(os.environ['DIFFICULTY'])
|
||||
|
||||
# nettype should not be TESTNET
|
||||
assert 'testnet' in res.keys()
|
||||
|
@ -10,7 +10,7 @@ import string
|
||||
import os
|
||||
|
||||
USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]'
|
||||
DEFAULT_TESTS = ['address_book', 'bans', 'blockchain', 'cold_signing', 'daemon_info', 'get_output_distribution', 'integrated_address', 'mining', 'multisig', 'proofs', 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet']
|
||||
DEFAULT_TESTS = ['address_book', 'bans', 'blockchain', 'cold_signing', 'daemon_info', 'get_output_distribution', 'integrated_address', 'mining', 'multisig', 'proofs', 'rpc_payment', 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet']
|
||||
try:
|
||||
python = sys.argv[1]
|
||||
srcdir = sys.argv[2]
|
||||
@ -34,12 +34,19 @@ try:
|
||||
except:
|
||||
tests = DEFAULT_TESTS
|
||||
|
||||
N_MONERODS = 1
|
||||
N_MONERODS = 2
|
||||
N_WALLETS = 4
|
||||
WALLET_DIRECTORY = builddir + "/functional-tests-directory"
|
||||
DIFFICULTY = 10
|
||||
|
||||
monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
|
||||
monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
|
||||
monerod_extra = [
|
||||
[],
|
||||
["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--data-dir", builddir + "/functional-tests-directory/monerod1"],
|
||||
]
|
||||
wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", WALLET_DIRECTORY, "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"]
|
||||
wallet_extra = [
|
||||
]
|
||||
|
||||
command_lines = []
|
||||
processes = []
|
||||
@ -48,11 +55,15 @@ ports = []
|
||||
|
||||
for i in range(N_MONERODS):
|
||||
command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else x for x in monerod_base])
|
||||
if i < len(monerod_extra):
|
||||
command_lines[-1] += monerod_extra[i]
|
||||
outputs.append(open(builddir + '/tests/functional_tests/monerod' + str(i) + '.log', 'a+'))
|
||||
ports.append(18180+i)
|
||||
|
||||
for i in range(N_WALLETS):
|
||||
command_lines.append([str(18090+i) if x == "wallet_port" else x for x in wallet_base])
|
||||
if i < len(wallet_extra):
|
||||
command_lines[-1] += wallet_extra[i]
|
||||
outputs.append(open(builddir + '/tests/functional_tests/wallet' + str(i) + '.log', 'a+'))
|
||||
ports.append(18090+i)
|
||||
|
||||
@ -65,6 +76,8 @@ try:
|
||||
os.environ['PYTHONPATH'] = PYTHONPATH
|
||||
os.environ['WALLET_DIRECTORY'] = WALLET_DIRECTORY
|
||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||
os.environ['DIFFICULTY'] = str(DIFFICULTY)
|
||||
os.environ['MAKE_TEST_SIGNATURE'] = builddir + '/tests/functional_tests/make_test_signature'
|
||||
for i in range(len(command_lines)):
|
||||
#print('Running: ' + str(command_lines[i]))
|
||||
processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i]))
|
||||
|
60
tests/functional_tests/make_test_signature.cc
Normal file
60
tests/functional_tests/make_test_signature.cc
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdio.h>
|
||||
#include "string_tools.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
if (argc > 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <secret_key>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
crypto::secret_key skey;
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
crypto::public_key pkey;
|
||||
crypto::random32_unbiased((unsigned char*)skey.data);
|
||||
crypto::secret_key_to_public_key(skey, pkey);
|
||||
printf("%s %s\n", epee::string_tools::pod_to_hex(skey).c_str(), epee::string_tools::pod_to_hex(pkey).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!epee::string_tools::hex_to_pod(argv[1], skey))
|
||||
{
|
||||
fprintf(stderr, "invalid secret key\n");
|
||||
return 1;
|
||||
}
|
||||
std::string signature = cryptonote::make_rpc_payment_signature(skey);
|
||||
printf("%s\n", signature.c_str());
|
||||
return 0;
|
||||
}
|
@ -92,7 +92,7 @@ class MiningTest():
|
||||
assert res_status.block_reward >= 600000000000
|
||||
|
||||
# wait till we mined a few of them
|
||||
timeout = 5
|
||||
timeout = 60 # randomx is slow to init
|
||||
timeout_height = prev_height
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
412
tests/functional_tests/rpc_payment.py
Executable file
412
tests/functional_tests/rpc_payment.py
Executable file
@ -0,0 +1,412 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2019 The Monero Project
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are
|
||||
# permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
# conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
# of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import print_function
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
"""Test daemon RPC payment calls
|
||||
"""
|
||||
|
||||
from framework.daemon import Daemon
|
||||
from framework.wallet import Wallet
|
||||
|
||||
class RPCPaymentTest():
|
||||
def run_test(self):
|
||||
self.make_test_signature = os.environ['MAKE_TEST_SIGNATURE']
|
||||
assert len(self.make_test_signature) > 0
|
||||
self.secret_key, self.public_key = self.get_keys()
|
||||
self.reset()
|
||||
self.test_access_tracking()
|
||||
self.test_access_mining()
|
||||
self.test_access_payment()
|
||||
self.test_access_account()
|
||||
self.test_free_access()
|
||||
|
||||
def get_keys(self):
|
||||
output = subprocess.check_output([self.make_test_signature]).rstrip()
|
||||
fields = output.split()
|
||||
assert len(fields) == 2
|
||||
return fields
|
||||
|
||||
def get_signature(self):
|
||||
return subprocess.check_output([self.make_test_signature, self.secret_key]).rstrip()
|
||||
|
||||
def reset(self):
|
||||
print('Resetting blockchain')
|
||||
daemon = Daemon(idx=1)
|
||||
res = daemon.get_height()
|
||||
daemon.pop_blocks(res.height - 1)
|
||||
daemon.flush_txpool()
|
||||
|
||||
def test_access_tracking(self):
|
||||
print('Testing access tracking')
|
||||
daemon = Daemon(idx=1)
|
||||
|
||||
res = daemon.rpc_access_tracking(True)
|
||||
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 1
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'rpc_access_tracking'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_connections()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 2
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_connections()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 2
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 2
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_alternate_chains()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 3
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_alternate_chains'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
entry = res.data[1]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 2
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
res = daemon.rpc_access_tracking(True)
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 1
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'rpc_access_tracking'
|
||||
assert entry.count == 1
|
||||
|
||||
def test_access_mining(self):
|
||||
print('Testing access mining')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert len(res.hashing_blob) > 39
|
||||
assert res.height == 1
|
||||
assert res.top_hash == '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3'
|
||||
assert res.credits_per_hash_found == 5000
|
||||
assert res.diff == 10
|
||||
assert res.credits == 0
|
||||
cookie = res.cookie
|
||||
|
||||
# Try random nonces till we find one that's valid and one that's invalid
|
||||
nonce = 0
|
||||
found_valid = 0
|
||||
found_invalid = 0
|
||||
last_credits = 0
|
||||
while found_valid == 0 or found_invalid == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
assert res.credits == last_credits + 5000
|
||||
except Exception as e:
|
||||
found_invalid += 1
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits < last_credits or res.credits == 0
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
last_credits = res.credits
|
||||
|
||||
# we should now have 1 valid nonce, and a number of bad ones
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert len(res.hashing_blob) > 39
|
||||
assert res.height > 1
|
||||
assert res.top_hash != '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3' # here, any share matches network diff
|
||||
assert res.credits_per_hash_found == 5000
|
||||
assert res.diff == 10
|
||||
cookie = res.cookie
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
|
||||
# Try random nonces till we find one that's valid so we get a load of credits
|
||||
while last_credits == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
last_credits = res.credits
|
||||
break
|
||||
except:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find a valid none -> the RPC probably fails
|
||||
|
||||
# we should now have at least 5000
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits == last_credits
|
||||
assert res.credits >= 5000 # last one was a valid nonce
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
assert e.balance == 5000
|
||||
assert e.credits_total >= 5000
|
||||
|
||||
# find a valid one, then check dupes aren't allowed
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
cookie = res.cookie
|
||||
old_cookie = cookie # we keep that so can submit a stale later
|
||||
while True:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
break
|
||||
except:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
|
||||
ok = False
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
except:
|
||||
ok = True
|
||||
assert ok
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
# find stales without updating cookie, one within 5 seconds (accepted), one later (rejected)
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
found_close_stale = 0
|
||||
found_late_stale = 0
|
||||
while found_close_stale == 0 or found_late_stale == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_close_stale += 1
|
||||
found_valid += 1
|
||||
except Exception as e:
|
||||
if e[0]['error']['code'] == -18: # stale
|
||||
found_late_stale += 1
|
||||
else:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == found_late_stale # close stales are accepted, don't count here
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
# find very stale with old cookie (rejected)
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
nonce += 1
|
||||
ok = False
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = old_cookie, client = self.get_signature())
|
||||
except:
|
||||
found_late_stale += 1
|
||||
ok = True
|
||||
assert ok
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == found_late_stale
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
def test_access_payment(self):
|
||||
print('Testing access payment')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
# Try random nonces till we find one that's valid so we get a load of credits
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
cookie = res.cookie
|
||||
nonce = 0
|
||||
while credits <= 100:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
break
|
||||
except:
|
||||
pass
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
assert credits > 0
|
||||
|
||||
res = daemon.get_info(client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
credits = res.credits
|
||||
|
||||
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 100)
|
||||
block_hashes = res.blocks
|
||||
|
||||
# ask for 1 block -> 1 credit
|
||||
res = daemon.getblockheadersrange(0, 0, client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
credits = res.credits
|
||||
|
||||
# ask for 100 blocks -> >1 credit
|
||||
res = daemon.getblockheadersrange(1, 100, client = self.get_signature())
|
||||
assert res.credits < credits - 1
|
||||
credits = res.credits
|
||||
|
||||
# external users
|
||||
res = daemon.rpc_access_pay(payment = 1, paying_for = 'foo', client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
res = daemon.rpc_access_pay(payment = 4, paying_for = 'bar', client = self.get_signature())
|
||||
assert res.credits == credits - 5
|
||||
res = daemon.rpc_access_pay(payment = credits, paying_for = 'baz', client = self.get_signature())
|
||||
assert "PAYMENT REQUIRED" in res.status
|
||||
res = daemon.rpc_access_pay(payment = 2, paying_for = 'quux', client = self.get_signature())
|
||||
assert res.credits == credits - 7
|
||||
res = daemon.rpc_access_pay(payment = 3, paying_for = 'bar', client = self.get_signature())
|
||||
assert res.credits == credits - 10
|
||||
|
||||
# that should be rejected because its cost is massive
|
||||
ok = False
|
||||
try: res = daemon.get_output_histogram(amounts = [], client = self.get_signature())
|
||||
except Exception as e: print('e: ' + str(e)); ok = "PAYMENT REQUIRED" in e.status
|
||||
assert ok or "PAYMENT REQUIRED" in res.status
|
||||
|
||||
def test_access_account(self):
|
||||
print('Testing access account')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
res = daemon.rpc_access_account(self.get_signature(), 0)
|
||||
assert res.credits == credits
|
||||
res = daemon.rpc_access_account(self.get_signature(), 50)
|
||||
assert res.credits == credits + 50
|
||||
res = daemon.rpc_access_account(self.get_signature(), -10)
|
||||
assert res.credits == credits + 40
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(credits + 50))
|
||||
assert res.credits == 0
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 5)
|
||||
assert res.credits == 2**63 - 5
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 1)
|
||||
assert res.credits == 2**64 - 6
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2)
|
||||
assert res.credits == 2**64 - 4
|
||||
res = daemon.rpc_access_account(self.get_signature(), 8)
|
||||
assert res.credits == 2**64 - 1
|
||||
res = daemon.rpc_access_account(self.get_signature(), -1)
|
||||
assert res.credits == 2**64 - 2
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
|
||||
assert res.credits == 2**64 - 2 -(2**63 - 1)
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
|
||||
assert res.credits == 0
|
||||
|
||||
def test_free_access(self):
|
||||
print('Testing free access')
|
||||
daemon = Daemon(idx=0)
|
||||
wallet = Wallet(idx=0)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits_per_hash_found == 0
|
||||
assert res.diff == 0
|
||||
assert res.credits == 0
|
||||
|
||||
res = daemon.get_info(client = self.get_signature())
|
||||
assert res.credits == 0
|
||||
|
||||
# any nonce will do here
|
||||
res = daemon.rpc_access_submit_nonce(nonce = 0, cookie = 0, client = self.get_signature())
|
||||
assert res.credits == 0
|
||||
|
||||
|
||||
class Guard:
|
||||
def __enter__(self):
|
||||
for i in range(4):
|
||||
Wallet(idx = i).auto_refresh(False)
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
for i in range(4):
|
||||
Wallet(idx = i).auto_refresh(True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with Guard() as guard:
|
||||
RPCPaymentTest().run_test()
|
@ -37,10 +37,11 @@ class Daemon(object):
|
||||
self.port = port
|
||||
self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx))
|
||||
|
||||
def getblocktemplate(self, address, prev_block = ""):
|
||||
def getblocktemplate(self, address, prev_block = "", client = ""):
|
||||
getblocktemplate = {
|
||||
'method': 'getblocktemplate',
|
||||
'params': {
|
||||
'client': client,
|
||||
'wallet_address': address,
|
||||
'reserve_size' : 1,
|
||||
'prev_block' : prev_block,
|
||||
@ -51,8 +52,9 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblocktemplate)
|
||||
get_block_template = getblocktemplate
|
||||
|
||||
def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True):
|
||||
def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True, client = ""):
|
||||
send_raw_transaction = {
|
||||
'client': client,
|
||||
'tx_as_hex': tx_as_hex,
|
||||
'do_not_relay': do_not_relay,
|
||||
'do_sanity_checks': do_sanity_checks,
|
||||
@ -70,10 +72,11 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(submitblock)
|
||||
submit_block = submitblock
|
||||
|
||||
def getblock(self, hash = '', height = 0, fill_pow_hash = False):
|
||||
def getblock(self, hash = '', height = 0, fill_pow_hash = False, client = ""):
|
||||
getblock = {
|
||||
'method': 'getblock',
|
||||
'params': {
|
||||
'client': client,
|
||||
'hash': hash,
|
||||
'height': height,
|
||||
'fill_pow_hash': fill_pow_hash,
|
||||
@ -84,10 +87,11 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblock)
|
||||
get_block = getblock
|
||||
|
||||
def getlastblockheader(self):
|
||||
def getlastblockheader(self, client = ""):
|
||||
getlastblockheader = {
|
||||
'method': 'getlastblockheader',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
@ -95,10 +99,11 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getlastblockheader)
|
||||
get_last_block_header = getlastblockheader
|
||||
|
||||
def getblockheaderbyhash(self, hash = "", hashes = []):
|
||||
def getblockheaderbyhash(self, hash = "", hashes = [], client = ""):
|
||||
getblockheaderbyhash = {
|
||||
'method': 'getblockheaderbyhash',
|
||||
'params': {
|
||||
'client': client,
|
||||
'hash': hash,
|
||||
'hashes': hashes,
|
||||
},
|
||||
@ -108,10 +113,11 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblockheaderbyhash)
|
||||
get_block_header_by_hash = getblockheaderbyhash
|
||||
|
||||
def getblockheaderbyheight(self, height):
|
||||
def getblockheaderbyheight(self, height, client = ""):
|
||||
getblockheaderbyheight = {
|
||||
'method': 'getblockheaderbyheight',
|
||||
'params': {
|
||||
'client': client,
|
||||
'height': height,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
@ -120,10 +126,11 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblockheaderbyheight)
|
||||
get_block_header_by_height = getblockheaderbyheight
|
||||
|
||||
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False):
|
||||
def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False, client = ""):
|
||||
getblockheadersrange = {
|
||||
'method': 'getblockheadersrange',
|
||||
'params': {
|
||||
'client': client,
|
||||
'start_height': start_height,
|
||||
'end_height': end_height,
|
||||
'fill_pow_hash': fill_pow_hash,
|
||||
@ -134,26 +141,33 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblockheadersrange)
|
||||
get_block_headers_range = getblockheadersrange
|
||||
|
||||
def get_connections(self):
|
||||
def get_connections(self, client = ""):
|
||||
get_connections = {
|
||||
'client': client,
|
||||
'method': 'get_connections',
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_connections)
|
||||
|
||||
def get_info(self):
|
||||
def get_info(self, client = ""):
|
||||
get_info = {
|
||||
'method': 'get_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_info)
|
||||
getinfo = get_info
|
||||
|
||||
def hard_fork_info(self):
|
||||
def hard_fork_info(self, client = ""):
|
||||
hard_fork_info = {
|
||||
'method': 'hard_fork_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
@ -174,7 +188,7 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(generateblocks)
|
||||
|
||||
def get_height(self):
|
||||
def get_height(self, client = ""):
|
||||
get_height = {
|
||||
'method': 'get_height',
|
||||
'jsonrpc': '2.0',
|
||||
@ -208,18 +222,21 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_request('/mining_status', mining_status)
|
||||
|
||||
def get_transaction_pool(self):
|
||||
def get_transaction_pool(self, client = ""):
|
||||
get_transaction_pool = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
|
||||
|
||||
def get_transaction_pool_hashes(self):
|
||||
def get_transaction_pool_hashes(self, client = ""):
|
||||
get_transaction_pool_hashes = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
|
||||
|
||||
def get_transaction_pool_stats(self):
|
||||
def get_transaction_pool_stats(self, client = ""):
|
||||
get_transaction_pool_stats = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool_stats', get_transaction_pool_stats)
|
||||
|
||||
@ -263,8 +280,9 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(set_bans)
|
||||
|
||||
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False):
|
||||
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False, client = ""):
|
||||
get_transactions = {
|
||||
'client': client,
|
||||
'txs_hashes': txs_hashes,
|
||||
'decode_as_json': decode_as_json,
|
||||
'prune': prune,
|
||||
@ -273,17 +291,19 @@ class Daemon(object):
|
||||
return self.rpc.send_request('/get_transactions', get_transactions)
|
||||
gettransactions = get_transactions
|
||||
|
||||
def get_outs(self, outputs = [], get_txid = False):
|
||||
def get_outs(self, outputs = [], get_txid = False, client = ""):
|
||||
get_outs = {
|
||||
'client': client,
|
||||
'outputs': outputs,
|
||||
'get_txid': get_txid,
|
||||
}
|
||||
return self.rpc.send_request('/get_outs', get_outs)
|
||||
|
||||
def get_coinbase_tx_sum(self, height, count):
|
||||
def get_coinbase_tx_sum(self, height, count, client = ""):
|
||||
get_coinbase_tx_sum = {
|
||||
'method': 'get_coinbase_tx_sum',
|
||||
'params': {
|
||||
'client': client,
|
||||
'height': height,
|
||||
'count': count,
|
||||
},
|
||||
@ -292,10 +312,11 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_coinbase_tx_sum)
|
||||
|
||||
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False):
|
||||
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False, client = ""):
|
||||
get_output_distribution = {
|
||||
'method': 'get_output_distribution',
|
||||
'params': {
|
||||
'client': client,
|
||||
'amounts': amounts,
|
||||
'from_height': from_height,
|
||||
'to_height': to_height,
|
||||
@ -308,10 +329,11 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_output_distribution)
|
||||
|
||||
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0):
|
||||
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0, client = ""):
|
||||
get_output_histogram = {
|
||||
'method': 'get_output_histogram',
|
||||
'params': {
|
||||
'client': client,
|
||||
'amounts': amounts,
|
||||
'min_count': min_count,
|
||||
'max_count': max_count,
|
||||
@ -335,15 +357,17 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_request('/set_log_categories', set_log_categories)
|
||||
|
||||
def get_alt_blocks_hashes(self):
|
||||
def get_alt_blocks_hashes(self, client = ""):
|
||||
get_alt_blocks_hashes = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_alt_blocks_hashes', get_alt_blocks_hashes)
|
||||
|
||||
def get_alternate_chains(self):
|
||||
def get_alternate_chains(self, client = ""):
|
||||
get_alternate_chains = {
|
||||
'method': 'get_alternate_chains',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
@ -361,9 +385,10 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_fee_estimate)
|
||||
|
||||
def is_key_image_spent(self, key_images = []):
|
||||
def is_key_image_spent(self, key_images = [], client = ""):
|
||||
is_key_image_spent = {
|
||||
'key_images': key_images,
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/is_key_image_spent', is_key_image_spent)
|
||||
|
||||
@ -413,7 +438,7 @@ class Daemon(object):
|
||||
|
||||
def in_peers(self, in_peers):
|
||||
in_peers = {
|
||||
'in_peers': in_peers,
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/in_peers', in_peers)
|
||||
|
||||
@ -446,31 +471,34 @@ class Daemon(object):
|
||||
on_get_block_hash = get_block_hash
|
||||
on_getblockhash = get_block_hash
|
||||
|
||||
def relay_tx(self, txids = []):
|
||||
def relay_tx(self, txids = [], client = ""):
|
||||
relay_tx = {
|
||||
'method': 'relay_tx',
|
||||
'params': {
|
||||
'txids': txids,
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(relay_tx)
|
||||
|
||||
def sync_info(self):
|
||||
def sync_info(self, client = ""):
|
||||
sync_info = {
|
||||
'method': 'sync_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(sync_info)
|
||||
|
||||
def get_txpool_backlog(self):
|
||||
def get_txpool_backlog(self, client = ""):
|
||||
get_txpool_backlog = {
|
||||
'method': 'get_txpool_backlog',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
@ -498,3 +526,73 @@ class Daemon(object):
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_block_rate)
|
||||
|
||||
def rpc_access_info(self, client):
|
||||
rpc_access_info = {
|
||||
'method': 'rpc_access_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_info)
|
||||
|
||||
def rpc_access_submit_nonce(self, client, nonce, cookie):
|
||||
rpc_access_submit_nonce = {
|
||||
'method': 'rpc_access_submit_nonce',
|
||||
'params': {
|
||||
'client': client,
|
||||
'nonce': nonce,
|
||||
'cookie': cookie,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_submit_nonce)
|
||||
|
||||
def rpc_access_pay(self, client, paying_for, payment):
|
||||
rpc_access_pay = {
|
||||
'method': 'rpc_access_pay',
|
||||
'params': {
|
||||
'client': client,
|
||||
'paying_for': paying_for,
|
||||
'payment': payment,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_pay)
|
||||
|
||||
def rpc_access_tracking(self, clear = False):
|
||||
rpc_access_tracking = {
|
||||
'method': 'rpc_access_tracking',
|
||||
'params': {
|
||||
'clear': clear,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_tracking)
|
||||
|
||||
def rpc_access_data(self):
|
||||
rpc_access_data = {
|
||||
'method': 'rpc_access_data',
|
||||
'params': {
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_data)
|
||||
|
||||
def rpc_access_account(self, client, delta_balance = 0):
|
||||
rpc_access_account = {
|
||||
'method': 'rpc_access_account',
|
||||
'params': {
|
||||
'client': client,
|
||||
'delta_balance': delta_balance,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_account)
|
||||
|
Loading…
Reference in New Issue
Block a user