aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/CMakeLists.txt6
-rw-r--r--src/wallet/node_rpc_proxy.cpp174
-rw-r--r--src/wallet/node_rpc_proxy.h65
-rw-r--r--src/wallet/wallet2.cpp686
-rw-r--r--src/wallet/wallet2.h85
-rw-r--r--src/wallet/wallet_args.cpp4
-rw-r--r--src/wallet/wallet_args.h1
-rw-r--r--src/wallet/wallet_errors.h23
-rw-r--r--src/wallet/wallet_rpc_helpers.h94
-rw-r--r--src/wallet/wallet_rpc_payments.cpp196
-rw-r--r--src/wallet/wallet_rpc_server.cpp14
11 files changed, 1024 insertions, 324 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt
index 553445a39..3be1a6f6b 100644
--- a/src/wallet/CMakeLists.txt
+++ b/src/wallet/CMakeLists.txt
@@ -37,6 +37,7 @@ set(wallet_sources
node_rpc_proxy.cpp
message_store.cpp
message_transporter.cpp
+ wallet_rpc_payments.cpp
)
set(wallet_private_headers
@@ -49,7 +50,8 @@ set(wallet_private_headers
ringdb.h
node_rpc_proxy.h
message_store.h
- message_transporter.h)
+ message_transporter.h
+ wallet_rpc_helpers.h)
monero_private_headers(wallet
${wallet_private_headers})
@@ -58,6 +60,7 @@ monero_add_library(wallet
${wallet_private_headers})
target_link_libraries(wallet
PUBLIC
+ rpc_base
multisig
common
cryptonote_core
@@ -116,6 +119,7 @@ if (BUILD_GUI_DEPS)
set(libs_to_merge
wallet_api
wallet
+ rpc_base
multisig
blockchain_db
cryptonote_core
diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp
index 1d5078a11..731896715 100644
--- a/src/wallet/node_rpc_proxy.cpp
+++ b/src/wallet/node_rpc_proxy.cpp
@@ -28,8 +28,22 @@
#include "node_rpc_proxy.h"
#include "rpc/core_rpc_server_commands_defs.h"
+#include "rpc/rpc_payment_signature.h"
+#include "rpc/rpc_payment_costs.h"
#include "storages/http_abstract_invoke.h"
+#define RETURN_ON_RPC_RESPONSE_ERROR(r, error, res, method) \
+ do { \
+ CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \
+ handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
+ CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); \
+ /* empty string -> not connection */ \
+ CHECK_AND_ASSERT_MES(!res.status.empty(), res.status, "No connection to daemon"); \
+ CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, res.status, "Daemon busy"); \
+ CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_PAYMENT_REQUIRED, res.status, "Payment required"); \
+ CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, res.status, "Error calling " + std::string(method) + " daemon RPC"); \
+ } while(0)
+
using namespace epee;
namespace tools
@@ -37,8 +51,9 @@ namespace tools
static const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
-NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex)
+NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex)
: m_http_client(http_client)
+ , m_rpc_payment_state(rpc_payment_state)
, m_daemon_rpc_mutex(mutex)
, m_offline(false)
{
@@ -58,9 +73,13 @@ void NodeRPCProxy::invalidate()
m_target_height = 0;
m_block_weight_limit = 0;
m_get_info_time = 0;
+ m_rpc_payment_info_time = 0;
+ m_rpc_payment_seed_height = 0;
+ m_rpc_payment_seed_hash = crypto::null_hash;
+ m_rpc_payment_next_seed_hash = crypto::null_hash;
}
-boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const
+boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version)
{
if (m_offline)
return boost::optional<std::string>("offline");
@@ -68,12 +87,11 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version
{
cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t);
- m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version");
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version");
+ }
m_rpc_version = resp_t.version;
}
rpc_version = m_rpc_version;
@@ -85,7 +103,7 @@ void NodeRPCProxy::set_height(uint64_t h)
m_height = h;
}
-boost::optional<std::string> NodeRPCProxy::get_info() const
+boost::optional<std::string> NodeRPCProxy::get_info()
{
if (m_offline)
return boost::optional<std::string>("offline");
@@ -95,13 +113,15 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
- m_daemon_rpc_mutex.lock();
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_info");
+ check_rpc_cost(m_rpc_payment_state, "get_info", resp_t.credits, pre_call_credits, COST_PER_GET_INFO);
+ }
- CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height");
m_height = resp_t.height;
m_target_height = resp_t.target_height;
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
@@ -110,7 +130,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() const
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
+boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height)
{
auto res = get_info();
if (res)
@@ -119,7 +139,7 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) const
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) const
+boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
{
auto res = get_info();
if (res)
@@ -128,7 +148,7 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) c
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit) const
+boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &block_weight_limit)
{
auto res = get_info();
if (res)
@@ -137,7 +157,7 @@ boost::optional<std::string> NodeRPCProxy::get_block_weight_limit(uint64_t &bloc
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height) const
+boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, uint64_t &earliest_height)
{
if (m_offline)
return boost::optional<std::string>("offline");
@@ -145,14 +165,17 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
{
cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_HARD_FORK_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
-
- m_daemon_rpc_mutex.lock();
req_t.version = version;
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "hard_fork_info");
+ check_rpc_cost(m_rpc_payment_state, "hard_fork_info", resp_t.credits, pre_call_credits, COST_PER_HARD_FORK_INFO);
+ }
+
m_earliest_height[version] = resp_t.earliest_height;
}
@@ -160,7 +183,7 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version,
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const
+boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee)
{
uint64_t height;
@@ -174,14 +197,17 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
{
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
-
- m_daemon_rpc_mutex.lock();
req_t.grace_blocks = grace_blocks;
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
+ check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
+ }
+
m_dynamic_base_fee_estimate = resp_t.fee;
m_dynamic_base_fee_estimate_cached_height = height;
m_dynamic_base_fee_estimate_grace_blocks = grace_blocks;
@@ -192,7 +218,7 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_base_fee_estimate(uint64_
return boost::optional<std::string>();
}
-boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask) const
+boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &fee_quantization_mask)
{
uint64_t height;
@@ -206,14 +232,17 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
{
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t);
-
- m_daemon_rpc_mutex.lock();
req_t.grace_blocks = m_dynamic_base_fee_estimate_grace_blocks;
- bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- CHECK_AND_ASSERT_MES(r, std::string("Failed to connect to daemon"), "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon");
- CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_fee_estimate");
+ check_rpc_cost(m_rpc_payment_state, "get_fee_estimate", resp_t.credits, pre_call_credits, COST_PER_FEE_ESTIMATE);
+ }
+
m_dynamic_base_fee_estimate = resp_t.fee;
m_dynamic_base_fee_estimate_cached_height = height;
m_fee_quantization_mask = resp_t.quantization_mask;
@@ -228,4 +257,65 @@ boost::optional<std::string> NodeRPCProxy::get_fee_quantization_mask(uint64_t &f
return boost::optional<std::string>();
}
+boost::optional<std::string> NodeRPCProxy::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
+{
+ const time_t now = time(NULL);
+ if (m_rpc_payment_state.stale || now >= m_rpc_payment_info_time + 5*60 || (mining && now >= m_rpc_payment_info_time + 10)) // re-cache every 10 seconds if mining, 5 minutes otherwise
+ {
+ cryptonote::COMMAND_RPC_ACCESS_INFO::request req_t = AUTO_VAL_INIT(req_t);
+ cryptonote::COMMAND_RPC_ACCESS_INFO::response resp_t = AUTO_VAL_INIT(resp_t);
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ req_t.client = cryptonote::make_rpc_payment_signature(m_client_id_secret_key);
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_info", req_t, resp_t, m_http_client, rpc_timeout);
+ RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "rpc_access_info");
+ m_rpc_payment_state.stale = false;
+ }
+
+ m_rpc_payment_diff = resp_t.diff;
+ m_rpc_payment_credits_per_hash_found = resp_t.credits_per_hash_found;
+ m_rpc_payment_height = resp_t.height;
+ m_rpc_payment_seed_height = resp_t.seed_height;
+ m_rpc_payment_cookie = resp_t.cookie;
+
+ if (!epee::string_tools::parse_hexstr_to_binbuff(resp_t.hashing_blob, m_rpc_payment_blob) || m_rpc_payment_blob.size() < 43)
+ {
+ MERROR("Invalid hashing blob: " << resp_t.hashing_blob);
+ return std::string("Invalid hashing blob");
+ }
+ if (resp_t.seed_hash.empty())
+ {
+ m_rpc_payment_seed_hash = crypto::null_hash;
+ }
+ else if (!epee::string_tools::hex_to_pod(resp_t.seed_hash, m_rpc_payment_seed_hash))
+ {
+ MERROR("Invalid seed_hash: " << resp_t.seed_hash);
+ return std::string("Invalid seed hash");
+ }
+ if (resp_t.next_seed_hash.empty())
+ {
+ m_rpc_payment_next_seed_hash = crypto::null_hash;
+ }
+ else if (!epee::string_tools::hex_to_pod(resp_t.next_seed_hash, m_rpc_payment_next_seed_hash))
+ {
+ MERROR("Invalid next_seed_hash: " << resp_t.next_seed_hash);
+ return std::string("Invalid next seed hash");
+ }
+ m_rpc_payment_info_time = now;
+ }
+
+ payment_required = m_rpc_payment_diff > 0;
+ credits = m_rpc_payment_state.credits;
+ diff = m_rpc_payment_diff;
+ credits_per_hash_found = m_rpc_payment_credits_per_hash_found;
+ blob = m_rpc_payment_blob;
+ height = m_rpc_payment_height;
+ seed_height = m_rpc_payment_seed_height;
+ seed_hash = m_rpc_payment_seed_hash;
+ next_seed_hash = m_rpc_payment_next_seed_hash;
+ cookie = m_rpc_payment_cookie;
+ return boost::none;
+}
+
}
diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h
index 3b75c8b94..a9d8167ac 100644
--- a/src/wallet/node_rpc_proxy.h
+++ b/src/wallet/node_rpc_proxy.h
@@ -32,6 +32,8 @@
#include <boost/thread/mutex.hpp>
#include "include_base_utils.h"
#include "net/http_client.h"
+#include "rpc/core_rpc_server_commands_defs.h"
+#include "wallet_rpc_helpers.h"
namespace tools
{
@@ -39,37 +41,62 @@ namespace tools
class NodeRPCProxy
{
public:
- NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, boost::recursive_mutex &mutex);
+ NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex);
+ void set_client_secret_key(const crypto::secret_key &skey) { m_client_id_secret_key = skey; }
void invalidate();
void set_offline(bool offline) { m_offline = offline; }
- boost::optional<std::string> get_rpc_version(uint32_t &version) const;
- boost::optional<std::string> get_height(uint64_t &height) const;
+ boost::optional<std::string> get_rpc_version(uint32_t &version);
+ boost::optional<std::string> get_height(uint64_t &height);
void set_height(uint64_t h);
- boost::optional<std::string> get_target_height(uint64_t &height) const;
- boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit) const;
- boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height) const;
- boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee) const;
- boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask) const;
+ boost::optional<std::string> get_target_height(uint64_t &height);
+ boost::optional<std::string> get_block_weight_limit(uint64_t &block_weight_limit);
+ boost::optional<std::string> get_earliest_height(uint8_t version, uint64_t &earliest_height);
+ boost::optional<std::string> get_dynamic_base_fee_estimate(uint64_t grace_blocks, uint64_t &fee);
+ boost::optional<std::string> get_fee_quantization_mask(uint64_t &fee_quantization_mask);
+ boost::optional<std::string> get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
private:
- boost::optional<std::string> get_info() const;
+ template<typename T> void handle_payment_changes(const T &res, std::true_type) {
+ if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
+ m_rpc_payment_state.credits = res.credits;
+ if (res.top_hash != m_rpc_payment_state.top_hash)
+ {
+ m_rpc_payment_state.top_hash = res.top_hash;
+ m_rpc_payment_state.stale = true;
+ }
+ }
+ template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
+
+private:
+ boost::optional<std::string> get_info();
epee::net_utils::http::http_simple_client &m_http_client;
+ rpc_payment_state_t &m_rpc_payment_state;
boost::recursive_mutex &m_daemon_rpc_mutex;
+ crypto::secret_key m_client_id_secret_key;
bool m_offline;
- mutable uint64_t m_height;
- mutable uint64_t m_earliest_height[256];
- mutable uint64_t m_dynamic_base_fee_estimate;
- mutable uint64_t m_dynamic_base_fee_estimate_cached_height;
- mutable uint64_t m_dynamic_base_fee_estimate_grace_blocks;
- mutable uint64_t m_fee_quantization_mask;
- mutable uint32_t m_rpc_version;
- mutable uint64_t m_target_height;
- mutable uint64_t m_block_weight_limit;
- mutable time_t m_get_info_time;
+ uint64_t m_height;
+ uint64_t m_earliest_height[256];
+ uint64_t m_dynamic_base_fee_estimate;
+ uint64_t m_dynamic_base_fee_estimate_cached_height;
+ uint64_t m_dynamic_base_fee_estimate_grace_blocks;
+ uint64_t m_fee_quantization_mask;
+ uint32_t m_rpc_version;
+ uint64_t m_target_height;
+ uint64_t m_block_weight_limit;
+ time_t m_get_info_time;
+ time_t m_rpc_payment_info_time;
+ uint64_t m_rpc_payment_diff;
+ uint64_t m_rpc_payment_credits_per_hash_found;
+ cryptonote::blobdata m_rpc_payment_blob;
+ uint64_t m_rpc_payment_height;
+ uint64_t m_rpc_payment_seed_height;
+ crypto::hash m_rpc_payment_seed_hash;
+ crypto::hash m_rpc_payment_next_seed_hash;
+ uint32_t m_rpc_payment_cookie;
};
}
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 48c10d06e..f50720a77 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -44,15 +44,20 @@
using namespace epee;
#include "cryptonote_config.h"
+#include "wallet_rpc_helpers.h"
#include "wallet2.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "rpc/core_rpc_server_commands_defs.h"
+#include "rpc/core_rpc_server_error_codes.h"
+#include "rpc/rpc_payment_signature.h"
+#include "rpc/rpc_payment_costs.h"
#include "misc_language.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "multisig/multisig.h"
#include "common/boost_serialization_helper.h"
#include "common/command_line.h"
#include "common/threadpool.h"
+#include "int-util.h"
#include "profile_tools.h"
#include "crypto/crypto.h"
#include "serialization/binary_utils.h"
@@ -1133,13 +1138,15 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_track_uses(false),
m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT),
m_setup_background_mining(BackgroundMiningMaybe),
+ m_persistent_rpc_client_id(false),
+ m_auto_mine_for_rpc_payment_threshold(-1.0f),
m_is_initialized(false),
m_kdf_rounds(kdf_rounds),
is_old_file_format(false),
m_watch_only(false),
m_multisig(false),
m_multisig_threshold(0),
- m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex),
+ m_node_rpc_proxy(m_http_client, m_rpc_payment_state, m_daemon_rpc_mutex),
m_account_public_address{crypto::null_pkey, crypto::null_pkey},
m_subaddress_lookahead_major(SUBADDRESS_LOOKAHEAD_MAJOR),
m_subaddress_lookahead_minor(SUBADDRESS_LOOKAHEAD_MINOR),
@@ -1162,8 +1169,10 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_use_dns(true),
m_offline(false),
m_rpc_version(0),
- m_export_format(ExportFormat::Binary)
+ m_export_format(ExportFormat::Binary),
+ m_credits_target(0)
{
+ set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
}
wallet2::~wallet2()
@@ -1269,9 +1278,16 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u
if(m_http_client.is_connected())
m_http_client.disconnect();
+ const bool changed = m_daemon_address != daemon_address;
m_daemon_address = std::move(daemon_address);
m_daemon_login = std::move(daemon_login);
m_trusted_daemon = trusted_daemon;
+ if (changed)
+ {
+ m_rpc_payment_state.expected_spent = 0;
+ m_rpc_payment_state.discrepancy = 0;
+ m_node_rpc_proxy.invalidate();
+ }
MINFO("setting daemon to " << get_daemon_address());
return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options));
@@ -2507,15 +2523,18 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height,
req.prune = true;
req.start_height = start_height;
req.no_miner_tx = m_refresh_type == RefreshNoCoinbase;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_bin("/getblocks.bin", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(res.status));
- THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error,
- "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" +
- boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = net_utils::invoke_http_bin("/getblocks.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "getblocks.bin", error::get_blocks_error, get_rpc_status(res.status));
+ THROW_WALLET_EXCEPTION_IF(res.blocks.size() != res.output_indices.size(), error::wallet_internal_error,
+ "mismatched blocks (" + boost::lexical_cast<std::string>(res.blocks.size()) + ") and output_indices (" +
+ boost::lexical_cast<std::string>(res.output_indices.size()) + ") sizes from daemon");
+ check_rpc_cost("/getblocks.bin", res.credits, pre_call_credits, 1 + res.blocks.size() * COST_PER_BLOCK);
+ }
blocks_start_height = res.start_height;
blocks = std::move(res.blocks);
@@ -2529,12 +2548,15 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height,
req.block_ids = short_chain_history;
req.start_height = start_height;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_bin("/gethashes.bin", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gethashes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gethashes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_hashes_error, get_rpc_status(res.status));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ req.client = get_client_signature();
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ bool r = net_utils::invoke_http_bin("/gethashes.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "gethashes.bin", error::get_hashes_error, get_rpc_status(res.status));
+ check_rpc_cost("/gethashes.bin", res.credits, pre_call_credits, 1 + res.m_block_ids.size() * COST_PER_BLOCK_HASH);
+ }
blocks_start_height = res.start_height;
hashes = std::move(res.m_block_ids);
@@ -2699,9 +2721,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
refresh(trusted_daemon, start_height, blocks_fetched, received_money);
}
//----------------------------------------------------------------------------------------------------
-void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error)
+void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception)
{
error = false;
+ exception = NULL;
try
{
@@ -2760,6 +2783,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks
catch(...)
{
error = true;
+ exception = std::current_exception();
}
}
@@ -2806,12 +2830,15 @@ void wallet2::update_pool_state(bool refreshed)
// get the pool state
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request req;
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response res;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/get_transaction_pool_hashes.bin", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_transaction_pool_hashes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_transaction_pool_hashes.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_json("/get_transaction_pool_hashes.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_transaction_pool_hashes.bin", error::get_tx_pool_error);
+ check_rpc_cost("/get_transaction_pool_hashes.bin", res.credits, pre_call_credits, 1 + res.tx_hashes.size() * COST_PER_POOL_HASH);
+ }
MTRACE("update_pool_state got pool");
// remove any pending tx that's not in the pool
@@ -2953,9 +2980,17 @@ void wallet2::update_pool_state(bool refreshed)
MDEBUG("asking for " << txids.size() << " transactions");
req.decode_as_json = false;
req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
+
+ bool r;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
+ if (r && res.status == CORE_RPC_STATUS_OK)
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX);
+ }
+
MDEBUG("Got " << r << " and " << res.status);
if (r && res.status == CORE_RPC_STATUS_OK)
{
@@ -3222,19 +3257,22 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
std::vector<cryptonote::block_complete_entry> next_blocks;
std::vector<parsed_block> next_parsed_blocks;
bool error;
+ std::exception_ptr exception;
try
{
// pull the next set of blocks while we're processing the current one
error = false;
+ exception = NULL;
next_blocks.clear();
next_parsed_blocks.clear();
added_blocks = 0;
if (!first && blocks.empty())
{
- refreshed = false;
+ m_node_rpc_proxy.set_height(m_blockchain.size());
+ refreshed = true;
break;
}
- tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, error);});
+ tpool.submit(&waiter, [&]{pull_and_parse_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, parsed_blocks, next_blocks, next_parsed_blocks, error, exception);});
if (!first)
{
@@ -3285,7 +3323,10 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// handle error from async fetching thread
if (error)
{
- throw std::runtime_error("proxy exception in refresh thread");
+ if (exception)
+ std::rethrow_exception(exception);
+ else
+ throw std::runtime_error("proxy exception in refresh thread");
}
// if we've got at least 10 blocks to refresh, assume we're starting
@@ -3304,6 +3345,12 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
waiter.wait(&tpool);
throw;
}
+ catch (const error::payment_required&)
+ {
+ // no point in trying again, it'd just eat up credits
+ waiter.wait(&tpool);
+ throw;
+ }
catch (const std::exception&)
{
blocks_fetched += added_blocks;
@@ -3395,22 +3442,19 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
req.cumulative = false;
req.binary = true;
req.compress = true;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_bin("/get_output_distribution.bin", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- if (!r)
- {
- MWARNING("Failed to request output distribution: no connection to daemon");
- return false;
- }
- if (res.status == CORE_RPC_STATUS_BUSY)
+
+ bool r;
+ try
{
- MWARNING("Failed to request output distribution: daemon is busy");
- return false;
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = net_utils::invoke_http_bin("/get_output_distribution.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_output_distribution.bin");
+ check_rpc_cost("/get_output_distribution.bin", res.credits, pre_call_credits, COST_PER_OUTPUT_DISTRIBUTION_0);
}
- if (res.status != CORE_RPC_STATUS_OK)
+ catch(...)
{
- MWARNING("Failed to request output distribution: " << res.status);
return false;
}
if (res.distributions.size() != 1)
@@ -3746,6 +3790,15 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
json.AddMember("original_view_secret_key", value, json.GetAllocator());
}
+ value2.SetInt(m_persistent_rpc_client_id ? 1 : 0);
+ json.AddMember("persistent_rpc_client_id", value2, json.GetAllocator());
+
+ value2.SetFloat(m_auto_mine_for_rpc_payment_threshold);
+ json.AddMember("auto_mine_for_rpc_payment", value2, json.GetAllocator());
+
+ value2.SetUint64(m_credits_target);
+ json.AddMember("credits_target", value2, json.GetAllocator());
+
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -3877,6 +3930,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_device_derivation_path = "";
m_key_device_type = hw::device::device_type::SOFTWARE;
encrypted_secret_keys = false;
+ m_persistent_rpc_client_id = false;
+ m_auto_mine_for_rpc_payment_threshold = -1.0f;
+ m_credits_target = 0;
}
else if(json.IsObject())
{
@@ -4085,6 +4141,14 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
{
m_original_keys_available = false;
}
+
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, persistent_rpc_client_id, int, Int, false, false);
+ m_persistent_rpc_client_id = field_persistent_rpc_client_id;
+ // save as float, load as double, because it can happen you can't load back as float...
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_mine_for_rpc_payment, float, Double, false, FLT_MAX);
+ m_auto_mine_for_rpc_payment_threshold = field_auto_mine_for_rpc_payment;
+ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, credits_target, uint64_t, Uint64, false, 0);
+ m_credits_target = field_credits_target;
}
else
{
@@ -5434,6 +5498,9 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file);
}
+ if (!m_persistent_rpc_client_id)
+ set_rpc_client_secret_key(rct::rct2sk(rct::skGen()));
+
cryptonote::block genesis;
generate_genesis(genesis);
crypto::hash genesis_hash = get_block_hash(genesis);
@@ -5485,10 +5552,18 @@ void wallet2::trim_hashchain()
MINFO("Fixing empty hashchain");
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res);
- m_daemon_rpc_mutex.lock();
- req.height = m_blockchain.size() - 1;
- bool r = invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
+
+ bool r;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ req.height = m_blockchain.size() - 1;
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, m_http_client, rpc_timeout);
+ if (r && res.status == CORE_RPC_STATUS_OK)
+ check_rpc_cost("getblockheaderbyheight", res.credits, pre_call_credits, COST_PER_BLOCK_HEADER);
+ }
+
if (r && res.status == CORE_RPC_STATUS_OK)
{
crypto::hash hash;
@@ -5845,15 +5920,19 @@ void wallet2::rescan_spent()
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
for (size_t n = start_offset; n < start_offset + n_outputs; ++n)
req.key_images.push_back(string_tools::pod_to_hex(m_transfers[n].m_key_image));
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, get_rpc_status(daemon_resp.status));
- THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error,
- "daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
- std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "is_key_image_spent", error::is_key_image_spent_error, get_rpc_status(daemon_resp.status));
+ THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != n_outputs, error::wallet_internal_error,
+ "daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
+ std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(n_outputs));
+ check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, n_outputs * COST_PER_KEY_IMAGE);
+ }
+
std::copy(daemon_resp.spent_status.begin(), daemon_resp.spent_status.end(), std::back_inserter(spent_status));
}
@@ -6173,12 +6252,13 @@ void wallet2::commit_tx(pending_tx& ptx)
oreq.address = get_account().get_public_address_str(m_nettype);
oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/submit_raw_tx", oreq, ores, rpc_timeout, "POST");
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
- // MyMonero and OpenMonero use different status strings
- THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error);
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ bool r = epee::net_utils::invoke_http_json("/submit_raw_tx", oreq, ores, m_http_client, rpc_timeout, "POST");
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "submit_raw_tx");
+ // MyMonero and OpenMonero use different status strings
+ THROW_WALLET_EXCEPTION_IF(ores.status != "OK" && ores.status != "success" , error::tx_rejected, ptx.tx, get_rpc_status(ores.status), ores.error);
+ }
}
else
{
@@ -6188,12 +6268,16 @@ void wallet2::commit_tx(pending_tx& ptx)
req.do_not_relay = false;
req.do_sanity_checks = true;
COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/sendrawtransaction", req, daemon_send_resp, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
- THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_json("/sendrawtransaction", req, daemon_send_resp, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_send_resp, "sendrawtransaction", error::tx_rejected, ptx.tx, get_rpc_status(daemon_send_resp.status), get_text_reason(daemon_send_resp));
+ check_rpc_cost("/sendrawtransaction", daemon_send_resp.credits, pre_call_credits, COST_PER_TX_RELAY);
+ }
+
// sanity checks
for (size_t idx: ptx.selected_transfers)
{
@@ -6966,7 +7050,7 @@ bool wallet2::sign_multisig_tx_from_file(const std::string &filename, std::vecto
return sign_multisig_tx_to_file(exported_txs, filename, txids);
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const
+uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm)
{
static const struct
{
@@ -7008,7 +7092,7 @@ uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) const
return 1;
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_dynamic_base_fee_estimate() const
+uint64_t wallet2::get_dynamic_base_fee_estimate()
{
uint64_t fee;
boost::optional<std::string> result = m_node_rpc_proxy.get_dynamic_base_fee_estimate(FEE_ESTIMATE_GRACE_BLOCKS, fee);
@@ -7019,7 +7103,7 @@ uint64_t wallet2::get_dynamic_base_fee_estimate() const
return base_fee;
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_base_fee() const
+uint64_t wallet2::get_base_fee()
{
if(m_light_wallet)
{
@@ -7035,7 +7119,7 @@ uint64_t wallet2::get_base_fee() const
return get_dynamic_base_fee_estimate();
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_fee_quantization_mask() const
+uint64_t wallet2::get_fee_quantization_mask()
{
if(m_light_wallet)
{
@@ -7052,7 +7136,7 @@ uint64_t wallet2::get_fee_quantization_mask() const
return fee_quantization_mask;
}
//----------------------------------------------------------------------------------------------------
-int wallet2::get_fee_algorithm() const
+int wallet2::get_fee_algorithm()
{
// changes at v3, v5, v8
if (use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0))
@@ -7064,7 +7148,7 @@ int wallet2::get_fee_algorithm() const
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_min_ring_size() const
+uint64_t wallet2::get_min_ring_size()
{
if (use_fork_rules(8, 10))
return 11;
@@ -7077,14 +7161,14 @@ uint64_t wallet2::get_min_ring_size() const
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_max_ring_size() const
+uint64_t wallet2::get_max_ring_size()
{
if (use_fork_rules(8, 10))
return 11;
return 0;
}
//------------------------------------------------------------------------------------------------------------------------------
-uint64_t wallet2::adjust_mixin(uint64_t mixin) const
+uint64_t wallet2::adjust_mixin(uint64_t mixin)
{
const uint64_t min_ring_size = get_min_ring_size();
if (mixin + 1 < min_ring_size)
@@ -7127,7 +7211,8 @@ uint32_t wallet2::adjust_priority(uint32_t priority)
// get the current full reward zone
uint64_t block_weight_limit = 0;
const auto result = m_node_rpc_proxy.get_block_weight_limit(block_weight_limit);
- throw_on_rpc_response_error(result, "get_info");
+ if (result)
+ return priority;
const uint64_t full_reward_zone = block_weight_limit / 2;
// get the last N block headers and sum the block sizes
@@ -7139,14 +7224,18 @@ uint32_t wallet2::adjust_priority(uint32_t priority)
}
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request getbh_req = AUTO_VAL_INIT(getbh_req);
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response getbh_res = AUTO_VAL_INIT(getbh_res);
- m_daemon_rpc_mutex.lock();
getbh_req.start_height = m_blockchain.size() - N;
getbh_req.end_height = m_blockchain.size() - 1;
- bool r = invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange");
- THROW_WALLET_EXCEPTION_IF(getbh_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange");
- THROW_WALLET_EXCEPTION_IF(getbh_res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, get_rpc_status(getbh_res.status));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ getbh_req.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, getbh_res, "getblockheadersrange", error::get_blocks_error, get_rpc_status(getbh_res.status));
+ check_rpc_cost("/sendrawtransaction", getbh_res.credits, pre_call_credits, N * COST_PER_BLOCK_HEADER);
+ }
+
if (getbh_res.headers.size() != N)
{
MERROR("Bad blockheaders size");
@@ -7363,17 +7452,18 @@ bool wallet2::find_and_save_rings(bool force)
size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE;
for (size_t s = slice; s < slice + ntxes; ++s)
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txs_hashes[s]));
- bool r;
+
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions");
+ THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong txs count = " +
+ std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size()));
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error,
- "daemon returned wrong response for gettransactions, wrong txs count = " +
- std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size()));
MDEBUG("Scanning " << res.txs.size() << " transactions");
THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size");
@@ -7506,11 +7596,15 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_
}
oreq.count = light_wallet_requested_outputs_count;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/get_random_outs", oreq, ores, rpc_timeout, "POST");
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
- THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error);
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ bool r = epee::net_utils::invoke_http_json("/get_random_outs", oreq, ores, m_http_client, rpc_timeout, "POST");
+ m_daemon_rpc_mutex.unlock();
+ THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_random_outs");
+ THROW_WALLET_EXCEPTION_IF(ores.amount_outs.empty() , error::wallet_internal_error, "No outputs received from light wallet node. Error: " + ores.Error);
+ size_t n_outs = 0; for (const auto &e: ores.amount_outs) n_outs += e.outputs.size();
+ }
// Check if we got enough outputs for each amount
for(auto& out: ores.amount_outs) {
@@ -7607,7 +7701,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// check whether we're shortly after the fork
uint64_t height;
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
- throw_on_rpc_response_error(result, "get_info");
+ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height");
bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY;
bool is_after_segregation_fork = height >= segregation_fork_height;
@@ -7646,12 +7740,15 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
req_t.amounts.resize(std::distance(req_t.amounts.begin(), end));
req_t.unlocked = true;
req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
- THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, get_rpc_status(resp_t.status));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, get_rpc_status(resp_t.status));
+ check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size());
+ }
}
// if we want to segregate fake outs pre or post fork, get distribution
@@ -7669,12 +7766,17 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
req_t.to_height = segregation_fork_height + 1;
req_t.cumulative = true;
req_t.binary = true;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, rpc_timeout * 1000);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
- THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, get_rpc_status(resp_t.status));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_distribution", error::get_output_distribution, get_rpc_status(resp_t.status));
+ uint64_t expected_cost = 0;
+ for (uint64_t amount: req_t.amounts) expected_cost += (amount ? COST_PER_OUTPUT_DISTRIBUTION : COST_PER_OUTPUT_DISTRIBUTION_0);
+ check_rpc_cost("get_output_distribution", resp_t.credits, pre_call_credits, expected_cost);
+ }
// check we got all data
for(size_t idx: selected_transfers)
@@ -8018,15 +8120,18 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
// get the keys for those
req.get_txid = false;
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_bin("/get_outs.bin", req, daemon_resp, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_outs_error, get_rpc_status(daemon_resp.status));
- THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error,
- "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
- std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, daemon_resp, "get_outs.bin", error::get_outs_error, get_rpc_status(daemon_resp.status));
+ THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error,
+ "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
+ std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
+ check_rpc_cost("/get_outs.bin", daemon_resp.credits, pre_call_credits, daemon_resp.outs.size() * COST_PER_OUT);
+ }
std::unordered_map<uint64_t, uint64_t> scanty_outs;
size_t base = 0;
@@ -10237,22 +10342,21 @@ uint8_t wallet2::get_current_hard_fork()
return resp_t.version;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
+void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height)
{
boost::optional<std::string> result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
- throw_on_rpc_response_error(result, "get_hard_fork_info");
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const
+bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks)
{
// TODO: How to get fork rule info from light wallet node?
if(m_light_wallet)
return true;
uint64_t height, earliest_height;
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
- throw_on_rpc_response_error(result, "get_info");
+ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get height");
result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
- throw_on_rpc_response_error(result, "get_hard_fork_info");
+ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Failed to get earliest fork height");
bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand
if (close_enough)
@@ -10262,7 +10366,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const
return close_enough;
}
//----------------------------------------------------------------------------------------------------
-uint64_t wallet2::get_upper_transaction_weight_limit() const
+uint64_t wallet2::get_upper_transaction_weight_limit()
{
if (m_upper_transaction_weight_limit > 0)
return m_upper_transaction_weight_limit;
@@ -10293,7 +10397,7 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c
return outputs;
}
//----------------------------------------------------------------------------------------------------
-std::vector<uint64_t> wallet2::get_unspent_amounts_vector(bool strict) const
+std::vector<uint64_t> wallet2::get_unspent_amounts_vector(bool strict)
{
std::set<uint64_t> set;
for (const auto &td: m_transfers)
@@ -10314,18 +10418,22 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co
{
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
- m_daemon_rpc_mutex.lock();
if (is_trusted_daemon())
req_t.amounts = get_unspent_amounts_vector(false);
req_t.min_count = count;
req_t.max_count = 0;
req_t.unlocked = unlocked;
req_t.recent_cutoff = 0;
- bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram");
- THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status);
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status);
+ uint64_t cost = req_t.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * req_t.amounts.size());
+ check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, cost);
+ }
std::set<uint64_t> mixable;
for (const auto &i: resp_t.histogram)
@@ -10353,19 +10461,22 @@ uint64_t wallet2::get_num_rct_outputs()
{
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
- m_daemon_rpc_mutex.lock();
req_t.amounts.push_back(0);
req_t.min_count = 0;
req_t.max_count = 0;
req_t.unlocked = true;
req_t.recent_cutoff = 0;
- bool r = invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs");
- THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
- THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status);
- THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response");
- THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req_t.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, resp_t, "get_output_histogram", error::get_histogram_error, resp_t.status);
+ THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response");
+ THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount");
+ check_rpc_cost("get_output_histogram", resp_t.credits, pre_call_credits, COST_PER_OUTPUT_HISTOGRAM);
+ }
return resp_t.histogram[0].total_instances;
}
@@ -10486,17 +10597,22 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
- error::wallet_internal_error, "Failed to get transaction from daemon");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ req.client = get_client_signature();
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
+ THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
+ error::wallet_internal_error, "Failed to get transaction from daemon");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, res.txs.size() * COST_PER_TX);
+ }
cryptonote::transaction tx;
crypto::hash tx_hash{};
cryptonote::blobdata tx_data;
crypto::hash tx_prefix_hash{};
- ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
+ bool ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data);
THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon");
THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash),
error::wallet_internal_error, "Failed to validate transaction from daemon");
@@ -10534,16 +10650,19 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
+ uint64_t pre_call_credits;
{
- const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/gettransactions");
+ THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong txs count = " +
+ std::to_string(res.txs.size()) + ", expected 1");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
- "daemon returned wrong response for gettransactions, wrong txs count = " +
- std::to_string(res.txs.size()) + ", expected 1");
+
cryptonote::transaction tx;
crypto::hash tx_hash;
THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(res.txs[0], tx, tx_hash), error::wallet_internal_error,
@@ -10584,16 +10703,18 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
+ uint64_t pre_call_credits;
{
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
+ pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions");
+ THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong txs count = " +
+ std::to_string(res.txs.size()) + ", expected 1");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
- "daemon returned wrong response for gettransactions, wrong txs count = " +
- std::to_string(res.txs.size()) + ", expected 1");
cryptonote::transaction tx;
crypto::hash tx_hash;
@@ -10646,16 +10767,18 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string
}
COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
bool r;
+ uint64_t pre_call_credits;
{
- const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout);
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status);
+ THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error,
+ "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
+ std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size));
+ check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, ring_size * COST_PER_OUT);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error,
- "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
- std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size));
// copy pubkey pointers
std::vector<const crypto::public_key *> p_output_keys;
@@ -10702,16 +10825,18 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
req.prune = true;
COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res);
bool r;
+ uint64_t pre_call_credits;
{
- const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_json("/gettransactions", req, res, rpc_timeout);
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "gettransactions");
+ THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong txs count = " +
+ std::to_string(res.txs.size()) + ", expected 1");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error,
- "daemon returned wrong response for gettransactions, wrong txs count = " +
- std::to_string(res.txs.size()) + ", expected 1");
cryptonote::transaction tx;
crypto::hash tx_hash;
@@ -10775,16 +10900,18 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes
}
COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
bool r;
+ uint64_t pre_call_credits;
{
- const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
- r = invoke_http_bin("/get_outs.bin", req, res, rpc_timeout);
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_outs.bin", error::get_outs_error, res.status);
+ THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error,
+ "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
+ std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
+ check_rpc_cost("/get_outs.bin", res.credits, pre_call_credits, req.outputs.size() * COST_PER_OUT);
}
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin");
- THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error,
- "daemon returned wrong response for get_outs.bin, wrong amounts count = " +
- std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
// copy pointers
std::vector<const crypto::public_key *> p_output_keys;
@@ -10874,11 +11001,17 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
- error::wallet_internal_error, "Failed to get transaction from daemon");
+
+ bool ok;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
+ THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
+ error::wallet_internal_error, "Failed to get transaction from daemon");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
+ }
cryptonote::transaction tx;
crypto::hash tx_hash;
@@ -10923,11 +11056,17 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
- error::wallet_internal_error, "Failed to get transaction from daemon");
+
+ bool ok;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
+ THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
+ error::wallet_internal_error, "Failed to get transaction from daemon");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
+ }
cryptonote::transaction tx;
crypto::hash tx_hash;
@@ -11078,11 +11217,17 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid));
req.decode_as_json = false;
req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool ok = invoke_http_json("/gettransactions", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
- error::wallet_internal_error, "Failed to get transaction from daemon");
+
+ bool ok;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client);
+ THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1),
+ error::wallet_internal_error, "Failed to get transaction from daemon");
+ check_rpc_cost("/gettransactions", res.credits, pre_call_credits, COST_PER_TX);
+ }
cryptonote::transaction tx;
crypto::hash tx_hash;
@@ -11370,22 +11515,33 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid));
gettx_req.decode_as_json = false;
gettx_req.prune = true;
- m_daemon_rpc_mutex.lock();
- bool ok = invoke_http_json("/gettransactions", gettx_req, gettx_res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(),
- error::wallet_internal_error, "Failed to get transaction from daemon");
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ gettx_req.client = get_client_signature();
+ bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client);
+ THROW_WALLET_EXCEPTION_IF(!ok || gettx_res.txs.size() != proofs.size(),
+ error::wallet_internal_error, "Failed to get transaction from daemon");
+ check_rpc_cost("/gettransactions", gettx_res.credits, pre_call_credits, gettx_res.txs.size() * COST_PER_TX);
+ }
// check spent status
COMMAND_RPC_IS_KEY_IMAGE_SPENT::request kispent_req;
COMMAND_RPC_IS_KEY_IMAGE_SPENT::response kispent_res;
for (size_t i = 0; i < proofs.size(); ++i)
kispent_req.key_images.push_back(epee::string_tools::pod_to_hex(proofs[i].key_image));
- m_daemon_rpc_mutex.lock();
- ok = invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(),
- error::wallet_internal_error, "Failed to get key image spent status from daemon");
+
+ bool ok;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ kispent_req.client = get_client_signature();
+ ok = epee::net_utils::invoke_http_json("/is_key_image_spent", kispent_req, kispent_res, m_http_client, rpc_timeout);
+ THROW_WALLET_EXCEPTION_IF(!ok || kispent_res.spent_status.size() != proofs.size(),
+ error::wallet_internal_error, "Failed to get key image spent status from daemon");
+ check_rpc_cost("/is_key_image_spent", kispent_res.credits, pre_call_credits, kispent_res.spent_status.size() * COST_PER_KEY_IMAGE);
+ }
total = spent = 0;
for (size_t i = 0; i < proofs.size(); ++i)
@@ -11471,7 +11627,7 @@ std::string wallet2::get_daemon_address() const
return m_daemon_address;
}
-uint64_t wallet2::get_daemon_blockchain_height(string &err) const
+uint64_t wallet2::get_daemon_blockchain_height(string &err)
{
uint64_t height;
@@ -11937,15 +12093,18 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
if(check_spent)
{
PERF_TIMER(import_key_images_RPC);
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/is_key_image_spent", req, daemon_resp, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent");
- THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status);
- THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error,
- "daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
- std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size()));
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = epee::net_utils::invoke_http_json("/is_key_image_spent", req, daemon_resp, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, daemon_resp, "is_key_image_spent");
+ THROW_WALLET_EXCEPTION_IF(daemon_resp.spent_status.size() != signed_key_images.size(), error::wallet_internal_error,
+ "daemon returned wrong response for is_key_image_spent, wrong amounts count = " +
+ std::to_string(daemon_resp.spent_status.size()) + ", expected " + std::to_string(signed_key_images.size()));
+ check_rpc_cost("/is_key_image_spent", daemon_resp.credits, pre_call_credits, daemon_resp.spent_status.size() * COST_PER_KEY_IMAGE);
+ }
+
for (size_t n = 0; n < daemon_resp.spent_status.size(); ++n)
{
transfer_details &td = m_transfers[n + offset];
@@ -12023,13 +12182,16 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
PERF_TIMER_START(import_key_images_E);
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json("/gettransactions", gettxs_req, gettxs_res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(gettxs_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions");
- THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
- "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ gettxs_req.client = get_client_signature();
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ bool r = epee::net_utils::invoke_http_json("/gettransactions", gettxs_req, gettxs_res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, gettxs_res, "gettransactions");
+ THROW_WALLET_EXCEPTION_IF(gettxs_res.txs.size() != spent_txids.size(), error::wallet_internal_error,
+ "daemon returned wrong response for gettransactions, wrong count = " + std::to_string(gettxs_res.txs.size()) + ", expected " + std::to_string(spent_txids.size()));
+ check_rpc_cost("/gettransactions", gettxs_res.credits, pre_call_credits, spent_txids.size() * COST_PER_TX);
+ }
PERF_TIMER_STOP(import_key_images_E);
// process each outgoing tx
@@ -12958,7 +13120,17 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
height_mid,
height_max
};
- bool r = invoke_http_bin("/getblocks_by_height.bin", req, res, rpc_timeout);
+
+ bool r;
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ r = net_utils::invoke_http_bin("/getblocks_by_height.bin", req, res, m_http_client, rpc_timeout);
+ if (r && res.status == CORE_RPC_STATUS_OK)
+ check_rpc_cost("/getblocks_by_height.bin", res.credits, pre_call_credits, 3 * COST_PER_BLOCK);
+ }
+
if (!r || res.status != CORE_RPC_STATUS_OK)
{
std::ostringstream oss;
@@ -13007,7 +13179,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui
}
}
//----------------------------------------------------------------------------------------------------
-bool wallet2::is_synced() const
+bool wallet2::is_synced()
{
uint64_t height;
boost::optional<std::string> result = m_node_rpc_proxy.get_target_height(height);
@@ -13027,16 +13199,19 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std::
// get txpool backlog
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request req = AUTO_VAL_INIT(req);
cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response res = AUTO_VAL_INIT(res);
- m_daemon_rpc_mutex.lock();
- bool r = invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, rpc_timeout);
- m_daemon_rpc_mutex.unlock();
- THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "Failed to connect to daemon");
- THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_txpool_backlog");
- THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error);
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR(r, {}, res, "get_txpool_backlog", error::get_tx_pool_error);
+ check_rpc_cost("get_txpool_backlog", res.credits, pre_call_credits, COST_PER_TX_POOL_STATS * res.backlog.size());
+ }
uint64_t block_weight_limit = 0;
const auto result = m_node_rpc_proxy.get_block_weight_limit(block_weight_limit);
- throw_on_rpc_response_error(result, "get_info");
+ THROW_WALLET_EXCEPTION_IF(result, error::wallet_internal_error, "Invalid block weight limit from daemon");
uint64_t full_reward_zone = block_weight_limit / 2;
THROW_WALLET_EXCEPTION_IF(full_reward_zone == 0, error::wallet_internal_error, "Invalid block weight limit from daemon");
@@ -13208,22 +13383,22 @@ std::string wallet2::get_rpc_status(const std::string &s) const
{
if (m_trusted_daemon)
return s;
+ if (s == CORE_RPC_STATUS_OK)
+ return s;
+ if (s == CORE_RPC_STATUS_BUSY || s == CORE_RPC_STATUS_PAYMENT_REQUIRED)
+ return s;
return "<error>";
}
//----------------------------------------------------------------------------------------------------
-void wallet2::throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const
+void wallet2::throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const
{
- // no error
- if (!status)
- return;
-
- MERROR("RPC error: " << method << ": status " << *status);
-
+ THROW_WALLET_EXCEPTION_IF(error.code, tools::error::wallet_coded_rpc_error, method, error.code, get_rpc_server_error_message(error.code));
+ THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, method);
// empty string -> not connection
- THROW_WALLET_EXCEPTION_IF(status->empty(), tools::error::no_connection_to_daemon, method);
+ THROW_WALLET_EXCEPTION_IF(status.empty(), tools::error::no_connection_to_daemon, method);
- THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
- THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error");
+ THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
+ THROW_WALLET_EXCEPTION_IF(status == CORE_RPC_STATUS_PAYMENT_REQUIRED, tools::error::payment_required, method);
}
//----------------------------------------------------------------------------------------------------
@@ -13375,4 +13550,25 @@ uint64_t wallet2::get_bytes_received() const
{
return m_http_client.get_bytes_received();
}
+//----------------------------------------------------------------------------------------------------
+std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only)
+{
+ cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::request req = AUTO_VAL_INIT(req);
+ cryptonote::COMMAND_RPC_GET_PUBLIC_NODES::response res = AUTO_VAL_INIT(res);
+
+ req.white = true;
+ req.gray = !white_only;
+
+ {
+ const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex};
+ bool r = epee::net_utils::invoke_http_json("/get_public_nodes", req, res, m_http_client, rpc_timeout);
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, {}, res, "/get_public_nodes");
+ }
+
+ std::vector<cryptonote::public_node> nodes;
+ nodes = res.white;
+ nodes.reserve(nodes.size() + res.gray.size());
+ std::copy(res.gray.begin(), res.gray.end(), std::back_inserter(nodes));
+ return nodes;
+}
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 52118c426..640565a4e 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -64,10 +64,21 @@
#include "node_rpc_proxy.h"
#include "message_store.h"
#include "wallet_light_rpc.h"
+#include "wallet_rpc_helpers.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
+#define THROW_ON_RPC_RESPONSE_ERROR(r, error, res, method, ...) \
+ do { \
+ handle_payment_changes(res, std::integral_constant<bool, HasCredits<decltype(res)>::Has>()); \
+ throw_on_rpc_response_error(r, error, res.status, method); \
+ THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, ## __VA_ARGS__); \
+ } while(0)
+
+#define THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, err, res, method) \
+ THROW_ON_RPC_RESPONSE_ERROR(r, err, res, method, tools::error::wallet_generic_rpc_error, method, res.status)
+
class Serialization_portability_wallet_Test;
class wallet_accessor_test;
@@ -875,6 +886,8 @@ private:
uint64_t get_last_block_reward() const { return m_last_block_reward; }
uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
+ std::vector<cryptonote::public_node> get_public_nodes(bool white_only = true);
+
template <class t_archive>
inline void serialize(t_archive &a, const unsigned int ver)
{
@@ -988,6 +1001,9 @@ private:
if(ver < 28)
return;
a & m_cold_key_images;
+ if(ver < 29)
+ return;
+ a & m_rpc_client_secret_key;
}
/*!
@@ -1061,6 +1077,14 @@ private:
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
const ExportFormat & export_format() const { return m_export_format; }
inline void set_export_format(const ExportFormat& export_format) { m_export_format = export_format; }
+ bool persistent_rpc_client_id() const { return m_persistent_rpc_client_id; }
+ void persistent_rpc_client_id(bool persistent) { m_persistent_rpc_client_id = persistent; }
+ void auto_mine_for_rpc_payment_threshold(float threshold) { m_auto_mine_for_rpc_payment_threshold = threshold; }
+ float auto_mine_for_rpc_payment_threshold() const { return m_auto_mine_for_rpc_payment_threshold; }
+ crypto::secret_key get_rpc_client_secret_key() const { return m_rpc_client_secret_key; }
+ void set_rpc_client_secret_key(const crypto::secret_key &key) { m_rpc_client_secret_key = key; m_node_rpc_proxy.set_client_secret_key(key); }
+ uint64_t credits_target() const { return m_credits_target; }
+ void credits_target(uint64_t threshold) { m_credits_target = threshold; }
bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const;
void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys);
@@ -1108,15 +1132,15 @@ private:
const transfer_details &get_transfer_details(size_t idx) const;
uint8_t get_current_hard_fork();
- void get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const;
- bool use_fork_rules(uint8_t version, int64_t early_blocks = 0) const;
- int get_fee_algorithm() const;
+ void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
+ bool use_fork_rules(uint8_t version, int64_t early_blocks = 0);
+ int get_fee_algorithm();
std::string get_wallet_file() const;
std::string get_keys_file() const;
std::string get_daemon_address() const;
const boost::optional<epee::net_utils::http::login>& get_daemon_login() const { return m_daemon_login; }
- uint64_t get_daemon_blockchain_height(std::string& err) const;
+ uint64_t get_daemon_blockchain_height(std::string& err);
uint64_t get_daemon_blockchain_target_height(std::string& err);
/*!
* \brief Calculates the approximate blockchain height from current date/time.
@@ -1211,21 +1235,37 @@ private:
uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31
- bool is_synced() const;
+ bool is_synced();
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(const std::vector<std::pair<double, double>> &fee_levels);
std::vector<std::pair<uint64_t, uint64_t>> estimate_backlog(uint64_t min_tx_weight, uint64_t max_tx_weight, const std::vector<uint64_t> &fees);
- uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1) const;
- uint64_t get_base_fee() const;
- uint64_t get_fee_quantization_mask() const;
- uint64_t get_min_ring_size() const;
- uint64_t get_max_ring_size() const;
- uint64_t adjust_mixin(uint64_t mixin) const;
+ uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1);
+ uint64_t get_base_fee();
+ uint64_t get_fee_quantization_mask();
+ uint64_t get_min_ring_size();
+ uint64_t get_max_ring_size();
+ uint64_t adjust_mixin(uint64_t mixin);
+
uint32_t adjust_priority(uint32_t priority);
bool is_unattended() const { return m_unattended; }
+ bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie);
+ bool daemon_requires_payment();
+ bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance);
+ bool search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL);
+ template<typename T> void handle_payment_changes(const T &res, std::true_type) {
+ if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED)
+ m_rpc_payment_state.credits = res.credits;
+ if (res.top_hash != m_rpc_payment_state.top_hash)
+ {
+ m_rpc_payment_state.top_hash = res.top_hash;
+ m_rpc_payment_state.stale = true;
+ }
+ }
+ template<typename T> void handle_payment_changes(const T &res, std::false_type) {}
+
// Light wallet specific functions
// fetch unspent outs from lw node and store in m_transfers
void light_wallet_get_unspent_outs();
@@ -1338,6 +1378,9 @@ private:
void enable_dns(bool enable) { m_use_dns = enable; }
void set_offline(bool offline = true);
+ uint64_t credits() const { return m_rpc_payment_state.credits; }
+ void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; }
+
private:
/*!
* \brief Stores wallet information to wallet file.
@@ -1363,7 +1406,7 @@ private:
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
- void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error);
+ void pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &error, std::exception_ptr &exception);
void process_parsed_blocks(uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<parsed_block> &parsed_blocks, uint64_t& blocks_added, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers) const;
bool prepare_file_names(const std::string& file_path);
@@ -1379,9 +1422,9 @@ private:
void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info) const;
void check_acc_out_precomp_once(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, const is_out_data *is_out_data, tx_scan_info_t &tx_scan_info, bool &already_seen) const;
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
- uint64_t get_upper_transaction_weight_limit() const;
- std::vector<uint64_t> get_unspent_amounts_vector(bool strict) const;
- uint64_t get_dynamic_base_fee_estimate() const;
+ uint64_t get_upper_transaction_weight_limit();
+ std::vector<uint64_t> get_unspent_amounts_vector(bool strict);
+ uint64_t get_dynamic_base_fee_estimate();
float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const;
std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const;
void set_spent(size_t idx, uint64_t height);
@@ -1435,7 +1478,10 @@ private:
void on_device_progress(const hw::device_progress& event);
std::string get_rpc_status(const std::string &s) const;
- void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) const;
+ void throw_on_rpc_response_error(bool r, const epee::json_rpc::error &error, const std::string &status, const char *method) const;
+
+ std::string get_client_signature() const;
+ void check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_credits, double expected_cost);
cryptonote::account_base m_account;
boost::optional<epee::net_utils::http::login> m_daemon_login;
@@ -1516,6 +1562,8 @@ private:
bool m_track_uses;
uint32_t m_inactivity_lock_timeout;
BackgroundMiningSetupType m_setup_background_mining;
+ bool m_persistent_rpc_client_id;
+ float m_auto_mine_for_rpc_payment_threshold;
bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
@@ -1526,6 +1574,9 @@ private:
bool m_use_dns;
bool m_offline;
uint32_t m_rpc_version;
+ crypto::secret_key m_rpc_client_secret_key;
+ rpc_payment_state_t m_rpc_payment_state;
+ uint64_t m_credits_target;
// Aux transaction data from device
std::unordered_map<crypto::hash, std::string> m_tx_device;
@@ -1569,7 +1620,7 @@ private:
ExportFormat m_export_format;
};
}
-BOOST_CLASS_VERSION(tools::wallet2, 28)
+BOOST_CLASS_VERSION(tools::wallet2, 29)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp
index 9da9d109c..350f016c7 100644
--- a/src/wallet/wallet_args.cpp
+++ b/src/wallet/wallet_args.cpp
@@ -76,6 +76,10 @@ namespace wallet_args
{
return {"wallet-file", wallet_args::tr("Use wallet <arg>"), ""};
}
+ command_line::arg_descriptor<std::string> arg_rpc_client_secret_key()
+ {
+ return {"rpc-client-secret-key", wallet_args::tr("Set RPC client secret key for RPC payments"), ""};
+ }
const char* tr(const char* str)
{
diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h
index c861dca11..59529662f 100644
--- a/src/wallet/wallet_args.h
+++ b/src/wallet/wallet_args.h
@@ -36,6 +36,7 @@ namespace wallet_args
{
command_line::arg_descriptor<std::string> arg_generate_from_json();
command_line::arg_descriptor<std::string> arg_wallet_file();
+ command_line::arg_descriptor<std::string> arg_rpc_client_secret_key();
const char* tr(const char* str);
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 6ebaaa395..3e94e604a 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -90,6 +90,7 @@ namespace tools
// is_key_image_spent_error
// get_histogram_error
// get_output_distribution
+ // payment_required
// wallet_files_doesnt_correspond
//
// * - class with protected ctor
@@ -781,6 +782,20 @@ namespace tools
const std::string m_status;
};
//----------------------------------------------------------------------------------------------------
+ struct wallet_coded_rpc_error : public wallet_rpc_error
+ {
+ explicit wallet_coded_rpc_error(std::string&& loc, const std::string& request, int code, const std::string& status)
+ : wallet_rpc_error(std::move(loc), std::string("error ") + std::to_string(code) + (" in ") + request + " RPC: " + status, request),
+ m_code(code), m_status(status)
+ {
+ }
+ int code() const { return m_code; }
+ const std::string& status() const { return m_status; }
+ private:
+ int m_code;
+ const std::string m_status;
+ };
+ //----------------------------------------------------------------------------------------------------
struct daemon_busy : public wallet_rpc_error
{
explicit daemon_busy(std::string&& loc, const std::string& request)
@@ -821,6 +836,14 @@ namespace tools
}
};
//----------------------------------------------------------------------------------------------------
+ struct payment_required: public wallet_rpc_error
+ {
+ explicit payment_required(std::string&& loc, const std::string& request)
+ : wallet_rpc_error(std::move(loc), "payment required", request)
+ {
+ }
+ };
+ //----------------------------------------------------------------------------------------------------
struct wallet_files_doesnt_correspond : public wallet_logic_error
{
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
diff --git a/src/wallet/wallet_rpc_helpers.h b/src/wallet/wallet_rpc_helpers.h
new file mode 100644
index 000000000..91803ff77
--- /dev/null
+++ b/src/wallet/wallet_rpc_helpers.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2018-2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include <type_traits>
+
+namespace
+{
+ // credits to yrp (https://stackoverflow.com/questions/87372/check-if-a-class-has-a-member-function-of-a-given-signature
+ template <typename T>
+ struct HasCredits
+ {
+ template<typename U, uint64_t (U::*)> struct SFINAE {};
+ template<typename U> static char Test(SFINAE<U, &U::credits>*);
+ template<typename U> static int Test(...);
+ static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
+ };
+}
+
+namespace tools
+{
+ struct rpc_payment_state_t
+ {
+ uint64_t credits;
+ uint64_t expected_spent;
+ uint64_t discrepancy;
+ std::string top_hash;
+ bool stale;
+
+ rpc_payment_state_t(): credits(0), expected_spent(0), discrepancy(0), stale(true) {}
+ };
+
+ static inline void check_rpc_cost(rpc_payment_state_t &rpc_payment_state, const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
+ {
+ uint64_t expected_credits = (uint64_t)expected_cost;
+ if (expected_credits == 0)
+ expected_credits = 1;
+
+ rpc_payment_state.credits = post_call_credits;
+ rpc_payment_state.expected_spent += expected_credits;
+
+ if (pre_call_credits < post_call_credits)
+ return;
+
+ uint64_t cost = pre_call_credits - post_call_credits;
+
+ if (cost == expected_credits)
+ {
+ MDEBUG("Call " << call << " cost " << cost << " credits");
+ return;
+ }
+ MWARNING("Call " << call << " cost " << cost << " credits, expected " << expected_credits);
+
+ if (cost > expected_credits)
+ {
+ uint64_t d = cost - expected_credits;
+ if (rpc_payment_state.discrepancy > std::numeric_limits<uint64_t>::max() - d)
+ {
+ MERROR("Integer overflow in credit discrepancy calculation, setting to max");
+ rpc_payment_state.discrepancy = std::numeric_limits<uint64_t>::max();
+ }
+ else
+ {
+ rpc_payment_state.discrepancy += d;
+ }
+ }
+ }
+}
diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp
new file mode 100644
index 000000000..41696d13b
--- /dev/null
+++ b/src/wallet/wallet_rpc_payments.cpp
@@ -0,0 +1,196 @@
+// Copyright (c) 2018-2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <boost/optional/optional.hpp>
+#include <boost/utility/value_init.hpp>
+#include "include_base_utils.h"
+#include "cryptonote_config.h"
+#include "wallet_rpc_helpers.h"
+#include "wallet2.h"
+#include "cryptonote_basic/cryptonote_format_utils.h"
+#include "rpc/core_rpc_server_commands_defs.h"
+#include "rpc/rpc_payment_signature.h"
+#include "misc_language.h"
+#include "cryptonote_basic/cryptonote_basic_impl.h"
+#include "int-util.h"
+#include "crypto/crypto.h"
+#include "cryptonote_basic/blobdatatype.h"
+#include "common/i18n.h"
+#include "common/util.h"
+
+#undef MONERO_DEFAULT_LOG_CATEGORY
+#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2.rpc_payments"
+
+#define RPC_PAYMENT_POLL_PERIOD 10 /* seconds*/
+
+namespace tools
+{
+//----------------------------------------------------------------------------------------------------
+std::string wallet2::get_client_signature() const
+{
+ return cryptonote::make_rpc_payment_signature(m_rpc_client_secret_key);
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie)
+{
+ boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_payment_info(mining, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie);
+ credits = m_rpc_payment_state.credits;
+ if (result && *result != CORE_RPC_STATUS_OK)
+ return false;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::daemon_requires_payment()
+{
+ bool payment_required = false;
+ uint64_t credits, diff, credits_per_hash_found, height, seed_height;
+ uint32_t cookie;
+ cryptonote::blobdata blob;
+ crypto::hash seed_hash, next_seed_hash;
+ return get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance)
+{
+ cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::request req = AUTO_VAL_INIT(req);
+ cryptonote::COMMAND_RPC_ACCESS_SUBMIT_NONCE::response res = AUTO_VAL_INIT(res);
+ req.nonce = nonce;
+ req.cookie = cookie;
+ m_daemon_rpc_mutex.lock();
+ uint64_t pre_call_credits = m_rpc_payment_state.credits;
+ req.client = get_client_signature();
+ epee::json_rpc::error error;
+ bool r = epee::net_utils::invoke_http_json_rpc("/json_rpc", "rpc_access_submit_nonce", req, res, error, m_http_client, rpc_timeout);
+ m_daemon_rpc_mutex.unlock();
+ THROW_ON_RPC_RESPONSE_ERROR_GENERIC(r, error, res, "rpc_access_submit_nonce");
+ THROW_WALLET_EXCEPTION_IF(res.credits < pre_call_credits, error::wallet_internal_error, "RPC payment did not increase balance");
+ if (m_rpc_payment_state.top_hash != res.top_hash)
+ {
+ m_rpc_payment_state.top_hash = res.top_hash;
+ m_rpc_payment_state.stale = true;
+ }
+
+ m_rpc_payment_state.credits = res.credits;
+ balance = res.credits;
+ credits = balance - pre_call_credits;
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc)
+{
+ bool need_payment = false;
+ bool payment_required;
+ uint64_t credits, diff, credits_per_hash_found, height, seed_height;
+ uint32_t cookie;
+ unsigned int n_hashes = 0;
+ cryptonote::blobdata hashing_blob;
+ crypto::hash seed_hash, next_seed_hash;
+ try
+ {
+ need_payment = get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target;
+ if (!need_payment)
+ return true;
+ if (!startfunc(diff, credits_per_hash_found))
+ return true;
+ }
+ catch (const std::exception &e) { return false; }
+
+ static std::atomic<uint32_t> nonce(0);
+ while (contfunc(n_hashes))
+ {
+ try
+ {
+ need_payment = get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required && credits < credits_target;
+ if (!need_payment)
+ return true;
+ }
+ catch (const std::exception &e) { return false; }
+ if (hashing_blob.empty())
+ {
+ MERROR("Bad hashing blob from daemon");
+ if (errorfunc)
+ errorfunc("Bad hashing blob from daemon, trying again");
+ epee::misc_utils::sleep_no_w(1000);
+ continue;
+ }
+
+ crypto::hash hash;
+ const uint32_t local_nonce = nonce++; // wrapping's OK
+ *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce);
+ const uint8_t major_version = hashing_blob[0];
+ if (major_version >= RX_BLOCK_VERSION)
+ {
+ const int miners = 1;
+ crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, miners, 0);
+ }
+ else
+ {
+ int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0;
+ crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, height);
+ }
+ ++n_hashes;
+ if (cryptonote::check_hash(hash, diff))
+ {
+ uint64_t credits, balance;
+ try
+ {
+ make_rpc_payment(local_nonce, cookie, credits, balance);
+ if (credits != credits_per_hash_found)
+ {
+ MERROR("Found nonce, but daemon did not credit us with the expected amount");
+ if (errorfunc)
+ errorfunc("Found nonce, but daemon did not credit us with the expected amount");
+ return false;
+ }
+ MDEBUG("Found nonce " << local_nonce << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits");
+ if (!foundfunc(credits))
+ break;
+ }
+ catch (const tools::error::wallet_coded_rpc_error &e)
+ {
+ MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
+ if (errorfunc)
+ errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing");
+ }
+ catch (const std::exception &e)
+ {
+ MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon");
+ if (errorfunc)
+ errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing");
+ }
+ }
+ }
+ return true;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::check_rpc_cost(const char *call, uint64_t post_call_credits, uint64_t pre_call_credits, double expected_cost)
+{
+ return tools::check_rpc_cost(m_rpc_payment_state, call, post_call_credits, pre_call_credits, expected_cost);
+}
+//----------------------------------------------------------------------------------------------------
+}
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index dbd42ab81..c128210c4 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -4323,6 +4323,7 @@ public:
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const auto arg_from_json = wallet_args::arg_generate_from_json();
+ const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
const auto wallet_file = command_line::get_arg(vm, arg_wallet_file);
const auto from_json = command_line::get_arg(vm, arg_from_json);
@@ -4371,6 +4372,17 @@ public:
return false;
}
+ if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key))
+ {
+ crypto::secret_key client_secret_key;
+ if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), client_secret_key))
+ {
+ MERROR(arg_rpc_client_secret_key.name << ": RPC client secret key should be 32 byte in hex format");
+ return false;
+ }
+ wal->set_rpc_client_secret_key(client_secret_key);
+ }
+
bool quit = false;
tools::signal_handler::install([&wal, &quit](int) {
assert(wal);
@@ -4469,6 +4481,7 @@ int main(int argc, char** argv) {
const auto arg_wallet_file = wallet_args::arg_wallet_file();
const auto arg_from_json = wallet_args::arg_generate_from_json();
+ const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key();
po::options_description hidden_options("Hidden");
@@ -4482,6 +4495,7 @@ int main(int argc, char** argv) {
command_line::add_arg(desc_params, arg_from_json);
command_line::add_arg(desc_params, arg_wallet_dir);
command_line::add_arg(desc_params, arg_prompt_for_password);
+ command_line::add_arg(desc_params, arg_rpc_client_secret_key);
daemonizer::init_options(hidden_options, desc_params);
desc_params.add(hidden_options);