mirror of
https://codeberg.org/anoncontributorxmr/monero.git
synced 2024-11-23 10:37:37 -07:00
Merge pull request #5357
b3a9a4d
add a quick early out to get_blocks.bin when up to date (moneromooo-monero)2899379
daemon, wallet: new pay for RPC use system (moneromooo-monero)ffa4602
simplewallet: add public_nodes command (moneromooo-monero)
This commit is contained in:
commit
960c215801
@ -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)
|
||||
@ -2364,4 +2373,45 @@ bool t_rpc_command_executor::set_bootstrap_daemon(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool t_rpc_command_executor::rpc_payments()
|
||||
{
|
||||
cryptonote::COMMAND_RPC_ACCESS_DATA::request req;
|
||||
cryptonote::COMMAND_RPC_ACCESS_DATA::response res;
|
||||
std::string fail_message = "Unsuccessful";
|
||||
epee::json_rpc::error error_resp;
|
||||
|
||||
if (m_is_rpc)
|
||||
{
|
||||
if (!m_rpc_client->json_rpc_request(req, res, "rpc_access_data", fail_message.c_str()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_rpc_server->on_rpc_access_data(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK)
|
||||
{
|
||||
tools::fail_msg_writer() << make_error(fail_message, res.status);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
uint64_t balance = 0;
|
||||
tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s")
|
||||
% "Client ID" % "Balance" % "Total mined" % "Good" % "Stale" % "Bad" % "Dupes" % "Last update";
|
||||
for (const auto &entry: res.entries)
|
||||
{
|
||||
tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s")
|
||||
% entry.client % entry.balance % entry.credits_total
|
||||
% entry.nonces_good % entry.nonces_stale % entry.nonces_bad % entry.nonces_dupe
|
||||
% (entry.last_update_time == 0 ? "never" : get_human_time_ago(entry.last_update_time, now).c_str());
|
||||
balance += entry.balance;
|
||||
}
|
||||
tools::msg_writer() << res.entries.size() << " clients with a total of " << balance << " credits";
|
||||
tools::msg_writer() << "Aggregated client hash rate: " << get_mining_speed(res.hashrate);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}// namespace daemonize
|
||||
|
@ -167,6 +167,8 @@ public:
|
||||
const std::string &address,
|
||||
const std::string &username,
|
||||
const std::string &password);
|
||||
|
||||
bool rpc_payments();
|
||||
};
|
||||
|
||||
} // namespace daemonize
|
||||
|
@ -231,6 +231,7 @@ namespace nodetool
|
||||
: m_payload_handler(payload_handler),
|
||||
m_external_port(0),
|
||||
m_rpc_port(0),
|
||||
m_rpc_credits_per_hash(0),
|
||||
m_allow_local_ip(false),
|
||||
m_hide_my_port(false),
|
||||
m_igd(no_igd),
|
||||
@ -431,6 +432,11 @@ namespace nodetool
|
||||
m_rpc_port = rpc_port;
|
||||
}
|
||||
|
||||
void set_rpc_credits_per_hash(uint32_t rpc_credits_per_hash)
|
||||
{
|
||||
m_rpc_credits_per_hash = rpc_credits_per_hash;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_config_folder;
|
||||
|
||||
@ -440,6 +446,7 @@ namespace nodetool
|
||||
uint32_t m_listening_port_ipv6;
|
||||
uint32_t m_external_port;
|
||||
uint16_t m_rpc_port;
|
||||
uint32_t m_rpc_credits_per_hash;
|
||||
bool m_allow_local_ip;
|
||||
bool m_hide_my_port;
|
||||
igd_t m_igd;
|
||||
|
@ -1057,7 +1057,8 @@ namespace nodetool
|
||||
|
||||
pi = context.peer_id = rsp.node_data.peer_id;
|
||||
context.m_rpc_port = rsp.node_data.rpc_port;
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
|
||||
context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash;
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
|
||||
|
||||
// move
|
||||
for (auto const& zone : m_network_zones)
|
||||
@ -1123,7 +1124,7 @@ namespace nodetool
|
||||
add_host_fail(context.m_remote_address);
|
||||
}
|
||||
if(!context.m_is_income)
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port);
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash);
|
||||
if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false))
|
||||
{
|
||||
m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id );
|
||||
@ -1292,6 +1293,7 @@ namespace nodetool
|
||||
pe_local.last_seen = static_cast<int64_t>(last_seen);
|
||||
pe_local.pruning_seed = con->m_pruning_seed;
|
||||
pe_local.rpc_port = con->m_rpc_port;
|
||||
pe_local.rpc_credits_per_hash = con->m_rpc_credits_per_hash;
|
||||
zone.m_peerlist.append_with_peer_white(pe_local);
|
||||
//update last seen and push it to peerlist manager
|
||||
|
||||
@ -1922,6 +1924,7 @@ namespace nodetool
|
||||
else
|
||||
node_data.my_port = 0;
|
||||
node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0;
|
||||
node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0;
|
||||
node_data.network_id = m_network_id;
|
||||
return true;
|
||||
}
|
||||
@ -2366,6 +2369,7 @@ namespace nodetool
|
||||
context.peer_id = arg.node_data.peer_id;
|
||||
context.m_in_timedsync = false;
|
||||
context.m_rpc_port = arg.node_data.rpc_port;
|
||||
context.m_rpc_credits_per_hash = arg.node_data.rpc_credits_per_hash;
|
||||
|
||||
if(arg.node_data.my_port && zone.m_can_pingback)
|
||||
{
|
||||
@ -2393,6 +2397,7 @@ namespace nodetool
|
||||
pe.id = peer_id_l;
|
||||
pe.pruning_seed = context.m_pruning_seed;
|
||||
pe.rpc_port = context.m_rpc_port;
|
||||
pe.rpc_credits_per_hash = context.m_rpc_credits_per_hash;
|
||||
this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe);
|
||||
LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l);
|
||||
});
|
||||
@ -2710,7 +2715,7 @@ namespace nodetool
|
||||
}
|
||||
else
|
||||
{
|
||||
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port);
|
||||
zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port, pe.rpc_credits_per_hash);
|
||||
LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id));
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +111,7 @@ namespace nodetool
|
||||
bool append_with_peer_white(const peerlist_entry& pr);
|
||||
bool append_with_peer_gray(const peerlist_entry& pr);
|
||||
bool append_with_peer_anchor(const anchor_peerlist_entry& ple);
|
||||
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port);
|
||||
bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash);
|
||||
bool set_peer_unreachable(const peerlist_entry& pr);
|
||||
bool is_host_allowed(const epee::net_utils::network_address &address);
|
||||
bool get_random_gray_peer(peerlist_entry& pe);
|
||||
@ -315,7 +315,7 @@ namespace nodetool
|
||||
}
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
inline
|
||||
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port)
|
||||
bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash)
|
||||
{
|
||||
TRY_ENTRY();
|
||||
CRITICAL_REGION_LOCAL(m_peerlist_lock);
|
||||
@ -326,6 +326,7 @@ namespace nodetool
|
||||
ple.last_seen = time(NULL);
|
||||
ple.pruning_seed = pruning_seed;
|
||||
ple.rpc_port = rpc_port;
|
||||
ple.rpc_credits_per_hash = rpc_credits_per_hash;
|
||||
return append_with_peer_white(ple);
|
||||
CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false);
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
||||
#include "common/pruning.h"
|
||||
#endif
|
||||
|
||||
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2)
|
||||
BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3)
|
||||
|
||||
namespace boost
|
||||
{
|
||||
@ -241,6 +241,13 @@ namespace boost
|
||||
return;
|
||||
}
|
||||
a & pl.rpc_port;
|
||||
if (ver < 3)
|
||||
{
|
||||
if (!typename Archive::is_saving())
|
||||
pl.rpc_credits_per_hash = 0;
|
||||
return;
|
||||
}
|
||||
a & pl.rpc_credits_per_hash;
|
||||
}
|
||||
|
||||
template <class Archive, class ver_type>
|
||||
|
@ -77,6 +77,7 @@ namespace nodetool
|
||||
int64_t last_seen;
|
||||
uint32_t pruning_seed;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(adr)
|
||||
@ -85,6 +86,7 @@ namespace nodetool
|
||||
KV_SERIALIZE_OPT(last_seen, (int64_t)0)
|
||||
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
|
||||
KV_SERIALIZE_OPT(rpc_port, (uint16_t)0)
|
||||
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry;
|
||||
@ -132,6 +134,7 @@ namespace nodetool
|
||||
{
|
||||
ss << pe.id << "\t" << pe.adr.str()
|
||||
<< " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-")
|
||||
<< " \trpc credits per hash " << (pe.rpc_credits_per_hash > 0 ? std::to_string(pe.rpc_credits_per_hash) : "-")
|
||||
<< " \tpruning seed " << pe.pruning_seed
|
||||
<< " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen))
|
||||
<< std::endl;
|
||||
@ -166,6 +169,7 @@ namespace nodetool
|
||||
uint64_t local_time;
|
||||
uint32_t my_port;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
peerid_type peer_id;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
@ -174,6 +178,7 @@ namespace nodetool
|
||||
KV_SERIALIZE(local_time)
|
||||
KV_SERIALIZE(my_port)
|
||||
KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0))
|
||||
KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
@ -220,7 +225,7 @@ namespace nodetool
|
||||
{
|
||||
const epee::net_utils::network_address &na = p.adr;
|
||||
const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>();
|
||||
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
|
||||
local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
|
||||
}
|
||||
else
|
||||
MDEBUG("Not including in legacy peer list: " << p.adr.str());
|
||||
@ -235,7 +240,7 @@ namespace nodetool
|
||||
std::vector<peerlist_entry_base<network_address_old>> local_peerlist;
|
||||
epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist");
|
||||
for (const auto &p: local_peerlist)
|
||||
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port}));
|
||||
((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash}));
|
||||
}
|
||||
}
|
||||
END_KV_SERIALIZE_MAP()
|
||||
|
@ -29,12 +29,14 @@
|
||||
include_directories(SYSTEM ${ZMQ_INCLUDE_PATH})
|
||||
|
||||
set(rpc_base_sources
|
||||
rpc_args.cpp)
|
||||
rpc_args.cpp
|
||||
rpc_payment_signature.cpp
|
||||
rpc_handler.cpp)
|
||||
|
||||
set(rpc_sources
|
||||
bootstrap_daemon.cpp
|
||||
core_rpc_server.cpp
|
||||
rpc_handler.cpp
|
||||
rpc_payment.cpp
|
||||
instanciations)
|
||||
|
||||
set(daemon_messages_sources
|
||||
@ -47,7 +49,9 @@ set(daemon_rpc_server_sources
|
||||
|
||||
|
||||
set(rpc_base_headers
|
||||
rpc_args.h)
|
||||
rpc_args.h
|
||||
rpc_payment_signature.h
|
||||
rpc_handler.h)
|
||||
|
||||
set(rpc_headers
|
||||
rpc_handler.h)
|
||||
@ -58,6 +62,7 @@ set(daemon_rpc_server_headers)
|
||||
set(rpc_daemon_private_headers
|
||||
bootstrap_daemon.h
|
||||
core_rpc_server.h
|
||||
rpc_payment.h
|
||||
core_rpc_server_commands_defs.h
|
||||
core_rpc_server_error_codes.h)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
#include "cryptonote_core/cryptonote_core.h"
|
||||
#include "p2p/net_node.h"
|
||||
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
|
||||
#include "rpc_payment.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc"
|
||||
@ -71,6 +72,9 @@ namespace cryptonote
|
||||
static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert;
|
||||
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address;
|
||||
static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login;
|
||||
static const command_line::arg_descriptor<std::string> arg_rpc_payment_address;
|
||||
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty;
|
||||
static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits;
|
||||
|
||||
typedef epee::net_utils::connection_context_base connection_context;
|
||||
|
||||
@ -78,6 +82,7 @@ namespace cryptonote
|
||||
core& cr
|
||||
, nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p
|
||||
);
|
||||
~core_rpc_server();
|
||||
|
||||
static void init_options(boost::program_options::options_description& desc);
|
||||
bool init(
|
||||
@ -111,7 +116,7 @@ namespace cryptonote
|
||||
MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted)
|
||||
MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted)
|
||||
MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted)
|
||||
MAP_URI_AUTO_JON2_IF("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES, !m_restricted)
|
||||
MAP_URI_AUTO_JON2("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES)
|
||||
MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted)
|
||||
MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted)
|
||||
MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted)
|
||||
@ -169,6 +174,12 @@ namespace cryptonote
|
||||
MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG)
|
||||
MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION)
|
||||
MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted)
|
||||
MAP_JON_RPC_WE("rpc_access_info", on_rpc_access_info, COMMAND_RPC_ACCESS_INFO)
|
||||
MAP_JON_RPC_WE("rpc_access_submit_nonce",on_rpc_access_submit_nonce, COMMAND_RPC_ACCESS_SUBMIT_NONCE)
|
||||
MAP_JON_RPC_WE("rpc_access_pay", on_rpc_access_pay, COMMAND_RPC_ACCESS_PAY)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_tracking", on_rpc_access_tracking, COMMAND_RPC_ACCESS_TRACKING, !m_restricted)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_data", on_rpc_access_data, COMMAND_RPC_ACCESS_DATA, !m_restricted)
|
||||
MAP_JON_RPC_WE_IF("rpc_access_account", on_rpc_access_account, COMMAND_RPC_ACCESS_ACCOUNT, !m_restricted)
|
||||
END_JSON_RPC_MAP()
|
||||
END_URI_MAP2()
|
||||
|
||||
@ -236,6 +247,12 @@ namespace cryptonote
|
||||
bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_tracking(const COMMAND_RPC_ACCESS_TRACKING::request& req, COMMAND_RPC_ACCESS_TRACKING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_data(const COMMAND_RPC_ACCESS_DATA::request& req, COMMAND_RPC_ACCESS_DATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
bool on_rpc_access_account(const COMMAND_RPC_ACCESS_ACCOUNT::request& req, COMMAND_RPC_ACCESS_ACCOUNT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL);
|
||||
//-----------------------
|
||||
|
||||
private:
|
||||
@ -252,6 +269,8 @@ private:
|
||||
enum invoke_http_mode { JON, BIN, JON_RPC };
|
||||
template <typename COMMAND_TYPE>
|
||||
bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r);
|
||||
bool get_block_template(const account_public_address &address, const crypto::hash *prev_block, const cryptonote::blobdata &extra_nonce, size_t &reserved_offset, cryptonote::difficulty_type &difficulty, uint64_t &height, uint64_t &expected_reward, block &b, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, epee::json_rpc::error &error_resp);
|
||||
bool check_payment(const std::string &client, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash);
|
||||
|
||||
core& m_core;
|
||||
nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p;
|
||||
@ -260,10 +279,10 @@ private:
|
||||
bool m_should_use_bootstrap_daemon;
|
||||
std::chrono::system_clock::time_point m_bootstrap_height_check_time;
|
||||
bool m_was_bootstrap_ever_used;
|
||||
network_type m_nettype;
|
||||
bool m_restricted;
|
||||
epee::critical_section m_host_fails_score_lock;
|
||||
std::map<std::string, uint64_t> m_host_fails_score;
|
||||
std::unique_ptr<rpc_payment> m_rpc_payment;
|
||||
};
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -43,5 +43,34 @@
|
||||
#define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11
|
||||
#define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12
|
||||
#define CORE_RPC_ERROR_CODE_REGTEST_REQUIRED -13
|
||||
#define CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED -14
|
||||
#define CORE_RPC_ERROR_CODE_INVALID_CLIENT -15
|
||||
#define CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW -16
|
||||
#define CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT -17
|
||||
#define CORE_RPC_ERROR_CODE_STALE_PAYMENT -18
|
||||
|
||||
static inline const char *get_rpc_server_error_message(int64_t code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case CORE_RPC_ERROR_CODE_WRONG_PARAM: return "Invalid parameter";
|
||||
case CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT: return "Height is too large";
|
||||
case CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE: return "Reserve size is too large";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS: return "Wrong wallet address";
|
||||
case CORE_RPC_ERROR_CODE_INTERNAL_ERROR: return "Internal error";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB: return "Wrong block blob";
|
||||
case CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED: return "Block not accepted";
|
||||
case CORE_RPC_ERROR_CODE_CORE_BUSY: return "Core is busy";
|
||||
case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE: return "Wrong block blob size";
|
||||
case CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC: return "Unsupported RPC";
|
||||
case CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS: return "Mining to subaddress is not supported";
|
||||
case CORE_RPC_ERROR_CODE_REGTEST_REQUIRED: return "Regtest mode required";
|
||||
case CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED: return "Payment required";
|
||||
case CORE_RPC_ERROR_CODE_INVALID_CLIENT: return "Invalid client";
|
||||
case CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW: return "Payment too low";
|
||||
case CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT: return "Duplicate payment";
|
||||
case CORE_RPC_ERROR_CODE_STALE_PAYMENT: return "Stale payment";
|
||||
default: MERROR("Unknown error: " << code); return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,7 @@ namespace rpc
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
uint16_t rpc_port;
|
||||
uint32_t rpc_credits_per_hash;
|
||||
uint64_t last_seen;
|
||||
uint32_t pruning_seed;
|
||||
};
|
||||
|
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);
|
50
src/rpc/rpc_payment_costs.h
Normal file
50
src/rpc/rpc_payment_costs.h
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define COST_PER_BLOCK 0.05
|
||||
#define COST_PER_TX_RELAY 100
|
||||
#define COST_PER_OUT 1
|
||||
#define COST_PER_OUTPUT_INDEXES 1
|
||||
#define COST_PER_TX 0.5
|
||||
#define COST_PER_KEY_IMAGE 0.01
|
||||
#define COST_PER_POOL_HASH 0.01
|
||||
#define COST_PER_TX_POOL_STATS 0.2
|
||||
#define COST_PER_BLOCK_HEADER 0.1
|
||||
#define COST_PER_GET_INFO 1
|
||||
#define COST_PER_OUTPUT_HISTOGRAM 25000
|
||||
#define COST_PER_FULL_OUTPUT_HISTOGRAM 5000000
|
||||
#define COST_PER_OUTPUT_DISTRIBUTION_0 20
|
||||
#define COST_PER_OUTPUT_DISTRIBUTION 50000
|
||||
#define COST_PER_COINBASE_TX_SUM_BLOCK 2
|
||||
#define COST_PER_BLOCK_HASH 0.002
|
||||
#define COST_PER_FEE_ESTIMATE 1
|
||||
#define COST_PER_SYNC_INFO 2
|
||||
#define COST_PER_HARD_FORK_INFO 1
|
||||
#define COST_PER_PEER_LIST 2
|
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"), ""};
|
||||
@ -247,7 +262,11 @@ namespace
|
||||
const char* USAGE_FROZEN("frozen <key_image>");
|
||||
const char* USAGE_LOCK("lock");
|
||||
const char* USAGE_NET_STATS("net_stats");
|
||||
const char* USAGE_PUBLIC_NODES("public_nodes");
|
||||
const char* USAGE_WELCOME("welcome");
|
||||
const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info");
|
||||
const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc");
|
||||
const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc");
|
||||
const char* USAGE_VERSION("version");
|
||||
const char* USAGE_HELP("help [<command>]");
|
||||
|
||||
@ -490,22 +509,28 @@ namespace
|
||||
fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>");
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
|
||||
{
|
||||
void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon)
|
||||
{
|
||||
bool warn_of_possible_attack = !trusted_daemon;
|
||||
try
|
||||
{
|
||||
std::rethrow_exception(e);
|
||||
}
|
||||
catch (const tools::error::daemon_busy&)
|
||||
catch (const tools::error::payment_required&)
|
||||
{
|
||||
fail_msg_writer() << sw::tr("daemon is busy. Please try again later.");
|
||||
fail_msg_writer() << tr("Payment required, see the 'rpc_payment_info' command");
|
||||
m_need_payment = true;
|
||||
}
|
||||
catch (const tools::error::no_connection_to_daemon&)
|
||||
{
|
||||
fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running.");
|
||||
}
|
||||
catch (const tools::error::daemon_busy&)
|
||||
{
|
||||
fail_msg_writer() << tr("daemon is busy. Please try again later.");
|
||||
}
|
||||
catch (const tools::error::wallet_rpc_error& e)
|
||||
{
|
||||
LOG_ERROR("RPC error: " << e.to_string());
|
||||
@ -602,8 +627,10 @@ namespace
|
||||
|
||||
if (warn_of_possible_attack)
|
||||
fail_msg_writer() << sw::tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information.");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
bool check_file_overwrite(const std::string &filename)
|
||||
{
|
||||
boost::system::error_code errcode;
|
||||
@ -1908,6 +1935,77 @@ bool simple_wallet::unset_ring(const std::vector<std::string> &args)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args)
|
||||
{
|
||||
if (!try_connect_to_daemon())
|
||||
return true;
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
|
||||
try
|
||||
{
|
||||
bool payment_required;
|
||||
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
|
||||
uint32_t cookie;
|
||||
std::string hashing_blob;
|
||||
crypto::hash seed_hash, next_seed_hash;
|
||||
crypto::public_key pkey;
|
||||
crypto::secret_key_to_public_key(m_wallet->get_rpc_client_secret_key(), pkey);
|
||||
message_writer() << tr("RPC client ID: ") << pkey;
|
||||
message_writer() << tr("RPC client secret key: ") << m_wallet->get_rpc_client_secret_key();
|
||||
if (!m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to query daemon");
|
||||
return true;
|
||||
}
|
||||
if (payment_required)
|
||||
{
|
||||
uint64_t target = m_wallet->credits_target();
|
||||
if (target == 0)
|
||||
target = CREDITS_TARGET;
|
||||
message_writer() << tr("Using daemon: ") << m_wallet->get_daemon_address();
|
||||
message_writer() << tr("Payments required for node use, current credits: ") << credits;
|
||||
message_writer() << tr("Credits target: ") << target;
|
||||
uint64_t expected, discrepancy;
|
||||
m_wallet->credit_report(expected, discrepancy);
|
||||
message_writer() << tr("Credits spent this session: ") << expected;
|
||||
if (expected)
|
||||
message_writer() << tr("Credit discrepancy this session: ") << discrepancy << " (" << 100.0f * discrepancy / expected << "%)";
|
||||
float cph = credits_per_hash_found / (float)diff;
|
||||
message_writer() << tr("Difficulty: ") << diff << ", " << credits_per_hash_found << " " << tr("credits per hash found, ") << cph << " " << tr("credits/hash");;
|
||||
const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
|
||||
bool mining = (now - m_last_rpc_payment_mining_time).total_microseconds() < 1000000;
|
||||
if (mining)
|
||||
{
|
||||
float hash_rate = m_rpc_payment_hash_rate;
|
||||
if (hash_rate > 0)
|
||||
{
|
||||
message_writer() << (boost::format(tr("Mining for payment at %.1f H/s")) % hash_rate).str();
|
||||
if (credits < target)
|
||||
{
|
||||
std::chrono::seconds seconds((unsigned)((target - credits) / cph / hash_rate));
|
||||
std::string target_string = get_human_readable_timespan(seconds);
|
||||
message_writer() << (boost::format(tr("Estimated time till %u credits target mined: %s")) % target % target_string).str();
|
||||
}
|
||||
}
|
||||
else
|
||||
message_writer() << tr("Mining for payment");
|
||||
}
|
||||
else
|
||||
message_writer() << tr("Not mining");
|
||||
}
|
||||
else
|
||||
message_writer() << tr("No payment needed for node use");
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG_ERROR("unexpected error: " << e.what());
|
||||
fail_msg_writer() << tr("unexpected error: ") << e.what();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::blackball(const std::vector<std::string> &args)
|
||||
{
|
||||
uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets;
|
||||
@ -2150,6 +2248,45 @@ bool simple_wallet::net_stats(const std::vector<std::string> &args)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::public_nodes(const std::vector<std::string> &args)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto nodes = m_wallet->get_public_nodes(false);
|
||||
m_claimed_cph.clear();
|
||||
if (nodes.empty())
|
||||
{
|
||||
fail_msg_writer() << tr("No known public nodes");
|
||||
return true;
|
||||
}
|
||||
std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) {
|
||||
if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0)
|
||||
return true;
|
||||
if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash)
|
||||
return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash;
|
||||
return false;
|
||||
});
|
||||
|
||||
const uint64_t now = time(NULL);
|
||||
message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen");
|
||||
for (const auto &node: nodes)
|
||||
{
|
||||
const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE;
|
||||
char cphs[9];
|
||||
snprintf(cphs, sizeof(cphs), "%.3f", cph);
|
||||
const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen));
|
||||
std::string host = node.host + ":" + std::to_string(node.rpc_port);
|
||||
message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen;
|
||||
m_claimed_cph[host] = node.rpc_credits_per_hash;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fail_msg_writer() << tr("Error retrieving public node list: ") << e.what();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::welcome(const std::vector<std::string> &args)
|
||||
{
|
||||
message_writer() << tr("Welcome to Monero, the private cryptocurrency.");
|
||||
@ -2214,6 +2351,50 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>&
|
||||
return m_wallet->import_key_images(exported_txs, 0, true);
|
||||
}
|
||||
|
||||
bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args)
|
||||
{
|
||||
if (!try_connect_to_daemon())
|
||||
return true;
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
|
||||
bool payment_required;
|
||||
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
|
||||
uint32_t cookie;
|
||||
std::string hashing_blob;
|
||||
crypto::hash seed_hash, next_seed_hash;
|
||||
if (!m_wallet->get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie))
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to query daemon");
|
||||
return true;
|
||||
}
|
||||
if (!payment_required)
|
||||
{
|
||||
fail_msg_writer() << tr("Daemon does not require payment for RPC access");
|
||||
return true;
|
||||
}
|
||||
|
||||
m_rpc_payment_mining_requested = true;
|
||||
m_rpc_payment_checker.trigger();
|
||||
const float cph = credits_per_hash_found / (float)diff;
|
||||
bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE);
|
||||
success_msg_writer() << (boost::format(tr("Starting mining for RPC access: diff %llu, %f credits/hash%s")) % diff % cph % (low ? " - this is low" : "")).str();
|
||||
success_msg_writer() << tr("Run stop_mining_for_rpc to stop");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::stop_mining_for_rpc(const std::vector<std::string> &args)
|
||||
{
|
||||
if (!try_connect_to_daemon())
|
||||
return true;
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
m_rpc_payment_mining_requested = false;
|
||||
m_last_rpc_payment_mining_time = boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1));
|
||||
m_rpc_payment_hash_rate = -1.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
const auto pwd_container = get_and_verify_password();
|
||||
@ -2602,6 +2783,53 @@ bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::set_persistent_rpc_client_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
parse_bool_and_use(args[1], [&](bool r) {
|
||||
m_wallet->persistent_rpc_client_id(r);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
float threshold;
|
||||
if (!epee::string_tools::get_xtype_from_string(threshold, args[1]) || threshold < 0.0f)
|
||||
{
|
||||
fail_msg_writer() << tr("Invalid threshold");
|
||||
return true;
|
||||
}
|
||||
m_wallet->auto_mine_for_rpc_payment_threshold(threshold);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::set_credits_target(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
const auto pwd_container = get_and_verify_password();
|
||||
if (pwd_container)
|
||||
{
|
||||
uint64_t target;
|
||||
if (!epee::string_tools::get_xtype_from_string(target, args[1]))
|
||||
{
|
||||
fail_msg_writer() << tr("Invalid target");
|
||||
return true;
|
||||
}
|
||||
m_wallet->credits_target(target);
|
||||
m_wallet->rewrite(m_wallet_file, pwd_container->password());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool simple_wallet::set_key_reuse_mitigation2(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
const auto pwd_container = get_and_verify_password();
|
||||
@ -2844,6 +3072,12 @@ simple_wallet::simple_wallet()
|
||||
, m_last_activity_time(time(NULL))
|
||||
, m_locked(false)
|
||||
, m_in_command(false)
|
||||
, m_need_payment(false)
|
||||
, m_rpc_payment_mining_requested(false)
|
||||
, m_last_rpc_payment_mining_time(boost::gregorian::date(1970, 1, 1))
|
||||
, m_daemon_rpc_payment_message_displayed(false)
|
||||
, m_rpc_payment_hash_rate(-1.0f)
|
||||
, m_suspend_rpc_payment_mining(false)
|
||||
{
|
||||
m_cmd_binder.set_handler("start_mining",
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1),
|
||||
@ -3022,7 +3256,13 @@ simple_wallet::simple_wallet()
|
||||
"device-name <device_name[:device_spec]>\n "
|
||||
" Device name for hardware wallet.\n "
|
||||
"export-format <\"binary\"|\"ascii\">\n "
|
||||
" Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n "));
|
||||
" Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n "
|
||||
"persistent-client-id <1|0>\n "
|
||||
" Whether to keep using the same client id for RPC payment over wallet restarts.\n"
|
||||
"auto-mine-for-rpc-payment-threshold <float>\n "
|
||||
" Whether to automatically start mining for RPC payment if the daemon requires it.\n"
|
||||
"credits-target <unsigned int>\n"
|
||||
" The RPC payment credits balance to target (0 for default)."));
|
||||
m_cmd_binder.set_handler("encrypted_seed",
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1),
|
||||
tr("Display the encrypted Electrum-style mnemonic seed."));
|
||||
@ -3325,6 +3565,10 @@ simple_wallet::simple_wallet()
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1),
|
||||
tr(USAGE_NET_STATS),
|
||||
tr("Prints simple network stats"));
|
||||
m_cmd_binder.set_handler("public_nodes",
|
||||
boost::bind(&simple_wallet::public_nodes, this, _1),
|
||||
tr(USAGE_PUBLIC_NODES),
|
||||
tr("Lists known public nodes"));
|
||||
m_cmd_binder.set_handler("welcome",
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1),
|
||||
tr(USAGE_WELCOME),
|
||||
@ -3333,6 +3577,18 @@ simple_wallet::simple_wallet()
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1),
|
||||
tr(USAGE_VERSION),
|
||||
tr("Returns version information"));
|
||||
m_cmd_binder.set_handler("rpc_payment_info",
|
||||
boost::bind(&simple_wallet::rpc_payment_info, this, _1),
|
||||
tr(USAGE_RPC_PAYMENT_INFO),
|
||||
tr("Get info about RPC payments to current node"));
|
||||
m_cmd_binder.set_handler("start_mining_for_rpc",
|
||||
boost::bind(&simple_wallet::start_mining_for_rpc, this, _1),
|
||||
tr(USAGE_START_MINING_FOR_RPC),
|
||||
tr("Start mining to pay for RPC access"));
|
||||
m_cmd_binder.set_handler("stop_mining_for_rpc",
|
||||
boost::bind(&simple_wallet::stop_mining_for_rpc, this, _1),
|
||||
tr(USAGE_STOP_MINING_FOR_RPC),
|
||||
tr("Stop mining to pay for RPC access"));
|
||||
m_cmd_binder.set_handler("help",
|
||||
boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1),
|
||||
tr(USAGE_HELP),
|
||||
@ -3402,6 +3658,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
||||
<< " (disabled on Windows)"
|
||||
#endif
|
||||
;
|
||||
success_msg_writer() << "persistent-rpc-client-id = " << m_wallet->persistent_rpc_client_id();
|
||||
success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold();
|
||||
success_msg_writer() << "credits-target = " << m_wallet->credits_target();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@ -3463,6 +3722,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
|
||||
CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no"));
|
||||
CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
|
||||
CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\""));
|
||||
CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1"));
|
||||
CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0"));
|
||||
CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer"));
|
||||
}
|
||||
fail_msg_writer() << tr("set: unrecognized argument(s)");
|
||||
return true;
|
||||
@ -4227,6 +4489,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key))
|
||||
{
|
||||
crypto::secret_key rpc_client_secret_key;
|
||||
if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), rpc_client_secret_key))
|
||||
{
|
||||
fail_msg_writer() << tr("RPC client secret key should be 32 byte in hex format");
|
||||
return false;
|
||||
}
|
||||
m_wallet->set_rpc_client_secret_key(rpc_client_secret_key);
|
||||
}
|
||||
|
||||
if (!m_wallet->is_trusted_daemon())
|
||||
{
|
||||
message_writer(console_color_red, true) << (boost::format(tr("Warning: using an untrusted daemon at %s")) % m_wallet->get_daemon_address()).str();
|
||||
@ -4742,6 +5015,7 @@ bool simple_wallet::close_wallet()
|
||||
if (m_idle_run.load(std::memory_order_relaxed))
|
||||
{
|
||||
m_idle_run.store(false, std::memory_order_relaxed);
|
||||
m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed);
|
||||
m_wallet->stop();
|
||||
{
|
||||
boost::unique_lock<boost::mutex> lock(m_idle_mutex);
|
||||
@ -5009,6 +5283,40 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args)
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto i = m_claimed_cph.find(daemon_url);
|
||||
if (i == m_claimed_cph.end())
|
||||
return false;
|
||||
|
||||
claimed_cph = m_claimed_cph[daemon_url];
|
||||
bool payment_required;
|
||||
uint64_t credits, diff, credits_per_hash_found, height, seed_height;
|
||||
uint32_t cookie;
|
||||
cryptonote::blobdata hashing_blob;
|
||||
crypto::hash seed_hash, next_seed_hash;
|
||||
if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required)
|
||||
{
|
||||
actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fail_msg_writer() << tr("Error checking daemon RPC access prices");
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
// can't check
|
||||
fail_msg_writer() << tr("Error checking daemon RPC access prices: ") << e.what();
|
||||
return false;
|
||||
}
|
||||
// no record found for this daemon
|
||||
return false;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::set_daemon(const std::vector<std::string>& args)
|
||||
{
|
||||
std::string daemon_url;
|
||||
@ -5032,7 +5340,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args)
|
||||
// If no port has been provided, use the default from config
|
||||
if (!match[3].length())
|
||||
{
|
||||
int daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT;
|
||||
uint16_t daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT;
|
||||
daemon_url = match[1] + match[2] + std::string(":") + std::to_string(daemon_port);
|
||||
} else {
|
||||
daemon_url = args[0];
|
||||
@ -5065,7 +5373,28 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args)
|
||||
}
|
||||
catch (const std::exception &e) { }
|
||||
}
|
||||
|
||||
if (!try_connect_to_daemon())
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to connect to daemon");
|
||||
return true;
|
||||
}
|
||||
|
||||
success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted"));
|
||||
|
||||
// check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying
|
||||
uint32_t actual_cph, claimed_cph;
|
||||
if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph))
|
||||
{
|
||||
if (actual_cph < claimed_cph)
|
||||
{
|
||||
fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently.");
|
||||
fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE;
|
||||
fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
m_daemon_rpc_payment_message_displayed = false;
|
||||
} else {
|
||||
fail_msg_writer() << tr("This does not seem to be a valid daemon URL.");
|
||||
}
|
||||
@ -5304,6 +5633,11 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
|
||||
{
|
||||
ss << tr("no connection to daemon. Please make sure daemon is running.");
|
||||
}
|
||||
catch (const tools::error::payment_required&)
|
||||
{
|
||||
ss << tr("payment required.");
|
||||
m_need_payment = true;
|
||||
}
|
||||
catch (const tools::error::wallet_rpc_error& e)
|
||||
{
|
||||
LOG_ERROR("RPC error: " << e.to_string());
|
||||
@ -5630,6 +5964,11 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
|
||||
{
|
||||
fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running.");
|
||||
}
|
||||
catch (const tools::error::payment_required&)
|
||||
{
|
||||
fail_msg_writer() << tr("payment required.");
|
||||
m_need_payment = true;
|
||||
}
|
||||
catch (const tools::error::is_key_image_spent_error&)
|
||||
{
|
||||
fail_msg_writer() << tr("failed to get spent status");
|
||||
@ -5733,6 +6072,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
|
||||
req.outputs[j].index = absolute_offsets[j];
|
||||
}
|
||||
COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
|
||||
req.client = cryptonote::make_rpc_payment_signature(m_wallet->get_rpc_client_secret_key());
|
||||
bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res);
|
||||
err = interpret_rpc_response(r, res.status);
|
||||
if (!err.empty())
|
||||
@ -8474,6 +8814,7 @@ void simple_wallet::wallet_idle_thread()
|
||||
#endif
|
||||
m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this));
|
||||
m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this));
|
||||
m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this));
|
||||
|
||||
if (!m_idle_run.load(std::memory_order_relaxed))
|
||||
break;
|
||||
@ -8527,6 +8868,78 @@ bool simple_wallet::check_mms()
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::check_rpc_payment()
|
||||
{
|
||||
if (!m_rpc_payment_mining_requested && m_wallet->auto_mine_for_rpc_payment_threshold() == 0.0f)
|
||||
return true;
|
||||
|
||||
uint64_t target = m_wallet->credits_target();
|
||||
if (target == 0)
|
||||
target = CREDITS_TARGET;
|
||||
if (m_rpc_payment_mining_requested)
|
||||
target = std::numeric_limits<uint64_t>::max();
|
||||
bool need_payment = m_need_payment || m_rpc_payment_mining_requested || (m_wallet->credits() < target && m_wallet->daemon_requires_payment());
|
||||
if (need_payment)
|
||||
{
|
||||
const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time();
|
||||
auto startfunc = [this](uint64_t diff, uint64_t credits_per_hash_found)
|
||||
{
|
||||
const float cph = credits_per_hash_found / (float)diff;
|
||||
bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE);
|
||||
if (credits_per_hash_found > 0 && cph >= m_wallet->auto_mine_for_rpc_payment_threshold())
|
||||
{
|
||||
MINFO(std::to_string(cph) << " credits per hash is >= our threshold (" << m_wallet->auto_mine_for_rpc_payment_threshold() << "), starting mining");
|
||||
return true;
|
||||
}
|
||||
else if (m_rpc_payment_mining_requested)
|
||||
{
|
||||
MINFO("Mining for RPC payment was requested, starting mining");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_daemon_rpc_payment_message_displayed)
|
||||
{
|
||||
success_msg_writer() << boost::format(tr("Daemon requests payment at diff %llu, with %f credits/hash%s. Run start_mining_for_rpc to start mining to pay for RPC access, or use another daemon")) %
|
||||
diff % cph % (low ? " - this is low" : "");
|
||||
m_cmd_binder.print_prompt();
|
||||
m_daemon_rpc_payment_message_displayed = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
auto contfunc = [&,this](unsigned n_hashes)
|
||||
{
|
||||
if (m_suspend_rpc_payment_mining.load(std::memory_order_relaxed))
|
||||
return false;
|
||||
const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time();
|
||||
m_last_rpc_payment_mining_time = now;
|
||||
if ((now - start_time).total_microseconds() >= 2 * 1000000)
|
||||
m_rpc_payment_hash_rate = n_hashes / (float)((now - start_time).total_seconds());
|
||||
if ((now - start_time).total_microseconds() >= REFRESH_PERIOD * 1000000)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
auto foundfunc = [this, target](uint64_t credits)
|
||||
{
|
||||
m_need_payment = false;
|
||||
return credits < target;
|
||||
};
|
||||
auto errorfunc = [this](const std::string &error)
|
||||
{
|
||||
fail_msg_writer() << tr("Error mining to daemon: ") << error;
|
||||
m_cmd_binder.print_prompt();
|
||||
};
|
||||
bool ret = m_wallet->search_for_rpc_payment(target, startfunc, contfunc, foundfunc, errorfunc);
|
||||
if (!ret)
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to start mining for RPC payment");
|
||||
m_cmd_binder.print_prompt();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::string simple_wallet::get_prompt() const
|
||||
{
|
||||
if (m_locked)
|
||||
@ -9728,6 +10141,7 @@ int main(int argc, char* argv[])
|
||||
command_line::add_arg(desc_params, arg_create_address_file);
|
||||
command_line::add_arg(desc_params, arg_subaddress_lookahead);
|
||||
command_line::add_arg(desc_params, arg_use_english_language_names);
|
||||
command_line::add_arg(desc_params, arg_rpc_client_secret_key);
|
||||
|
||||
po::positional_options_description positional_options;
|
||||
positional_options.add(arg_command.name, -1);
|
||||
|
@ -151,6 +151,9 @@ namespace cryptonote
|
||||
bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool help(const std::vector<std::string> &args = std::vector<std::string>());
|
||||
bool start_mining(const std::vector<std::string> &args);
|
||||
bool stop_mining(const std::vector<std::string> &args);
|
||||
@ -250,7 +253,11 @@ namespace cryptonote
|
||||
bool thaw(const std::vector<std::string>& args);
|
||||
bool frozen(const std::vector<std::string>& args);
|
||||
bool lock(const std::vector<std::string>& args);
|
||||
bool rpc_payment_info(const std::vector<std::string> &args);
|
||||
bool start_mining_for_rpc(const std::vector<std::string> &args);
|
||||
bool stop_mining_for_rpc(const std::vector<std::string> &args);
|
||||
bool net_stats(const std::vector<std::string>& args);
|
||||
bool public_nodes(const std::vector<std::string>& args);
|
||||
bool welcome(const std::vector<std::string>& args);
|
||||
bool version(const std::vector<std::string>& args);
|
||||
bool on_unknown_command(const std::vector<std::string>& args);
|
||||
@ -325,6 +332,11 @@ namespace cryptonote
|
||||
bool check_inactivity();
|
||||
bool check_refresh();
|
||||
bool check_mms();
|
||||
bool check_rpc_payment();
|
||||
|
||||
void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon);
|
||||
|
||||
bool check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph);
|
||||
|
||||
//----------------- i_wallet2_callback ---------------------
|
||||
virtual void on_new_block(uint64_t height, const cryptonote::block& block);
|
||||
@ -439,7 +451,17 @@ namespace cryptonote
|
||||
epee::math_helper::once_a_time_seconds<1> m_inactivity_checker;
|
||||
epee::math_helper::once_a_time_seconds<90> m_refresh_checker;
|
||||
epee::math_helper::once_a_time_seconds<90> m_mms_checker;
|
||||
epee::math_helper::once_a_time_seconds<90> m_rpc_payment_checker;
|
||||
|
||||
std::atomic<bool> m_need_payment;
|
||||
boost::posix_time::ptime m_last_rpc_payment_mining_time;
|
||||
bool m_rpc_payment_mining_requested;
|
||||
bool m_daemon_rpc_payment_message_displayed;
|
||||
float m_rpc_payment_hash_rate;
|
||||
std::atomic<bool> m_suspend_rpc_payment_mining;
|
||||
|
||||
std::unordered_map<std::string, uint32_t> m_claimed_cph;
|
||||
|
||||
// MMS
|
||||
mms::message_store& get_message_store() const { return m_wallet->get_message_store(); };
|
||||
mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); };
|
||||
|
@ -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();
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version");
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
|
||||
}
|
||||
m_rpc_version = resp_t.version;
|
||||
}
|
||||
rpc_version = m_rpc_version;
|
||||
@ -85,7 +103,7 @@ void NodeRPCProxy::set_height(uint64_t h)
|
||||
m_height = h;
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
boost::optional<std::string> NodeRPCProxy::get_info()
|
||||
{
|
||||
if (m_offline)
|
||||
return boost::optional<std::string>("offline");
|
||||
@ -95,13 +113,15 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_info");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_info", resp_t.credits, pre_call_credits, COST_PER_GET_INFO);
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height");
|
||||
m_height = resp_t.height;
|
||||
m_target_height = resp_t.target_height;
|
||||
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
|
||||
@ -110,7 +130,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -119,7 +139,7 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -128,7 +148,7 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) c
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit)
|
||||
{
|
||||
auto res = get_info();
|
||||
if (res)
|
||||
@ -137,7 +157,7 @@ boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &bloc
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height)
|
||||
{
|
||||
if (m_offline)
|
||||
return boost::optional<std::string>("offline");
|
||||
@ -145,14 +165,17 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
|
||||
{
|
||||
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.version = version;
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status");
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "hard_fork_info");
|
||||
check_rpc_cost(m_rpc_payment_state, "hard_fork_info", resp_t.credits, pre_call_credits, COST_PER_HARD_FORK_INFO);
|
||||
}
|
||||
|
||||
m_earliest_height[version] = resp_t.earliest_height;
|
||||
}
|
||||
|
||||
@ -160,7 +183,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee)
|
||||
{
|
||||
uint64_t height;
|
||||
|
||||
@ -174,14 +197,17 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.grace_blocks = grace_blocks;
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
|
||||
}
|
||||
|
||||
m_dynamic_base_fee_estimate = resp_t.fee;
|
||||
m_dynamic_base_fee_estimate_cached_height = height;
|
||||
m_dynamic_base_fee_estimate_grace_blocks = grace_blocks;
|
||||
@ -192,7 +218,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) const
|
||||
boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask)
|
||||
{
|
||||
uint64_t height;
|
||||
|
||||
@ -206,14 +232,17 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
|
||||
{
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks;
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
uint64_t pre_call_credits = m_rpc_payment_state.credits;
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
|
||||
check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
|
||||
}
|
||||
|
||||
m_dynamic_base_fee_estimate = resp_t.fee;
|
||||
m_dynamic_base_fee_estimate_cached_height = height;
|
||||
m_fee_quantization_mask = resp_t.quantization_mask;
|
||||
@ -228,4 +257,65 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
|
||||
return boost::optional<std::string>();
|
||||
}
|
||||
|
||||
boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
|
||||
{
|
||||
const time_t now = time(NULL);
|
||||
if (m_rpc_payment_state.stale || now >= m_rpc_payment_info_time + 5*60 || (mining && now >= m_rpc_payment_info_time + 10)) // re-cache every 10 seconds if mining, 5 minutes otherwise
|
||||
{
|
||||
cryptonote::COMMAND_RPC_ACCESS_INFO::request req_t = AUTO_VAL_INIT(req_t);
|
||||
cryptonote::COMMAND_RPC_ACCESS_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
|
||||
|
||||
{
|
||||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
|
||||
req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
|
||||
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_info", req_t, resp_t, m_http_client, rpc_timeout);
|
||||
RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "rpc_access_info");
|
||||
m_rpc_payment_state.stale = false;
|
||||
}
|
||||
|
||||
m_rpc_payment_diff = resp_t.diff;
|
||||
m_rpc_payment_credits_per_hash_found = resp_t.credits_per_hash_found;
|
||||
m_rpc_payment_height = resp_t.height;
|
||||
m_rpc_payment_seed_height = resp_t.seed_height;
|
||||
m_rpc_payment_cookie = resp_t.cookie;
|
||||
|
||||
if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43)
|
||||
{
|
||||
MERROR("Invalid hashing blob: " << resp_t.hashing_blob);
|
||||
return std::string("Invalid hashing blob");
|
||||
}
|
||||
if (resp_t.seed_hash.empty())
|
||||
{
|
||||
m_rpc_payment_seed_hash = crypto::null_hash;
|
||||
}
|
||||
else if (!epee::string_tools::hex_to_pod(resp_t.seed_hash, m_rpc_payment_seed_hash))
|
||||
{
|
||||
MERROR("Invalid seed_hash: " << resp_t.seed_hash);
|
||||
return std::string("Invalid seed hash");
|
||||
}
|
||||
if (resp_t.next_seed_hash.empty())
|
||||
{
|
||||
m_rpc_payment_next_seed_hash = crypto::null_hash;
|
||||
}
|
||||
else if (!epee::string_tools::hex_to_pod(resp_t.next_seed_hash, m_rpc_payment_next_seed_hash))
|
||||
{
|
||||
MERROR("Invalid next_seed_hash: " << resp_t.next_seed_hash);
|
||||
return std::string("Invalid next seed hash");
|
||||
}
|
||||
m_rpc_payment_info_time = now;
|
||||
}
|
||||
|
||||
payment_required = m_rpc_payment_diff > 0;
|
||||
credits = m_rpc_payment_state.credits;
|
||||
diff = m_rpc_payment_diff;
|
||||
credits_per_hash_found = m_rpc_payment_credits_per_hash_found;
|
||||
blob = m_rpc_payment_blob;
|
||||
height = m_rpc_payment_height;
|
||||
seed_height = m_rpc_payment_seed_height;
|
||||
seed_hash = m_rpc_payment_seed_hash;
|
||||
next_seed_hash = m_rpc_payment_next_seed_hash;
|
||||
cookie = m_rpc_payment_cookie;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -32,6 +32,8 @@
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include "include_base_utils.h"
|
||||
#include "net/http_client.h"
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include "wallet_rpc_helpers.h"
|
||||
|
||||
namespace tools
|
||||
{
|
||||
@ -39,37 +41,62 @@ namespace tools
|
||||
class NodeRPCProxy
|
||||
{
|
||||
public:
|
||||
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex);
|
||||
NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex);
|
||||
|
||||
void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; }
|
||||
void invalidate();
|
||||
void set_offline(bool offline) { m_offline = offline; }
|
||||
|
||||
boost::optional<std::string> get_rpc_version(uint32_t &version) const;
|
||||
boost::optional<std::string> get_height(uint64_t &height) const;
|
||||
boost::optional<std::string> get_rpc_version(uint32_t &version);
|
||||
boost::optional<std::string> get_height(uint64_t &height);
|
||||
void set_height(uint64_t h);
|
||||
boost::optional<std::string> get_target_height(uint64_t &height) const;
|
||||
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit) const;
|
||||
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height) const;
|
||||
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const;
|
||||
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask) const;
|
||||
boost::optional<std::string> get_target_height(uint64_t &height);
|
||||
boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit);
|
||||
boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height);
|
||||
boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee);
|
||||
boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask);
|
||||
boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
|
||||
|
||||
private:
|
||||
boost::optional<std::string> get_info() const;
|
||||
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
|
||||
if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
|
||||
m_rpc_payment_state.credits = res.credits;
|
||||
if (res.top_hash != m_rpc_payment_state.top_hash)
|
||||
{
|
||||
m_rpc_payment_state.top_hash = res.top_hash;
|
||||
m_rpc_payment_state.stale = true;
|
||||
}
|
||||
}
|
||||
template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
|
||||
|
||||
private:
|
||||
boost::optional<std::string> get_info();
|
||||
|
||||
epee::net_utils::http::http_simple_client &m_http_client;
|
||||
rpc_payment_state_t &m_rpc_payment_state;
|
||||
boost::recursive_mutex &m_daemon_rpc_mutex;
|
||||
crypto::secret_key m_client_id_secret_key;
|
||||
bool m_offline;
|
||||
|
||||
mutable uint64_t m_height;
|
||||
mutable uint64_t m_earliest_height[256];
|
||||
mutable uint64_t m_dynamic_base_fee_estimate;
|
||||
mutable uint64_t m_dynamic_base_fee_estimate_cached_height;
|
||||
mutable uint64_t m_dynamic_base_fee_estimate_grace_blocks;
|
||||
mutable uint64_t m_fee_quantization_mask;
|
||||
mutable uint32_t m_rpc_version;
|
||||
mutable uint64_t m_target_height;
|
||||
mutable uint64_t m_block_weight_limit;
|
||||
mutable time_t m_get_info_time;
|
||||
uint64_t m_height;
|
||||
uint64_t m_earliest_height[256];
|
||||
uint64_t m_dynamic_base_fee_estimate;
|
||||
uint64_t m_dynamic_base_fee_estimate_cached_height;
|
||||
uint64_t m_dynamic_base_fee_estimate_grace_blocks;
|
||||
uint64_t m_fee_quantization_mask;
|
||||
uint32_t m_rpc_version;
|
||||
uint64_t m_target_height;
|
||||
uint64_t m_block_weight_limit;
|
||||
time_t m_get_info_time;
|
||||
time_t m_rpc_payment_info_time;
|
||||
uint64_t m_rpc_payment_diff;
|
||||
uint64_t m_rpc_payment_credits_per_hash_found;
|
||||
cryptonote::blobdata m_rpc_payment_blob;
|
||||
uint64_t m_rpc_payment_height;
|
||||
uint64_t m_rpc_payment_seed_height;
|
||||
crypto::hash m_rpc_payment_seed_hash;
|
||||
crypto::hash m_rpc_payment_next_seed_hash;
|
||||
uint32_t m_rpc_payment_cookie;
|
||||
};
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -64,10 +64,21 @@
|
||||
#include "node_rpc_proxy.h"
|
||||
#include "message_store.h"
|
||||
#include "wallet_light_rpc.h"
|
||||
#include "wallet_rpc_helpers.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
|
||||
|
||||
#define THROW_ON_RPC_RESPONSE_ERROR(r, error, res, method, ...) \
|
||||
do { \
|
||||
handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
|
||||
throw_on_rpc_response_error(r, error, res.status, method); \
|
||||
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, ## __VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, err, res, method) \
|
||||
THROW_ON_RPC_RESPONSE_ERROR(r, err, res, method, tools::error::wallet_generic_rpc_error, method, res.status)
|
||||
|
||||
class Serialization_portability_wallet_Test;
|
||||
class wallet_accessor_test;
|
||||
|
||||
@ -875,6 +886,8 @@ private:
|
||||
uint64_t get_last_block_reward() const { return m_last_block_reward; }
|
||||
uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
|
||||
|
||||
std::vector<cryptonote::public_node> get_public_nodes(bool white_only = true);
|
||||
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int ver)
|
||||
{
|
||||
@ -988,6 +1001,9 @@ private:
|
||||
if(ver < 28)
|
||||
return;
|
||||
a & m_cold_key_images;
|
||||
if(ver < 29)
|
||||
return;
|
||||
a & m_rpc_client_secret_key;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ -1061,6 +1077,14 @@ private:
|
||||
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
|
||||
const ExportFormat & export_format() const { return m_export_format; }
|
||||
inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; }
|
||||
bool persistent_rpc_client_id() const { return m_persistent_rpc_client_id; }
|
||||
void persistent_rpc_client_id(bool persistent) { m_persistent_rpc_client_id = persistent; }
|
||||
void auto_mine_for_rpc_payment_threshold(float threshold) { m_auto_mine_for_rpc_payment_threshold = threshold; }
|
||||
float auto_mine_for_rpc_payment_threshold() const { return m_auto_mine_for_rpc_payment_threshold; }
|
||||
crypto::secret_key get_rpc_client_secret_key() const { return m_rpc_client_secret_key; }
|
||||
void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); }
|
||||
uint64_t credits_target() const { return m_credits_target; }
|
||||
void credits_target(uint64_t threshold) { m_credits_target = threshold; }
|
||||
|
||||
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
|
||||
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
|
||||
@ -1108,15 +1132,15 @@ private:
|
||||
const transfer_details &get_transfer_details(size_t idx) const;
|
||||
|
||||
uint8_t get_current_hard_fork();
|
||||
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const;
|
||||
bool use_fork_rules(uint8_t version, int64_t early_blocks = 0) const;
|
||||
int get_fee_algorithm() const;
|
||||
void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
|
||||
bool use_fork_rules(uint8_t version, int64_t early_blocks = 0);
|
||||
int get_fee_algorithm();
|
||||
|
||||
std::string get_wallet_file() const;
|
||||
std::string get_keys_file() const;
|
||||
std::string get_daemon_address() const;
|
||||
const boost::optional<epee::net_utils::http::login>& get_daemon_login() const { return m_daemon_login; }
|
||||
uint64_t get_daemon_blockchain_height(std::string& err) const;
|
||||
uint64_t get_daemon_blockchain_height(std::string& err);
|
||||
uint64_t get_daemon_blockchain_target_height(std::string& err);
|
||||
/*!
|
||||
* \brief Calculates the approximate blockchain height from current date/time.
|
||||
@ -1211,21 +1235,37 @@ private:
|
||||
|
||||
uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31
|
||||
|
||||
bool is_synced() const;
|
||||
bool is_synced();
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
|
||||
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
|
||||
|
||||
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1) const;
|
||||
uint64_t get_base_fee() const;
|
||||
uint64_t get_fee_quantization_mask() const;
|
||||
uint64_t get_min_ring_size() const;
|
||||
uint64_t get_max_ring_size() const;
|
||||
uint64_t adjust_mixin(uint64_t mixin) const;
|
||||
uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
|
||||
uint64_t get_base_fee();
|
||||
uint64_t get_fee_quantization_mask();
|
||||
uint64_t get_min_ring_size();
|
||||
uint64_t get_max_ring_size();
|
||||
uint64_t adjust_mixin(uint64_t mixin);
|
||||
|
||||
uint32_t adjust_priority(uint32_t priority);
|
||||
|
||||
bool is_unattended() const { return m_unattended; }
|
||||
|
||||
bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
|
||||
bool daemon_requires_payment();
|
||||
bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance);
|
||||
bool search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL);
|
||||
template<typename T> void handle_payment_changes(const T &res, std::true_type) {
|
||||
if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
|
||||
m_rpc_payment_state.credits = res.credits;
|
||||
if (res.top_hash != m_rpc_payment_state.top_hash)
|
||||
{
|
||||
m_rpc_payment_state.top_hash = res.top_hash;
|
||||
m_rpc_payment_state.stale = true;
|
||||
}
|
||||
}
|
||||
template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
|
||||
|
||||
// Light wallet specific functions
|
||||
// fetch unspent outs from lw node and store in m_transfers
|
||||
void light_wallet_get_unspent_outs();
|
||||
@ -1338,6 +1378,9 @@ private:
|
||||
void enable_dns(bool enable) { m_use_dns = enable; }
|
||||
void set_offline(bool offline = true);
|
||||
|
||||
uint64_t credits() const { return m_rpc_payment_state.credits; }
|
||||
void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; }
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Stores wallet information to wallet file.
|
||||
@ -1363,7 +1406,7 @@ private:
|
||||
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
|
||||
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
|
||||
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
|
||||
void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error);
|
||||
void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception);
|
||||
void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
|
||||
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const;
|
||||
bool prepare_file_names(const std::string& file_path);
|
||||
@ -1379,9 +1422,9 @@ private:
|
||||
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info) const;
|
||||
void check_acc_out_precomp_once(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info, bool &already_seen) const;
|
||||
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
|
||||
uint64_t get_upper_transaction_weight_limit() const;
|
||||
std::vector<uint64_t> get_unspent_amounts_vector(bool strict) const;
|
||||
uint64_t get_dynamic_base_fee_estimate() const;
|
||||
uint64_t get_upper_transaction_weight_limit();
|
||||
std::vector<uint64_t> get_unspent_amounts_vector(bool strict);
|
||||
uint64_t get_dynamic_base_fee_estimate();
|
||||
float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const;
|
||||
std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const;
|
||||
void set_spent(size_t idx, uint64_t height);
|
||||
@ -1435,7 +1478,10 @@ private:
|
||||
void on_device_progress(const hw::device_progress& event);
|
||||
|
||||
std::string get_rpc_status(const std::string &s) const;
|
||||
void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
|
||||
void throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const;
|
||||
|
||||
std::string get_client_signature() const;
|
||||
void check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_credits, double expected_cost);
|
||||
|
||||
cryptonote::account_base m_account;
|
||||
boost::optional<epee::net_utils::http::login> m_daemon_login;
|
||||
@ -1516,6 +1562,8 @@ private:
|
||||
bool m_track_uses;
|
||||
uint32_t m_inactivity_lock_timeout;
|
||||
BackgroundMiningSetupType m_setup_background_mining;
|
||||
bool m_persistent_rpc_client_id;
|
||||
float m_auto_mine_for_rpc_payment_threshold;
|
||||
bool m_is_initialized;
|
||||
NodeRPCProxy m_node_rpc_proxy;
|
||||
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
|
||||
@ -1526,6 +1574,9 @@ private:
|
||||
bool m_use_dns;
|
||||
bool m_offline;
|
||||
uint32_t m_rpc_version;
|
||||
crypto::secret_key m_rpc_client_secret_key;
|
||||
rpc_payment_state_t m_rpc_payment_state;
|
||||
uint64_t m_credits_target;
|
||||
|
||||
// Aux transaction data from device
|
||||
std::unordered_map<crypto::hash, std::string> m_tx_device;
|
||||
@ -1569,7 +1620,7 @@ private:
|
||||
ExportFormat m_export_format;
|
||||
};
|
||||
}
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 28)
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 29)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
|
||||
|
@ -76,6 +76,10 @@ namespace wallet_args
|
||||
{
|
||||
return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""};
|
||||
}
|
||||
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key()
|
||||
{
|
||||
return {"rpc-client-secret-key", wallet_args::tr("Set RPC client secret key for RPC payments"), ""};
|
||||
}
|
||||
|
||||
const char* tr(const char* str)
|
||||
{
|
||||
|
@ -36,6 +36,7 @@ namespace wallet_args
|
||||
{
|
||||
command_line::arg_descriptor<std::string> arg_generate_from_json();
|
||||
command_line::arg_descriptor<std::string> arg_wallet_file();
|
||||
command_line::arg_descriptor<std::string> arg_rpc_client_secret_key();
|
||||
|
||||
const char* tr(const char* str);
|
||||
|
||||
|
@ -90,6 +90,7 @@ namespace tools
|
||||
// is_key_image_spent_error
|
||||
// get_histogram_error
|
||||
// get_output_distribution
|
||||
// payment_required
|
||||
// wallet_files_doesnt_correspond
|
||||
//
|
||||
// * - class with protected ctor
|
||||
@ -781,6 +782,20 @@ namespace tools
|
||||
const std::string m_status;
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_coded_rpc_error : public wallet_rpc_error
|
||||
{
|
||||
explicit wallet_coded_rpc_error(std::string&& loc, const std::string& request, int code, const std::string& status)
|
||||
: wallet_rpc_error(std::move(loc), std::string("error ") + std::to_string(code) + (" in ") + request + " RPC: " + status, request),
|
||||
m_code(code), m_status(status)
|
||||
{
|
||||
}
|
||||
int code() const { return m_code; }
|
||||
const std::string& status() const { return m_status; }
|
||||
private:
|
||||
int m_code;
|
||||
const std::string m_status;
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct daemon_busy : public wallet_rpc_error
|
||||
{
|
||||
explicit daemon_busy(std::string&& loc, const std::string& request)
|
||||
@ -821,6 +836,14 @@ namespace tools
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct payment_required: public wallet_rpc_error
|
||||
{
|
||||
explicit payment_required(std::string&& loc, const std::string& request)
|
||||
: wallet_rpc_error(std::move(loc), "payment required", request)
|
||||
{
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_files_doesnt_correspond : public wallet_logic_error
|
||||
{
|
||||
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
|
||||
|
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,28 +141,35 @@ class Daemon(object):
|
||||
return self.rpc.send_json_rpc_request(getblockheadersrange)
|
||||
get_block_headers_range = getblockheadersrange
|
||||
|
||||
def get_connections(self):
|
||||
def get_connections(self, client = ""):
|
||||
get_connections = {
|
||||
'client': client,
|
||||
'method': 'get_connections',
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_connections)
|
||||
|
||||
def get_info(self):
|
||||
def get_info(self, client = ""):
|
||||
get_info = {
|
||||
'method': 'get_info',
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
'method': 'get_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_info)
|
||||
getinfo = get_info
|
||||
|
||||
def hard_fork_info(self):
|
||||
def hard_fork_info(self, client = ""):
|
||||
hard_fork_info = {
|
||||
'method': 'hard_fork_info',
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
'method': 'hard_fork_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(hard_fork_info)
|
||||
|
||||
@ -174,7 +188,7 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(generateblocks)
|
||||
|
||||
def get_height(self):
|
||||
def get_height(self, client = ""):
|
||||
get_height = {
|
||||
'method': 'get_height',
|
||||
'jsonrpc': '2.0',
|
||||
@ -208,18 +222,21 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_request('/mining_status', mining_status)
|
||||
|
||||
def get_transaction_pool(self):
|
||||
def get_transaction_pool(self, client = ""):
|
||||
get_transaction_pool = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
|
||||
|
||||
def get_transaction_pool_hashes(self):
|
||||
def get_transaction_pool_hashes(self, client = ""):
|
||||
get_transaction_pool_hashes = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
|
||||
|
||||
def get_transaction_pool_stats(self):
|
||||
def get_transaction_pool_stats(self, client = ""):
|
||||
get_transaction_pool_stats = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_transaction_pool_stats', get_transaction_pool_stats)
|
||||
|
||||
@ -263,8 +280,9 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(set_bans)
|
||||
|
||||
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False):
|
||||
def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False, client = ""):
|
||||
get_transactions = {
|
||||
'client': client,
|
||||
'txs_hashes': txs_hashes,
|
||||
'decode_as_json': decode_as_json,
|
||||
'prune': prune,
|
||||
@ -273,17 +291,19 @@ class Daemon(object):
|
||||
return self.rpc.send_request('/get_transactions', get_transactions)
|
||||
gettransactions = get_transactions
|
||||
|
||||
def get_outs(self, outputs = [], get_txid = False):
|
||||
def get_outs(self, outputs = [], get_txid = False, client = ""):
|
||||
get_outs = {
|
||||
'client': client,
|
||||
'outputs': outputs,
|
||||
'get_txid': get_txid,
|
||||
}
|
||||
return self.rpc.send_request('/get_outs', get_outs)
|
||||
|
||||
def get_coinbase_tx_sum(self, height, count):
|
||||
def get_coinbase_tx_sum(self, height, count, client = ""):
|
||||
get_coinbase_tx_sum = {
|
||||
'method': 'get_coinbase_tx_sum',
|
||||
'params': {
|
||||
'client': client,
|
||||
'height': height,
|
||||
'count': count,
|
||||
},
|
||||
@ -292,10 +312,11 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_coinbase_tx_sum)
|
||||
|
||||
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False):
|
||||
def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False, client = ""):
|
||||
get_output_distribution = {
|
||||
'method': 'get_output_distribution',
|
||||
'params': {
|
||||
'client': client,
|
||||
'amounts': amounts,
|
||||
'from_height': from_height,
|
||||
'to_height': to_height,
|
||||
@ -308,10 +329,11 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_output_distribution)
|
||||
|
||||
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0):
|
||||
def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0, client = ""):
|
||||
get_output_histogram = {
|
||||
'method': 'get_output_histogram',
|
||||
'params': {
|
||||
'client': client,
|
||||
'amounts': amounts,
|
||||
'min_count': min_count,
|
||||
'max_count': max_count,
|
||||
@ -335,15 +357,17 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_request('/set_log_categories', set_log_categories)
|
||||
|
||||
def get_alt_blocks_hashes(self):
|
||||
def get_alt_blocks_hashes(self, client = ""):
|
||||
get_alt_blocks_hashes = {
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/get_alt_blocks_hashes', get_alt_blocks_hashes)
|
||||
|
||||
def get_alternate_chains(self):
|
||||
def get_alternate_chains(self, client = ""):
|
||||
get_alternate_chains = {
|
||||
'method': 'get_alternate_chains',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
@ -361,9 +385,10 @@ class Daemon(object):
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_fee_estimate)
|
||||
|
||||
def is_key_image_spent(self, key_images = []):
|
||||
def is_key_image_spent(self, key_images = [], client = ""):
|
||||
is_key_image_spent = {
|
||||
'key_images': key_images,
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/is_key_image_spent', is_key_image_spent)
|
||||
|
||||
@ -413,7 +438,7 @@ class Daemon(object):
|
||||
|
||||
def in_peers(self, in_peers):
|
||||
in_peers = {
|
||||
'in_peers': in_peers,
|
||||
'client': client,
|
||||
}
|
||||
return self.rpc.send_request('/in_peers', in_peers)
|
||||
|
||||
@ -446,31 +471,34 @@ class Daemon(object):
|
||||
on_get_block_hash = get_block_hash
|
||||
on_getblockhash = get_block_hash
|
||||
|
||||
def relay_tx(self, txids = []):
|
||||
def relay_tx(self, txids = [], client = ""):
|
||||
relay_tx = {
|
||||
'method': 'relay_tx',
|
||||
'params': {
|
||||
'txids': txids,
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(relay_tx)
|
||||
|
||||
def sync_info(self):
|
||||
def sync_info(self, client = ""):
|
||||
sync_info = {
|
||||
'method': 'sync_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(sync_info)
|
||||
|
||||
def get_txpool_backlog(self):
|
||||
def get_txpool_backlog(self, client = ""):
|
||||
get_txpool_backlog = {
|
||||
'method': 'get_txpool_backlog',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
@ -498,3 +526,73 @@ class Daemon(object):
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(get_block_rate)
|
||||
|
||||
def rpc_access_info(self, client):
|
||||
rpc_access_info = {
|
||||
'method': 'rpc_access_info',
|
||||
'params': {
|
||||
'client': client,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_info)
|
||||
|
||||
def rpc_access_submit_nonce(self, client, nonce, cookie):
|
||||
rpc_access_submit_nonce = {
|
||||
'method': 'rpc_access_submit_nonce',
|
||||
'params': {
|
||||
'client': client,
|
||||
'nonce': nonce,
|
||||
'cookie': cookie,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_submit_nonce)
|
||||
|
||||
def rpc_access_pay(self, client, paying_for, payment):
|
||||
rpc_access_pay = {
|
||||
'method': 'rpc_access_pay',
|
||||
'params': {
|
||||
'client': client,
|
||||
'paying_for': paying_for,
|
||||
'payment': payment,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_pay)
|
||||
|
||||
def rpc_access_tracking(self, clear = False):
|
||||
rpc_access_tracking = {
|
||||
'method': 'rpc_access_tracking',
|
||||
'params': {
|
||||
'clear': clear,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_tracking)
|
||||
|
||||
def rpc_access_data(self):
|
||||
rpc_access_data = {
|
||||
'method': 'rpc_access_data',
|
||||
'params': {
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_data)
|
||||
|
||||
def rpc_access_account(self, client, delta_balance = 0):
|
||||
rpc_access_account = {
|
||||
'method': 'rpc_access_account',
|
||||
'params': {
|
||||
'client': client,
|
||||
'delta_balance': delta_balance,
|
||||
},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self.rpc.send_json_rpc_request(rpc_access_account)
|
||||
|
Loading…
Reference in New Issue
Block a user