forked from wownero/wownero
Merge pull request #5357
release-v0.7.1.0b3a9a4d
add a quick early out to get_blocks.bin when up to date (moneromooo-monero)2899379
daemon, wallet: new pay for RPC use system (moneromooo-monero)ffa4602
simplewallet: add public_nodes command (moneromooo-monero)
commit
960c215801
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,402 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <boost/archive/portable_binary_iarchive.hpp>
|
||||
#include <boost/archive/portable_binary_oarchive.hpp>
|
||||
#include "cryptonote_config.h"
|
||||
#include "include_base_utils.h"
|
||||
#include "string_tools.h"
|
||||
#include "file_io_utils.h"
|
||||
#include "int-util.h"
|
||||
#include "common/util.h"
|
||||
#include "serialization/crypto.h"
|
||||
#include "common/unordered_containers_boost_serialization.h"
|
||||
#include "cryptonote_basic/cryptonote_boost_serialization.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "cryptonote_basic/difficulty.h"
|
||||
#include "core_rpc_server_error_codes.h"
|
||||
#include "rpc_payment.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment"
|
||||
|
||||
#define STALE_THRESHOLD 15 /* seconds */
|
||||
|
||||
#define PENALTY_FOR_STALE 0
|
||||
#define PENALTY_FOR_BAD_HASH 20
|
||||
#define PENALTY_FOR_DUPLICATE 20
|
||||
|
||||
#define DEFAULT_FLUSH_AGE (3600 * 24 * 180) // half a year
|
||||
#define DEFAULT_ZERO_FLUSH_AGE (60 * 2) // 2 minutes
|
||||
|
||||
#define RPC_PAYMENT_NONCE_TAIL 0x58
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
rpc_payment::client_info::client_info():
|
||||
cookie(0),
|
||||
top(crypto::null_hash),
|
||||
previous_top(crypto::null_hash),
|
||||
credits(0),
|
||||
update_time(time(NULL)),
|
||||
last_request_timestamp(0),
|
||||
block_template_update_time(0),
|
||||
credits_total(0),
|
||||
credits_used(0),
|
||||
nonces_good(0),
|
||||
nonces_stale(0),
|
||||
nonces_bad(0),
|
||||
nonces_dupe(0)
|
||||
{
|
||||
}
|
||||
|
||||
rpc_payment::rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found):
|
||||
m_address(address),
|
||||
m_diff(diff),
|
||||
m_credits_per_hash_found(credits_per_hash_found),
|
||||
m_credits_total(0),
|
||||
m_credits_used(0),
|
||||
m_nonces_good(0),
|
||||
m_nonces_stale(0),
|
||||
m_nonces_bad(0),
|
||||
m_nonces_dupe(0)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t rpc_payment::balance(const crypto::public_key &client, int64_t delta)
|
||||
{
|
||||
client_info &info = m_client_info[client]; // creates if not found
|
||||
uint64_t credits = info.credits;
|
||||
if (delta > 0 && credits > std::numeric_limits<uint64_t>::max() - delta)
|
||||
credits = std::numeric_limits<uint64_t>::max();
|
||||
else if (delta < 0 && credits < (uint64_t)-delta)
|
||||
credits = 0;
|
||||
else
|
||||
credits += delta;
|
||||
if (delta)
|
||||
MINFO("Client " << client << ": balance change from " << info.credits << " to " << credits);
|
||||
return info.credits = credits;
|
||||
}
|
||||
|
||||
bool rpc_payment::pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits)
|
||||
{
|
||||
client_info &info = m_client_info[client]; // creates if not found
|
||||
if (ts < info.last_request_timestamp || (ts == info.last_request_timestamp && !same_ts))
|
||||
{
|
||||
MDEBUG("Invalid ts: " << ts << " <= " << info.last_request_timestamp);
|
||||
return false;
|
||||
}
|
||||
info.last_request_timestamp = ts;
|
||||
if (info.credits < payment)
|
||||
{
|
||||
MDEBUG("Not enough credits: " << info.credits << " < " << payment);
|
||||
credits = info.credits;
|
||||
return false;
|
||||
}
|
||||
info.credits -= payment;
|
||||
add64clamp(&info.credits_used, payment);
|
||||
add64clamp(&m_credits_used, payment);
|
||||
MDEBUG("client " << client << " paying " << payment << " for " << rpc << ", " << info.credits << " left");
|
||||
credits = info.credits;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rpc_payment::get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie)
|
||||
{
|
||||
client_info &info = m_client_info[client]; // creates if not found
|
||||
const uint64_t now = time(NULL);
|
||||
bool need_template = top != info.top || now >= info.block_template_update_time + STALE_THRESHOLD;
|
||||
if (need_template)
|
||||
{
|
||||
cryptonote::block new_block;
|
||||
uint64_t new_seed_height;
|
||||
crypto::hash new_seed_hash;
|
||||
cryptonote::blobdata extra_nonce("\x42\x42\x42\x42", 4);
|
||||
if (!get_block_template(extra_nonce, new_block, new_seed_height, new_seed_hash))
|
||||
return false;
|
||||
if(!remove_field_from_tx_extra(new_block.miner_tx.extra, typeid(cryptonote::tx_extra_nonce)))
|
||||
return false;
|
||||
char data[33];
|
||||
memcpy(data, &client, 32);
|
||||
data[32] = RPC_PAYMENT_NONCE_TAIL;
|
||||
crypto::hash hash;
|
||||
cn_fast_hash(data, sizeof(data), hash);
|
||||
extra_nonce = cryptonote::blobdata((const char*)&hash, 4);
|
||||
if(!add_extra_nonce_to_tx_extra(new_block.miner_tx.extra, extra_nonce))
|
||||
return false;
|
||||
info.previous_block = std::move(info.block);
|
||||
info.block = std::move(new_block);
|
||||
hashing_blob = get_block_hashing_blob(info.block);
|
||||
info.previous_hashing_blob = info.hashing_blob;
|
||||
info.hashing_blob = hashing_blob;
|
||||
info.previous_top = info.top;
|
||||
info.previous_seed_height = info.seed_height;
|
||||
info.seed_height = new_seed_height;
|
||||
info.previous_seed_hash = info.seed_hash;
|
||||
info.seed_hash = new_seed_hash;
|
||||
std::swap(info.previous_payments, info.payments);
|
||||
info.payments.clear();
|
||||
++info.cookie;
|
||||
info.block_template_update_time = now;
|
||||
}
|
||||
info.top = top;
|
||||
info.update_time = now;
|
||||
hashing_blob = info.hashing_blob;
|
||||
diff = m_diff;
|
||||
credits_per_hash_found = m_credits_per_hash_found;
|
||||
credits = info.credits;
|
||||
seed_height = info.seed_height;
|
||||
seed_hash = info.seed_hash;
|
||||
cookie = info.cookie;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rpc_payment::submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale)
|
||||
{
|
||||
client_info &info = m_client_info[client]; // creates if not found
|
||||
if (cookie != info.cookie && cookie != info.cookie - 1)
|
||||
{
|
||||
MWARNING("Very stale nonce");
|
||||
++m_nonces_stale;
|
||||
++info.nonces_stale;
|
||||
sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found);
|
||||
error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT;
|
||||
error_message = "Very stale payment";
|
||||
return false;
|
||||
}
|
||||
const bool is_current = cookie == info.cookie;
|
||||
MINFO("client " << client << " sends nonce: " << nonce << ", " << (is_current ? "current" : "stale"));
|
||||
std::unordered_set<uint64_t> &payments = is_current ? info.payments : info.previous_payments;
|
||||
if (!payments.insert(nonce).second)
|
||||
{
|
||||
MWARNING("Duplicate nonce " << nonce << " from " << (is_current ? "current" : "previous"));
|
||||
++m_nonces_dupe;
|
||||
++info.nonces_dupe;
|
||||
sub64clamp(&info.credits, PENALTY_FOR_DUPLICATE * m_credits_per_hash_found);
|
||||
error_code = CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT;
|
||||
error_message = "Duplicate payment";
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t now = time(NULL);
|
||||
if (!is_current)
|
||||
{
|
||||
if (now > info.update_time + STALE_THRESHOLD)
|
||||
{
|
||||
MWARNING("Nonce is stale (top " << top << ", should be " << info.top << " or within " << STALE_THRESHOLD << " seconds");
|
||||
++m_nonces_stale;
|
||||
++info.nonces_stale;
|
||||
sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found);
|
||||
error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT;
|
||||
error_message = "stale payment";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cryptonote::blobdata hashing_blob = is_current ? info.hashing_blob : info.previous_hashing_blob;
|
||||
if (hashing_blob.size() < 43)
|
||||
{
|
||||
// not initialized ?
|
||||
error_code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB;
|
||||
error_message = "not initialized";
|
||||
return false;
|
||||
}
|
||||
|
||||
block = is_current ? info.block : info.previous_block;
|
||||
*(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce);
|
||||
if (block.major_version >= RX_BLOCK_VERSION)
|
||||
{
|
||||
const uint64_t seed_height = is_current ? info.seed_height : info.previous_seed_height;
|
||||
const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash;
|
||||
const uint64_t height = cryptonote::get_block_height(block);
|
||||
crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0;
|
||||
crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, cryptonote::get_block_height(block));
|
||||
}
|
||||
if (!check_hash(hash, m_diff))
|
||||
{
|
||||
MWARNING("Payment too low");
|
||||
++m_nonces_bad;
|
||||
++info.nonces_bad;
|
||||
error_code = CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW;
|
||||
error_message = "Hash does not meet difficulty (could be wrong PoW hash, or mining at lower difficulty than required, or attempt to defraud)";
|
||||
sub64clamp(&info.credits, PENALTY_FOR_BAD_HASH * m_credits_per_hash_found);
|
||||
return false;
|
||||
}
|
||||
|
||||
add64clamp(&info.credits, m_credits_per_hash_found);
|
||||
MINFO("client " << client << " credited for " << m_credits_per_hash_found << ", now " << info.credits << (is_current ? "" : " (close)"));
|
||||
|
||||
m_hashrate[now] += m_diff;
|
||||
add64clamp(&m_credits_total, m_credits_per_hash_found);
|
||||
add64clamp(&info.credits_total, m_credits_per_hash_found);
|
||||
++m_nonces_good;
|
||||
++info.nonces_good;
|
||||
|
||||
credits = info.credits;
|
||||
block = info.block;
|
||||
block.nonce = nonce;
|
||||
stale = !is_current;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rpc_payment::foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const
|
||||
{
|
||||
for (std::unordered_map<crypto::public_key, client_info>::const_iterator i = m_client_info.begin(); i != m_client_info.end(); ++i)
|
||||
{
|
||||
if (!f(i->first, i->second))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rpc_payment::load(std::string directory)
|
||||
{
|
||||
TRY_ENTRY();
|
||||
m_directory = std::move(directory);
|
||||
std::string state_file_path = directory + "/" + RPC_PAYMENTS_DATA_FILENAME;
|
||||
MINFO("loading rpc payments data from " << state_file_path);
|
||||
std::ifstream data;
|
||||
data.open(state_file_path, std::ios_base::binary | std::ios_base::in);
|
||||
if (!data.fail())
|
||||
{
|
||||
try
|
||||
{
|
||||
boost::archive::portable_binary_iarchive a(data);
|
||||
a >> *this;
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
MERROR("Failed to load RPC payments file: " << e.what());
|
||||
m_client_info.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_client_info.clear();
|
||||
}
|
||||
|
||||
CATCH_ENTRY_L0("rpc_payment::load", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rpc_payment::store(const std::string &directory_) const
|
||||
{
|
||||
TRY_ENTRY();
|
||||
const std::string &directory = directory_.empty() ? m_directory : directory_;
|
||||
MDEBUG("storing rpc payments data to " << directory);
|
||||
if (!tools::create_directories_if_necessary(directory))
|
||||
{
|
||||
MWARNING("Failed to create data directory: " << directory);
|
||||
return false;
|
||||
}
|
||||
const boost::filesystem::path state_file_path = (boost::filesystem::path(directory) / RPC_PAYMENTS_DATA_FILENAME);
|
||||
if (boost::filesystem::exists(state_file_path))
|
||||
{
|
||||
std::string state_file_path_old = state_file_path.string() + ".old";
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(state_file_path_old, ec);
|
||||
std::error_code e = tools::replace_file(state_file_path.string(), state_file_path_old);
|
||||
if (e)
|
||||
MWARNING("Failed to rename " << state_file_path << " to " << state_file_path_old << ": " << e);
|
||||
}
|
||||
std::ofstream data;
|
||||
data.open(state_file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc);
|
||||
if (data.fail())
|
||||
{
|
||||
MWARNING("Failed to save RPC payments to file " << state_file_path);
|
||||
return false;
|
||||
};
|
||||
boost::archive::portable_binary_oarchive a(data);
|
||||
a << *this;
|
||||
return true;
|
||||
CATCH_ENTRY_L0("rpc_payment::store", false);
|
||||
}
|
||||
|
||||
unsigned int rpc_payment::flush_by_age(time_t seconds)
|
||||
{
|
||||
unsigned int count = 0;
|
||||
const time_t now = time(NULL);
|
||||
time_t seconds0 = seconds;
|
||||
if (seconds == 0)
|
||||
{
|
||||
seconds = DEFAULT_FLUSH_AGE;
|
||||
seconds0 = DEFAULT_ZERO_FLUSH_AGE;
|
||||
}
|
||||
const time_t threshold = seconds > now ? 0 : now - seconds;
|
||||
const time_t threshold0 = seconds0 > now ? 0 : now - seconds0;
|
||||
for (std::unordered_map<crypto::public_key, client_info>::iterator i = m_client_info.begin(); i != m_client_info.end(); )
|
||||
{
|
||||
std::unordered_map<crypto::public_key, client_info>::iterator j = i++;
|
||||
const time_t t = std::max(j->second.last_request_timestamp, j->second.update_time);
|
||||
const bool erase = t < ((j->second.credits == 0) ? threshold0 : threshold);
|
||||
if (erase)
|
||||
{
|
||||
MINFO("Erasing " << j->first << " with " << j->second.credits << " credits, inactive for " << (now-t)/86400 << " days");
|
||||
m_client_info.erase(j);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t rpc_payment::get_hashes(unsigned int seconds) const
|
||||
{
|
||||
const uint64_t now = time(NULL);
|
||||
uint64_t hashes = 0;
|
||||
for (std::map<uint64_t, uint64_t>::const_reverse_iterator i = m_hashrate.crbegin(); i != m_hashrate.crend(); ++i)
|
||||
{
|
||||
if (now > i->first + seconds)
|
||||
break;
|
||||
hashes += i->second;
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
void rpc_payment::prune_hashrate(unsigned int seconds)
|
||||
{
|
||||
const uint64_t now = time(NULL);
|
||||
std::map<uint64_t, uint64_t>::iterator i;
|
||||
for (i = m_hashrate.begin(); i != m_hashrate.end(); ++i)
|
||||
{
|
||||
if (now <= i->first + seconds)
|
||||
break;
|
||||
}
|
||||
m_hashrate.erase(m_hashrate.begin(), i);
|
||||
}
|
||||
|
||||
bool rpc_payment::on_idle()
|
||||
{
|
||||
flush_by_age();
|
||||
prune_hashrate(3600);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <boost/serialization/version.hpp>
|
||||
#include "cryptonote_basic/blobdatatype.h"
|
||||
#include "cryptonote_basic/cryptonote_basic.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
class rpc_payment
|
||||
{
|
||||
public:
|
||||
struct client_info
|
||||
{
|
||||
cryptonote::block block;
|
||||
cryptonote::block previous_block;
|
||||
cryptonote::blobdata hashing_blob;
|
||||
cryptonote::blobdata previous_hashing_blob;
|
||||
uint64_t previous_seed_height;
|
||||
uint64_t seed_height;
|
||||
crypto::hash previous_seed_hash;
|
||||
crypto::hash seed_hash;
|
||||
uint32_t cookie;
|
||||
crypto::hash top;
|
||||
crypto::hash previous_top;
|
||||
uint64_t credits;
|
||||
std::unordered_set<uint64_t> payments;
|
||||
std::unordered_set<uint64_t> previous_payments;
|
||||
uint64_t update_time;
|
||||
uint64_t last_request_timestamp;
|
||||
uint64_t block_template_update_time;
|
||||
uint64_t credits_total;
|
||||
uint64_t credits_used;
|
||||
uint64_t nonces_good;
|
||||
uint64_t nonces_stale;
|
||||
uint64_t nonces_bad;
|
||||
uint64_t nonces_dupe;
|
||||
|
||||
client_info();
|
||||
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int ver)
|
||||
{
|
||||
a & block;
|
||||
a & previous_block;
|
||||
a & hashing_blob;
|
||||
a & previous_hashing_blob;
|
||||
a & seed_height;
|
||||
a & previous_seed_height;
|
||||
a & seed_hash;
|
||||
a & previous_seed_hash;
|
||||
a & cookie;
|
||||
a & top;
|
||||
a & previous_top;
|
||||
a & credits;
|
||||
a & payments;
|
||||
a & previous_payments;
|
||||
a & update_time;
|
||||
a & last_request_timestamp;
|
||||
a & block_template_update_time;
|
||||
a & credits_total;
|
||||
a & credits_used;
|
||||
a & nonces_good;
|
||||
a & nonces_stale;
|
||||
a & nonces_bad;
|
||||
a & nonces_dupe;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found);
|
||||
uint64_t balance(const crypto::public_key &client, int64_t delta = 0);
|
||||
bool pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits);
|
||||
bool get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie);
|
||||
bool submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale);
|
||||
const cryptonote::account_public_address &get_payment_address() const { return m_address; }
|
||||
bool foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const;
|
||||
unsigned int flush_by_age(time_t seconds = 0);
|
||||
uint64_t get_hashes(unsigned int seconds) const;
|
||||
void prune_hashrate(unsigned int seconds);
|
||||
bool on_idle();
|
||||
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int ver)
|
||||
{
|
||||
a & m_client_info;
|
||||
a & m_hashrate;
|
||||
a & m_credits_total;
|
||||
a & m_credits_used;
|
||||
a & m_nonces_good;
|
||||
a & m_nonces_stale;
|
||||
a & m_nonces_bad;
|
||||
a & m_nonces_dupe;
|
||||
}
|
||||
|
||||
bool load(std::string directory);
|
||||
bool store(const std::string &directory = std::string()) const;
|
||||
|
||||
private:
|
||||
cryptonote::account_public_address m_address;
|
||||
uint64_t m_diff;
|
||||
uint64_t m_credits_per_hash_found;
|
||||
std::unordered_map<crypto::public_key, client_info> m_client_info;
|
||||
std::string m_directory;
|
||||
std::map<uint64_t, uint64_t> m_hashrate;
|
||||
uint64_t m_credits_total;
|
||||
uint64_t m_credits_used;
|
||||
uint64_t m_nonces_good;
|
||||
uint64_t m_nonces_stale;
|
||||
uint64_t m_nonces_bad;
|
||||
uint64_t m_nonces_dupe;
|
||||
};
|
||||
}
|
||||
|
||||
BOOST_CLASS_VERSION(cryptonote::rpc_payment, 0);
|
||||
BOOST_CLASS_VERSION(cryptonote::rpc_payment::client_info, 0);
|
@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#define COST_PER_BLOCK 0.05
|
||||
#define COST_PER_TX_RELAY 100
|
||||
#define COST_PER_OUT 1
|
||||
#define COST_PER_OUTPUT_INDEXES 1
|
||||
#define COST_PER_TX 0.5
|
||||
#define COST_PER_KEY_IMAGE 0.01
|
||||
#define COST_PER_POOL_HASH 0.01
|
||||
#define COST_PER_TX_POOL_STATS 0.2
|
||||
#define COST_PER_BLOCK_HEADER 0.1
|
||||
#define COST_PER_GET_INFO 1
|
||||
#define COST_PER_OUTPUT_HISTOGRAM 25000
|
||||
#define COST_PER_FULL_OUTPUT_HISTOGRAM 5000000
|
||||
#define COST_PER_OUTPUT_DISTRIBUTION_0 20
|
||||
#define COST_PER_OUTPUT_DISTRIBUTION 50000
|
||||
#define COST_PER_COINBASE_TX_SUM_BLOCK 2
|
||||
#define COST_PER_BLOCK_HASH 0.002
|
||||
#define COST_PER_FEE_ESTIMATE 1
|
||||
#define COST_PER_SYNC_INFO 2
|
||||
#define COST_PER_HARD_FORK_INFO 1
|
||||
#define COST_PER_PEER_LIST 2
|
@ -0,0 +1,107 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <chrono>
|
||||
#include "include_base_utils.h"
|
||||
#include "string_tools.h"
|
||||
#include "rpc_payment_signature.h"
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment"
|
||||
|
||||
#define TIMESTAMP_LEEWAY (60 * 1000000) /* 60 seconds, in microseconds */
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
std::string make_rpc_payment_signature(const crypto::secret_key &skey)
|
||||
{
|
||||
std::string s;
|
||||
crypto::public_key pkey;
|
||||
crypto::secret_key_to_public_key(skey, pkey);
|
||||
crypto::signature sig;
|
||||
const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
char ts[17];
|
||||
int ret = snprintf(ts, sizeof(ts), "%16.16" PRIx64, now);
|
||||
CHECK_AND_ASSERT_MES(ret == 16, "", "snprintf failed");
|
||||
ts[16] = 0;
|
||||
CHECK_AND_ASSERT_MES(strlen(ts) == 16, "", "Invalid time conversion");
|
||||
crypto::hash hash;
|
||||
crypto::cn_fast_hash(ts, 16, hash);
|
||||
crypto::generate_signature(hash, pkey, skey, sig);
|
||||
s = epee::string_tools::pod_to_hex(pkey) + ts + epee::string_tools::pod_to_hex(sig);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts)
|
||||
{
|
||||
if (message.size() != 2 * sizeof(crypto::public_key) + 16 + 2 * sizeof(crypto::signature))
|
||||
{
|
||||
MDEBUG("Bad message size: " << message.size());
|
||||
return false;
|
||||
}
|
||||
const std::string pkey_string = message.substr(0, 2 * sizeof(crypto::public_key));
|
||||
const std::string ts_string = message.substr(2 * sizeof(crypto::public_key), 16);
|
||||
const std::string signature_string = message.substr(2 * sizeof(crypto::public_key) + 16);
|
||||
if (!epee::string_tools::hex_to_pod(pkey_string, pkey))
|
||||
{
|
||||
MDEBUG("Bad client id");
|
||||
return false;
|
||||
}
|
||||
crypto::signature signature;
|
||||
if (!epee::string_tools::hex_to_pod(signature_string, signature))
|
||||
{
|
||||
MDEBUG("Bad signature");
|
||||
return false;
|
||||
}
|
||||
crypto::hash hash;
|
||||
crypto::cn_fast_hash(ts_string.data(), 16, hash);
|
||||
if (!crypto::check_signature(hash, pkey, signature))
|
||||
{
|
||||
MDEBUG("signature does not verify");
|
||||
return false;
|
||||
}
|
||||
char *endptr = NULL;
|
||||
errno = 0;
|
||||
unsigned long long ull = strtoull(ts_string.c_str(), &endptr, 16);
|
||||
if (ull == ULLONG_MAX && errno == ERANGE)
|
||||
{
|
||||
MDEBUG("bad timestamp");
|
||||
return false;
|
||||
}
|
||||
ts = ull;
|
||||
const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
if (ts > now + TIMESTAMP_LEEWAY)
|
||||
{
|
||||
MDEBUG("Timestamp is in the future");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2018-2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
namespace cryptonote
|
||||
{
|
||||
std::string make_rpc_payment_signature(const crypto::secret_key &skey);
|
||||
bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2019, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <stdio.h>
|
||||
#include "string_tools.h"
|
||||
#include "rpc/rpc_payment_signature.h"
|
||||
|
||||
int main(int argc, const char **argv)
|
||||
{
|
||||
if (argc > 2)
|
||||
{
|
||||
fprintf(stderr, "usage: %s <secret_key>\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
crypto::secret_key skey;
|
||||
|
||||
if (argc == 1)
|
||||
{
|
||||
crypto::public_key pkey;
|
||||
crypto::random32_unbiased((unsigned char*)skey.data);
|
||||
crypto::secret_key_to_public_key(skey, pkey);
|
||||
printf("%s %s\n", epee::string_tools::pod_to_hex(skey).c_str(), epee::string_tools::pod_to_hex(pkey).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!epee::string_tools::hex_to_pod(argv[1], skey))
|
||||
{
|
||||
fprintf(stderr, "invalid secret key\n");
|
||||
return 1;
|
||||
}
|
||||
std::string signature = cryptonote::make_rpc_payment_signature(skey);
|
||||
printf("%s\n", signature.c_str());
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,412 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2019 The Monero Project
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are
|
||||
# permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
# conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
# of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import print_function
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
"""Test daemon RPC payment calls
|
||||
"""
|
||||
|
||||
from framework.daemon import Daemon
|
||||
from framework.wallet import Wallet
|
||||
|
||||
class RPCPaymentTest():
|
||||
def run_test(self):
|
||||
self.make_test_signature = os.environ['MAKE_TEST_SIGNATURE']
|
||||
assert len(self.make_test_signature) > 0
|
||||
self.secret_key, self.public_key = self.get_keys()
|
||||
self.reset()
|
||||
self.test_access_tracking()
|
||||
self.test_access_mining()
|
||||
self.test_access_payment()
|
||||
self.test_access_account()
|
||||
self.test_free_access()
|
||||
|
||||
def get_keys(self):
|
||||
output = subprocess.check_output([self.make_test_signature]).rstrip()
|
||||
fields = output.split()
|
||||
assert len(fields) == 2
|
||||
return fields
|
||||
|
||||
def get_signature(self):
|
||||
return subprocess.check_output([self.make_test_signature, self.secret_key]).rstrip()
|
||||
|
||||
def reset(self):
|
||||
print('Resetting blockchain')
|
||||
daemon = Daemon(idx=1)
|
||||
res = daemon.get_height()
|
||||
daemon.pop_blocks(res.height - 1)
|
||||
daemon.flush_txpool()
|
||||
|
||||
def test_access_tracking(self):
|
||||
print('Testing access tracking')
|
||||
daemon = Daemon(idx=1)
|
||||
|
||||
res = daemon.rpc_access_tracking(True)
|
||||
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 1
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'rpc_access_tracking'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_connections()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 2
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_connections()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 2
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 2
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
daemon.get_alternate_chains()
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 3
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'get_alternate_chains'
|
||||
assert entry.count == 1
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
entry = res.data[1]
|
||||
assert entry.rpc == 'get_connections'
|
||||
assert entry.count == 2
|
||||
assert entry.time >= 0
|
||||
assert entry.credits == 0
|
||||
|
||||
res = daemon.rpc_access_tracking(True)
|
||||
res = daemon.rpc_access_tracking()
|
||||
data = sorted(res.data, key = lambda k: k['rpc'])
|
||||
assert len(data) == 1
|
||||
entry = data[0]
|
||||
assert entry.rpc == 'rpc_access_tracking'
|
||||
assert entry.count == 1
|
||||
|
||||
def test_access_mining(self):
|
||||
print('Testing access mining')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert len(res.hashing_blob) > 39
|
||||
assert res.height == 1
|
||||
assert res.top_hash == '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3'
|
||||
assert res.credits_per_hash_found == 5000
|
||||
assert res.diff == 10
|
||||
assert res.credits == 0
|
||||
cookie = res.cookie
|
||||
|
||||
# Try random nonces till we find one that's valid and one that's invalid
|
||||
nonce = 0
|
||||
found_valid = 0
|
||||
found_invalid = 0
|
||||
last_credits = 0
|
||||
while found_valid == 0 or found_invalid == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
assert res.credits == last_credits + 5000
|
||||
except Exception as e:
|
||||
found_invalid += 1
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits < last_credits or res.credits == 0
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
last_credits = res.credits
|
||||
|
||||
# we should now have 1 valid nonce, and a number of bad ones
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert len(res.hashing_blob) > 39
|
||||
assert res.height > 1
|
||||
assert res.top_hash != '418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3' # here, any share matches network diff
|
||||
assert res.credits_per_hash_found == 5000
|
||||
assert res.diff == 10
|
||||
cookie = res.cookie
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
|
||||
# Try random nonces till we find one that's valid so we get a load of credits
|
||||
while last_credits == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
last_credits = res.credits
|
||||
break
|
||||
except:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find a valid none -> the RPC probably fails
|
||||
|
||||
# we should now have at least 5000
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits == last_credits
|
||||
assert res.credits >= 5000 # last one was a valid nonce
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
assert e.balance == 5000
|
||||
assert e.credits_total >= 5000
|
||||
|
||||
# find a valid one, then check dupes aren't allowed
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
cookie = res.cookie
|
||||
old_cookie = cookie # we keep that so can submit a stale later
|
||||
while True:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_valid += 1
|
||||
break
|
||||
except:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 0
|
||||
|
||||
ok = False
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
except:
|
||||
ok = True
|
||||
assert ok
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == 0
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
# find stales without updating cookie, one within 5 seconds (accepted), one later (rejected)
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
found_close_stale = 0
|
||||
found_late_stale = 0
|
||||
while found_close_stale == 0 or found_late_stale == 0:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
found_close_stale += 1
|
||||
found_valid += 1
|
||||
except Exception as e:
|
||||
if e[0]['error']['code'] == -18: # stale
|
||||
found_late_stale += 1
|
||||
else:
|
||||
found_invalid += 1
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == found_late_stale # close stales are accepted, don't count here
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
# find very stale with old cookie (rejected)
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
nonce += 1
|
||||
ok = False
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = old_cookie, client = self.get_signature())
|
||||
except:
|
||||
found_late_stale += 1
|
||||
ok = True
|
||||
assert ok
|
||||
|
||||
res = daemon.rpc_access_data()
|
||||
assert len(res.entries) > 0
|
||||
e = [x for x in res.entries if x['client'] == self.public_key]
|
||||
assert len(e) == 1
|
||||
e = e[0]
|
||||
assert e.nonces_stale == found_late_stale
|
||||
assert e.nonces_bad == found_invalid
|
||||
assert e.nonces_good == found_valid
|
||||
assert e.nonces_dupe == 1
|
||||
|
||||
def test_access_payment(self):
|
||||
print('Testing access payment')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
# Try random nonces till we find one that's valid so we get a load of credits
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
cookie = res.cookie
|
||||
nonce = 0
|
||||
while credits <= 100:
|
||||
nonce += 1
|
||||
try:
|
||||
res = daemon.rpc_access_submit_nonce(nonce = nonce, cookie = cookie, client = self.get_signature())
|
||||
break
|
||||
except:
|
||||
pass
|
||||
assert nonce < 1000 # can't find both valid and invalid -> the RPC probably fails
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
assert credits > 0
|
||||
|
||||
res = daemon.get_info(client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
credits = res.credits
|
||||
|
||||
res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 100)
|
||||
block_hashes = res.blocks
|
||||
|
||||
# ask for 1 block -> 1 credit
|
||||
res = daemon.getblockheadersrange(0, 0, client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
credits = res.credits
|
||||
|
||||
# ask for 100 blocks -> >1 credit
|
||||
res = daemon.getblockheadersrange(1, 100, client = self.get_signature())
|
||||
assert res.credits < credits - 1
|
||||
credits = res.credits
|
||||
|
||||
# external users
|
||||
res = daemon.rpc_access_pay(payment = 1, paying_for = 'foo', client = self.get_signature())
|
||||
assert res.credits == credits - 1
|
||||
res = daemon.rpc_access_pay(payment = 4, paying_for = 'bar', client = self.get_signature())
|
||||
assert res.credits == credits - 5
|
||||
res = daemon.rpc_access_pay(payment = credits, paying_for = 'baz', client = self.get_signature())
|
||||
assert "PAYMENT REQUIRED" in res.status
|
||||
res = daemon.rpc_access_pay(payment = 2, paying_for = 'quux', client = self.get_signature())
|
||||
assert res.credits == credits - 7
|
||||
res = daemon.rpc_access_pay(payment = 3, paying_for = 'bar', client = self.get_signature())
|
||||
assert res.credits == credits - 10
|
||||
|
||||
# that should be rejected because its cost is massive
|
||||
ok = False
|
||||
try: res = daemon.get_output_histogram(amounts = [], client = self.get_signature())
|
||||
except Exception as e: print('e: ' + str(e)); ok = "PAYMENT REQUIRED" in e.status
|
||||
assert ok or "PAYMENT REQUIRED" in res.status
|
||||
|
||||
def test_access_account(self):
|
||||
print('Testing access account')
|
||||
daemon = Daemon(idx=1)
|
||||
wallet = Wallet(idx=3)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
credits = res.credits
|
||||
res = daemon.rpc_access_account(self.get_signature(), 0)
|
||||
assert res.credits == credits
|
||||
res = daemon.rpc_access_account(self.get_signature(), 50)
|
||||
assert res.credits == credits + 50
|
||||
res = daemon.rpc_access_account(self.get_signature(), -10)
|
||||
assert res.credits == credits + 40
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(credits + 50))
|
||||
assert res.credits == 0
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 5)
|
||||
assert res.credits == 2**63 - 5
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2**63 - 1)
|
||||
assert res.credits == 2**64 - 6
|
||||
res = daemon.rpc_access_account(self.get_signature(), 2)
|
||||
assert res.credits == 2**64 - 4
|
||||
res = daemon.rpc_access_account(self.get_signature(), 8)
|
||||
assert res.credits == 2**64 - 1
|
||||
res = daemon.rpc_access_account(self.get_signature(), -1)
|
||||
assert res.credits == 2**64 - 2
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
|
||||
assert res.credits == 2**64 - 2 -(2**63 - 1)
|
||||
res = daemon.rpc_access_account(self.get_signature(), -(2**63 - 1))
|
||||
assert res.credits == 0
|
||||
|
||||
def test_free_access(self):
|
||||
print('Testing free access')
|
||||
daemon = Daemon(idx=0)
|
||||
wallet = Wallet(idx=0)
|
||||
|
||||
res = daemon.rpc_access_info(client = self.get_signature())
|
||||
assert res.credits_per_hash_found == 0
|
||||
assert res.diff == 0
|
||||
assert res.credits == 0
|
||||
|
||||
res = daemon.get_info(client = self.get_signature())
|
||||
assert res.credits == 0
|
||||
|
||||
# any nonce will do here
|
||||
res = daemon.rpc_access_submit_nonce(nonce = 0, cookie = 0, client = self.get_signature())
|
||||
assert res.credits == 0
|
||||
|
||||
|
||||
class Guard:
|
||||
def __enter__(self):
|
||||
for i in range(4):
|
||||
Wallet(idx = i).auto_refresh(False)
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
for i in range(4):
|
||||
Wallet(idx = i).auto_refresh(True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with Guard() as guard:
|
||||
RPCPaymentTest().run_test()
|
Loading…
Reference in new issue