diff options
Diffstat (limited to 'src')
81 files changed, 5158 insertions, 1309 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bab56200..f332af3d3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -129,6 +129,7 @@ add_subdirectory(cryptonote_protocol) if(NOT IOS) add_subdirectory(simplewallet) add_subdirectory(gen_multisig) + add_subdirectory(gen_ssl_cert) add_subdirectory(daemonizer) add_subdirectory(daemon) add_subdirectory(blockchain_utilities) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 8a6695cd8..d1e4919be 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -155,7 +155,8 @@ struct txpool_tx_meta_t uint8_t relayed; uint8_t do_not_relay; uint8_t double_spend_seen: 1; - uint8_t bf_padding: 7; + uint8_t pruned: 1; + uint8_t bf_padding: 6; uint8_t padding[76]; // till 192 bytes }; diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 760e380a9..f978ef307 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2056,7 +2056,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) ++n_prunable_records; result = mdb_cursor_get(c_txs_prunable, &k, &v, MDB_SET); if (result == MDB_NOTFOUND) - MWARNING("Already pruned at height " << block_height << "/" << blockchain_height); + MDEBUG("Already pruned at height " << block_height << "/" << blockchain_height); else if (result) throw0(DB_ERROR(lmdb_error("Failed to find transaction prunable data: ", result).c_str())); else @@ -2152,7 +2152,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) { ++n_prunable_records; if (result == MDB_NOTFOUND) - MWARNING("Already pruned at height " << block_height << "/" << blockchain_height); + MDEBUG("Already pruned at height " << block_height << "/" << blockchain_height); else { MDEBUG("Pruning at height " << block_height << "/" << blockchain_height); diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index a285c2bd0..5d039d7f4 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -152,7 +152,7 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block } hashes.push_back(cryptonote::get_block_hash(block)); } - core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes); + core.prevalidate_block_hashes(core.get_blockchain_storage().get_db().height(), hashes, {}); std::vector<block> pblocks; if (!core.prepare_handle_incoming_blocks(blocks, pblocks)) @@ -178,7 +178,7 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block if(tvc.m_verifivation_failed) { MERROR("transaction verification failed, tx_id = " - << epee::string_tools::pod_to_hex(get_blob_hash(tx_blob))); + << epee::string_tools::pod_to_hex(get_blob_hash(tx_blob.blob))); core.cleanup_handle_incoming_blocks(); return 1; } @@ -429,13 +429,17 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path { cryptonote::blobdata block; cryptonote::block_to_blob(bp.block, block); - std::vector<cryptonote::blobdata> txs; + std::vector<tx_blob_entry> txs; for (const auto &tx: bp.txs) { - txs.push_back(cryptonote::blobdata()); - cryptonote::tx_to_blob(tx, txs.back()); + txs.push_back({cryptonote::blobdata(), crypto::null_hash}); + cryptonote::tx_to_blob(tx, txs.back().blob); } - blocks.push_back({block, txs}); + block_complete_entry bce; + bce.pruned = false; + bce.block = std::move(block); + bce.txs = std::move(txs); + blocks.push_back(bce); int ret = check_flush(core, blocks, false); if (ret) { diff --git a/src/blockchain_utilities/blockchain_utilities.h b/src/blockchain_utilities/blockchain_utilities.h index 78487b995..e8615cf86 100644 --- a/src/blockchain_utilities/blockchain_utilities.h +++ b/src/blockchain_utilities/blockchain_utilities.h @@ -33,7 +33,7 @@ // bounds checking is done before writing to buffer, but buffer size // should be a sensible maximum -#define BUFFER_SIZE 1000000 +#define BUFFER_SIZE (2 * 1024 * 1024) #define CHUNK_SIZE_WARNING_THRESHOLD 500000 #define NUM_BLOCKS_PER_CHUNK 1 #define BLOCKCHAIN_RAW "blockchain.raw" diff --git a/src/blockchain_utilities/blocksdat_file.cpp b/src/blockchain_utilities/blocksdat_file.cpp index f56ff5f94..df3c6cafc 100644 --- a/src/blockchain_utilities/blocksdat_file.cpp +++ b/src/blockchain_utilities/blocksdat_file.cpp @@ -99,17 +99,23 @@ bool BlocksdatFile::initialize_file(uint64_t block_stop) return true; } -void BlocksdatFile::write_block(const crypto::hash& block_hash) +void BlocksdatFile::write_block(const crypto::hash& block_hash, uint64_t weight) { m_hashes.push_back(block_hash); + m_weights.push_back(weight); while (m_hashes.size() >= HASH_OF_HASHES_STEP) { crypto::hash hash; crypto::cn_fast_hash(m_hashes.data(), HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); memmove(m_hashes.data(), m_hashes.data() + HASH_OF_HASHES_STEP, (m_hashes.size() - HASH_OF_HASHES_STEP) * sizeof(crypto::hash)); m_hashes.resize(m_hashes.size() - HASH_OF_HASHES_STEP); - const std::string data(hash.data, sizeof(hash)); - *m_raw_data_file << data; + const std::string data_hashes(hash.data, sizeof(hash)); + *m_raw_data_file << data_hashes; + crypto::cn_fast_hash(m_weights.data(), HASH_OF_HASHES_STEP * sizeof(uint64_t), hash); + memmove(m_weights.data(), m_weights.data() + HASH_OF_HASHES_STEP, (m_weights.size() - HASH_OF_HASHES_STEP) * sizeof(uint64_t)); + m_weights.resize(m_weights.size() - HASH_OF_HASHES_STEP); + const std::string data_weights(hash.data, sizeof(hash)); + *m_raw_data_file << data_weights; } } @@ -154,7 +160,8 @@ bool BlocksdatFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem { // this method's height refers to 0-based height (genesis block = height 0) crypto::hash hash = m_blockchain_storage->get_block_id_by_height(m_cur_height); - write_block(hash); + uint64_t weight = m_blockchain_storage->get_db().get_block_weight(m_cur_height); + write_block(hash, weight); if (m_cur_height % NUM_BLOCKS_PER_CHUNK == 0) { num_blocks_written += NUM_BLOCKS_PER_CHUNK; } diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index 315713424..72b7afc17 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -72,10 +72,11 @@ protected: bool open_writer(const boost::filesystem::path& file_path, uint64_t block_stop); bool initialize_file(uint64_t block_stop); bool close(); - void write_block(const crypto::hash &block_hash); + void write_block(const crypto::hash &block_hash, uint64_t weight); private: uint64_t m_cur_height; // tracks current height during export std::vector<crypto::hash> m_hashes; + std::vector<uint64_t> m_weights; }; diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex a7d309753..eb614d58d 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index 717391623..ea2237348 100644 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -76,14 +76,15 @@ private: void set_performance_timer_log_level(el::Level level); -#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level) -#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer pt_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l) +#define PERF_TIMER_NAME(name) pt_##name +#define PERF_TIMER_UNIT(name, unit) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, tools::performance_timer_log_level) +#define PERF_TIMER_UNIT_L(name, unit, l) tools::LoggingPerformanceTimer PERF_TIMER_NAME(name)t_##name(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, l) #define PERF_TIMER(name) PERF_TIMER_UNIT(name, 1000000) #define PERF_TIMER_L(name, l) PERF_TIMER_UNIT_L(name, 1000000, l) -#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> pt_##name(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info)) +#define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> PERF_TIMER_NAME(name)(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info)) #define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000000) -#define PERF_TIMER_STOP(name) do { pt_##name.reset(NULL); } while(0) -#define PERF_TIMER_PAUSE(name) pt_##name->pause() -#define PERF_TIMER_RESUME(name) pt_##name->resume() +#define PERF_TIMER_STOP(name) do { PERF_TIMER_NAME(name).reset(NULL); } while(0) +#define PERF_TIMER_PAUSE(name) PERF_TIMER_NAME(name)->pause() +#define PERF_TIMER_RESUME(name) PERF_TIMER_NAME(name)->resume() } diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index ada372864..a7a459ad3 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -34,6 +34,7 @@ #include <stdio.h> #include <stdlib.h> #include <unistd.h> +#include <limits.h> #include "randomx.h" #include "c_threads.h" @@ -50,7 +51,7 @@ typedef struct rx_state { CTHR_MUTEX_TYPE rs_mutex; - char rs_hash[32]; + char rs_hash[HASH_SIZE]; uint64_t rs_height; randomx_cache *rs_cache; } rx_state; @@ -63,7 +64,6 @@ static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static randomx_dataset *rx_dataset; static uint64_t rx_dataset_height; static THREADV randomx_vm *rx_vm = NULL; -static THREADV int rx_toggle; static void local_abort(const char *msg) { @@ -75,84 +75,41 @@ static void local_abort(const char *msg) #endif } -/** - * @brief uses cpuid to determine if the CPU supports the AES instructions - * @return true if the CPU supports AES, false otherwise - */ +static inline int disabled_flags(void) { + static int flags = -1; -static inline int force_software_aes(void) -{ - static int use = -1; - - if (use != -1) - return use; + if (flags != -1) { + return flags; + } - const char *env = getenv("MONERO_USE_SOFTWARE_AES"); + const char *env = getenv("MONERO_RANDOMX_UMASK"); if (!env) { - use = 0; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use = 0; + flags = 0; } else { - use = 1; + char* endptr; + long int value = strtol(env, &endptr, 0); + if (endptr != env && value >= 0 && value < INT_MAX) { + flags = value; + } + else { + flags = 0; + } } - return use; -} -static void cpuid(int CPUInfo[4], int InfoType) -{ -#if defined(__x86_64__) - __asm __volatile__ - ( - "cpuid": - "=a" (CPUInfo[0]), - "=b" (CPUInfo[1]), - "=c" (CPUInfo[2]), - "=d" (CPUInfo[3]) : - "a" (InfoType), "c" (0) - ); -#endif -} -static inline int check_aes_hw(void) -{ -#if defined(__x86_64__) - int cpuid_results[4]; - static int supported = -1; - - if(supported >= 0) - return supported; - - cpuid(cpuid_results,1); - return supported = cpuid_results[2] & (1 << 25); -#else - return 0; -#endif + return flags; } -static volatile int use_rx_jit_flag = -1; +static inline int enabled_flags(void) { + static int flags = -1; -static inline int use_rx_jit(void) -{ -#if defined(__x86_64__) + if (flags != -1) { + return flags; + } - if (use_rx_jit_flag != -1) - return use_rx_jit_flag; + flags = randomx_get_flags(); - const char *env = getenv("MONERO_USE_RX_JIT"); - if (!env) { - use_rx_jit_flag = 1; - } - else if (!strcmp(env, "0") || !strcmp(env, "no")) { - use_rx_jit_flag = 0; - } - else { - use_rx_jit_flag = 1; - } - return use_rx_jit_flag; -#else - return 0; -#endif + return flags; } #define SEEDHASH_EPOCH_BLOCKS 2048 /* Must be same as BLOCKS_SYNCHRONIZING_MAX_COUNT in cryptonote_config.h */ @@ -162,8 +119,11 @@ void rx_reorg(const uint64_t split_height) { int i; CTHR_MUTEX_LOCK(rx_mutex); for (i=0; i<2; i++) { - if (split_height < rx_s[i].rs_height) + if (split_height <= rx_s[i].rs_height) { + if (rx_s[i].rs_height == rx_dataset_height) + rx_dataset_height = 1; rx_s[i].rs_height = 1; /* set to an invalid seed height */ + } } CTHR_MUTEX_UNLOCK(rx_mutex); } @@ -233,27 +193,27 @@ static void rx_initdata(randomx_cache *rs_cache, const int miners, const uint64_ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const char *seedhash, const void *data, size_t length, char *hash, int miners, int is_alt) { uint64_t s_height = rx_seedheight(mainheight); - int changed = 0; - int toggle = is_alt ? s_height : seedheight; - randomx_flags flags = RANDOMX_FLAG_DEFAULT; + int toggle = (s_height & SEEDHASH_EPOCH_BLOCKS) != 0; + randomx_flags flags = enabled_flags() & ~disabled_flags(); rx_state *rx_sp; randomx_cache *cache; - toggle = (toggle & SEEDHASH_EPOCH_BLOCKS) != 0; CTHR_MUTEX_LOCK(rx_mutex); /* if alt block but with same seed as mainchain, no need for alt cache */ - if (is_alt && s_height == seedheight && !memcmp(rx_s[toggle].rs_hash, seedhash, sizeof(rx_s[toggle].rs_hash))) - is_alt = 0; - + if (is_alt) { + if (s_height == seedheight && !memcmp(rx_s[toggle].rs_hash, seedhash, HASH_SIZE)) + is_alt = 0; + } else { /* RPC could request an earlier block on mainchain */ - if (!is_alt && s_height > seedheight) - is_alt = 1; + if (s_height > seedheight) + is_alt = 1; + /* miner can be ahead of mainchain */ + else if (s_height < seedheight) + toggle ^= 1; + } toggle ^= (is_alt != 0); - if (toggle != rx_toggle) - changed = 1; - rx_toggle = toggle; rx_sp = &rx_s[toggle]; CTHR_MUTEX_LOCK(rx_sp->rs_mutex); @@ -261,8 +221,6 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch cache = rx_sp->rs_cache; if (cache == NULL) { - if (use_rx_jit()) - flags |= RANDOMX_FLAG_JIT; if (cache == NULL) { cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES); if (cache == NULL) { @@ -273,22 +231,19 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch local_abort("Couldn't allocate RandomX cache"); } } - if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(seedhash, rx_sp->rs_hash, sizeof(rx_sp->rs_hash))) { - randomx_init_cache(cache, seedhash, 32); + if (rx_sp->rs_height != seedheight || rx_sp->rs_cache == NULL || memcmp(seedhash, rx_sp->rs_hash, HASH_SIZE)) { + randomx_init_cache(cache, seedhash, HASH_SIZE); rx_sp->rs_cache = cache; rx_sp->rs_height = seedheight; - memcpy(rx_sp->rs_hash, seedhash, sizeof(rx_sp->rs_hash)); - changed = 1; + memcpy(rx_sp->rs_hash, seedhash, HASH_SIZE); } if (rx_vm == NULL) { - randomx_flags flags = RANDOMX_FLAG_DEFAULT; - if (use_rx_jit()) { - flags |= RANDOMX_FLAG_JIT; - if (!miners) - flags |= RANDOMX_FLAG_SECURE; + if ((flags & RANDOMX_FLAG_JIT) && !miners) { + flags |= RANDOMX_FLAG_SECURE & ~disabled_flags(); + } + if (miners && (disabled_flags() & RANDOMX_FLAG_FULL_MEM)) { + miners = 0; } - if(!force_software_aes() && check_aes_hw()) - flags |= RANDOMX_FLAG_HARD_AES; if (miners) { CTHR_MUTEX_LOCK(rx_dataset_mutex); if (rx_dataset == NULL) { @@ -324,7 +279,8 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch if (rx_dataset != NULL && rx_dataset_height != seedheight) rx_initdata(cache, miners, seedheight); CTHR_MUTEX_UNLOCK(rx_dataset_mutex); - } else if (changed) { + } else { + /* this is a no-op if the cache hasn't changed */ randomx_vm_set_cache(rx_vm, rx_sp->rs_cache); } /* mainchain users can run in parallel */ diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 51076e8c0..a682bebf2 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -43,7 +43,7 @@ namespace cryptonote { cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), m_last_request_time(boost::date_time::not_a_date_time), m_callback_request_count(0), - m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_anchor(false) {} + m_last_known_hash(crypto::null_hash), m_pruning_seed(0), m_rpc_port(0), m_rpc_credits_per_hash(0), m_anchor(false) {} enum state { @@ -55,7 +55,7 @@ namespace cryptonote }; state m_state; - std::vector<crypto::hash> m_needed_objects; + std::vector<std::pair<crypto::hash, uint64_t>> m_needed_objects; std::unordered_set<crypto::hash> m_requested_objects; uint64_t m_remote_blockchain_height; uint64_t m_last_response_height; @@ -64,6 +64,7 @@ namespace cryptonote crypto::hash m_last_known_hash; uint32_t m_pruning_seed; uint16_t m_rpc_port; + uint32_t m_rpc_credits_per_hash; bool m_anchor; //size_t m_score; TODO: add score calculations }; diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 055c4a22b..e2286ae8c 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -195,6 +195,7 @@ namespace cryptonote private: // hash cash mutable std::atomic<bool> hash_valid; + mutable std::atomic<bool> prunable_hash_valid; mutable std::atomic<bool> blob_size_valid; public: @@ -203,6 +204,7 @@ namespace cryptonote // hash cash mutable crypto::hash hash; + mutable crypto::hash prunable_hash; mutable size_t blob_size; bool pruned; @@ -211,22 +213,26 @@ namespace cryptonote std::atomic<unsigned int> prefix_size; transaction(); - transaction(const transaction &t): transaction_prefix(t), hash_valid(false), blob_size_valid(false), signatures(t.signatures), rct_signatures(t.rct_signatures), pruned(t.pruned), unprunable_size(t.unprunable_size.load()), prefix_size(t.prefix_size.load()) { if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } } - transaction &operator=(const transaction &t) { transaction_prefix::operator=(t); set_hash_valid(false); set_blob_size_valid(false); signatures = t.signatures; rct_signatures = t.rct_signatures; if (t.is_hash_valid()) { hash = t.hash; set_hash_valid(true); } if (t.is_blob_size_valid()) { blob_size = t.blob_size; set_blob_size_valid(true); } pruned = t.pruned; unprunable_size = t.unprunable_size.load(); prefix_size = t.prefix_size.load(); return *this; } + transaction(const transaction &t); + transaction &operator=(const transaction &t); virtual ~transaction(); void set_null(); void invalidate_hashes(); bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + bool is_prunable_hash_valid() const { return prunable_hash_valid.load(std::memory_order_acquire); } + void set_prunable_hash_valid(bool v) const { prunable_hash_valid.store(v,std::memory_order_release); } bool is_blob_size_valid() const { return blob_size_valid.load(std::memory_order_acquire); } void set_blob_size_valid(bool v) const { blob_size_valid.store(v,std::memory_order_release); } - void set_hash(const crypto::hash &h) { hash = h; set_hash_valid(true); } - void set_blob_size(size_t sz) { blob_size = sz; set_blob_size_valid(true); } + void set_hash(const crypto::hash &h) const { hash = h; set_hash_valid(true); } + void set_prunable_hash(const crypto::hash &h) const { prunable_hash = h; set_prunable_hash_valid(true); } + void set_blob_size(size_t sz) const { blob_size = sz; set_blob_size_valid(true); } BEGIN_SERIALIZE_OBJECT() if (!typename Archive<W>::is_saving()) { set_hash_valid(false); + set_prunable_hash_valid(false); set_blob_size_valid(false); } @@ -327,6 +333,63 @@ namespace cryptonote static size_t get_signature_size(const txin_v& tx_in); }; + inline transaction::transaction(const transaction &t): + transaction_prefix(t), + hash_valid(false), + prunable_hash_valid(false), + blob_size_valid(false), + signatures(t.signatures), + rct_signatures(t.rct_signatures), + pruned(t.pruned), + unprunable_size(t.unprunable_size.load()), + prefix_size(t.prefix_size.load()) + { + if (t.is_hash_valid()) + { + hash = t.hash; + set_hash_valid(true); + } + if (t.is_blob_size_valid()) + { + blob_size = t.blob_size; + set_blob_size_valid(true); + } + if (t.is_prunable_hash_valid()) + { + prunable_hash = t.prunable_hash; + set_prunable_hash_valid(true); + } + } + + inline transaction &transaction::operator=(const transaction &t) + { + transaction_prefix::operator=(t); + + set_hash_valid(false); + set_prunable_hash_valid(false); + set_blob_size_valid(false); + signatures = t.signatures; + rct_signatures = t.rct_signatures; + if (t.is_hash_valid()) + { + hash = t.hash; + set_hash_valid(true); + } + if (t.is_prunable_hash_valid()) + { + prunable_hash = t.prunable_hash; + set_prunable_hash_valid(true); + } + if (t.is_blob_size_valid()) + { + blob_size = t.blob_size; + set_blob_size_valid(true); + } + pruned = t.pruned; + unprunable_size = t.unprunable_size.load(); + prefix_size = t.prefix_size.load(); + return *this; + } inline transaction::transaction() @@ -346,6 +409,7 @@ namespace cryptonote signatures.clear(); rct_signatures.type = rct::RCTTypeNull; set_hash_valid(false); + set_prunable_hash_valid(false); set_blob_size_valid(false); pruned = false; unprunable_size = 0; @@ -356,6 +420,7 @@ namespace cryptonote void transaction::invalidate_hashes() { set_hash_valid(false); + set_prunable_hash_valid(false); set_blob_size_valid(false); } @@ -408,6 +473,7 @@ namespace cryptonote void invalidate_hashes() { set_hash_valid(false); } bool is_hash_valid() const { return hash_valid.load(std::memory_order_acquire); } void set_hash_valid(bool v) const { hash_valid.store(v,std::memory_order_release); } + void set_hash(const crypto::hash &h) const { hash = h; set_hash_valid(true); } transaction miner_tx; std::vector<crypto::hash> tx_hashes; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index d8de65b81..9bafcfc86 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -110,9 +110,6 @@ namespace cryptonote { return false; } - assert(median_weight < std::numeric_limits<uint32_t>::max()); - assert(current_block_weight < std::numeric_limits<uint32_t>::max()); - uint64_t product_hi; // BUGFIX: 32-bit saturation bug (e.g. ARM7), the result was being // treated as 32-bit by default. @@ -122,8 +119,8 @@ namespace cryptonote { uint64_t reward_hi; uint64_t reward_lo; - div128_32(product_hi, product_lo, static_cast<uint32_t>(median_weight), &reward_hi, &reward_lo); - div128_32(reward_hi, reward_lo, static_cast<uint32_t>(median_weight), &reward_hi, &reward_lo); + div128_64(product_hi, product_lo, median_weight, &reward_hi, &reward_lo, NULL, NULL); + div128_64(reward_hi, reward_lo, median_weight, &reward_hi, &reward_lo, NULL, NULL); assert(0 == reward_hi); assert(reward_lo < base_reward); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 8bf3574db..138cf49f4 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -103,6 +103,26 @@ namespace cryptonote ge_p1p1_to_p3(&A2, &tmp3); ge_p3_tobytes(&AB, &A2); } + + uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) + { + const rct::rctSig &rv = tx.rct_signatures; + const uint64_t bp_base = 368; + const size_t n_outputs = tx.vout.size(); + if (n_padded_outputs <= 2) + return 0; + size_t nlr = 0; + while ((1u << nlr) < n_padded_outputs) + ++nlr; + nlr += 6; + const size_t bp_size = 32 * (9 + 2 * nlr); + CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); + CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback: bp_base " + std::to_string(bp_base) + ", n_padded_outputs " + + std::to_string(n_padded_outputs) + ", bp_size " + std::to_string(bp_size)); + const uint64_t bp_clawback = (bp_base * n_padded_outputs - bp_size) * 4 / 5; + return bp_clawback; + } + //--------------------------------------------------------------- } namespace cryptonote @@ -386,27 +406,61 @@ namespace cryptonote //--------------------------------------------------------------- uint64_t get_transaction_weight(const transaction &tx, size_t blob_size) { + CHECK_AND_ASSERT_MES(!tx.pruned, std::numeric_limits<uint64_t>::max(), "get_transaction_weight does not support pruned txes"); if (tx.version < 2) return blob_size; const rct::rctSig &rv = tx.rct_signatures; if (!rct::is_rct_bulletproof(rv.type)) return blob_size; - const size_t n_outputs = tx.vout.size(); - if (n_outputs <= 2) - return blob_size; - const uint64_t bp_base = 368; const size_t n_padded_outputs = rct::n_bulletproof_max_amounts(rv.p.bulletproofs); - size_t nlr = 0; - for (const auto &bp: rv.p.bulletproofs) - nlr += bp.L.size() * 2; - const size_t bp_size = 32 * (9 + nlr); - CHECK_AND_ASSERT_THROW_MES_L1(n_outputs <= BULLETPROOF_MAX_OUTPUTS, "maximum number of outputs is " + std::to_string(BULLETPROOF_MAX_OUTPUTS) + " per transaction"); - CHECK_AND_ASSERT_THROW_MES_L1(bp_base * n_padded_outputs >= bp_size, "Invalid bulletproof clawback"); - const uint64_t bp_clawback = (bp_base * n_padded_outputs - bp_size) * 4 / 5; + uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - blob_size, "Weight overflow"); return blob_size + bp_clawback; } //--------------------------------------------------------------- + uint64_t get_pruned_transaction_weight(const transaction &tx) + { + CHECK_AND_ASSERT_MES(tx.pruned, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support non pruned txes"); + CHECK_AND_ASSERT_MES(tx.version >= 2, std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support v1 txes"); + CHECK_AND_ASSERT_MES(tx.rct_signatures.type >= rct::RCTTypeBulletproof2, + std::numeric_limits<uint64_t>::max(), "get_pruned_transaction_weight does not support older range proof types"); + CHECK_AND_ASSERT_MES(!tx.vin.empty(), std::numeric_limits<uint64_t>::max(), "empty vin"); + CHECK_AND_ASSERT_MES(tx.vin[0].type() == typeid(cryptonote::txin_to_key), std::numeric_limits<uint64_t>::max(), "empty vin"); + + // get pruned data size + std::ostringstream s; + binary_archive<true> a(s); + ::serialization::serialize(a, const_cast<transaction&>(tx)); + uint64_t weight = s.str().size(), extra; + + // nbps (technically varint) + weight += 1; + + // calculate deterministic bulletproofs size (assumes canonical BP format) + size_t nrl = 0, n_padded_outputs; + while ((n_padded_outputs = (1u << nrl)) < tx.vout.size()) + ++nrl; + nrl += 6; + extra = 32 * (9 + 2 * nrl) + 2; + weight += extra; + + // calculate deterministic MLSAG data size + const size_t ring_size = boost::get<cryptonote::txin_to_key>(tx.vin[0]).key_offsets.size(); + extra = tx.vin.size() * (ring_size * (1 + 1) * 32 + 32 /* cc */); + weight += extra; + + // calculate deterministic pseudoOuts size + extra = 32 * (tx.vin.size()); + weight += extra; + + // clawback + uint64_t bp_clawback = get_transaction_weight_clawback(tx, n_padded_outputs); + CHECK_AND_ASSERT_THROW_MES_L1(bp_clawback <= std::numeric_limits<uint64_t>::max() - weight, "Weight overflow"); + weight += bp_clawback; + + return weight; + } + //--------------------------------------------------------------- uint64_t get_transaction_weight(const transaction &tx) { size_t blob_size; @@ -1011,7 +1065,19 @@ namespace cryptonote crypto::hash get_transaction_prunable_hash(const transaction& t, const cryptonote::blobdata *blobdata) { crypto::hash res; + if (t.is_prunable_hash_valid()) + { +#ifdef ENABLE_HASH_CASH_INTEGRITY_CHECK + CHECK_AND_ASSERT_THROW_MES(!calculate_transaction_prunable_hash(t, blobdata, res) || t.hash == res, "tx hash cash integrity failure"); +#endif + res = t.prunable_hash; + ++tx_hashes_cached_count; + return res; + } + + ++tx_hashes_calculated_count; CHECK_AND_ASSERT_THROW_MES(calculate_transaction_prunable_hash(t, blobdata, res), "Failed to calculate tx prunable hash"); + t.set_prunable_hash(res); return res; } //--------------------------------------------------------------- @@ -1047,11 +1113,14 @@ namespace cryptonote // the tx hash is the hash of the 3 hashes crypto::hash res = cn_fast_hash(hashes, sizeof(hashes)); + t.set_hash(res); return res; } //--------------------------------------------------------------- bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) { + CHECK_AND_ASSERT_MES(!t.pruned, false, "Cannot calculate the hash of a pruned transaction"); + // v1 transactions hash the entire blob if (t.version == 1) { @@ -1091,8 +1160,7 @@ namespace cryptonote { if (!t.is_blob_size_valid()) { - t.blob_size = blob.size(); - t.set_blob_size_valid(true); + t.set_blob_size(blob.size()); } *blob_size = t.blob_size; } @@ -1112,8 +1180,7 @@ namespace cryptonote { if (!t.is_blob_size_valid()) { - t.blob_size = get_object_blobsize(t); - t.set_blob_size_valid(true); + t.set_blob_size(get_object_blobsize(t)); } *blob_size = t.blob_size; } @@ -1124,12 +1191,10 @@ namespace cryptonote bool ret = calculate_transaction_hash(t, res, blob_size); if (!ret) return false; - t.hash = res; - t.set_hash_valid(true); + t.set_hash(res); if (blob_size) { - t.blob_size = *blob_size; - t.set_blob_size_valid(true); + t.set_blob_size(*blob_size); } return true; } @@ -1206,8 +1271,7 @@ namespace cryptonote bool ret = calculate_block_hash(b, res); if (!ret) return false; - b.hash = res; - b.set_hash_valid(true); + b.set_hash(res); return true; } //--------------------------------------------------------------- @@ -1251,8 +1315,7 @@ namespace cryptonote { calculate_block_hash(b, *block_hash, &b_blob); ++block_hashes_calculated_count; - b.hash = *block_hash; - b.set_hash_valid(true); + b.set_hash(*block_hash); } return true; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 284494299..29e4def64 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -127,6 +127,7 @@ namespace cryptonote bool parse_amount(uint64_t& amount, const std::string& str_amount); uint64_t get_transaction_weight(const transaction &tx); uint64_t get_transaction_weight(const transaction &tx, size_t blob_size); + uint64_t get_pruned_transaction_weight(const transaction &tx); bool check_money_overflow(const transaction& tx); bool check_outs_overflow(const transaction& tx); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 0188bf114..c4b5c8455 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -62,7 +62,9 @@ #include <devstat.h> #include <errno.h> #include <fcntl.h> +#if defined(__amd64__) || defined(__i386__) || defined(__x86_64__) #include <machine/apm_bios.h> +#endif #include <stdio.h> #include <sys/resource.h> #include <sys/sysctl.h> @@ -1086,6 +1088,7 @@ namespace cryptonote return boost::logic::tribool(boost::logic::indeterminate); } +#if defined(__amd64__) || defined(__i386__) || defined(__x86_64__) apm_info info; if( ioctl(fd, APMIO_GETINFO, &info) == -1 ) { close(fd); @@ -1126,6 +1129,7 @@ namespace cryptonote LOG_ERROR("sysctlbyname(\"hw.acpi.acline\") output is unexpectedly " << n << " bytes instead of the expected " << sizeof(ac) << " bytes."); return boost::logic::tribool(boost::logic::indeterminate); +#endif } return boost::logic::tribool(ac == 0); #endif diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 3d7200fae..f5f663464 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -58,5 +58,6 @@ namespace cryptonote bool m_marked_as_orphaned; bool m_already_exists; bool m_partial_block_reward; + bool m_bad_pow; // if bad pow, bad peer outright for DoS protection }; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 6b1c2a546..ca127c3ee 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -148,6 +148,7 @@ #define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb" #define CRYPTONOTE_BLOCKCHAINDATA_LOCK_FILENAME "lock.mdb" #define P2P_NET_DATA_FILENAME "p2pstate.bin" +#define RPC_PAYMENTS_DATA_FILENAME "rpcpayments.bin" #define MINER_CONFIG_FILE_NAME "miner_conf.json" #define THREAD_STACK_SIZE 5 * 1024 * 1024 @@ -165,10 +166,11 @@ #define HF_VERSION_SAME_MIXIN 12 #define HF_VERSION_REJECT_SIGS_IN_COINBASE 12 #define HF_VERSION_ENFORCE_MIN_AGE 12 +#define HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY 12 #define PER_KB_FEE_QUANTIZATION_DECIMALS 8 -#define HASH_OF_HASHES_STEP 256 +#define HASH_OF_HASHES_STEP 512 #define DEFAULT_TXPOOL_MAX_WEIGHT 648000000ull // 3 days at 300000, in bytes @@ -179,6 +181,8 @@ #define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved //#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED +#define RPC_CREDITS_PER_HASH_SCALE ((float)(1<<24)) + // New constants are intended to go here namespace config { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index bcf99bbed..b7e9f4ca2 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1176,16 +1176,25 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl } } - std::vector<uint64_t> last_blocks_weights; - get_last_n_blocks_weights(last_blocks_weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - if (!get_block_reward(epee::misc_utils::median(last_blocks_weights), cumulative_block_weight, already_generated_coins, base_reward, version)) + uint64_t median_weight; + if (version >= HF_VERSION_EFFECTIVE_SHORT_TERM_MEDIAN_IN_PENALTY) + { + median_weight = m_current_block_cumul_weight_median; + } + else + { + std::vector<uint64_t> last_blocks_weights; + get_last_n_blocks_weights(last_blocks_weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW); + median_weight = epee::misc_utils::median(last_blocks_weights); + } + if (!get_block_reward(median_weight, cumulative_block_weight, already_generated_coins, base_reward, version)) { MERROR_VER("block weight " << cumulative_block_weight << " is bigger than allowed for this blockchain"); return false; } if(base_reward + fee < money_in_use) { - MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << ")"); + MERROR_VER("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) << "), cumulative_block_weight " << cumulative_block_weight); return false; } // From hard fork 2, we allow a miner to claim less block reward than is allowed, in case a miner wants less dust @@ -1315,7 +1324,9 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, if (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address)) && m_btc_nonce == ex_nonce && m_btc_pool_cookie == m_tx_pool.cookie() && m_btc.prev_id == get_tail_id()) { MDEBUG("Using cached template"); - m_btc.timestamp = time(NULL); // update timestamp unconditionally + const uint64_t now = time(NULL); + if (m_btc.timestamp < now) // ensures it can't get below the median of the last few blocks + m_btc.timestamp = now; b = m_btc; diffic = m_btc_difficulty; height = m_btc_height; @@ -1692,7 +1703,8 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id // Check the block's hash against the difficulty target for its alt chain difficulty_type current_diff = get_next_difficulty_for_alternative_chain(alt_chain, bei); CHECK_AND_ASSERT_MES(current_diff, false, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!"); - crypto::hash proof_of_work = null_hash; + crypto::hash proof_of_work; + memset(proof_of_work.data, 0xff, sizeof(proof_of_work.data)); if (b.major_version >= RX_BLOCK_VERSION) { crypto::hash seedhash = null_hash; @@ -1721,6 +1733,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id { MERROR_VER("Block with id: " << id << std::endl << " for alternative chain, does not have enough proof of work: " << proof_of_work << std::endl << " expected difficulty: " << current_diff); bvc.m_verifivation_failed = true; + bvc.m_bad_pow = true; return false; } @@ -1747,6 +1760,34 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id } bei.cumulative_difficulty += current_diff; + bei.block_cumulative_weight = cryptonote::get_transaction_weight(b.miner_tx); + for (const crypto::hash &txid: b.tx_hashes) + { + cryptonote::tx_memory_pool::tx_details td; + cryptonote::blobdata blob; + if (m_tx_pool.get_transaction_info(txid, td)) + { + bei.block_cumulative_weight += td.weight; + } + else if (m_db->get_pruned_tx_blob(txid, blob)) + { + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_base_from_blob(blob, tx)) + { + MERROR_VER("Block with id: " << epee::string_tools::pod_to_hex(id) << " (as alternative) refers to unparsable transaction hash " << txid << "."); + bvc.m_verifivation_failed = true; + return false; + } + bei.block_cumulative_weight += cryptonote::get_pruned_transaction_weight(tx); + } + else + { + // we can't determine the block weight, set it to 0 and break out of the loop + bei.block_cumulative_weight = 0; + break; + } + } + // add block to alternate blocks storage, // as well as the current "alt chain" container CHECK_AND_ASSERT_MES(!m_db->get_alt_block(id, NULL, NULL), false, "insertion of new alternative block returned as it already exists"); @@ -1861,8 +1902,9 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO std::vector<std::pair<cryptonote::blobdata,block>> blocks; get_blocks(arg.blocks, blocks, rsp.missed_ids); - for (auto& bl: blocks) + for (size_t i = 0; i < blocks.size(); ++i) { + auto& bl = blocks[i]; std::vector<crypto::hash> missed_tx_ids; rsp.blocks.push_back(block_complete_entry()); @@ -1870,8 +1912,8 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO // FIXME: s/rsp.missed_ids/missed_tx_id/ ? Seems like rsp.missed_ids // is for missed blocks, not missed transactions as well. - get_transactions_blobs(bl.second.tx_hashes, e.txs, missed_tx_ids); - + e.pruned = arg.prune; + get_transactions_blobs(bl.second.tx_hashes, e.txs, missed_tx_ids, arg.prune); if (missed_tx_ids.size() != 0) { // do not display an error if the peer asked for an unpruned block which we are not meant to have @@ -1892,6 +1934,9 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO //pack block e.block = std::move(bl.first); + e.block_weight = 0; + if (arg.prune && m_db->block_exists(arg.blocks[i])) + e.block_weight = m_db->get_block_weight(m_db->get_block_height(arg.blocks[i])); } return true; @@ -2170,23 +2215,95 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container return true; } //------------------------------------------------------------------ +static bool fill(BlockchainDB *db, const crypto::hash &tx_hash, cryptonote::blobdata &tx, bool pruned) +{ + if (pruned) + { + if (!db->get_pruned_tx_blob(tx_hash, tx)) + { + MDEBUG("Pruned transaction blob not found for " << tx_hash); + return false; + } + } + else + { + if (!db->get_tx_blob(tx_hash, tx)) + { + MDEBUG("Transaction blob not found for " << tx_hash); + return false; + } + } + return true; +} +//------------------------------------------------------------------ +static bool fill(BlockchainDB *db, const crypto::hash &tx_hash, tx_blob_entry &tx, bool pruned) +{ + if (!fill(db, tx_hash, tx.blob, pruned)) + return false; + if (pruned) + { + if (is_v1_tx(tx.blob)) + { + // v1 txes aren't pruned, so fetch the whole thing + cryptonote::blobdata prunable_blob; + if (!db->get_prunable_tx_blob(tx_hash, prunable_blob)) + { + MDEBUG("Prunable transaction blob not found for " << tx_hash); + return false; + } + tx.blob.append(prunable_blob); + tx.prunable_hash = crypto::null_hash; + } + else + { + if (!db->get_prunable_tx_hash(tx_hash, tx.prunable_hash)) + { + MDEBUG("Prunable transaction data hash not found for " << tx_hash); + return false; + } + } + } + return true; +} +//------------------------------------------------------------------ //TODO: return type should be void, throw on exception // alternatively, return true only if no transactions missed -template<class t_ids_container, class t_tx_container, class t_missed_container> -bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned) const +bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs, bool pruned) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - reserve_container(txs, txs_ids.size()); + txs.reserve(txs_ids.size()); for (const auto& tx_hash : txs_ids) { try { cryptonote::blobdata tx; - if (pruned && m_db->get_pruned_tx_blob(tx_hash, tx)) + if (fill(m_db, tx_hash, tx, pruned)) txs.push_back(std::move(tx)); - else if (!pruned && m_db->get_tx_blob(tx_hash, tx)) + else + missed_txs.push_back(tx_hash); + } + catch (const std::exception& e) + { + return false; + } + } + return true; +} +//------------------------------------------------------------------ +bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<tx_blob_entry>& txs, std::vector<crypto::hash>& missed_txs, bool pruned) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + + txs.reserve(txs_ids.size()); + for (const auto& tx_hash : txs_ids) + { + try + { + tx_blob_entry tx; + if (fill(m_db, tx_hash, tx, pruned)) txs.push_back(std::move(tx)); else missed_txs.push_back(tx_hash); @@ -2279,7 +2396,7 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container // Find the split point between us and foreign blockchain and return // (by reference) the most recent common block hash along with up to // BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. -bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, std::vector<uint64_t>* weights, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2296,25 +2413,34 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc if (clip_pruned) { const uint32_t pruning_seed = get_blockchain_pruning_seed(); - start_height = tools::get_next_unpruned_block_height(start_height, current_height, pruning_seed); + if (start_height < tools::get_next_unpruned_block_height(start_height, current_height, pruning_seed)) + { + MDEBUG("We only have a pruned version of the common ancestor"); + return false; + } stop_height = tools::get_next_pruned_block_height(start_height, current_height, pruning_seed); } size_t count = 0; - hashes.reserve(std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT)); + const size_t reserve = std::min((size_t)(stop_height - start_height), (size_t)BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT); + hashes.reserve(reserve); + if (weights) + weights->reserve(reserve); for(size_t i = start_height; i < stop_height && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++) { hashes.push_back(m_db->get_block_hash_from_height(i)); + if (weights) + weights->push_back(m_db->get_block_weight(i)); } return true; } -bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const +bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); - bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height, true); + bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, &resp.m_block_weights, resp.start_height, resp.total_height, clip_pruned); if (result) { cryptonote::difficulty_type wide_cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1); @@ -2753,18 +2879,24 @@ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_pr // II if (rv.type == rct::RCTTypeFull) { - rv.p.MGs.resize(1); - rv.p.MGs[0].II.resize(tx.vin.size()); - for (size_t n = 0; n < tx.vin.size(); ++n) - rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + if (!tx.pruned) + { + rv.p.MGs.resize(1); + rv.p.MGs[0].II.resize(tx.vin.size()); + for (size_t n = 0; n < tx.vin.size(); ++n) + rv.p.MGs[0].II[n] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } } else if (rv.type == rct::RCTTypeSimple || rv.type == rct::RCTTypeBulletproof || rv.type == rct::RCTTypeBulletproof2) { - CHECK_AND_ASSERT_MES(rv.p.MGs.size() == tx.vin.size(), false, "Bad MGs size"); - for (size_t n = 0; n < tx.vin.size(); ++n) + if (!tx.pruned) { - rv.p.MGs[n].II.resize(1); - rv.p.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + CHECK_AND_ASSERT_MES(rv.p.MGs.size() == tx.vin.size(), false, "Bad MGs size"); + for (size_t n = 0; n < tx.vin.size(); ++n) + { + rv.p.MGs[n].II.resize(1); + rv.p.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); + } } } else @@ -2790,6 +2922,10 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, if(pmax_used_block_height) *pmax_used_block_height = 0; + // pruned txes are skipped, as they're only allowed in sync-pruned-blocks mode, which is within the builtin hashes + if (tx.pruned) + return true; + crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); const uint8_t hf_version = m_hardfork->get_current_version(); @@ -3221,8 +3357,8 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b if (version >= HF_VERSION_PER_BYTE_FEE) { lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); - div128_32(hi, lo, min_block_weight, &hi, &lo); - div128_32(hi, lo, median_block_weight, &hi, &lo); + div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); + div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); assert(hi == 0); lo /= 5; return lo; @@ -3232,12 +3368,7 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b uint64_t unscaled_fee_base = (fee_base * min_block_weight / median_block_weight); lo = mul128(unscaled_fee_base, block_reward, &hi); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD % 1000000 == 0, "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD must be divisible by 1000000"); - static_assert(DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000 <= std::numeric_limits<uint32_t>::max(), "DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD is too large"); - - // divide in two steps, since the divisor must be 32 bits, but DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD isn't - div128_32(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD / 1000000, &hi, &lo); - div128_32(hi, lo, 1000000, &hi, &lo); + div128_64(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD, &hi, &lo, NULL, NULL); assert(hi == 0); // quantize fee up to 8 decimals @@ -3334,7 +3465,8 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const } const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - uint64_t fee = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? m_long_term_effective_median_block_weight : median, version); + const uint64_t use_median_value = use_long_term_median_in_fee ? std::min<uint64_t>(median, m_long_term_effective_median_block_weight) : median; + const uint64_t fee = get_dynamic_base_fee(base_reward, use_median_value, version); const bool per_byte = version < HF_VERSION_PER_BYTE_FEE; MDEBUG("Estimating " << grace_blocks << "-block fee at " << print_money(fee) << "/" << (per_byte ? "byte" : "kB")); return fee; @@ -3518,9 +3650,9 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids) cryptonote::blobdata txblob; size_t tx_weight; uint64_t fee; - bool relayed, do_not_relay, double_spend_seen; + bool relayed, do_not_relay, double_spend_seen, pruned; MINFO("Removing txid " << txid << " from the pool"); - if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen)) + if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) { MERROR("Failed to remove txid " << txid << " from the pool"); res = false; @@ -3603,7 +3735,8 @@ leave: TIME_MEASURE_START(longhash_calculating_time); - crypto::hash proof_of_work = null_hash; + crypto::hash proof_of_work; + memset(proof_of_work.data, 0xff, sizeof(proof_of_work.data)); // Formerly the code below contained an if loop with the following condition // !m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height()) @@ -3619,7 +3752,7 @@ leave: #if defined(PER_BLOCK_CHECKPOINT) if (blockchain_height < m_blocks_hash_check.size()) { - const auto &expected_hash = m_blocks_hash_check[blockchain_height]; + const auto &expected_hash = m_blocks_hash_check[blockchain_height].first; if (expected_hash != crypto::null_hash) { if (memcmp(&id, &expected_hash, sizeof(hash)) != 0) @@ -3652,6 +3785,7 @@ leave: { MERROR_VER("Block with id: " << id << std::endl << "does not have enough proof of work: " << proof_of_work << " at height " << blockchain_height << ", unexpected difficulty: " << current_diffic); bvc.m_verifivation_failed = true; + bvc.m_bad_pow = true; goto leave; } } @@ -3693,6 +3827,7 @@ leave: uint64_t t_exists = 0; uint64_t t_pool = 0; uint64_t t_dblspnd = 0; + uint64_t n_pruned = 0; TIME_MEASURE_FINISH(t3); // XXX old code adds miner tx here @@ -3708,7 +3843,7 @@ leave: blobdata txblob; size_t tx_weight = 0; uint64_t fee = 0; - bool relayed = false, do_not_relay = false, double_spend_seen = false; + bool relayed = false, do_not_relay = false, double_spend_seen = false, pruned = false; TIME_MEASURE_START(aa); // XXX old code does not check whether tx exists @@ -3725,13 +3860,15 @@ leave: TIME_MEASURE_START(bb); // get transaction with hash <tx_id> from tx_pool - if(!m_tx_pool.take_tx(tx_id, tx_tmp, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen)) + if(!m_tx_pool.take_tx(tx_id, tx_tmp, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) { MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; } + if (pruned) + ++n_pruned; TIME_MEASURE_FINISH(bb); t_pool += bb; @@ -3802,6 +3939,17 @@ leave: cumulative_block_weight += tx_weight; } + // if we were syncing pruned blocks + if (n_pruned > 0) + { + if (blockchain_height >= m_blocks_hash_check.size() || m_blocks_hash_check[blockchain_height].second == 0) + { + MERROR("Block at " << blockchain_height << " is pruned, but we do not have a weight for it"); + goto leave; + } + cumulative_block_weight = m_blocks_hash_check[blockchain_height].second; + } + m_blocks_txs_check.clear(); TIME_MEASURE_START(vmt); @@ -4242,11 +4390,13 @@ void Blockchain::output_scan_worker(const uint64_t amount, const std::vector<uin } } -uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) +uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights) { // new: . . . . . X X X X X . . . . . . // pre: A A A A B B B B C C C C D D D D + CHECK_AND_ASSERT_MES(weights.empty() || weights.size() == hashes.size(), 0, "Unexpected weights size"); + // easy case: height >= hashes if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP) return hashes.size(); @@ -4265,8 +4415,11 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector return hashes.size(); // build hashes vector to hash hashes together - std::vector<crypto::hash> data; - data.reserve(hashes.size() + HASH_OF_HASHES_STEP - 1); // may be a bit too much + std::vector<crypto::hash> data_hashes; + std::vector<uint64_t> data_weights; + data_hashes.reserve(hashes.size() + HASH_OF_HASHES_STEP - 1); // may be a bit too much + if (!weights.empty()) + data_weights.reserve(data_hashes.size()); // we expect height to be either equal or a bit below db height bool disconnected = (height > m_db->height()); @@ -4281,18 +4434,24 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector // we might need some already in the chain for the first part of the first hash for (uint64_t h = first_index * HASH_OF_HASHES_STEP; h < height; ++h) { - data.push_back(m_db->get_block_hash_from_height(h)); + data_hashes.push_back(m_db->get_block_hash_from_height(h)); + if (!weights.empty()) + data_weights.push_back(m_db->get_block_weight(h)); } pop = 0; } // push the data to check - for (const auto &h: hashes) + for (size_t i = 0; i < hashes.size(); ++i) { if (pop) --pop; else - data.push_back(h); + { + data_hashes.push_back(hashes[i]); + if (!weights.empty()) + data_weights.push_back(weights[i]); + } } // hash and check @@ -4302,12 +4461,17 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector if (n < m_blocks_hash_of_hashes.size()) { // if the last index isn't fully filled, we can't tell if valid - if (data.size() < (n - first_index) * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP) + if (data_hashes.size() < (n - first_index) * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP) break; crypto::hash hash; - cn_fast_hash(data.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); - bool valid = hash == m_blocks_hash_of_hashes[n]; + cn_fast_hash(data_hashes.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(crypto::hash), hash); + bool valid = hash == m_blocks_hash_of_hashes[n].first; + if (valid && !weights.empty()) + { + cn_fast_hash(data_weights.data() + (n - first_index) * HASH_OF_HASHES_STEP, HASH_OF_HASHES_STEP * sizeof(uint64_t), hash); + valid &= hash == m_blocks_hash_of_hashes[n].second; + } // add to the known hashes array if (!valid) @@ -4319,9 +4483,15 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector size_t end = n * HASH_OF_HASHES_STEP + HASH_OF_HASHES_STEP; for (size_t i = n * HASH_OF_HASHES_STEP; i < end; ++i) { - CHECK_AND_ASSERT_MES(m_blocks_hash_check[i] == crypto::null_hash || m_blocks_hash_check[i] == data[i - first_index * HASH_OF_HASHES_STEP], + CHECK_AND_ASSERT_MES(m_blocks_hash_check[i].first == crypto::null_hash || m_blocks_hash_check[i].first == data_hashes[i - first_index * HASH_OF_HASHES_STEP], 0, "Consistency failure in m_blocks_hash_check construction"); - m_blocks_hash_check[i] = data[i - first_index * HASH_OF_HASHES_STEP]; + m_blocks_hash_check[i].first = data_hashes[i - first_index * HASH_OF_HASHES_STEP]; + if (!weights.empty()) + { + CHECK_AND_ASSERT_MES(m_blocks_hash_check[i].second == 0 || m_blocks_hash_check[i].second == data_weights[i - first_index * HASH_OF_HASHES_STEP], + 0, "Consistency failure in m_blocks_hash_check construction"); + m_blocks_hash_check[i].second = data_weights[i - first_index * HASH_OF_HASHES_STEP]; + } } usable += HASH_OF_HASHES_STEP; } @@ -4338,6 +4508,18 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector return usable; } +bool Blockchain::has_block_weights(uint64_t height, uint64_t nblocks) const +{ + CHECK_AND_ASSERT_MES(nblocks > 0, false, "nblocks is 0"); + uint64_t last_block_height = height + nblocks - 1; + if (last_block_height >= m_blocks_hash_check.size()) + return false; + for (uint64_t h = height; h <= last_block_height; ++h) + if (m_blocks_hash_check[h].second == 0) + return false; + return true; +} + //------------------------------------------------------------------ // ND: Speedups: // 1. Thread long_hash computations if possible (m_max_prepare_blocks_threads = nthreads, default = 4) @@ -4379,7 +4561,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete bytes += entry.block.size(); for (const auto &tx_blob : entry.txs) { - bytes += tx_blob.size(); + bytes += tx_blob.blob.size(); } total_txs += entry.txs.size(); } @@ -4539,7 +4721,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete crypto::hash &tx_prefix_hash = txes[tx_index].second; ++tx_index; - if (!parse_and_validate_tx_base_from_blob(tx_blob, tx)) + if (!parse_and_validate_tx_base_from_blob(tx_blob.blob, tx)) SCAN_TABLE_QUIT("Could not parse tx from incoming blocks."); cryptonote::get_transaction_prefix_hash(tx, tx_prefix_hash); @@ -4838,7 +5020,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "7dafb40b414a0e59bfced6682ef519f0b416bc914dd3d622b72e0dd1a47117c2"; +static const char expected_block_hashes_hash[] = "95e60612c1a16f4cd992c335b66daabd98e2d351c2b02b66e43ced0296848d33"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) @@ -4882,19 +5064,21 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get MERROR("Block hash data is too large"); return; } - const size_t size_needed = 4 + nblocks * sizeof(crypto::hash); + const size_t size_needed = 4 + nblocks * (sizeof(crypto::hash) * 2); if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && checkpoints.size() >= size_needed) { p += sizeof(uint32_t); m_blocks_hash_of_hashes.reserve(nblocks); for (uint32_t i = 0; i < nblocks; i++) { - crypto::hash hash; - memcpy(hash.data, p, sizeof(hash.data)); - p += sizeof(hash.data); - m_blocks_hash_of_hashes.push_back(hash); + crypto::hash hash_hashes, hash_weights; + memcpy(hash_hashes.data, p, sizeof(hash_hashes.data)); + p += sizeof(hash_hashes.data); + memcpy(hash_weights.data, p, sizeof(hash_weights.data)); + p += sizeof(hash_weights.data); + m_blocks_hash_of_hashes.push_back(std::make_pair(hash_hashes, hash_weights)); } - m_blocks_hash_check.resize(m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP, crypto::null_hash); + m_blocks_hash_check.resize(m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP, std::make_pair(crypto::null_hash, 0)); MINFO(nblocks << " block hashes loaded"); // FIXME: clear tx_pool because the process might have been @@ -4909,13 +5093,13 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get size_t tx_weight; uint64_t fee; - bool relayed, do_not_relay, double_spend_seen; + bool relayed, do_not_relay, double_spend_seen, pruned; transaction pool_tx; blobdata txblob; for(const transaction &tx : txs) { crypto::hash tx_hash = get_transaction_hash(tx); - m_tx_pool.take_tx(tx_hash, pool_tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen); + m_tx_pool.take_tx(tx_hash, pool_tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned); } } } @@ -4988,6 +5172,5 @@ void Blockchain::cache_block_template(const block &b, const cryptonote::account_ namespace cryptonote { template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::vector<transaction>&, std::vector<crypto::hash>&) const; -template bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>&, std::vector<cryptonote::blobdata>&, std::vector<crypto::hash>&, bool) const; template bool Blockchain::get_split_transactions_blobs(const std::vector<crypto::hash>&, std::vector<std::tuple<crypto::hash, cryptonote::blobdata, crypto::hash, cryptonote::blobdata>>&, std::vector<crypto::hash>&) const; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 552a53e89..6467031c2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -393,13 +393,14 @@ namespace cryptonote * * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) * @param hashes the hashes to be returned, return-by-reference + * @param weights the block weights to be returned, return-by-reference * @param start_height the start height, return-by-reference * @param current_height the current blockchain height, return-by-reference * @param clip_pruned whether to constrain results to unpruned data * * @return true if a block found in common, else false */ - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const; + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, std::vector<uint64_t>* weights, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const; /** * @brief get recent block hashes for a foreign chain @@ -409,11 +410,12 @@ namespace cryptonote * BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes. * * @param qblock_ids the foreign chain's "short history" (see get_short_chain_history) + * @param clip_pruned clip pruned blocks if true, include them otherwise * @param resp return-by-reference the split height and subsequent blocks' hashes * * @return true if a block found in common, else false */ - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; /** * @brief find the most recent common point between ours and a foreign chain @@ -687,8 +689,8 @@ namespace cryptonote * * @return false if an unexpected exception occurs, else true */ - template<class t_ids_container, class t_tx_container, class t_missed_container> - bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned = false) const; + bool get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs, bool pruned = false) const; + bool get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<tx_blob_entry>& txs, std::vector<crypto::hash>& missed_txs, bool pruned = false) const; template<class t_ids_container, class t_tx_container, class t_missed_container> bool get_split_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; template<class t_ids_container, class t_tx_container, class t_missed_container> @@ -968,9 +970,8 @@ namespace cryptonote cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const; - bool is_within_compiled_block_hash_area(uint64_t height) const; bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } - uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes); + uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights); uint32_t get_blockchain_pruning_seed() const { return m_db->get_blockchain_pruning_seed(); } bool prune_blockchain(uint32_t pruning_seed = 0); bool update_blockchain_pruning(); @@ -1000,6 +1001,21 @@ namespace cryptonote */ void pop_blocks(uint64_t nblocks); + /** + * @brief checks whether a given block height is included in the precompiled block hash area + * + * @param height the height to check for + */ + bool is_within_compiled_block_hash_area(uint64_t height) const; + + /** + * @brief checks whether we have known weights for the given block heights + * + * @param height the start height to check for + * @param nblocks how many blocks to check from that height + */ + bool has_block_weights(uint64_t height, uint64_t nblocks) const; + #ifndef IN_UNIT_TESTS private: #endif @@ -1027,8 +1043,8 @@ namespace cryptonote std::unordered_map<crypto::hash, crypto::hash> m_blocks_longhash_table; // SHA-3 hashes for each block and for fast pow checking - std::vector<crypto::hash> m_blocks_hash_of_hashes; - std::vector<crypto::hash> m_blocks_hash_check; + std::vector<std::pair<crypto::hash, crypto::hash>> m_blocks_hash_of_hashes; + std::vector<std::pair<crypto::hash, uint64_t>> m_blocks_hash_check; std::vector<crypto::hash> m_blocks_txs_check; blockchain_db_sync_mode m_db_sync_mode; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 245be5778..acb494a49 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -114,6 +114,10 @@ namespace cryptonote , "Set maximum size of block download queue in bytes (0 for default)" , 0 }; + const command_line::arg_descriptor<bool> arg_sync_pruned_blocks = { + "sync-pruned-blocks" + , "Allow syncing from nodes with only pruned blocks" + }; static const command_line::arg_descriptor<bool> arg_test_drop_download = { "test-drop-download" @@ -324,6 +328,7 @@ namespace cryptonote command_line::add_arg(desc, arg_offline); command_line::add_arg(desc, arg_disable_dns_checkpoints); command_line::add_arg(desc, arg_block_download_max_size); + command_line::add_arg(desc, arg_sync_pruned_blocks); command_line::add_arg(desc, arg_max_txpool_weight); command_line::add_arg(desc, arg_pad_transactions); command_line::add_arg(desc, arg_block_notify); @@ -746,13 +751,13 @@ namespace cryptonote return false; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) { tvc = {}; - if(tx_blob.size() > get_max_tx_size()) + if(tx_blob.blob.size() > get_max_tx_size()) { - LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected"); + LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.blob.size() << ", rejected"); tvc.m_verifivation_failed = true; tvc.m_too_big = true; return false; @@ -760,7 +765,23 @@ namespace cryptonote tx_hash = crypto::null_hash; - if(!parse_tx_from_blob(tx, tx_hash, tx_blob)) + bool r; + if (tx_blob.prunable_hash == crypto::null_hash) + { + r = parse_tx_from_blob(tx, tx_hash, tx_blob.blob); + } + else + { + r = parse_and_validate_tx_base_from_blob(tx_blob.blob, tx); + if (r) + { + tx.set_prunable_hash(tx_blob.prunable_hash); + tx_hash = cryptonote::get_pruned_transaction_hash(tx, tx_blob.prunable_hash); + tx.set_hash(tx_hash); + } + } + + if (!r) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to parse, rejected"); tvc.m_verifivation_failed = true; @@ -786,6 +807,7 @@ namespace cryptonote if (tx.version == 0 || tx.version > max_tx_version) { // v2 is the latest one we know + MERROR_VER("Bad tx version (" << tx.version << ", max is " << max_tx_version << ")"); tvc.m_verifivation_failed = true; return false; } @@ -793,7 +815,7 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) { if(!check_tx_syntax(tx)) { @@ -922,7 +944,7 @@ namespace cryptonote return ret; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_txs(const std::vector<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { TRY_ENTRY(); CRITICAL_REGION_LOCAL(m_incoming_tx_lock); @@ -933,7 +955,7 @@ namespace cryptonote tvc.resize(tx_blobs.size()); tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; - std::vector<blobdata>::const_iterator it = tx_blobs.begin(); + std::vector<tx_blob_entry>::const_iterator it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { tpool.submit(&waiter, [&, i, it] { try @@ -1005,8 +1027,8 @@ namespace cryptonote if (already_have[i]) continue; - const size_t weight = get_transaction_weight(results[i].tx, it->size()); - ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i], weight, tvc[i], keeped_by_block, relayed, do_not_relay); + const uint64_t weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size()); + ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, weight, tvc[i], keeped_by_block, relayed, do_not_relay); if(tvc[i].m_verifivation_failed) {MERROR_VER("Transaction verification failed: " << results[i].hash);} else if(tvc[i].m_verifivation_impossible) @@ -1020,9 +1042,9 @@ namespace cryptonote CATCH_ENTRY_L0("core::handle_incoming_txs()", false); } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { - std::vector<cryptonote::blobdata> tx_blobs; + std::vector<tx_blob_entry> tx_blobs; tx_blobs.push_back(tx_blob); std::vector<tx_verification_context> tvcv(1); bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay); @@ -1030,6 +1052,11 @@ namespace cryptonote return r; } //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + { + return handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, keeped_by_block, relayed, do_not_relay); + } + //----------------------------------------------------------------------------------------------- bool core::get_stat_info(core_stat_info& st_inf) const { st_inf.mining_speed = m_miner.get_speed(); @@ -1292,9 +1319,9 @@ namespace cryptonote return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce); } //----------------------------------------------------------------------------------------------- - bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const + bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { - return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); + return m_blockchain_storage.find_blockchain_supplement(qblock_ids, clip_pruned, resp); } //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_count) const @@ -1336,11 +1363,12 @@ namespace cryptonote { block_complete_entry bce; bce.block = cryptonote::block_to_blob(b); + bce.block_weight = 0; // we can leave it to 0, those txes aren't pruned for (const auto &tx_hash: b.tx_hashes) { cryptonote::blobdata txblob; CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob), "Transaction not found in pool"); - bce.txs.push_back(txblob); + bce.txs.push_back({txblob, crypto::null_hash}); } return bce; } @@ -1393,7 +1421,7 @@ namespace cryptonote block_to_blob(b, arg.b.block); //pack transactions for(auto& tx: txs) - arg.b.txs.push_back(tx); + arg.b.txs.push_back({tx, crypto::null_hash}); m_pprotocol->relay_block(arg, exclude_context); } @@ -1883,6 +1911,14 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + void core::flush_bad_txs_cache() + { + bad_semantics_txes_lock.lock(); + for (int idx = 0; idx < 2; ++idx) + bad_semantics_txes[idx].clear(); + bad_semantics_txes_lock.unlock(); + } + //----------------------------------------------------------------------------------------------- bool core::update_blockchain_pruning() { return m_blockchain_storage.update_blockchain_pruning(); @@ -1903,9 +1939,9 @@ namespace cryptonote return m_target_blockchain_height; } //----------------------------------------------------------------------------------------------- - uint64_t core::prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes) + uint64_t core::prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights) { - return get_blockchain_storage().prevalidate_block_hashes(height, hashes); + return get_blockchain_storage().prevalidate_block_hashes(height, hashes, weights); } //----------------------------------------------------------------------------------------------- uint64_t core::get_free_space() const @@ -1925,6 +1961,16 @@ namespace cryptonote return get_blockchain_storage().prune_blockchain(pruning_seed); } //----------------------------------------------------------------------------------------------- + bool core::is_within_compiled_block_hash_area(uint64_t height) const + { + return get_blockchain_storage().is_within_compiled_block_hash_area(height); + } + //----------------------------------------------------------------------------------------------- + bool core::has_block_weights(uint64_t height, uint64_t nblocks) const + { + return get_blockchain_storage().has_block_weights(height, nblocks); + } + //----------------------------------------------------------------------------------------------- std::time_t core::get_start_time() const { return start_time; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index badbaf936..f69ac3509 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -64,6 +64,7 @@ namespace cryptonote extern const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty; extern const command_line::arg_descriptor<bool> arg_offline; extern const command_line::arg_descriptor<size_t> arg_block_download_max_size; + extern const command_line::arg_descriptor<bool> arg_sync_pruned_blocks; /************************************************************************/ /* */ @@ -120,6 +121,7 @@ namespace cryptonote * * @return true if the transaction was accepted, false otherwise */ + bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** @@ -136,7 +138,7 @@ namespace cryptonote * * @return true if the transactions were accepted, false otherwise */ - bool handle_incoming_txs(const std::vector<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** * @brief handles an incoming block @@ -522,7 +524,7 @@ namespace cryptonote * * @note see Blockchain::find_blockchain_supplement(const std::list<crypto::hash>&, NOTIFY_RESPONSE_CHAIN_ENTRY::request&) const */ - bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; + bool find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const; /** * @copydoc Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::vector<std::pair<cryptonote::blobdata, std::vector<cryptonote::blobdata> > >&, uint64_t&, uint64_t&, size_t) const @@ -779,7 +781,7 @@ namespace cryptonote * * @return number of usable blocks */ - uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes); + uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights); /** * @brief get free disk space on the blockchain partition @@ -825,6 +827,23 @@ namespace cryptonote */ bool check_blockchain_pruning(); + /** + * @brief checks whether a given block height is included in the precompiled block hash area + * + * @param height the height to check for + */ + bool is_within_compiled_block_hash_area(uint64_t height) const; + + /** + * @brief checks whether block weights are known for the given range + */ + bool has_block_weights(uint64_t height, uint64_t nblocks) const; + + /** + * @brief flushes the bad txs cache + */ + void flush_bad_txs_cache(); + private: /** @@ -910,8 +929,8 @@ namespace cryptonote bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; void set_semantics_failed(const crypto::hash &tx_hash); - bool handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); - bool handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx_post(const tx_blob_entry &tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); struct tx_verification_batch_info { const cryptonote::transaction *tx; crypto::hash tx_hash; tx_verification_context &tvc; bool &result; }; bool handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 49d5a8ccc..392e611e9 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -247,6 +247,7 @@ namespace cryptonote meta.relayed = relayed; meta.do_not_relay = do_not_relay; meta.double_spend_seen = have_tx_keyimges_as_spent(tx); + meta.pruned = tx.pruned; meta.bf_padding = 0; memset(meta.padding, 0, sizeof(meta.padding)); try @@ -258,7 +259,7 @@ namespace cryptonote m_blockchain.add_txpool_tx(id, blob, meta); if (!insert_key_images(tx, id, kept_by_block)) return false; - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id); + m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); lock.commit(); } catch (const std::exception &e) @@ -290,6 +291,7 @@ namespace cryptonote meta.relayed = relayed; meta.do_not_relay = do_not_relay; meta.double_spend_seen = false; + meta.pruned = tx.pruned; meta.bf_padding = 0; memset(meta.padding, 0, sizeof(meta.padding)); @@ -303,7 +305,7 @@ namespace cryptonote m_blockchain.add_txpool_tx(id, blob, meta); if (!insert_key_images(tx, id, kept_by_block)) return false; - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)tx_weight, receive_time), id); + m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); lock.commit(); } catch (const std::exception &e) @@ -322,7 +324,7 @@ namespace cryptonote ++m_cookie; - MINFO("Transaction added to pool: txid " << id << " weight: " << tx_weight << " fee/byte: " << (fee / (double)tx_weight)); + MINFO("Transaction added to pool: txid " << id << " weight: " << tx_weight << " fee/byte: " << (fee / (double)(tx_weight ? tx_weight : 1))); prune(m_txpool_max_weight); @@ -460,7 +462,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen) + bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned) { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -482,7 +484,7 @@ namespace cryptonote { tx = ci->second; } - else if (!parse_and_validate_tx_from_blob(txblob, tx)) + else if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(txblob, tx) : parse_and_validate_tx_from_blob(txblob, tx))) { MERROR("Failed to parse tx from txpool"); return false; @@ -496,6 +498,7 @@ namespace cryptonote relayed = meta.relayed; do_not_relay = meta.do_not_relay; double_spend_seen = meta.double_spend_seen; + pruned = meta.pruned; // remove first, in case this throws, so key images aren't removed m_blockchain.remove_txpool_tx(id); @@ -515,6 +518,59 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_transaction_info(const crypto::hash &txid, tx_details &td) const + { + PERF_TIMER(get_transaction_info); + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + try + { + LockedTXN lock(m_blockchain); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(txid, meta)) + { + MERROR("Failed to find tx in txpool"); + return false; + } + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid); + auto ci = m_parsed_tx_cache.find(txid); + if (ci != m_parsed_tx_cache.end()) + { + td.tx = ci->second; + } + else if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(txblob, td.tx) : parse_and_validate_tx_from_blob(txblob, td.tx))) + { + MERROR("Failed to parse tx from txpool"); + return false; + } + else + { + td.tx.set_hash(txid); + } + td.blob_size = txblob.size(); + td.weight = meta.weight; + td.fee = meta.fee; + td.max_used_block_id = meta.max_used_block_id; + td.max_used_block_height = meta.max_used_block_height; + td.kept_by_block = meta.kept_by_block; + td.last_failed_height = meta.last_failed_height; + td.last_failed_id = meta.last_failed_id; + td.receive_time = meta.receive_time; + td.last_relayed_time = meta.last_relayed_time; + td.relayed = meta.relayed; + td.do_not_relay = meta.do_not_relay; + td.double_spend_seen = meta.double_spend_seen; + } + catch (const std::exception &e) + { + MERROR("Failed to get tx from txpool: " << e.what()); + return false; + } + + return true; + } + //--------------------------------------------------------------------------------- void tx_memory_pool::on_idle() { m_remove_stuck_tx_interval.do_call([this](){return remove_stuck_transactions();}); @@ -601,7 +657,7 @@ namespace cryptonote txs.reserve(m_blockchain.get_txpool_tx_count()); m_blockchain.for_all_txpool_txes([this, now, &txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *){ // 0 fee transactions are never relayed - if(meta.fee > 0 && !meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time)) + if(!meta.pruned && meta.fee > 0 && !meta.do_not_relay && now - meta.last_relayed_time > get_relay_delay(now, meta.receive_time)) { // if the tx is older than half the max lifetime, we don't re-relay it, to avoid a problem // mentioned by smooth where nodes would flush txes at slightly different times, causing @@ -667,7 +723,7 @@ namespace cryptonote txs.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes)); m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ transaction tx; - if (!parse_and_validate_tx_from_blob(*bd, tx)) + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) { MERROR("Failed to parse tx from txpool"); // continue @@ -733,7 +789,7 @@ namespace cryptonote if (meta.double_spend_seen) ++stats.num_double_spends; return true; - }, false, include_unrelayed_txes); + }, false, include_unrelayed_txes); stats.bytes_med = epee::misc_utils::median(weights); if (stats.txs_total > 1) { @@ -746,8 +802,15 @@ namespace cryptonote /* If enough txs, spread the first 98% of results across * the first 9 bins, drop final 2% in last bin. */ - it=agebytes.end(); - for (size_t n=0; n <= end; n++, it--); + it = agebytes.end(); + size_t cumulative_num = 0; + /* Since agebytes is not empty and end is nonzero, the + * below loop can always run at least once. + */ + do { + --it; + cumulative_num += it->second.txs; + } while (it != agebytes.begin() && cumulative_num < end); stats.histo_98pc = it->first; factor = 9; delta = it->first; @@ -791,7 +854,7 @@ namespace cryptonote txi.id_hash = epee::string_tools::pod_to_hex(txid); txi.tx_blob = *bd; transaction tx; - if (!parse_and_validate_tx_from_blob(*bd, tx)) + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) { MERROR("Failed to parse tx from txpool"); // continue @@ -863,7 +926,7 @@ namespace cryptonote m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ cryptonote::rpc::tx_in_pool txi; txi.tx_hash = txid; - if (!parse_and_validate_tx_from_blob(*bd, txi.tx)) + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, txi.tx) : parse_and_validate_tx_from_blob(*bd, txi.tx))) { MERROR("Failed to parse tx from txpool"); // continue @@ -901,7 +964,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const + bool tx_memory_pool::check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool>& spent) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -1143,7 +1206,7 @@ namespace cryptonote ss << "id: " << txid << std::endl; if (!short_format) { cryptonote::transaction tx; - if (!parse_and_validate_tx_from_blob(*txblob, tx)) + if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*txblob, tx) : parse_and_validate_tx_from_blob(*txblob, tx))) { MERROR("Failed to parse tx from txpool"); return true; // continue @@ -1199,6 +1262,12 @@ namespace cryptonote } LOG_PRINT_L2("Considering " << sorted_it->second << ", weight " << meta.weight << ", current block weight " << total_weight << "/" << max_total_weight << ", current coinbase " << print_money(best_coinbase)); + if (meta.pruned) + { + LOG_PRINT_L2(" tx is pruned"); + continue; + } + // Can not exceed maximum block weight if (max_total_weight < total_weight + meta.weight) { @@ -1322,7 +1391,7 @@ namespace cryptonote { cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid); cryptonote::transaction tx; - if (!parse_and_validate_tx_from_blob(txblob, tx)) + if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary { MERROR("Failed to parse tx from txpool"); continue; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 877f2b82f..dec7e3cd9 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -139,10 +139,11 @@ namespace cryptonote * @param relayed return-by-reference was transaction relayed to us by the network? * @param do_not_relay return-by-reference is transaction not to be relayed to the network? * @param double_spend_seen return-by-reference was a double spend seen for that transaction? + * @param pruned return-by-reference is the tx pruned * * @return true unless the transaction cannot be found in the pool */ - bool take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen); + bool take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned); /** * @brief checks if the pool has a transaction with the given hash @@ -300,7 +301,7 @@ namespace cryptonote * * @return true */ - bool check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool> spent) const; + bool check_for_key_images(const std::vector<crypto::key_image>& key_images, std::vector<bool>& spent) const; /** * @brief get a specific transaction from the pool @@ -428,6 +429,11 @@ namespace cryptonote bool double_spend_seen; //!< true iff another tx was seen double spending this one }; + /** + * @brief get infornation about a single transaction + */ + bool get_transaction_info(const crypto::hash &txid, tx_details &td) const; + private: /** diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp index b4f9daa74..67f0b3e5d 100644 --- a/src/cryptonote_protocol/block_queue.cpp +++ b/src/cryptonote_protocol/block_queue.cpp @@ -228,13 +228,14 @@ bool block_queue::have(const crypto::hash &hash) const return have_blocks.find(hash) != have_blocks.end(); } -std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time) +std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, bool sync_pruned_blocks, uint32_t local_pruning_seed, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<std::pair<crypto::hash, uint64_t>> &block_hashes, boost::posix_time::ptime time) { boost::unique_lock<boost::recursive_mutex> lock(mutex); MDEBUG("reserve_span: first_block_height " << first_block_height << ", last_block_height " << last_block_height - << ", max " << max_blocks << ", seed " << epee::string_tools::to_string_hex(pruning_seed) << ", blockchain_height " << - blockchain_height << ", block hashes size " << block_hashes.size()); + << ", max " << max_blocks << ", peer seed " << epee::string_tools::to_string_hex(pruning_seed) << ", blockchain_height " << + blockchain_height << ", block hashes size " << block_hashes.size() << ", local seed " << epee::string_tools::to_string_hex(local_pruning_seed) + << ", sync_pruned_blocks " << sync_pruned_blocks); if (last_block_height < first_block_height || max_blocks == 0) { MDEBUG("reserve_span: early out: first_block_height " << first_block_height << ", last_block_height " << last_block_height << ", max_blocks " << max_blocks); @@ -248,22 +249,25 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei // skip everything we've already requested uint64_t span_start_height = last_block_height - block_hashes.size() + 1; - std::vector<crypto::hash>::const_iterator i = block_hashes.begin(); - while (i != block_hashes.end() && requested_internal(*i)) + std::vector<std::pair<crypto::hash, uint64_t>>::const_iterator i = block_hashes.begin(); + while (i != block_hashes.end() && requested_internal((*i).first)) { ++i; ++span_start_height; } - // if the peer's pruned for the starting block and its unpruned stripe comes next, start downloading from there - const uint32_t next_unpruned_height = tools::get_next_unpruned_block_height(span_start_height, blockchain_height, pruning_seed); - MDEBUG("reserve_span: next_unpruned_height " << next_unpruned_height << " from " << span_start_height << " and seed " - << epee::string_tools::to_string_hex(pruning_seed) << ", limit " << span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE); - if (next_unpruned_height > span_start_height && next_unpruned_height < span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE) + if (!sync_pruned_blocks) { - MDEBUG("We can download from next span: ideal height " << span_start_height << ", next unpruned height " << next_unpruned_height << - "(+" << next_unpruned_height - span_start_height << "), current seed " << pruning_seed); - span_start_height = next_unpruned_height; + // if the peer's pruned for the starting block and its unpruned stripe comes next, start downloading from there + const uint32_t next_unpruned_height = tools::get_next_unpruned_block_height(span_start_height, blockchain_height, pruning_seed); + MDEBUG("reserve_span: next_unpruned_height " << next_unpruned_height << " from " << span_start_height << " and seed " + << epee::string_tools::to_string_hex(pruning_seed) << ", limit " << span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE); + if (next_unpruned_height > span_start_height && next_unpruned_height < span_start_height + CRYPTONOTE_PRUNING_STRIPE_SIZE) + { + MDEBUG("We can download from next span: ideal height " << span_start_height << ", next unpruned height " << next_unpruned_height << + "(+" << next_unpruned_height - span_start_height << "), current seed " << pruning_seed); + span_start_height = next_unpruned_height; + } } MDEBUG("span_start_height: " <<span_start_height); const uint64_t block_hashes_start_height = last_block_height - block_hashes.size() + 1; @@ -274,7 +278,7 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei } i = block_hashes.begin() + span_start_height - block_hashes_start_height; - while (i != block_hashes.end() && requested_internal(*i)) + while (i != block_hashes.end() && requested_internal((*i).first)) { ++i; ++span_start_height; @@ -282,9 +286,16 @@ std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_hei uint64_t span_length = 0; std::vector<crypto::hash> hashes; - while (i != block_hashes.end() && span_length < max_blocks && tools::has_unpruned_block(span_start_height + span_length, blockchain_height, pruning_seed)) + bool first_is_pruned = sync_pruned_blocks && !tools::has_unpruned_block(span_start_height + span_length, blockchain_height, local_pruning_seed); + while (i != block_hashes.end() && span_length < max_blocks && (sync_pruned_blocks || tools::has_unpruned_block(span_start_height + span_length, blockchain_height, pruning_seed))) { - hashes.push_back(*i); + // if we want to sync pruned blocks, stop at the first block for which we need full data + if (sync_pruned_blocks && first_is_pruned == tools::has_unpruned_block(span_start_height + span_length, blockchain_height, local_pruning_seed)) + { + MDEBUG("Stopping at " << span_start_height + span_length << " for peer on stripe " << tools::get_pruning_stripe(pruning_seed) << " as we need full data for " << tools::get_pruning_stripe(local_pruning_seed)); + break; + } + hashes.push_back((*i).first); ++i; ++span_length; } diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h index 1bef01d67..93c6532e7 100644 --- a/src/cryptonote_protocol/block_queue.h +++ b/src/cryptonote_protocol/block_queue.h @@ -78,7 +78,7 @@ namespace cryptonote void print() const; std::string get_overview(uint64_t blockchain_height) const; bool has_unpruned_height(uint64_t block_height, uint64_t blockchain_height, uint32_t pruning_seed) const; - std::pair<uint64_t, uint64_t> reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<crypto::hash> &block_hashes, boost::posix_time::ptime time = boost::posix_time::microsec_clock::universal_time()); + std::pair<uint64_t, uint64_t> reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, bool sync_pruned_blocks, uint32_t local_pruning_seed, uint32_t pruning_seed, uint64_t blockchain_height, const std::vector<std::pair<crypto::hash, uint64_t>> &block_hashes, boost::posix_time::ptime time = boost::posix_time::microsec_clock::universal_time()); uint64_t get_next_needed_height(uint64_t blockchain_height) const; std::pair<uint64_t, uint64_t> get_next_span_if_scheduled(std::vector<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const; void reset_next_span_time(boost::posix_time::ptime t = boost::posix_time::microsec_clock::universal_time()); diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index b2f8da399..201001c8e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -56,6 +56,7 @@ namespace cryptonote std::string ip; std::string port; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; std::string peer_id; @@ -94,6 +95,7 @@ namespace cryptonote KV_SERIALIZE(ip) KV_SERIALIZE(port) KV_SERIALIZE(rpc_port) + KV_SERIALIZE(rpc_credits_per_hash) KV_SERIALIZE(peer_id) KV_SERIALIZE(recv_count) KV_SERIALIZE(recv_idle_time) @@ -116,14 +118,51 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ + struct tx_blob_entry + { + blobdata blob; + crypto::hash prunable_hash; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(blob) + KV_SERIALIZE_VAL_POD_AS_BLOB(prunable_hash) + END_KV_SERIALIZE_MAP() + + tx_blob_entry(const blobdata &bd = {}, const crypto::hash &h = crypto::null_hash): blob(bd), prunable_hash(h) {} + }; struct block_complete_entry { + bool pruned; blobdata block; - std::vector<blobdata> txs; + uint64_t block_weight; + std::vector<tx_blob_entry> txs; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(pruned, false) KV_SERIALIZE(block) - KV_SERIALIZE(txs) + KV_SERIALIZE_OPT(block_weight, (uint64_t)0) + if (this_ref.pruned) + { + KV_SERIALIZE(txs) + } + else + { + std::vector<blobdata> txs; + if (is_store) + { + txs.reserve(this_ref.txs.size()); + for (const auto &e: this_ref.txs) txs.push_back(e.blob); + } + epee::serialization::selector<is_store>::serialize(txs, stg, hparent_section, "txs"); + if (!is_store) + { + block_complete_entry &self = const_cast<block_complete_entry&>(this_ref); + self.txs.clear(); + self.txs.reserve(txs.size()); + for (auto &e: txs) self.txs.push_back({std::move(e), crypto::null_hash}); + } + } END_KV_SERIALIZE_MAP() + + block_complete_entry(): pruned(false), block_weight(0) {} }; @@ -176,8 +215,11 @@ namespace cryptonote struct request_t { std::vector<crypto::hash> blocks; + bool prune; + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_CONTAINER_POD_AS_BLOB(blocks) + KV_SERIALIZE_OPT(prune, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -229,9 +271,11 @@ namespace cryptonote struct request_t { std::list<crypto::hash> block_ids; /*IDs of the first 10 blocks are sequential, next goes with pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ + bool prune; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) + KV_SERIALIZE_OPT(prune, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -248,6 +292,7 @@ namespace cryptonote uint64_t cumulative_difficulty; uint64_t cumulative_difficulty_top64; std::vector<crypto::hash> m_block_ids; + std::vector<uint64_t> m_block_weights; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(start_height) @@ -255,6 +300,7 @@ namespace cryptonote KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE(cumulative_difficulty_top64) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_weights) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index dcc5ec6ed..b16b42564 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -137,7 +137,9 @@ namespace cryptonote size_t get_synchronizing_connections_count(); bool on_connection_synchronized(); bool should_download_next_span(cryptonote_connection_context& context, bool standby); + bool should_ask_for_pruned_data(cryptonote_connection_context& context, uint64_t first_block_height, uint64_t nblocks, bool check_block_weights) const; void drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans); + void drop_connection_with_score(cryptonote_connection_context &context, unsigned int score, bool flush_all_spans); bool kick_idle_peers(); bool check_standby_peers(); bool update_sync_search(); @@ -164,6 +166,7 @@ namespace cryptonote uint64_t m_sync_spans_downloaded, m_sync_old_spans_downloaded, m_sync_bad_spans_downloaded; uint64_t m_sync_download_chain_size, m_sync_download_objects_size; size_t m_block_download_max_size; + bool m_sync_pruned_blocks; boost::mutex m_buffer_mutex; double get_avg_block_size(); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 82f9f96a0..74ceeb41d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -106,6 +106,7 @@ namespace cryptonote m_sync_download_objects_size = 0; m_block_download_max_size = command_line::get_arg(vm, cryptonote::arg_block_download_max_size); + m_sync_pruned_blocks = command_line::get_arg(vm, cryptonote::arg_sync_pruned_blocks); return true; } @@ -138,6 +139,7 @@ namespace cryptonote context.m_needed_objects.clear(); m_core.get_short_chain_history(r.block_ids); handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) + r.prune = m_sync_pruned_blocks; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify<NOTIFY_REQUEST_CHAIN>(r, context); MLOG_PEER_STATE("requesting chain"); @@ -244,6 +246,7 @@ namespace cryptonote cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port()); } cnx.rpc_port = cntxt.m_rpc_port; + cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash; std::stringstream peer_id_str; peer_id_str << std::hex << std::setw(16) << peer_id; @@ -475,7 +478,7 @@ namespace cryptonote if(bvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); - drop_connection(context, true, false); + drop_connection_with_score(context, bvc.m_bad_pow ? P2P_IP_FAILS_BEFORE_BLOCK : 1, false); return 1; } if(bvc.m_added_to_main_chain) @@ -488,6 +491,7 @@ namespace cryptonote context.m_state = cryptonote_connection_context::state_synchronizing; NOTIFY_REQUEST_CHAIN::request r = {}; m_core.get_short_chain_history(r.block_ids); + r.prune = m_sync_pruned_blocks; handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify<NOTIFY_REQUEST_CHAIN>(r, context); @@ -535,9 +539,9 @@ namespace cryptonote return 1; } } - - std::vector<blobdata> have_tx; - + + std::vector<tx_blob_entry> have_tx; + // Instead of requesting missing transactions by hash like BTC, // we do it by index (thanks to a suggestion from moneromooo) because // we're way cooler .. and also because they're smaller than hashes. @@ -551,7 +555,7 @@ namespace cryptonote for(auto& tx_blob: arg.b.txs) { - if(parse_and_validate_tx_from_blob(tx_blob, tx)) + if(parse_and_validate_tx_from_blob(tx_blob.blob, tx)) { try { @@ -636,7 +640,7 @@ namespace cryptonote LOG_ERROR_CCONTEXT ( "sent wrong tx: failed to parse and validate transaction: " - << epee::string_tools::buff_to_hex_nodelimer(tx_blob) + << epee::string_tools::buff_to_hex_nodelimer(tx_blob.blob) << ", dropping connection" ); @@ -671,7 +675,7 @@ namespace cryptonote cryptonote::blobdata txblob; if(m_core.get_pool_transaction(tx_hash, txblob)) { - have_tx.push_back(txblob); + have_tx.push_back({txblob, crypto::null_hash}); } else { @@ -683,7 +687,7 @@ namespace cryptonote { if (txes.size() == 1) { - have_tx.push_back(tx_to_blob(txes.front())); + have_tx.push_back({tx_to_blob(txes.front()), crypto::null_hash}); } else { @@ -748,7 +752,7 @@ namespace cryptonote if( bvc.m_verifivation_failed ) { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); - drop_connection(context, true, false); + drop_connection_with_score(context, bvc.m_bad_pow ? P2P_IP_FAILS_BEFORE_BLOCK : 1, false); return 1; } if( bvc.m_added_to_main_chain ) @@ -766,6 +770,7 @@ namespace cryptonote NOTIFY_REQUEST_CHAIN::request r = {}; m_core.get_short_chain_history(r.block_ids); handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) + r.prune = m_sync_pruned_blocks; MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); post_notify<NOTIFY_REQUEST_CHAIN>(r, context); MLOG_PEER_STATE("requesting chain"); @@ -867,7 +872,7 @@ namespace cryptonote for(auto& tx: txs) { - fluffy_response.b.txs.push_back(t_serializable_object_to_blob(tx)); + fluffy_response.b.txs.push_back({t_serializable_object_to_blob(tx), crypto::null_hash}); } MLOG_P2P_MESSAGE @@ -905,7 +910,7 @@ namespace cryptonote for (size_t i = 0; i < arg.txs.size(); ++i) { cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(arg.txs[i], tvc, false, true, false); + m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, false, true, false); if(tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); @@ -986,7 +991,7 @@ namespace cryptonote for (const auto &element : arg.blocks) { blocks_size += element.block.size(); for (const auto &tx : element.txs) - blocks_size += tx.size(); + blocks_size += tx.blob.size(); } size += blocks_size; @@ -1085,6 +1090,53 @@ namespace cryptonote return 1; } + const bool pruned_ok = should_ask_for_pruned_data(context, start_height, arg.blocks.size(), true); + if (!pruned_ok) + { + // if we don't want pruned data, check we did not get any + for (block_complete_entry& block_entry: arg.blocks) + { + if (block_entry.pruned) + { + MERROR(context << "returned a pruned block, dropping connection"); + drop_connection(context, false, false); + ++m_sync_bad_spans_downloaded; + return 1; + } + if (block_entry.block_weight) + { + MERROR(context << "returned a block weight for a non pruned block, dropping connection"); + drop_connection(context, false, false); + ++m_sync_bad_spans_downloaded; + return 1; + } + for (const tx_blob_entry &tx_entry: block_entry.txs) + { + if (tx_entry.prunable_hash != crypto::null_hash) + { + MERROR(context << "returned at least one pruned object which we did not expect, dropping connection"); + drop_connection(context, false, false); + ++m_sync_bad_spans_downloaded; + return 1; + } + } + } + } + else + { + // we accept pruned data, check that if we got some, then no weights are zero + for (block_complete_entry& block_entry: arg.blocks) + { + if (block_entry.block_weight == 0 && block_entry.pruned) + { + MERROR(context << "returned at least one pruned block with 0 weight, dropping connection"); + drop_connection(context, false, false); + ++m_sync_bad_spans_downloaded; + return 1; + } + } + } + { MLOG_YELLOW(el::Level::Debug, context << " Got NEW BLOCKS inside of " << __FUNCTION__ << ": size: " << arg.blocks.size() << ", blocks: " << start_height << " - " << (start_height + arg.blocks.size() - 1) << @@ -1268,18 +1320,32 @@ namespace cryptonote if (tvc.size() != block_entry.txs.size()) { LOG_ERROR_CCONTEXT("Internal error: tvc.size() != block_entry.txs.size()"); + if (!m_core.cleanup_handle_incoming_blocks()) + { + LOG_PRINT_CCONTEXT_L0("Failure in cleanup_handle_incoming_blocks"); + return 1; + } return 1; } - std::vector<blobdata>::const_iterator it = block_entry.txs.begin(); + std::vector<tx_blob_entry>::const_iterator it = block_entry.txs.begin(); for (size_t i = 0; i < tvc.size(); ++i, ++it) { if(tvc[i].m_verifivation_failed) { if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ cryptonote::transaction tx; - parse_and_validate_tx_from_blob(*it, tx); // must succeed if we got here + crypto::hash txid; + if (it->prunable_hash == crypto::null_hash) + { + parse_and_validate_tx_from_blob(it->blob, tx, txid); // must succeed if we got here + } + else + { + parse_and_validate_tx_base_from_blob(it->blob, tx); // must succeed if we got here + txid = get_pruned_transaction_hash(tx, it->prunable_hash); + } LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, tx_id = " - << epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(tx)) << ", dropping connection"); + << epee::string_tools::pod_to_hex(txid) << ", dropping connection"); drop_connection(context, false, true); return 1; })) @@ -1309,7 +1375,7 @@ namespace cryptonote { if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ LOG_PRINT_CCONTEXT_L1("Block verification failed, dropping connection"); - drop_connection(context, true, true); + drop_connection_with_score(context, bvc.m_bad_pow ? P2P_IP_FAILS_BEFORE_BLOCK : 1, true); return 1; })) LOG_ERROR_CCONTEXT("span connection id not found"); @@ -1538,7 +1604,7 @@ skip: { MLOG_P2P_MESSAGE("Received NOTIFY_REQUEST_CHAIN (" << arg.block_ids.size() << " blocks"); NOTIFY_RESPONSE_CHAIN_ENTRY::request r; - if(!m_core.find_blockchain_supplement(arg.block_ids, r)) + if(!m_core.find_blockchain_supplement(arg.block_ids, !arg.prune, r)) { LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN."); drop_connection(context, false, false); @@ -1657,6 +1723,12 @@ skip: MDEBUG(context << "This peer has needed stripe " << peer_stripe << ", not dropping"); return false; } + const uint32_t local_stripe = tools::get_pruning_stripe(m_core.get_blockchain_pruning_seed()); + if (m_sync_pruned_blocks && local_stripe && next_stripe != local_stripe) + { + MDEBUG(context << "We can sync pruned blocks off this peer, not dropping"); + return false; + } if (!context.m_needed_objects.empty()) { @@ -1696,22 +1768,42 @@ skip: { // take out blocks we already have size_t skip = 0; - while (skip < context.m_needed_objects.size() && (m_core.have_block(context.m_needed_objects[skip]) || (check_block_queue && m_block_queue.have(context.m_needed_objects[skip])))) + while (skip < context.m_needed_objects.size() && (m_core.have_block(context.m_needed_objects[skip].first) || (check_block_queue && m_block_queue.have(context.m_needed_objects[skip].first)))) { // if we're popping the last hash, record it so we can ask again from that hash, // this prevents never being able to progress on peers we get old hash lists from if (skip + 1 == context.m_needed_objects.size()) - context.m_last_known_hash = context.m_needed_objects[skip]; + context.m_last_known_hash = context.m_needed_objects[skip].first; ++skip; } if (skip > 0) { MDEBUG(context << "skipping " << skip << "/" << context.m_needed_objects.size() << " blocks"); - context.m_needed_objects = std::vector<crypto::hash>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end()); + context.m_needed_objects = std::vector<std::pair<crypto::hash, uint64_t>>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end()); } } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> + bool t_cryptonote_protocol_handler<t_core>::should_ask_for_pruned_data(cryptonote_connection_context& context, uint64_t first_block_height, uint64_t nblocks, bool check_block_weights) const + { + if (!m_sync_pruned_blocks) + return false; + if (!m_core.is_within_compiled_block_hash_area(first_block_height + nblocks - 1)) + return false; + const uint32_t local_stripe = tools::get_pruning_stripe(m_core.get_blockchain_pruning_seed()); + if (local_stripe == 0) + return false; + // assumes the span size is less or equal to the stripe size + bool full_data_needed = tools::get_pruning_stripe(first_block_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES) == local_stripe + || tools::get_pruning_stripe(first_block_height + nblocks - 1, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES) == local_stripe; + if (full_data_needed) + return false; + if (check_block_weights && !m_core.has_block_weights(first_block_height, nblocks)) + return false; + return true; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> bool t_cryptonote_protocol_handler<t_core>::request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span) { // flush stale spans @@ -1734,6 +1826,7 @@ skip: const auto next_needed_pruning_stripe = get_next_needed_pruning_stripe(); const uint32_t add_stripe = tools::get_pruning_stripe(bc_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed); + const uint32_t local_stripe = tools::get_pruning_stripe(m_core.get_blockchain_pruning_seed()); const size_t block_queue_size_threshold = m_block_download_max_size ? m_block_download_max_size : BLOCK_QUEUE_SIZE_THRESHOLD; bool queue_proceed = nspans < BLOCK_QUEUE_NSPANS_THRESHOLD || size < block_queue_size_threshold; // get rid of blocks we already requested, or already have @@ -1744,7 +1837,7 @@ skip: next_block_height = next_needed_height; else next_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1; - bool stripe_proceed_main = (add_stripe == 0 || peer_stripe == 0 || add_stripe == peer_stripe) && (next_block_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS || next_needed_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS); + bool stripe_proceed_main = ((m_sync_pruned_blocks && local_stripe && add_stripe != local_stripe) || add_stripe == 0 || peer_stripe == 0 || add_stripe == peer_stripe) && (next_block_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS || next_needed_height < bc_height + BLOCK_QUEUE_FORCE_DOWNLOAD_NEAR_BLOCKS); bool stripe_proceed_secondary = tools::has_unpruned_block(next_block_height, context.m_remote_blockchain_height, context.m_pruning_seed); bool proceed = stripe_proceed_main || (queue_proceed && stripe_proceed_secondary); if (!stripe_proceed_main && !stripe_proceed_secondary && should_drop_connection(context, tools::get_pruning_stripe(next_block_height, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES))) @@ -1807,8 +1900,9 @@ skip: { const uint64_t now = tools::get_tick_count(); const uint64_t dt = now - m_last_add_end_time; - if (tools::ticks_to_ns(dt) >= DROP_ON_SYNC_WEDGE_THRESHOLD) + if (m_last_add_end_time && tools::ticks_to_ns(dt) >= DROP_ON_SYNC_WEDGE_THRESHOLD) { + MDEBUG(context << "ns " << tools::ticks_to_ns(dt) << " from " << m_last_add_end_time << " and " << now); MDEBUG(context << "Block addition seems to have wedged, dropping connection"); return false; } @@ -1875,7 +1969,8 @@ skip: skip_unneeded_hashes(context, false); const uint64_t first_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1; - span = m_block_queue.reserve_span(first_block_height, context.m_last_response_height, count_limit, context.m_connection_id, context.m_pruning_seed, context.m_remote_blockchain_height, context.m_needed_objects); + bool sync_pruned_blocks = m_sync_pruned_blocks && m_core.get_blockchain_pruning_seed(); + span = m_block_queue.reserve_span(first_block_height, context.m_last_response_height, count_limit, context.m_connection_id, sync_pruned_blocks, m_core.get_blockchain_pruning_seed(), context.m_pruning_seed, context.m_remote_blockchain_height, context.m_needed_objects); MDEBUG(context << " span from " << first_block_height << ": " << span.first << "/" << span.second); if (span.second > 0) { @@ -1905,7 +2000,8 @@ skip: ++count; context.m_requested_objects.insert(hash); // that's atrocious O(n) wise, but this is rare - auto i = std::find(context.m_needed_objects.begin(), context.m_needed_objects.end(), hash); + auto i = std::find_if(context.m_needed_objects.begin(), context.m_needed_objects.end(), + [&hash](const std::pair<crypto::hash, uint64_t> &o) { return o.first == hash; }); if (i != context.m_needed_objects.end()) context.m_needed_objects.erase(i); } @@ -1924,7 +2020,7 @@ skip: return false; } if (skip > 0) - context.m_needed_objects = std::vector<crypto::hash>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end()); + context.m_needed_objects = std::vector<std::pair<crypto::hash, uint64_t>>(context.m_needed_objects.begin() + skip, context.m_needed_objects.end()); if (context.m_needed_objects.size() < span.second) { MERROR("ERROR: span " << span.first << "/" << span.second << ", m_needed_objects " << context.m_needed_objects.size()); @@ -1933,18 +2029,37 @@ skip: for (size_t n = 0; n < span.second; ++n) { - req.blocks.push_back(context.m_needed_objects[n]); + req.blocks.push_back(context.m_needed_objects[n].first); ++count; - context.m_requested_objects.insert(context.m_needed_objects[n]); + context.m_requested_objects.insert(context.m_needed_objects[n].first); } - context.m_needed_objects = std::vector<crypto::hash>(context.m_needed_objects.begin() + span.second, context.m_needed_objects.end()); + context.m_needed_objects = std::vector<std::pair<crypto::hash, uint64_t>>(context.m_needed_objects.begin() + span.second, context.m_needed_objects.end()); } + req.prune = should_ask_for_pruned_data(context, span.first, span.second, true); + + // if we need to ask for full data and that peer does not have the right stripe, we can't ask it + if (!req.prune && context.m_pruning_seed) + { + const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed); + const uint32_t first_stripe = tools::get_pruning_stripe(span.first, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + const uint32_t last_stripe = tools::get_pruning_stripe(span.first + span.second - 1, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); + if ((((first_stripe && peer_stripe != first_stripe) || (last_stripe && peer_stripe != last_stripe)) && !m_sync_pruned_blocks) || (m_sync_pruned_blocks && req.prune)) + { + MDEBUG(context << "We need full data, but the peer does not have it, dropping peer"); + return false; + } + } context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); MLOG_P2P_MESSAGE("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << "requested blocks count=" << count << " / " << count_limit << " from " << span.first << ", first hash " << req.blocks.front()); //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); + MDEBUG("Asking for " << (req.prune ? "pruned" : "full") << " data, start/end " + << tools::get_pruning_stripe(span.first, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES) + << "/" << tools::get_pruning_stripe(span.first + span.second - 1, context.m_remote_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES) + << ", ours " << tools::get_pruning_stripe(m_core.get_blockchain_pruning_seed()) << ", peer stripe " << tools::get_pruning_stripe(context.m_pruning_seed)); + post_notify<NOTIFY_REQUEST_GET_OBJECTS>(req, context); MLOG_PEER_STATE("requesting objects"); return true; @@ -1954,7 +2069,8 @@ skip: // drop it to make space for other peers, or ask for a span further down the line const uint32_t next_stripe = get_next_needed_pruning_stripe().first; const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed); - if (next_stripe && peer_stripe && next_stripe != peer_stripe) + const uint32_t local_stripe = tools::get_pruning_stripe(m_core.get_blockchain_pruning_seed()); + if (!(m_sync_pruned_blocks && peer_stripe == local_stripe) && next_stripe && peer_stripe && next_stripe != peer_stripe) { // at this point, we have to either close the connection, or start getting blocks past the // current point, or become dormant @@ -2017,6 +2133,7 @@ skip: } handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) + r.prune = m_sync_pruned_blocks; //std::string blob; // for calculate size of request //epee::serialization::store_t_to_binary(r, blob); @@ -2059,7 +2176,7 @@ skip: bool t_cryptonote_protocol_handler<t_core>::on_connection_synchronized() { bool val_expected = false; - if(m_synchronized.compare_exchange_strong(val_expected, true)) + if(!m_core.is_within_compiled_block_hash_area(m_core.get_current_blockchain_height()) && m_synchronized.compare_exchange_strong(val_expected, true)) { MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << "You are now synchronized with the network. You may now start monero-wallet-cli." << ENDL @@ -2123,6 +2240,12 @@ skip: drop_connection(context, true, false); return 1; } + if (!arg.m_block_weights.empty() && arg.m_block_weights.size() != arg.m_block_ids.size()) + { + LOG_ERROR_CCONTEXT("sent invalid block weight array, dropping connection"); + drop_connection(context, true, false); + return 1; + } MDEBUG(context << "first block hash " << arg.m_block_ids.front() << ", last " << arg.m_block_ids.back()); if (arg.total_height >= CRYPTONOTE_MAX_BLOCK_NUMBER || arg.m_block_ids.size() >= CRYPTONOTE_MAX_BLOCK_NUMBER) @@ -2142,7 +2265,7 @@ skip: return 1; } - uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids); + uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids, arg.m_block_weights); if (n_use_blocks + HASH_OF_HASHES_STEP <= arg.m_block_ids.size()) { LOG_ERROR_CCONTEXT("Most blocks are invalid, dropping connection"); @@ -2152,9 +2275,10 @@ skip: context.m_needed_objects.clear(); uint64_t added = 0; - for(auto& bl_id: arg.m_block_ids) + for (size_t i = 0; i < arg.m_block_ids.size(); ++i) { - context.m_needed_objects.push_back(bl_id); + const uint64_t block_weight = arg.m_block_weights.empty() ? 0 : arg.m_block_weights[i]; + context.m_needed_objects.push_back(std::make_pair(arg.m_block_ids[i], block_weight)); if (++added == n_use_blocks) break; } @@ -2178,7 +2302,7 @@ skip: { NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_arg = AUTO_VAL_INIT(fluffy_arg); fluffy_arg.current_blockchain_height = arg.current_blockchain_height; - std::vector<blobdata> fluffy_txs; + std::vector<tx_blob_entry> fluffy_txs; fluffy_arg.b = arg.b; fluffy_arg.b.txs = fluffy_txs; @@ -2305,14 +2429,14 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - void t_cryptonote_protocol_handler<t_core>::drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans) + void t_cryptonote_protocol_handler<t_core>::drop_connection_with_score(cryptonote_connection_context &context, unsigned score, bool flush_all_spans) { LOG_DEBUG_CC(context, "dropping connection id " << context.m_connection_id << " (pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << - "), add_fail " << add_fail << ", flush_all_spans " << flush_all_spans); + "), score " << score << ", flush_all_spans " << flush_all_spans); - if (add_fail) - m_p2p->add_host_fail(context.m_remote_address); + if (score > 0) + m_p2p->add_host_fail(context.m_remote_address, score); m_block_queue.flush_spans(context.m_connection_id, flush_all_spans); @@ -2320,6 +2444,12 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> + void t_cryptonote_protocol_handler<t_core>::drop_connection(cryptonote_connection_context &context, bool add_fail, bool flush_all_spans) + { + return drop_connection_with_score(context, add_fail ? 1 : 0, flush_all_spans); + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> void t_cryptonote_protocol_handler<t_core>::on_connection_close(cryptonote_connection_context &context) { uint64_t target = 0; diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 26cd93b5a..4b41b5bfc 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -187,14 +187,15 @@ namespace levin { struct zone { - explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in) + explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in, bool is_public) : p2p(std::move(p2p)), noise(std::move(noise_in)), next_epoch(io_service), strand(io_service), map(), channels(), - connection_count(0) + connection_count(0), + is_public(is_public) { for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count) channels.emplace_back(io_service); @@ -207,6 +208,7 @@ namespace levin net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand` std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time + const bool is_public; //!< Zone is public ipv4/ipv6 connections }; } // detail @@ -276,7 +278,10 @@ namespace levin std::vector<boost::uuids::uuid> connections; connections.reserve(connection_id_reserve_size); zone_->p2p->foreach_connection([this, &connections] (detail::p2p_context& context) { - if (this->source_ != context.m_connection_id) + /* Only send to outgoing connections when "flooding" over i2p/tor. + Otherwise this makes the tx linkable to a hidden service address, + making things linkable across connections. */ + if (this->source_ != context.m_connection_id && (this->zone_->is_public || !context.m_is_income)) connections.emplace_back(context.m_connection_id); return true; }); @@ -476,8 +481,8 @@ namespace levin }; } // anonymous - notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise) - : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise))) + notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, bool is_public) + : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise), is_public)) { if (!zone_->p2p) throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"}; @@ -528,7 +533,7 @@ namespace levin channel.next_noise.cancel(); } - bool notify::send_txs(std::vector<cryptonote::blobdata> txs, const boost::uuids::uuid& source, const bool pad_txs) + bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, const bool pad_txs) { if (!zone_) return false; diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 82d22680a..484243af5 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -86,7 +86,7 @@ namespace levin {} //! Construct an instance with available notification `zones`. - explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise); + explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, bool is_public); notify(const notify&) = delete; notify(notify&&) = default; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index d47823735..b827221f6 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -794,6 +794,13 @@ bool t_command_parser_executor::pop_blocks(const std::vector<std::string>& args) return false; } +bool t_command_parser_executor::rpc_payments(const std::vector<std::string>& args) +{ + if (args.size() != 0) return false; + + return m_executor.rpc_payments(); +} + bool t_command_parser_executor::version(const std::vector<std::string>& args) { std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << std::endl; @@ -837,4 +844,16 @@ bool t_command_parser_executor::set_bootstrap_daemon(const std::vector<std::stri args_count > 2 ? args[2] : std::string()); } +bool t_command_parser_executor::flush_cache(const std::vector<std::string>& args) +{ + if (args.empty()) + goto show_list; + if (args[0] == "bad-txs") + return m_executor.flush_cache(true); + +show_list: + std::cout << "Cache type needed: bad-txs" << std::endl; + return true; +} + } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index 25587dea8..8b85dcf69 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -143,6 +143,8 @@ public: bool pop_blocks(const std::vector<std::string>& args); + bool rpc_payments(const std::vector<std::string>& args); + bool version(const std::vector<std::string>& args); bool prune_blockchain(const std::vector<std::string>& args); @@ -152,6 +154,8 @@ public: bool print_net_stats(const std::vector<std::string>& args); bool set_bootstrap_daemon(const std::vector<std::string>& args); + + bool flush_cache(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 757e072a4..8ec690631 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -65,7 +65,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "print_pl" , std::bind(&t_command_parser_executor::print_peer_list, &m_parser, p::_1) - , "print_pl [white] [gray] [<limit>]" + , "print_pl [white] [gray] [pruned] [publicrpc] [<limit>]" , "Print the current peer list." ); m_command_lookup.set_handler( @@ -296,6 +296,11 @@ t_command_server::t_command_server( , "Remove blocks from end of blockchain" ); m_command_lookup.set_handler( + "rpc_payments" + , std::bind(&t_command_parser_executor::rpc_payments, &m_parser, p::_1) + , "Print information about RPC payments." + ); + m_command_lookup.set_handler( "version" , std::bind(&t_command_parser_executor::version, &m_parser, p::_1) , "Print version information." @@ -317,6 +322,12 @@ t_command_server::t_command_server( , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n" "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" ); + m_command_lookup.set_handler( + "flush_cache" + , std::bind(&t_command_parser_executor::flush_cache, &m_parser, p::_1) + , "flush_cache bad-txs" + , "Flush the specified cache(s)." + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/core.h b/src/daemon/core.h index 91dbb7a4b..9a3579e20 100644 --- a/src/daemon/core.h +++ b/src/daemon/core.h @@ -59,16 +59,6 @@ public: : m_core{nullptr} , m_vm_HACK{vm} { - } - - // TODO - get rid of circular dependencies in internals - void set_protocol(t_protocol_raw & protocol) - { - m_core.set_cryptonote_protocol(&protocol); - } - - bool run() - { //initialize core here MGINFO("Initializing core..."); #if defined(PER_BLOCK_CHECKPOINT) @@ -78,9 +68,19 @@ public: #endif if (!m_core.init(m_vm_HACK, nullptr, get_checkpoints)) { - return false; + throw std::runtime_error("Failed to initialize core"); } MGINFO("Core initialized OK"); + } + + // TODO - get rid of circular dependencies in internals + void set_protocol(t_protocol_raw & protocol) + { + m_core.set_cryptonote_protocol(&protocol); + } + + bool run() + { return true; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 014865730..ed614a89b 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -37,6 +37,7 @@ #include "cryptonote_core/cryptonote_core.h" #include "cryptonote_basic/difficulty.h" #include "cryptonote_basic/hardfork.h" +#include "rpc/rpc_payment_signature.h" #include <boost/format.hpp> #include <ctime> #include <string> @@ -60,6 +61,13 @@ namespace { } } + std::string print_float(float f, int prec) + { + char buf[16]; + snprintf(buf, sizeof(buf), "%*.*f", prec, prec, f); + return buf; + } + void print_peer(std::string const & prefix, cryptonote::peer const & peer, bool pruned_only, bool publicrpc_only) { if (pruned_only && peer.pruning_seed == 0) @@ -77,8 +85,9 @@ namespace { epee::string_tools::xtype_to_string(peer.port, port_str); std::string addr_str = peer.host + ":" + port_str; std::string rpc_port = peer.rpc_port ? std::to_string(peer.rpc_port) : "-"; + std::string rpc_credits_per_hash = peer.rpc_credits_per_hash ? print_float(peer.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE, 2) : "-"; std::string pruning_seed = epee::string_tools::to_string_hex(peer.pruning_seed); - tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % pruning_seed % elapsed; + tools::msg_writer() << boost::format("%-10s %-25s %-25s %-5s %-5s %-4s %s") % prefix % id_str % addr_str % rpc_port % rpc_credits_per_hash % pruning_seed % elapsed; } void print_block_header(cryptonote::block_header_response const & header) @@ -91,13 +100,15 @@ namespace { << "height: " << boost::lexical_cast<std::string>(header.height) << std::endl << "depth: " << boost::lexical_cast<std::string>(header.depth) << std::endl << "hash: " << header.hash << std::endl - << "difficulty: " << header.wide_difficulty << std::endl + << "difficulty: " << cryptonote::difficulty_type(header.wide_difficulty) << std::endl + << "cumulative difficulty: " << cryptonote::difficulty_type(header.wide_cumulative_difficulty) << std::endl << "POW hash: " << header.pow_hash << std::endl << "block size: " << header.block_size << std::endl << "block weight: " << header.block_weight << std::endl << "long term weight: " << header.long_term_weight << std::endl << "num txes: " << header.num_txes << std::endl - << "reward: " << cryptonote::print_money(header.reward); + << "reward: " << cryptonote::print_money(header.reward) << std::endl + << "miner tx hash: " << header.miner_tx_hash; } std::string get_human_time_ago(time_t t, time_t now) @@ -355,8 +366,8 @@ bool t_rpc_command_executor::show_difficulty() { tools::success_msg_writer() << "BH: " << res.height << ", TH: " << res.top_block_hash - << ", DIFF: " << res.wide_difficulty - << ", CUM_DIFF: " << res.wide_cumulative_difficulty + << ", DIFF: " << cryptonote::difficulty_type(res.wide_difficulty) + << ", CUM_DIFF: " << cryptonote::difficulty_type(res.wide_cumulative_difficulty) << ", HR: " << cryptonote::difficulty_type(res.wide_difficulty) / res.target << " H/s"; return true; @@ -769,7 +780,7 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u << ", size: " << header.block_size << ", weight: " << header.block_weight << " (long term " << header.long_term_weight << "), transactions: " << header.num_txes << std::endl << "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl << "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl - << "difficulty: " << header.wide_difficulty << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << std::endl; + << "difficulty: " << cryptonote::difficulty_type(header.wide_difficulty) << ", nonce " << header.nonce << ", reward " << cryptonote::print_money(header.reward) << std::endl; first = false; } @@ -960,10 +971,11 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, if (1 == res.txs.size()) { // only available for new style answers + bool pruned = res.txs.front().prunable_as_hex.empty() && res.txs.front().prunable_hash != epee::string_tools::pod_to_hex(crypto::null_hash); if (res.txs.front().in_pool) tools::success_msg_writer() << "Found in pool"; else - tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height << (res.txs.front().prunable_as_hex.empty() ? " (pruned)" : ""); + tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height << (pruned ? " (pruned)" : ""); } const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front(); @@ -1901,7 +1913,7 @@ bool t_rpc_command_executor::alt_chain_info(const std::string &tip, size_t above const auto &chain = chains[idx]; const uint64_t start_height = (chain.height - chain.length + 1); tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) - << " deep), diff " << chain.wide_difficulty << ": " << chain.block_hash; + << " deep), diff " << cryptonote::difficulty_type(chain.wide_difficulty) << ": " << chain.block_hash; } } else @@ -1914,7 +1926,7 @@ bool t_rpc_command_executor::alt_chain_info(const std::string &tip, size_t above tools::success_msg_writer() << "Found alternate chain with tip " << tip; uint64_t start_height = (chain.height - chain.length + 1); tools::msg_writer() << chain.length << " blocks long, from height " << start_height << " (" << (ires.height - start_height - 1) - << " deep), diff " << chain.wide_difficulty << ":"; + << " deep), diff " << cryptonote::difficulty_type(chain.wide_difficulty) << ":"; for (const std::string &block_id: chain.block_hashes) tools::msg_writer() << " " << block_id; tools::msg_writer() << "Chain parent on main chain: " << chain.main_chain_parent_block; @@ -2018,7 +2030,7 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) } } - tools::msg_writer() << "Height: " << ires.height << ", diff " << ires.wide_difficulty << ", cum. diff " << ires.wide_cumulative_difficulty + tools::msg_writer() << "Height: " << ires.height << ", diff " << cryptonote::difficulty_type(ires.wide_difficulty) << ", cum. diff " << cryptonote::difficulty_type(ires.wide_cumulative_difficulty) << ", target " << ires.target << " sec" << ", dyn fee " << cryptonote::print_money(feres.fee) << "/" << (hfres.enabled ? "byte" : "kB"); if (nblocks > 0) @@ -2361,4 +2373,73 @@ bool t_rpc_command_executor::set_bootstrap_daemon( return true; } +bool t_rpc_command_executor::flush_cache(bool bad_txs) +{ + cryptonote::COMMAND_RPC_FLUSH_CACHE::request req; + cryptonote::COMMAND_RPC_FLUSH_CACHE::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + req.bad_txs = bad_txs; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "flush_cache", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_flush_cache(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + return true; +} + +bool t_rpc_command_executor::rpc_payments() +{ + cryptonote::COMMAND_RPC_ACCESS_DATA::request req; + cryptonote::COMMAND_RPC_ACCESS_DATA::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "rpc_access_data", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_rpc_access_data(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + const uint64_t now = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t balance = 0; + tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s") + % "Client ID" % "Balance" % "Total mined" % "Good" % "Stale" % "Bad" % "Dupes" % "Last update"; + for (const auto &entry: res.entries) + { + tools::msg_writer() << boost::format("%64s %16u %16u %8u %8u %8u %8u %s") + % entry.client % entry.balance % entry.credits_total + % entry.nonces_good % entry.nonces_stale % entry.nonces_bad % entry.nonces_dupe + % (entry.last_update_time == 0 ? "never" : get_human_time_ago(entry.last_update_time, now).c_str()); + balance += entry.balance; + } + tools::msg_writer() << res.entries.size() << " clients with a total of " << balance << " credits"; + tools::msg_writer() << "Aggregated client hash rate: " << get_mining_speed(res.hashrate); + + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index af7081ef3..e8b12cb9b 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -167,6 +167,10 @@ public: const std::string &address, const std::string &username, const std::string &password); + + bool rpc_payments(); + + bool flush_cache(bool bad_txs); }; } // namespace daemonize diff --git a/src/gen_ssl_cert/CMakeLists.txt b/src/gen_ssl_cert/CMakeLists.txt new file mode 100644 index 000000000..471df021b --- /dev/null +++ b/src/gen_ssl_cert/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (c) 2017-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. + +set(gen_ssl_cert_sources + gen_ssl_cert.cpp) + +monero_add_executable(gen_ssl_cert + ${gen_ssl_cert_sources}) +target_link_libraries(gen_ssl_cert + PRIVATE + common + epee + version + ${EPEE_READLINE} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +add_dependencies(gen_ssl_cert + version) +set_property(TARGET gen_ssl_cert + PROPERTY + OUTPUT_NAME "monero-gen-ssl-cert") +install(TARGETS gen_ssl_cert DESTINATION bin) diff --git a/src/gen_ssl_cert/gen_ssl_cert.cpp b/src/gen_ssl_cert/gen_ssl_cert.cpp new file mode 100644 index 000000000..7a9b01700 --- /dev/null +++ b/src/gen_ssl_cert/gen_ssl_cert.cpp @@ -0,0 +1,254 @@ +// 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 <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <openssl/ssl.h> +#include <openssl/pem.h> +#include "include_base_utils.h" +#include "file_io_utils.h" +#include "net/net_ssl.h" +#include "crypto/crypto.h" +#include "common/util.h" +#include "common/i18n.h" +#include "common/command_line.h" +#include "common/scoped_message_writer.h" +#include "common/password.h" +#include "version.h" + +namespace po = boost::program_options; + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "gen_ssl_cert" + +namespace gencert +{ + const char* tr(const char* str) + { + return i18n_translate(str, "tools::gen_ssl_cert"); + } + +} + +namespace +{ + const command_line::arg_descriptor<std::string> arg_certificate_filename = {"certificate-filename", gencert::tr("Filename to save the certificate"), ""}; + const command_line::arg_descriptor<std::string> arg_private_key_filename = {"private-key-filename", gencert::tr("Filename to save the private key"), ""}; + const command_line::arg_descriptor<std::string> arg_passphrase = {"passphrase", gencert::tr("Passphrase with which to encrypt the private key"), ""}; + const command_line::arg_descriptor<std::string> arg_passphrase_file = {"passphrase-file", gencert::tr("File containing the passphrase with which to encrypt the private key"), ""}; + const command_line::arg_descriptor<bool> arg_prompt_for_passphrase = {"prompt-for-passphrase", gencert::tr("Prompt for a passphrase with which to encrypt the private key"), false}; +} + +// adapted from openssl's apps/x509.c +static std::string get_fingerprint(X509 *cert, const EVP_MD *fdig) +{ + unsigned int j; + unsigned int n; + unsigned char md[EVP_MAX_MD_SIZE]; + std::string fingerprint; + + if (!X509_digest(cert, fdig, md, &n)) + { + tools::fail_msg_writer() << tr("Failed to create fingerprint: ") << ERR_reason_error_string(ERR_get_error()); + return fingerprint; + } + fingerprint.resize(n * 3 - 1); + char *out = &fingerprint[0]; + for (j = 0; j < n; ++j) + { + snprintf(out, 3 + (j + 1 < n), "%02X%s", md[j], (j + 1 == n) ? "" : ":"); + out += 3; + } + return fingerprint; +} + +int main(int argc, char* argv[]) +{ + TRY_ENTRY(); + + tools::on_startup(); + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + + command_line::add_arg(desc_cmd_sett, arg_certificate_filename); + command_line::add_arg(desc_cmd_sett, arg_private_key_filename); + command_line::add_arg(desc_cmd_sett, arg_passphrase); + command_line::add_arg(desc_cmd_sett, arg_passphrase_file); + command_line::add_arg(desc_cmd_sett, arg_prompt_for_passphrase); + + command_line::add_arg(desc_cmd_only, command_line::arg_help); + command_line::add_arg(desc_cmd_only, command_line::arg_version); + + po::options_description desc_options("Allowed options"); + desc_options.add(desc_cmd_only).add(desc_cmd_sett); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (!r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 0; + } + if (command_line::get_arg(vm, command_line::arg_version)) + { + std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + return 0; + } + + const std::string certificate_filename = command_line::get_arg(vm, arg_certificate_filename); + if (certificate_filename.empty()) + { + tools::fail_msg_writer() << gencert::tr("Argument is needed: ") << "--" << arg_certificate_filename.name; + return 1; + } + const std::string private_key_filename = command_line::get_arg(vm, arg_private_key_filename); + if (private_key_filename.empty()) + { + tools::fail_msg_writer() << gencert::tr("Argument is needed: ") << "--" << arg_private_key_filename.name; + return 1; + } + + epee::wipeable_string private_key_passphrase; + if (command_line::get_arg(vm, arg_prompt_for_passphrase)) + { + auto pwd_container = tools::password_container::prompt(true, "Enter passphrase for the new SSL private key"); + if (!pwd_container) + { + tools::fail_msg_writer() << gencert::tr("Failed to read passphrase"); + return 1; + } + private_key_passphrase = pwd_container->password(); + } + else if (!command_line::is_arg_defaulted(vm, arg_passphrase_file)) + { + std::string passphrase_file = command_line::get_arg(vm, arg_passphrase_file); + if (!passphrase_file.empty()) + { + std::string passphrase; + if (!epee::file_io_utils::load_file_to_string(passphrase_file, passphrase)) + { + MERROR("Failed to load passphrase"); + return 1; + } + + // Remove line breaks the user might have inserted + boost::trim_right_if(passphrase, boost::is_any_of("\r\n")); + private_key_passphrase = passphrase; + memwipe(&passphrase[0], passphrase.size()); + } + } + else + { + private_key_passphrase = command_line::get_arg(vm, arg_passphrase); + } + if (private_key_passphrase.empty()) + tools::msg_writer(epee::console_color_yellow) << (boost::format(tr("Empty passphrase, the private key will be saved to disk unencrypted, use --%s to set a passphrase or --%s to prompt for one")) % arg_passphrase.name % arg_prompt_for_passphrase.name).str(); + + EVP_PKEY *pkey; + X509 *cert; + r = epee::net_utils::create_rsa_ssl_certificate(pkey, cert); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to create certificate"); + return 1; + } + + // write cert + BIO *bio_cert = BIO_new(BIO_s_mem()); + r = PEM_write_bio_X509(bio_cert, cert); + if (!r) + { + BIO_free(bio_cert); + tools::fail_msg_writer() << gencert::tr("Failed to write certificate: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + BUF_MEM *buf = NULL; + BIO_get_mem_ptr(bio_cert, &buf); + if (!buf || !buf->data || !buf->length) + { + BIO_free(bio_cert); + tools::fail_msg_writer() << gencert::tr("Failed to write certificate: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + const std::string certificate(std::string(buf->data, buf->length)); + BIO_free(bio_cert); + + // write private key + BIO *bio_pkey = BIO_new(BIO_s_mem()); + r = PEM_write_bio_PKCS8PrivateKey(bio_pkey, pkey, private_key_passphrase.empty() ? NULL : EVP_aes_128_cfb(), private_key_passphrase.data(), private_key_passphrase.size(), NULL, NULL); + if (!r) + { + BIO_free(bio_pkey); + tools::fail_msg_writer() << gencert::tr("Failed to write private key: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + buf = NULL; + BIO_get_mem_ptr(bio_pkey, &buf); + if (!buf || !buf->data || !buf->length) + { + BIO_free(bio_pkey); + tools::fail_msg_writer() << gencert::tr("Failed to write private key: ") << ERR_reason_error_string(ERR_get_error()); + return 1; + } + const std::string private_key(std::string(buf->data, buf->length)); + BIO_free(bio_pkey); + + // write files + tools::set_strict_default_file_permissions(true); + r = epee::file_io_utils::save_string_to_file(certificate_filename, certificate); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to save certificate file"); + return 1; + } + r = epee::file_io_utils::save_string_to_file(private_key_filename, private_key); + if (!r) + { + tools::fail_msg_writer() << gencert::tr("Failed to save private key file"); + return 1; + } + + tools::success_msg_writer() << tr("New certificate created:"); + tools::success_msg_writer() << tr("Certificate: ") << certificate_filename; + tools::success_msg_writer() << tr("SHA-256 Fingerprint: ") << get_fingerprint(cert, EVP_sha256()); + tools::success_msg_writer() << tr("Private key: ") << private_key_filename << " (" << (private_key_passphrase.empty() ? "unencrypted" : "encrypted") << ")"; + + return 0; + CATCH_ENTRY_L0("main", 1); +} diff --git a/src/hardforks/hardforks.cpp b/src/hardforks/hardforks.cpp index 41aa3e9cb..7ad09dbef 100644 --- a/src/hardforks/hardforks.cpp +++ b/src/hardforks/hardforks.cpp @@ -64,6 +64,9 @@ const hardfork_t mainnet_hard_forks[] = { // version 11 starts from block 1788720, which is on or around the 10th of March, 2019. Fork time finalised on 2019-02-15. { 11, 1788720, 0, 1550225678 }, + + // version 12 starts from block 1978433, which is on or around the 30th of November, 2019. Fork time finalised on 2019-10-18. + { 12, 1978433, 0, 1571419280 }, }; const size_t num_mainnet_hard_forks = sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); const uint64_t mainnet_hard_fork_version_1_till = 1009826; @@ -106,5 +109,6 @@ const hardfork_t stagenet_hard_forks[] = { { 9, 177176, 0, 1537821771 }, { 10, 269000, 0, 1550153694 }, { 11, 269720, 0, 1550225678 }, + { 12, 454721, 0, 1571419280 }, }; const size_t num_stagenet_hard_forks = sizeof(stagenet_hard_forks) / sizeof(stagenet_hard_forks[0]); diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index 339587ffa..1ce7118ad 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -32,5 +32,5 @@ set(net_headers dandelionpp.h error.h i2p_address.h parse.h socks.h socks_connec tor_address.h zmq.h) monero_add_library(net ${net_sources} ${net_headers}) -target_link_libraries(net common epee ${Boost_ASIO_LIBRARY}) +target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY}) diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index c7fc058ca..58c1717e0 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -144,7 +144,7 @@ namespace nodetool const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node = {"add-exclusive-node", "Specify list of peers to connect to only." " If this option is given the options add-priority-node and seed-node are ignored"}; const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"}; - const command_line::arg_descriptor<std::vector<std::string> > arg_proxy = {"proxy", "<network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""}; + const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy = {"tx-proxy", "Send local txes through proxy: <network-type>,<socks-ip:port>[,max_connections][,disable_noise] i.e. \"tor,127.0.0.1:9050,100,disable_noise\""}; const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound = {"anonymous-inbound", "<hidden-service-address>,<[bind-ip:]port>[,max_connections] i.e. \"x.onion,127.0.0.1:18083,100\""}; const command_line::arg_descriptor<bool> arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true}; const command_line::arg_descriptor<bool> arg_no_sync = {"no-sync", "Don't synchronize the blockchain with other peers", false}; @@ -152,7 +152,7 @@ namespace nodetool const command_line::arg_descriptor<bool> arg_no_igd = {"no-igd", "Disable UPnP port mapping"}; const command_line::arg_descriptor<std::string> arg_igd = {"igd", "UPnP port mapping (disabled, enabled, delayed)", "delayed"}; const command_line::arg_descriptor<bool> arg_p2p_use_ipv6 = {"p2p-use-ipv6", "Enable IPv6 for p2p", false}; - const command_line::arg_descriptor<bool> arg_p2p_require_ipv4 = {"p2p-require-ipv4", "Require successful IPv4 bind for p2p", true}; + const command_line::arg_descriptor<bool> arg_p2p_ignore_ipv4 = {"p2p-ignore-ipv4", "Ignore unsuccessful IPv4 bind for p2p", false}; const command_line::arg_descriptor<int64_t> arg_out_peers = {"out-peers", "set max number of out peers", -1}; const command_line::arg_descriptor<int64_t> arg_in_peers = {"in-peers", "set max number of in peers", -1}; const command_line::arg_descriptor<int> arg_tos_flag = {"tos-flag", "set TOS flag", -1}; @@ -167,7 +167,7 @@ namespace nodetool std::vector<proxy> proxies{}; - const std::vector<std::string> args = command_line::get_arg(vm, arg_proxy); + const std::vector<std::string> args = command_line::get_arg(vm, arg_tx_proxy); proxies.reserve(args.size()); for (const boost::string_ref arg : args) @@ -175,11 +175,11 @@ namespace nodetool proxies.emplace_back(); auto next = boost::algorithm::make_split_iterator(arg, boost::algorithm::first_finder(",")); - CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No network type for --" << arg_proxy.name); + CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No network type for --" << arg_tx_proxy.name); const boost::string_ref zone{next->begin(), next->size()}; ++next; - CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No ipv4:port given for --" << arg_proxy.name); + CHECK_AND_ASSERT_MES(!next.eof() && !next->empty(), boost::none, "No ipv4:port given for --" << arg_tx_proxy.name); const boost::string_ref proxy{next->begin(), next->size()}; ++next; @@ -187,7 +187,7 @@ namespace nodetool { if (2 <= count) { - MERROR("Too many ',' characters given to --" << arg_proxy.name); + MERROR("Too many ',' characters given to --" << arg_tx_proxy.name); return boost::none; } @@ -198,7 +198,7 @@ namespace nodetool proxies.back().max_connections = get_max_connections(*next); if (proxies.back().max_connections == 0) { - MERROR("Invalid max connections given to --" << arg_proxy.name); + MERROR("Invalid max connections given to --" << arg_tx_proxy.name); return boost::none; } } @@ -213,7 +213,7 @@ namespace nodetool proxies.back().zone = epee::net_utils::zone::i2p; break; default: - MERROR("Invalid network for --" << arg_proxy.name); + MERROR("Invalid network for --" << arg_tx_proxy.name); return boost::none; } @@ -221,7 +221,7 @@ namespace nodetool std::uint16_t port = 0; if (!epee::string_tools::parse_peer_from_string(ip, port, std::string{proxy}) || port == 0) { - MERROR("Invalid ipv4:port given for --" << arg_proxy.name); + MERROR("Invalid ipv4:port given for --" << arg_tx_proxy.name); return boost::none; } proxies.back().address = ip::tcp::endpoint{ip::address_v4{boost::endian::native_to_big(ip)}, port}; @@ -258,7 +258,7 @@ namespace nodetool inbounds.back().max_connections = get_max_connections(*next); if (inbounds.back().max_connections == 0) { - MERROR("Invalid max connections given to --" << arg_proxy.name); + MERROR("Invalid max connections given to --" << arg_tx_proxy.name); return boost::none; } } diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index d7e2e91f5..8d861ce29 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -231,6 +231,7 @@ namespace nodetool : m_payload_handler(payload_handler), m_external_port(0), m_rpc_port(0), + m_rpc_credits_per_hash(0), m_allow_local_ip(false), m_hide_my_port(false), m_igd(no_igd), @@ -342,7 +343,7 @@ namespace nodetool virtual void request_callback(const epee::net_utils::connection_context_base& context); virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); - virtual bool add_host_fail(const epee::net_utils::network_address &address); + virtual bool add_host_fail(const epee::net_utils::network_address &address, unsigned int score = 1); //----------------- i_connection_filter -------------------------------------------------------- virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address, time_t *t = NULL); //----------------------------------------------------------------------------------------------- @@ -431,6 +432,11 @@ namespace nodetool m_rpc_port = rpc_port; } + void set_rpc_credits_per_hash(uint32_t rpc_credits_per_hash) + { + m_rpc_credits_per_hash = rpc_credits_per_hash; + } + private: std::string m_config_folder; @@ -440,6 +446,7 @@ namespace nodetool uint32_t m_listening_port_ipv6; uint32_t m_external_port; uint16_t m_rpc_port; + uint32_t m_rpc_credits_per_hash; bool m_allow_local_ip; bool m_hide_my_port; igd_t m_igd; @@ -510,14 +517,14 @@ namespace nodetool extern const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port; extern const command_line::arg_descriptor<std::string, false, true, 2> arg_p2p_bind_port_ipv6; extern const command_line::arg_descriptor<bool> arg_p2p_use_ipv6; - extern const command_line::arg_descriptor<bool> arg_p2p_require_ipv4; + extern const command_line::arg_descriptor<bool> arg_p2p_ignore_ipv4; extern const command_line::arg_descriptor<uint32_t> arg_p2p_external_port; extern const command_line::arg_descriptor<bool> arg_p2p_allow_local_ip; extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_peer; extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_priority_node; extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_add_exclusive_node; extern const command_line::arg_descriptor<std::vector<std::string> > arg_p2p_seed_node; - extern const command_line::arg_descriptor<std::vector<std::string> > arg_proxy; + extern const command_line::arg_descriptor<std::vector<std::string> > arg_tx_proxy; extern const command_line::arg_descriptor<std::vector<std::string> > arg_anonymous_inbound; extern const command_line::arg_descriptor<bool> arg_p2p_hide_my_port; extern const command_line::arg_descriptor<bool> arg_no_sync; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 24c87cef8..f8094bfa8 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -97,14 +97,14 @@ namespace nodetool command_line::add_arg(desc, arg_p2p_bind_port, false); command_line::add_arg(desc, arg_p2p_bind_port_ipv6, false); command_line::add_arg(desc, arg_p2p_use_ipv6); - command_line::add_arg(desc, arg_p2p_require_ipv4); + command_line::add_arg(desc, arg_p2p_ignore_ipv4); command_line::add_arg(desc, arg_p2p_external_port); command_line::add_arg(desc, arg_p2p_allow_local_ip); command_line::add_arg(desc, arg_p2p_add_peer); command_line::add_arg(desc, arg_p2p_add_priority_node); command_line::add_arg(desc, arg_p2p_add_exclusive_node); command_line::add_arg(desc, arg_p2p_seed_node); - command_line::add_arg(desc, arg_proxy); + command_line::add_arg(desc, arg_tx_proxy); command_line::add_arg(desc, arg_anonymous_inbound); command_line::add_arg(desc, arg_p2p_hide_my_port); command_line::add_arg(desc, arg_no_sync); @@ -315,13 +315,13 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address) + bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address, unsigned int score) { if(!address.is_blockable()) return false; CRITICAL_REGION_LOCAL(m_host_fails_score_lock); - uint64_t fails = ++m_host_fails_score[address.host_str()]; + uint64_t fails = m_host_fails_score[address.host_str()] += score; MDEBUG("Host " << address.host_str() << " fail score=" << fails); if(fails > P2P_IP_FAILS_BEFORE_BLOCK) { @@ -382,9 +382,9 @@ namespace nodetool } m_offline = command_line::get_arg(vm, cryptonote::arg_offline); m_use_ipv6 = command_line::get_arg(vm, arg_p2p_use_ipv6); - m_require_ipv4 = command_line::get_arg(vm, arg_p2p_require_ipv4); + m_require_ipv4 = !command_line::get_arg(vm, arg_p2p_ignore_ipv4); public_zone.m_notifier = cryptonote::levin::notify{ - public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr + public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr, true }; if (command_line::has_arg(vm, arg_p2p_add_peer)) @@ -475,7 +475,7 @@ namespace nodetool network_zone& zone = add_zone(proxy.zone); if (zone.m_connect != nullptr) { - MERROR("Listed --" << arg_proxy.name << " twice with " << epee::net_utils::zone_to_string(proxy.zone)); + MERROR("Listed --" << arg_tx_proxy.name << " twice with " << epee::net_utils::zone_to_string(proxy.zone)); return false; } zone.m_connect = &socks_connect; @@ -495,7 +495,7 @@ namespace nodetool } zone.m_notifier = cryptonote::levin::notify{ - zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise) + zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise), false }; } @@ -503,7 +503,7 @@ namespace nodetool { if (zone.second.m_connect == nullptr) { - MERROR("Set outgoing peer for " << epee::net_utils::zone_to_string(zone.first) << " but did not set --" << arg_proxy.name); + MERROR("Set outgoing peer for " << epee::net_utils::zone_to_string(zone.first) << " but did not set --" << arg_tx_proxy.name); return false; } } @@ -525,7 +525,7 @@ namespace nodetool if (zone.m_connect == nullptr && tx_relay_zones <= 1) { - MERROR("Listed --" << arg_anonymous_inbound.name << " without listing any --" << arg_proxy.name << ". The latter is necessary for sending origin txes over anonymity networks"); + MERROR("Listed --" << arg_anonymous_inbound.name << " without listing any --" << arg_tx_proxy.name << ". The latter is necessary for sending local txes over anonymity networks"); return false; } @@ -604,11 +604,13 @@ namespace nodetool full_addrs.insert("163.172.182.165:28080"); full_addrs.insert("195.154.123.123:28080"); full_addrs.insert("212.83.172.165:28080"); + full_addrs.insert("192.110.160.146:28080"); } else if (nettype == cryptonote::STAGENET) { full_addrs.insert("162.210.173.150:38080"); full_addrs.insert("162.210.173.151:38080"); + full_addrs.insert("192.110.160.146:38080"); } else if (nettype == cryptonote::FAKECHAIN) { @@ -623,6 +625,7 @@ namespace nodetool full_addrs.insert("198.74.231.92:18080"); full_addrs.insert("195.154.123.123:18080"); full_addrs.insert("212.83.172.165:18080"); + full_addrs.insert("192.110.160.146:18080"); } return full_addrs; } @@ -670,11 +673,18 @@ namespace nodetool std::vector<std::vector<std::string>> dns_results; dns_results.resize(m_seed_nodes_list.size()); + // some libc implementation provide only a very small stack + // for threads, e.g. musl only gives +- 80kb, which is not + // enough to do a resolve with unbound. we request a stack + // of 1 mb, which should be plenty + boost::thread::attributes thread_attributes; + thread_attributes.set_stack_size(1024*1024); + std::list<boost::thread> dns_threads; uint64_t result_index = 0; for (const std::string& addr_str : m_seed_nodes_list) { - boost::thread th = boost::thread([=, &dns_results, &addr_str] + boost::thread th = boost::thread(thread_attributes, [=, &dns_results, &addr_str] { MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); // TODO: care about dnssec avail/valid @@ -1047,7 +1057,8 @@ namespace nodetool pi = context.peer_id = rsp.node_data.peer_id; context.m_rpc_port = rsp.node_data.rpc_port; - m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port); + context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash; + m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash); // move for (auto const& zone : m_network_zones) @@ -1113,7 +1124,7 @@ namespace nodetool add_host_fail(context.m_remote_address); } if(!context.m_is_income) - m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port); + m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash); if (!m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false)) { m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id ); @@ -1282,6 +1293,7 @@ namespace nodetool pe_local.last_seen = static_cast<int64_t>(last_seen); pe_local.pruning_seed = con->m_pruning_seed; pe_local.rpc_port = con->m_rpc_port; + pe_local.rpc_credits_per_hash = con->m_rpc_credits_per_hash; zone.m_peerlist.append_with_peer_white(pe_local); //update last seen and push it to peerlist manager @@ -1857,7 +1869,11 @@ namespace nodetool const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); if (ipv4.ip() == 0) ignore = true; + else if (ipv4.port() == be.rpc_port) + ignore = true; } + if (be.pruning_seed && (be.pruning_seed < tools::make_pruning_seed(1, CRYPTONOTE_PRUNING_LOG_STRIPES) || be.pruning_seed > tools::make_pruning_seed(1ul << CRYPTONOTE_PRUNING_LOG_STRIPES, CRYPTONOTE_PRUNING_LOG_STRIPES))) + ignore = true; if (ignore) { MDEBUG("Ignoring " << be.adr.str()); @@ -1908,6 +1924,7 @@ namespace nodetool else node_data.my_port = 0; node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0; + node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0; node_data.network_id = m_network_id; return true; } @@ -2352,6 +2369,7 @@ namespace nodetool context.peer_id = arg.node_data.peer_id; context.m_in_timedsync = false; context.m_rpc_port = arg.node_data.rpc_port; + context.m_rpc_credits_per_hash = arg.node_data.rpc_credits_per_hash; if(arg.node_data.my_port && zone.m_can_pingback) { @@ -2379,6 +2397,7 @@ namespace nodetool pe.id = peer_id_l; pe.pruning_seed = context.m_pruning_seed; pe.rpc_port = context.m_rpc_port; + pe.rpc_credits_per_hash = context.m_rpc_credits_per_hash; this->m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.append_with_peer_white(pe); LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); }); @@ -2696,7 +2715,7 @@ namespace nodetool } else { - zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port); + zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port, pe.rpc_credits_per_hash); LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); } } diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index e0046cd86..752873666 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -60,7 +60,7 @@ namespace nodetool virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual std::map<std::string, time_t> get_blocked_hosts()=0; virtual std::map<epee::net_utils::ipv4_network_subnet, time_t> get_blocked_subnets()=0; - virtual bool add_host_fail(const epee::net_utils::network_address &address)=0; + virtual bool add_host_fail(const epee::net_utils::network_address &address, unsigned int score = 1)=0; virtual void add_used_stripe_peer(const t_connection_context &context)=0; virtual void remove_used_stripe_peer(const t_connection_context &context)=0; virtual void clear_used_stripe_peers()=0; @@ -122,7 +122,7 @@ namespace nodetool { return std::map<epee::net_utils::ipv4_network_subnet, time_t>(); } - virtual bool add_host_fail(const epee::net_utils::network_address &address) + virtual bool add_host_fail(const epee::net_utils::network_address &address, unsigned int score) { return true; } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index c65b9dd82..58b704f73 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -111,7 +111,7 @@ namespace nodetool bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_anchor(const anchor_peerlist_entry& ple); - bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port); + bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash); bool set_peer_unreachable(const peerlist_entry& pr); bool is_host_allowed(const epee::net_utils::network_address &address); bool get_random_gray_peer(peerlist_entry& pe); @@ -315,7 +315,7 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port) + bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) { TRY_ENTRY(); CRITICAL_REGION_LOCAL(m_peerlist_lock); @@ -326,6 +326,7 @@ namespace nodetool ple.last_seen = time(NULL); ple.pruning_seed = pruning_seed; ple.rpc_port = rpc_port; + ple.rpc_credits_per_hash = rpc_credits_per_hash; return append_with_peer_white(ple); CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false); } diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index c2773981c..bd5063510 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -42,7 +42,7 @@ #include "common/pruning.h" #endif -BOOST_CLASS_VERSION(nodetool::peerlist_entry, 2) +BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3) namespace boost { @@ -241,6 +241,13 @@ namespace boost return; } a & pl.rpc_port; + if (ver < 3) + { + if (!typename Archive::is_saving()) + pl.rpc_credits_per_hash = 0; + return; + } + a & pl.rpc_credits_per_hash; } template <class Archive, class ver_type> diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 85774fcd5..44b278589 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -77,6 +77,7 @@ namespace nodetool int64_t last_seen; uint32_t pruning_seed; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(adr) @@ -85,6 +86,7 @@ namespace nodetool KV_SERIALIZE_OPT(last_seen, (int64_t)0) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) + KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0) END_KV_SERIALIZE_MAP() }; typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry; @@ -132,6 +134,7 @@ namespace nodetool { ss << pe.id << "\t" << pe.adr.str() << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") + << " \trpc credits per hash " << (pe.rpc_credits_per_hash > 0 ? std::to_string(pe.rpc_credits_per_hash) : "-") << " \tpruning seed " << pe.pruning_seed << " \tlast_seen: " << (pe.last_seen == 0 ? std::string("never") : epee::misc_utils::get_time_interval_string(now_time - pe.last_seen)) << std::endl; @@ -166,6 +169,7 @@ namespace nodetool uint64_t local_time; uint32_t my_port; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; peerid_type peer_id; BEGIN_KV_SERIALIZE_MAP() @@ -174,6 +178,7 @@ namespace nodetool KV_SERIALIZE(local_time) KV_SERIALIZE(my_port) KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0)) + KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0) END_KV_SERIALIZE_MAP() }; @@ -220,7 +225,7 @@ namespace nodetool { const epee::net_utils::network_address &na = p.adr; const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); - local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port})); + local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash})); } else MDEBUG("Not including in legacy peer list: " << p.adr.str()); @@ -235,7 +240,7 @@ namespace nodetool std::vector<peerlist_entry_base<network_address_old>> local_peerlist; epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); for (const auto &p: local_peerlist) - ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port})); + ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen, p.pruning_seed, p.rpc_port, p.rpc_credits_per_hash})); } } END_KV_SERIALIZE_MAP() diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index f8729b872..bf4b7b4aa 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -321,6 +321,7 @@ namespace rct { std::vector<mgSig> MGs; // simple rct has N, full has 1 keyV pseudoOuts; //C - for simple rct + // when changing this function, update cryptonote::get_pruned_transaction_weight template<bool W, template <bool> class Archive> bool serialize_rctsig_prunable(Archive<W> &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin) { diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 116e7f568..ebb1e767f 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -29,12 +29,14 @@ include_directories(SYSTEM ${ZMQ_INCLUDE_PATH}) set(rpc_base_sources - rpc_args.cpp) + rpc_args.cpp + rpc_payment_signature.cpp + rpc_handler.cpp) set(rpc_sources bootstrap_daemon.cpp core_rpc_server.cpp - rpc_handler.cpp + rpc_payment.cpp instanciations) set(daemon_messages_sources @@ -47,7 +49,9 @@ set(daemon_rpc_server_sources set(rpc_base_headers - rpc_args.h) + rpc_args.h + rpc_payment_signature.h + rpc_handler.h) set(rpc_headers rpc_handler.h) @@ -58,6 +62,7 @@ set(daemon_rpc_server_headers) set(rpc_daemon_private_headers bootstrap_daemon.h core_rpc_server.h + rpc_payment.h core_rpc_server_commands_defs.h core_rpc_server_error_codes.h) diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index 6f8426ee7..c97b2c95a 100644 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -12,7 +12,7 @@ namespace cryptonote { - bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept + bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) : m_get_next_public_node(get_next_public_node) { } diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 130a6458d..6276b1b21 100644 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -15,7 +15,7 @@ namespace cryptonote class bootstrap_daemon { public: - bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) noexcept; + bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node); bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials); std::string address() const noexcept; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 66af4a364..5a6a395bc 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -39,6 +39,7 @@ using namespace epee; #include "common/download.h" #include "common/util.h" #include "common/perf_timer.h" +#include "int-util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -49,6 +50,8 @@ using namespace epee; #include "crypto/hash.h" #include "rpc/rpc_args.h" #include "rpc/rpc_handler.h" +#include "rpc/rpc_payment_costs.h" +#include "rpc/rpc_payment_signature.h" #include "core_rpc_server_error_codes.h" #include "p2p/net_node.h" #include "version.h" @@ -61,8 +64,50 @@ using namespace epee; #define OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION (3 * 86400) // 3 days max, the wallet requests 1.8 days +#define DEFAULT_PAYMENT_DIFFICULTY 1000 +#define DEFAULT_PAYMENT_CREDITS_PER_HASH 100 + +#define RPC_TRACKER(rpc) \ + PERF_TIMER(rpc); \ + RPCTracker tracker(#rpc, PERF_TIMER_NAME(rpc)) + namespace { + class RPCTracker + { + public: + struct entry_t + { + uint64_t count; + uint64_t time; + uint64_t credits; + }; + + RPCTracker(const char *rpc, tools::LoggingPerformanceTimer &timer): rpc(rpc), timer(timer) { + } + ~RPCTracker() { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + ++e.count; + e.time += timer.value(); + } + void pay(uint64_t amount) { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + e.credits += amount; + } + const std::string &rpc_name() const { return rpc; } + static void clear() { boost::unique_lock<boost::mutex> lock(mutex); tracker.clear(); } + static std::unordered_map<std::string, entry_t> data() { boost::unique_lock<boost::mutex> lock(mutex); return tracker; } + private: + std::string rpc; + tools::LoggingPerformanceTimer &timer; + static boost::mutex mutex; + static std::unordered_map<std::string, entry_t> tracker; + }; + boost::mutex RPCTracker::mutex; + std::unordered_map<std::string, RPCTracker::entry_t> RPCTracker::tracker; + void add_reason(std::string &reasons, const char *reason) { if (!reasons.empty()) @@ -95,6 +140,9 @@ namespace cryptonote command_line::add_arg(desc, arg_bootstrap_daemon_address); command_line::add_arg(desc, arg_bootstrap_daemon_login); cryptonote::rpc_args::init_options(desc, true); + command_line::add_arg(desc, arg_rpc_payment_address); + command_line::add_arg(desc, arg_rpc_payment_difficulty); + command_line::add_arg(desc, arg_rpc_payment_credits); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -173,6 +221,11 @@ namespace cryptonote return true; } + core_rpc_server::~core_rpc_server() + { + if (m_rpc_payment) + m_rpc_payment->store(); + } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::init( const boost::program_options::variables_map& vm @@ -188,6 +241,45 @@ namespace cryptonote if (!rpc_config) return false; + std::string address = command_line::get_arg(vm, arg_rpc_payment_address); + if (!address.empty()) + { + if (!m_restricted && nettype() != FAKECHAIN) + { + MERROR("RPC payment enabled, but server is not restricted, anyone can adjust their balance to bypass payment"); + return false; + } + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, nettype(), address)) + { + MERROR("Invalid payment address: " << address); + return false; + } + if (info.is_subaddress) + { + MERROR("Payment address may not be a subaddress: " << address); + return false; + } + uint64_t diff = command_line::get_arg(vm, arg_rpc_payment_difficulty); + uint64_t credits = command_line::get_arg(vm, arg_rpc_payment_credits); + if (diff == 0 || credits == 0) + { + MERROR("Payments difficulty and/or payments credits are 0, but a payment address was given"); + return false; + } + m_rpc_payment.reset(new rpc_payment(info.address, diff, credits)); + m_rpc_payment->load(command_line::get_arg(vm, cryptonote::arg_data_dir)); + m_p2p.set_rpc_credits_per_hash(RPC_CREDITS_PER_HASH_SCALE * (credits / (float)diff)); + } + + if (!m_rpc_payment) + { + uint32_t bind_ip; + bool ok = epee::string_tools::get_ip_int32_from_string(bind_ip, rpc_config->bind_ip); + if (ok & !epee::net_utils::is_ip_loopback(bind_ip)) + MWARNING("The RPC server is accessible from the outside, but no RPC payment was setup. RPC access will be free for all."); + } + if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), command_line::get_arg(vm, arg_bootstrap_daemon_login))) { @@ -200,6 +292,9 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); + if (m_rpc_payment) + m_net_server.add_idle_handler([this](){ return m_rpc_payment->on_idle(); }, 60 * 1000); + auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( rng, std::move(port), std::move(rpc_config->bind_ip), @@ -208,6 +303,45 @@ namespace cryptonote ); } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::check_payment(const std::string &client_message, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash) + { + if (m_rpc_payment == NULL) + { + credits = 0; + return true; + } + uint64_t height; + crypto::hash hash; + m_core.get_blockchain_top(height, hash); + top_hash = epee::string_tools::pod_to_hex(hash); + crypto::public_key client; + uint64_t ts; +#ifndef NDEBUG + if (nettype() == TESTNET && client_message == "debug") + { + credits = 0; + return true; + } +#endif + if (!cryptonote::verify_rpc_payment_signature(client_message, client, ts)) + { + credits = 0; + message = "Client signature does not verify for " + rpc; + return false; + } + crypto::public_key local_client; + if (!m_rpc_payment->pay(client, ts, payment, rpc, same_ts, credits)) + { + message = CORE_RPC_STATUS_PAYMENT_REQUIRED; + return false; + } + return true; + } +#define CHECK_PAYMENT_BASE(req, res, payment, same_ts) do { if (!ctx) break; uint64_t P = (uint64_t)payment; if (P > 0 && !check_payment(req.client, P, tracker.rpc_name(), same_ts, res.status, res.credits, res.top_hash)){return true;} tracker.pay(P); } while(0) +#define CHECK_PAYMENT(req, res, payment) CHECK_PAYMENT_BASE(req, res, payment, false) +#define CHECK_PAYMENT_SAME_TS(req, res, payment) CHECK_PAYMENT_BASE(req, res, payment, true) +#define CHECK_PAYMENT_MIN1(req, res, payment, same_ts) do { if (!ctx) break; uint64_t P = (uint64_t)payment; if (P == 0) P = 1; if(!check_payment(req.client, P, tracker.rpc_name(), same_ts, res.status, res.credits, res.top_hash)){return true;} tracker.pay(P); } while(0) + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() { if(!m_p2p.get_payload_object().is_synchronized()) @@ -217,13 +351,13 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::add_host_fail(const connection_context *ctx) + bool core_rpc_server::add_host_fail(const connection_context *ctx, unsigned int score) { if(!ctx || !ctx->m_remote_address.is_blockable()) return false; CRITICAL_REGION_LOCAL(m_host_fails_score_lock); - uint64_t fails = ++m_host_fails_score[ctx->m_remote_address.host_str()]; + uint64_t fails = m_host_fails_score[ctx->m_remote_address.host_str()] += score; MDEBUG("Host " << ctx->m_remote_address.host_str() << " fail score=" << fails); if(fails > RPC_IP_FAILS_BEFORE_BLOCK) { @@ -239,7 +373,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_height); + RPC_TRACKER(get_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HEIGHT>(invoke_http_mode::JON, "/getheight", req, res, r)) return r; @@ -254,7 +388,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_info); + RPC_TRACKER(get_info); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_INFO>(invoke_http_mode::JON, "/getinfo", req, res, r)) { @@ -272,6 +406,8 @@ namespace cryptonote return r; } + CHECK_PAYMENT_MIN1(req, res, COST_PER_GET_INFO, false); + const bool restricted = m_restricted && ctx; crypto::hash top_hash; @@ -330,7 +466,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_net_stats); + RPC_TRACKER(get_net_stats); // No bootstrap daemon check: Only ever get stats about local server res.start_time = (uint64_t)m_core.get_start_time(); { @@ -357,26 +493,58 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_blocks); + RPC_TRACKER(get_blocks); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCKS_FAST>(invoke_http_mode::BIN, "/getblocks.bin", req, res, r)) return r; - std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; + CHECK_PAYMENT(req, res, 1); + + // quick check for noop + if (!req.block_ids.empty()) + { + uint64_t last_block_height; + crypto::hash last_block_hash; + m_core.get_blockchain_top(last_block_height, last_block_hash); + if (last_block_hash == req.block_ids.front()) + { + res.start_height = 0; + res.current_height = m_core.get_current_blockchain_height(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + } + + size_t max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT; + if (m_rpc_payment) + { + max_blocks = res.credits / COST_PER_BLOCK; + if (max_blocks > COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT) + max_blocks = COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT; + if (max_blocks == 0) + { + res.status = CORE_RPC_STATUS_PAYMENT_REQUIRED; + return false; + } + } - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > > bs; + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, !req.no_miner_tx, max_blocks)) { res.status = "Failed"; add_host_fail(ctx); return false; } + CHECK_PAYMENT_SAME_TS(req, res, bs.size() * COST_PER_BLOCK); + size_t pruned_size = 0, unpruned_size = 0, ntxes = 0; res.blocks.reserve(bs.size()); res.output_indices.reserve(bs.size()); for(auto& bd: bs) { res.blocks.resize(res.blocks.size()+1); + res.blocks.back().pruned = req.prune; res.blocks.back().block = bd.first.first; pruned_size += bd.first.first.size(); unpruned_size += bd.first.first.size(); @@ -389,10 +557,10 @@ namespace cryptonote for (std::vector<std::pair<crypto::hash, cryptonote::blobdata>>::iterator i = bd.second.begin(); i != bd.second.end(); ++i) { unpruned_size += i->second.size(); - res.blocks.back().txs.push_back(std::move(i->second)); + res.blocks.back().txs.push_back({std::move(i->second), crypto::null_hash}); i->second.clear(); i->second.shrink_to_fit(); - pruned_size += res.blocks.back().txs.back().size(); + pruned_size += res.blocks.back().txs.back().blob.size(); } const size_t n_txes_to_lookup = bd.second.size() + (req.no_miner_tx ? 0 : 1); @@ -421,7 +589,7 @@ namespace cryptonote } bool core_rpc_server::on_get_alt_blocks_hashes(const COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request& req, COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_alt_blocks_hashes); + RPC_TRACKER(get_alt_blocks_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_ALT_BLOCKS_HASHES>(invoke_http_mode::JON, "/get_alt_blocks_hashes", req, res, r)) return r; @@ -448,7 +616,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_blocks_by_height); + RPC_TRACKER(get_blocks_by_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCKS_BY_HEIGHT>(invoke_http_mode::BIN, "/getblocks_by_height.bin", req, res, r)) return r; @@ -456,6 +624,7 @@ namespace cryptonote res.status = "Failed"; res.blocks.clear(); res.blocks.reserve(req.heights.size()); + CHECK_PAYMENT_MIN1(req, res, req.heights.size() * COST_PER_BLOCK, false); for (uint64_t height : req.heights) { block blk; @@ -474,7 +643,7 @@ namespace cryptonote res.blocks.resize(res.blocks.size() + 1); res.blocks.back().block = block_to_blob(blk); for (auto& tx : txs) - res.blocks.back().txs.push_back(tx_to_blob(tx)); + res.blocks.back().txs.push_back({tx_to_blob(tx), crypto::null_hash}); } res.status = CORE_RPC_STATUS_OK; return true; @@ -482,30 +651,36 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_hashes); + RPC_TRACKER(get_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HASHES_FAST>(invoke_http_mode::BIN, "/gethashes.bin", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + res.start_height = req.start_height; - if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, res.start_height, res.current_height, false)) + if(!m_core.get_blockchain_storage().find_blockchain_supplement(req.block_ids, res.m_block_ids, NULL, res.start_height, res.current_height, false)) { res.status = "Failed"; add_host_fail(ctx); return false; } + CHECK_PAYMENT_SAME_TS(req, res, res.m_block_ids.size() * COST_PER_BLOCK_HASH); + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_outs_bin); + RPC_TRACKER(get_outs_bin); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUTS_BIN>(invoke_http_mode::BIN, "/get_outs.bin", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, req.outputs.size() * COST_PER_OUT, false); + res.status = "Failed"; const bool restricted = m_restricted && ctx; @@ -529,11 +704,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_outs); + RPC_TRACKER(get_outs); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUTS>(invoke_http_mode::JON, "/get_outs", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, req.outputs.size() * COST_PER_OUT, false); + res.status = "Failed"; const bool restricted = m_restricted && ctx; @@ -573,11 +750,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_indexes); + RPC_TRACKER(get_indexes); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES>(invoke_http_mode::BIN, "/get_o_indexes.bin", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, COST_PER_OUTPUT_INDEXES, false); + bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) { @@ -591,11 +770,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transactions); + RPC_TRACKER(get_transactions); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTIONS>(invoke_http_mode::JON, "/gettransactions", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, req.txs_hashes.size() * COST_PER_TX, false); + std::vector<crypto::hash> vh; for(const auto& tx_hex_str: req.txs_hashes) { @@ -817,13 +998,16 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, const connection_context *ctx) { - PERF_TIMER(on_is_key_image_spent); + RPC_TRACKER(is_key_image_spent); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_IS_KEY_IMAGE_SPENT>(invoke_http_mode::JON, "/is_key_image_spent", req, res, ok)) return ok; + CHECK_PAYMENT_MIN1(req, res, req.key_images.size() * COST_PER_KEY_IMAGE, false); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; + std::vector<crypto::key_image> key_images; for(const auto& ki_hex_str: req.key_images) { @@ -886,12 +1070,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res, const connection_context *ctx) { - PERF_TIMER(on_send_raw_tx); + RPC_TRACKER(send_raw_tx); bool ok; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_SEND_RAW_TX>(invoke_http_mode::JON, "/sendrawtransaction", req, res, ok)) return ok; CHECK_CORE_READY(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_RELAY, false); std::string tx_blob; if(!string_tools::parse_hexstr_to_binbuff(req.tx_as_hex, tx_blob)) @@ -912,7 +1097,7 @@ namespace cryptonote cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) { res.status = "Failed"; std::string reason = ""; @@ -965,7 +1150,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res, const connection_context *ctx) { - PERF_TIMER(on_start_mining); + RPC_TRACKER(start_mining); CHECK_CORE_READY(); cryptonote::address_parse_info info; if(!get_account_address_from_str(info, nettype(), req.miner_address)) @@ -1016,7 +1201,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res, const connection_context *ctx) { - PERF_TIMER(on_stop_mining); + RPC_TRACKER(stop_mining); cryptonote::miner &miner= m_core.get_miner(); if(!miner.is_mining()) { @@ -1036,7 +1221,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res, const connection_context *ctx) { - PERF_TIMER(on_mining_status); + RPC_TRACKER(mining_status); const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); @@ -1077,7 +1262,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx) { - PERF_TIMER(on_save_bc); + RPC_TRACKER(save_bc); if( !m_core.get_blockchain_storage().store_blockchain() ) { res.status = "Error while storing blockchain"; @@ -1089,7 +1274,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_peer_list); + RPC_TRACKER(get_peer_list); std::vector<nodetool::peerlist_entry> white_list; std::vector<nodetool::peerlist_entry> gray_list; @@ -1106,24 +1291,24 @@ namespace cryptonote { if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), - entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), - entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else - res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); } for (auto & entry : gray_list) { if (entry.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), - entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else if (entry.adr.get_type_id() == epee::net_utils::ipv6_network_address::get_type_id()) res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv6_network_address>().host_str(), - entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + entry.adr.as<epee::net_utils::ipv6_network_address>().port(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); else - res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port); + res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen, entry.pruning_seed, entry.rpc_port, entry.rpc_credits_per_hash); } res.status = CORE_RPC_STATUS_OK; @@ -1132,7 +1317,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_public_nodes(const COMMAND_RPC_GET_PUBLIC_NODES::request& req, COMMAND_RPC_GET_PUBLIC_NODES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_public_nodes); + RPC_TRACKER(get_public_nodes); COMMAND_RPC_GET_PEER_LIST::response peer_list_res; const bool success = on_get_peer_list(COMMAND_RPC_GET_PEER_LIST::request(), peer_list_res, ctx); @@ -1171,7 +1356,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_hash_rate); + RPC_TRACKER(set_log_hash_rate); if(m_core.get_miner().is_mining()) { m_core.get_miner().do_print_hashrate(req.visible); @@ -1186,7 +1371,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_level); + RPC_TRACKER(set_log_level); if (req.level < 0 || req.level > 4) { res.status = "Error: log level not valid"; @@ -1199,7 +1384,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_log_categories); + RPC_TRACKER(set_log_categories); mlog_set_log(req.categories.c_str()); res.categories = mlog_get_categories(); res.status = CORE_RPC_STATUS_OK; @@ -1208,62 +1393,92 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool); + RPC_TRACKER(get_transaction_pool); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL>(invoke_http_mode::JON, "/get_transaction_pool", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !restricted); - for (tx_info& txi : res.transactions) - txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_TX); + m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !restricted); + for (tx_info& txi : res.transactions) + txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_hashes_bin(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_hashes); + RPC_TRACKER(get_transaction_pool_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN>(invoke_http_mode::JON, "/get_transaction_pool_hashes.bin", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !restricted); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_POOL_HASH); + m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !restricted); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_hashes); + RPC_TRACKER(get_transaction_pool_hashes); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_HASHES>(invoke_http_mode::JON, "/get_transaction_pool_hashes", req, res, r)) return r; + CHECK_PAYMENT(req, res, 1); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; - std::vector<crypto::hash> tx_hashes; - m_core.get_pool_transaction_hashes(tx_hashes, !request_has_rpc_origin || !restricted); - res.tx_hashes.reserve(tx_hashes.size()); - for (const crypto::hash &tx_hash: tx_hashes) - res.tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + + size_t n_txes = m_core.get_pool_transactions_count(); + if (n_txes > 0) + { + CHECK_PAYMENT_SAME_TS(req, res, n_txes * COST_PER_POOL_HASH); + std::vector<crypto::hash> tx_hashes; + m_core.get_pool_transaction_hashes(tx_hashes, !request_has_rpc_origin || !restricted); + res.tx_hashes.reserve(tx_hashes.size()); + for (const crypto::hash &tx_hash: tx_hashes) + res.tx_hashes.push_back(epee::string_tools::pod_to_hex(tx_hash)); + } + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_transaction_pool_stats); + RPC_TRACKER(get_transaction_pool_stats); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_STATS>(invoke_http_mode::JON, "/get_transaction_pool_stats", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS, false); + const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; m_core.get_pool_transaction_stats(res.pool_stats, !request_has_rpc_origin || !restricted); + res.status = CORE_RPC_STATUS_OK; return true; } @@ -1292,7 +1507,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res, const connection_context *ctx) { - PERF_TIMER(on_stop_daemon); + RPC_TRACKER(stop_daemon); // FIXME: replace back to original m_p2p.send_stop_signal() after // investigating why that isn't working quite right. m_p2p.send_stop_signal(); @@ -1302,7 +1517,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx) { - PERF_TIMER(on_getblockcount); + RPC_TRACKER(getblockcount); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1318,7 +1533,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_getblockhash); + RPC_TRACKER(getblockhash); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1360,9 +1575,65 @@ namespace cryptonote return 0; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::get_block_template(const account_public_address &address, const crypto::hash *prev_block, const cryptonote::blobdata &extra_nonce, size_t &reserved_offset, cryptonote::difficulty_type &difficulty, uint64_t &height, uint64_t &expected_reward, block &b, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, epee::json_rpc::error &error_resp) + { + b = boost::value_initialized<cryptonote::block>(); + if(!m_core.get_block_template(b, prev_block, address, difficulty, height, expected_reward, extra_nonce)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to create block template"); + return false; + } + blobdata block_blob = t_serializable_object_to_blob(b); + crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); + if(tx_pub_key == crypto::null_pkey) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to get tx pub key in coinbase extra"); + return false; + } + + if (b.major_version >= RX_BLOCK_VERSION) + { + uint64_t next_height; + crypto::rx_seedheights(height, &seed_height, &next_height); + seed_hash = m_core.get_block_id_by_height(seed_height); + if (next_height != seed_height) + next_seed_hash = m_core.get_block_id_by_height(next_height); + else + next_seed_hash = seed_hash; + } + + if (extra_nonce.empty()) + { + reserved_offset = 0; + return true; + } + + reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key)); + if(!reserved_offset) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to find tx pub key in blockblob"); + return false; + } + reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) + if(reserved_offset + extra_nonce.size() > block_blob.size()) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Internal error: failed to create block template"; + LOG_ERROR("Failed to calculate offset for "); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_getblocktemplate); + RPC_TRACKER(getblocktemplate); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GETBLOCKTEMPLATE>(invoke_http_mode::JON_RPC, "getblocktemplate", req, res, r)) return r; @@ -1412,6 +1683,7 @@ namespace cryptonote block b; cryptonote::blobdata blob_reserve; + size_t reserved_offset; if(!req.extra_nonce.empty()) { if(!string_tools::parse_hexstr_to_binbuff(req.extra_nonce, blob_reserve)) @@ -1434,54 +1706,20 @@ namespace cryptonote return false; } } - if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to create block template"); + uint64_t seed_height; + crypto::hash seed_hash, next_seed_hash; + if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp)) return false; - } if (b.major_version >= RX_BLOCK_VERSION) { - uint64_t seed_height, next_height; - crypto::hash seed_hash; - crypto::rx_seedheights(res.height, &seed_height, &next_height); - seed_hash = m_core.get_block_id_by_height(seed_height); res.seed_hash = string_tools::pod_to_hex(seed_hash); - if (next_height != seed_height) { - seed_hash = m_core.get_block_id_by_height(next_height); - res.next_seed_hash = string_tools::pod_to_hex(seed_hash); - } + if (seed_hash != next_seed_hash) + res.next_seed_hash = string_tools::pod_to_hex(next_seed_hash); } + + res.reserved_offset = reserved_offset; store_difficulty(wdiff, res.difficulty, res.wide_difficulty, res.difficulty_top64); blobdata block_blob = t_serializable_object_to_blob(b); - crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); - if(tx_pub_key == crypto::null_pkey) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to get tx pub key in coinbase extra"); - return false; - } - res.reserved_offset = slow_memmem((void*)block_blob.data(), block_blob.size(), &tx_pub_key, sizeof(tx_pub_key)); - if(!res.reserved_offset) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to find tx pub key in blockblob"); - return false; - } - if (req.reserve_size) - res.reserved_offset += sizeof(tx_pub_key) + 2; //2 bytes: tag for TX_EXTRA_NONCE(1 byte), counter in TX_EXTRA_NONCE(1 byte) - else - res.reserved_offset = 0; - if(res.reserved_offset + req.reserve_size > block_blob.size()) - { - error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; - error_resp.message = "Internal error: failed to create block template"; - LOG_ERROR("Failed to calculate offset for "); - return false; - } blobdata hashing_blob = get_block_hashing_blob(b); res.prev_hash = string_tools::pod_to_hex(b.prev_id); res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); @@ -1492,7 +1730,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_submitblock); + RPC_TRACKER(submitblock); { boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); if (m_should_use_bootstrap_daemon) @@ -1548,7 +1786,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_generateblocks); + RPC_TRACKER(generateblocks); CHECK_CORE_READY(); @@ -1723,12 +1961,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_last_block_header); + RPC_TRACKER(get_last_block_header); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_LAST_BLOCK_HEADER>(invoke_http_mode::JON_RPC, "getlastblockheader", req, res, r)) return r; CHECK_CORE_READY(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); uint64_t last_block_height; crypto::hash last_block_hash; m_core.get_blockchain_top(last_block_height, last_block_hash); @@ -1754,11 +1993,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_header_by_hash); + RPC_TRACKER(get_block_header_by_hash); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH>(invoke_http_mode::JON_RPC, "getblockheaderbyhash", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); + auto get = [this](const std::string &hash, bool fill_pow_hash, block_header_response &block_header, bool restricted, epee::json_rpc::error& error_resp) -> bool { crypto::hash block_hash; bool hash_parsed = parse_hash256(hash, block_hash); @@ -1814,7 +2055,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_headers_range(const COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request& req, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_headers_range); + RPC_TRACKER(get_block_headers_range); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADERS_RANGE>(invoke_http_mode::JON_RPC, "getblockheadersrange", req, res, r)) return r; @@ -1826,6 +2067,7 @@ namespace cryptonote error_resp.message = "Invalid start/end heights."; return false; } + CHECK_PAYMENT_MIN1(req, res, (req.end_height - req.start_height + 1) * COST_PER_BLOCK_HEADER, false); for (uint64_t h = req.start_height; h <= req.end_height; ++h) { crypto::hash block_hash = m_core.get_block_id_by_height(h); @@ -1866,7 +2108,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block_header_by_height); + RPC_TRACKER(get_block_header_by_height); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT>(invoke_http_mode::JON_RPC, "getblockheaderbyheight", req, res, r)) return r; @@ -1877,6 +2119,7 @@ namespace cryptonote error_resp.message = std::string("Requested block height: ") + std::to_string(req.height) + " greater than current top block height: " + std::to_string(m_core.get_current_blockchain_height() - 1); return false; } + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK_HEADER, false); crypto::hash block_hash = m_core.get_block_id_by_height(req.height); block blk; bool have_block = m_core.get_block_by_hash(block_hash, blk); @@ -1900,11 +2143,13 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block(const COMMAND_RPC_GET_BLOCK::request& req, COMMAND_RPC_GET_BLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_block); + RPC_TRACKER(get_block); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BLOCK>(invoke_http_mode::JON_RPC, "getblock", req, res, r)) return r; + CHECK_PAYMENT_MIN1(req, res, COST_PER_BLOCK, false); + crypto::hash block_hash; if (!req.hash.empty()) { @@ -1950,7 +2195,6 @@ namespace cryptonote error_resp.message = "Internal error: can't produce valid response."; return false; } - res.miner_tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); for (size_t n = 0; n < blk.tx_hashes.size(); ++n) { res.tx_hashes.push_back(epee::string_tools::pod_to_hex(blk.tx_hashes[n])); @@ -1963,7 +2207,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_connections); + RPC_TRACKER(get_connections); res.connections = m_p2p.get_payload_object().get_connections(); @@ -1974,16 +2218,24 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - return on_get_info(req, res, ctx); + on_get_info(req, res, ctx); + if (res.status != CORE_RPC_STATUS_OK) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = res.status; + return false; + } + return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_hard_fork_info); + RPC_TRACKER(hard_fork_info); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_HARD_FORK_INFO>(invoke_http_mode::JON_RPC, "hard_fork_info", req, res, r)) return r; + CHECK_PAYMENT(req, res, COST_PER_HARD_FORK_INFO); const Blockchain &blockchain = m_core.get_blockchain_storage(); uint8_t version = req.version > 0 ? req.version : blockchain.get_next_hard_fork_version(); res.version = blockchain.get_current_hard_fork_version(); @@ -1995,7 +2247,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_bans); + RPC_TRACKER(get_bans); auto now = time(nullptr); std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); @@ -2059,7 +2311,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_set_bans); + RPC_TRACKER(set_bans); for (auto i = req.bans.begin(); i != req.bans.end(); ++i) { @@ -2107,7 +2359,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_flush_txpool); + RPC_TRACKER(flush_txpool); bool failed = false; std::vector<crypto::hash> txids; @@ -2162,12 +2414,22 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_output_histogram); + RPC_TRACKER(get_output_histogram); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_HISTOGRAM>(invoke_http_mode::JON_RPC, "get_output_histogram", req, res, r)) return r; const bool restricted = m_restricted && ctx; + size_t amounts = req.amounts.size(); + if (restricted && amounts == 0) + { + res.status = "Restricted RPC will not serve histograms on the whole blockchain. Use your own node."; + return true; + } + + uint64_t cost = req.amounts.empty() ? COST_PER_FULL_OUTPUT_HISTOGRAM : (COST_PER_OUTPUT_HISTOGRAM * amounts); + CHECK_PAYMENT_MIN1(req, res, cost, false); + if (restricted && req.recent_cutoff > 0 && req.recent_cutoff < (uint64_t)time(NULL) - OUTPUT_HISTOGRAM_RECENT_CUTOFF_RESTRICTION) { res.status = "Recent cutoff is too old"; @@ -2199,7 +2461,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_version); + RPC_TRACKER(get_version); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_VERSION>(invoke_http_mode::JON_RPC, "get_version", req, res, r)) return r; @@ -2212,7 +2474,14 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_coinbase_tx_sum); + RPC_TRACKER(get_coinbase_tx_sum); + const uint64_t bc_height = m_core.get_current_blockchain_height(); + if (req.height >= bc_height || req.count > bc_height) + { + res.status = "height or count is too large"; + return true; + } + CHECK_PAYMENT_MIN1(req, res, COST_PER_COINBASE_TX_SUM_BLOCK * req.count, false); std::pair<uint64_t, uint64_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); res.emission_amount = amounts.first; res.fee_amount = amounts.second; @@ -2222,11 +2491,12 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_base_fee_estimate(const COMMAND_RPC_GET_BASE_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_BASE_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_base_fee_estimate); + RPC_TRACKER(get_base_fee_estimate); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_BASE_FEE_ESTIMATE>(invoke_http_mode::JON_RPC, "get_fee_estimate", req, res, r)) return r; + CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); res.quantization_mask = Blockchain::get_fee_quantization_mask(); res.status = CORE_RPC_STATUS_OK; @@ -2235,7 +2505,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_alternate_chains); + RPC_TRACKER(get_alternate_chains); try { std::vector<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains(); @@ -2268,7 +2538,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_limit); + RPC_TRACKER(get_limit); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_LIMIT>(invoke_http_mode::JON, "/get_limit", req, res, r)) return r; @@ -2281,7 +2551,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res, const connection_context *ctx) { - PERF_TIMER(on_set_limit); + RPC_TRACKER(set_limit); // -1 = reset to default // 0 = do not modify @@ -2321,7 +2591,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res, const connection_context *ctx) { - PERF_TIMER(on_out_peers); + RPC_TRACKER(out_peers); if (req.set) m_p2p.change_max_out_public_peers(req.out_peers); res.out_peers = m_p2p.get_max_out_public_peers(); @@ -2331,7 +2601,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_in_peers(const COMMAND_RPC_IN_PEERS::request& req, COMMAND_RPC_IN_PEERS::response& res, const connection_context *ctx) { - PERF_TIMER(on_in_peers); + RPC_TRACKER(in_peers); if (req.set) m_p2p.change_max_in_public_peers(req.in_peers); res.in_peers = m_p2p.get_max_in_public_peers(); @@ -2341,7 +2611,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res, const connection_context *ctx) { - PERF_TIMER(on_update); + RPC_TRACKER(update); if (m_core.offline()) { @@ -2443,7 +2713,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_pop_blocks(const COMMAND_RPC_POP_BLOCKS::request& req, COMMAND_RPC_POP_BLOCKS::response& res, const connection_context *ctx) { - PERF_TIMER(on_pop_blocks); + RPC_TRACKER(pop_blocks); m_core.get_blockchain_storage().pop_blocks(req.nblocks); @@ -2455,7 +2725,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_relay_tx); + RPC_TRACKER(relay_tx); + CHECK_PAYMENT_MIN1(req, res, req.txids.size() * COST_PER_TX_RELAY, false); bool failed = false; res.status = ""; @@ -2501,7 +2772,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_sync_info); + RPC_TRACKER(sync_info); + CHECK_PAYMENT(req, res, COST_PER_SYNC_INFO); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); @@ -2530,10 +2802,12 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_txpool_backlog); + RPC_TRACKER(get_txpool_backlog); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG>(invoke_http_mode::JON_RPC, "get_txpool_backlog", req, res, r)) return r; + size_t n_txes = m_core.get_pool_transactions_count(); + CHECK_PAYMENT_MIN1(req, res, COST_PER_TX_POOL_STATS * n_txes, false); if (!m_core.get_txpool_backlog(res.backlog)) { @@ -2548,11 +2822,16 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_output_distribution); + RPC_TRACKER(get_output_distribution); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::JON_RPC, "get_output_distribution", req, res, r)) return r; + size_t n_0 = 0, n_non0 = 0; + for (uint64_t amount: req.amounts) + if (amount) ++n_non0; else ++n_0; + CHECK_PAYMENT_MIN1(req, res, n_0 * COST_PER_OUTPUT_DISTRIBUTION_0 + n_non0 * COST_PER_OUTPUT_DISTRIBUTION, false); + try { // 0 is placeholder for the whole chain @@ -2583,12 +2862,17 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_distribution_bin(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, const connection_context *ctx) { - PERF_TIMER(on_get_output_distribution_bin); + RPC_TRACKER(get_output_distribution_bin); bool r; if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_OUTPUT_DISTRIBUTION>(invoke_http_mode::BIN, "/get_output_distribution.bin", req, res, r)) return r; + size_t n_0 = 0, n_non0 = 0; + for (uint64_t amount: req.amounts) + if (amount) ++n_non0; else ++n_0; + CHECK_PAYMENT_MIN1(req, res, n_0 * COST_PER_OUTPUT_DISTRIBUTION_0 + n_non0 * COST_PER_OUTPUT_DISTRIBUTION, false); + res.status = "Failed"; if (!req.binary) @@ -2624,6 +2908,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { + RPC_TRACKER(prune_blockchain); + try { if (!(req.check ? m_core.check_blockchain_pruning() : m_core.prune_blockchain())) @@ -2641,13 +2927,256 @@ namespace cryptonote error_resp.message = "Failed to prune blockchain"; return false; } + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_info); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON, "rpc_access_info", req, res, r)) + return r; + + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.diff = 0; + res.credits_per_hash_found = 0; + res.credits = 0; + res.height = 0; + res.seed_height = 0; + res.status = CORE_RPC_STATUS_OK; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + crypto::hash top_hash; + m_core.get_blockchain_top(res.height, top_hash); + ++res.height; + cryptonote::blobdata hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (!m_rpc_payment->get_info(client, [&](const cryptonote::blobdata &extra_nonce, cryptonote::block &b, uint64_t &seed_height, crypto::hash &seed_hash)->bool{ + cryptonote::difficulty_type difficulty; + uint64_t height, expected_reward; + size_t reserved_offset; + if (!get_block_template(m_rpc_payment->get_payment_address(), NULL, extra_nonce, reserved_offset, difficulty, height, expected_reward, b, seed_height, seed_hash, next_seed_hash, error_resp)) + return false; + return true; + }, hashing_blob, res.seed_height, seed_hash, top_hash, res.diff, res.credits_per_hash_found, res.credits, res.cookie)) + { + return false; + } + if (hashing_blob.empty()) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Invalid hashing blob"; + return false; + } + res.hashing_blob = epee::string_tools::buff_to_hex_nodelimer(hashing_blob); + res.top_hash = epee::string_tools::pod_to_hex(top_hash); + if (hashing_blob[0] >= RX_BLOCK_VERSION) + { + res.seed_hash = string_tools::pod_to_hex(seed_hash); + if (seed_hash != next_seed_hash) + res.next_seed_hash = string_tools::pod_to_hex(next_seed_hash); + } res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_flush_cache(const COMMAND_RPC_FLUSH_CACHE::request& req, COMMAND_RPC_FLUSH_CACHE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(flush_cache); + if (req.bad_txs) + m_core.flush_bad_txs_cache(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_submit_nonce); + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON, "rpc_access_submit_nonce", req, res, r)) + return r; + + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.status = "Payment not necessary"; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + res.credits = 0; + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + crypto::hash hash; + cryptonote::block block; + crypto::hash top_hash; + uint64_t height; + bool stale; + m_core.get_blockchain_top(height, top_hash); + if (!m_rpc_payment->submit_nonce(client, req.nonce, top_hash, error_resp.code, error_resp.message, res.credits, hash, block, req.cookie, stale)) + { + return false; + } + + if (!stale) + { + // it might be a valid block! + const difficulty_type current_difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); + if (check_hash(hash, current_difficulty)) + { + MINFO("This payment meets the current network difficulty"); + block_verification_context bvc; + if(m_core.handle_block_found(block, bvc)) + MGINFO_GREEN("Block found by RPC user at height " << get_block_height(block) << ": " << + print_money(cryptonote::get_outs_money_amount(block.miner_tx))); + else + MERROR("Seemingly valid block was not accepted"); + } + } + m_core.get_blockchain_top(height, top_hash); + res.top_hash = epee::string_tools::pod_to_hex(top_hash); + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_pay); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON, "rpc_access_pay", req, res, r)) + return r; + + // if RPC payment is not enabled + if (m_rpc_payment == NULL) + { + res.status = "Payment not necessary"; + return true; + } + + crypto::public_key client; + uint64_t ts; + if (!cryptonote::verify_rpc_payment_signature(req.client, client, ts)) + { + res.credits = 0; + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + RPCTracker ext_tracker(("external:" + req.paying_for).c_str(), PERF_TIMER_NAME(rpc_access_pay)); + if (!check_payment(req.client, req.payment, req.paying_for, false, res.status, res.credits, res.top_hash)) + return true; + ext_tracker.pay(req.payment); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_tracking(const COMMAND_RPC_ACCESS_TRACKING::request& req, COMMAND_RPC_ACCESS_TRACKING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_tracking); + + if (req.clear) + { + RPCTracker::clear(); + res.status = CORE_RPC_STATUS_OK; + return true; + } + + auto data = RPCTracker::data(); + for (const auto &d: data) + { + res.data.resize(res.data.size() + 1); + res.data.back().rpc = d.first; + res.data.back().count = d.second.count; + res.data.back().time = d.second.time; + res.data.back().credits = d.second.credits; + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_data(const COMMAND_RPC_ACCESS_DATA::request& req, COMMAND_RPC_ACCESS_DATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_data); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON, "rpc_access_data", req, res, r)) + return r; + + if (!m_rpc_payment) + { + res.status = "Payments not enabled"; + return false; + } + + m_rpc_payment->foreach([&](const crypto::public_key &client, const rpc_payment::client_info &info){ + res.entries.push_back({ + epee::string_tools::pod_to_hex(client), info.credits, std::max(info.last_request_timestamp / 1000000, info.update_time), + info.credits_total, info.credits_used, info.nonces_good, info.nonces_stale, info.nonces_bad, info.nonces_dupe + }); + return true; + }); + + res.hashrate = m_rpc_payment->get_hashes(600) / 600; + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_rpc_access_account(const COMMAND_RPC_ACCESS_ACCOUNT::request& req, COMMAND_RPC_ACCESS_ACCOUNT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(rpc_access_account); + + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON, "rpc_access_account", req, res, r)) + return r; + + if (!m_rpc_payment) + { + res.status = "Payments not enabled"; + return false; + } + + crypto::public_key client; + if (!epee::string_tools::hex_to_pod(req.client.substr(0, 2 * sizeof(client)), client)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INVALID_CLIENT; + error_resp.message = "Invalid client ID"; + return false; + } + + res.credits = m_rpc_payment->balance(client, req.delta_balance); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ const command_line::arg_descriptor<std::string, false, true, 2> core_rpc_server::arg_rpc_bind_port = { "rpc-bind-port" , "Port for RPC server" @@ -2686,4 +3215,22 @@ namespace cryptonote , "Specify username:password for the bootstrap daemon login" , "" }; + + const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_payment_address = { + "rpc-payment-address" + , "Restrict RPC to clients sending micropayment to this address" + , "" + }; + + const command_line::arg_descriptor<uint64_t> core_rpc_server::arg_rpc_payment_difficulty = { + "rpc-payment-difficulty" + , "Restrict RPC to clients sending micropayment at this difficulty" + , DEFAULT_PAYMENT_DIFFICULTY + }; + + const command_line::arg_descriptor<uint64_t> core_rpc_server::arg_rpc_payment_credits = { + "rpc-payment-credits" + , "Restrict RPC to clients sending micropayment, yields that many credits per payment" + , DEFAULT_PAYMENT_CREDITS_PER_HASH + }; } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 379f6ed28..23c611470 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -42,6 +42,7 @@ #include "cryptonote_core/cryptonote_core.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" +#include "rpc_payment.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc" @@ -71,6 +72,9 @@ namespace cryptonote static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login; + static const command_line::arg_descriptor<std::string> arg_rpc_payment_address; + static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty; + static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits; typedef epee::net_utils::connection_context_base connection_context; @@ -78,6 +82,7 @@ namespace cryptonote core& cr , nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& p2p ); + ~core_rpc_server(); static void init_options(boost::program_options::options_description& desc); bool init( @@ -111,7 +116,7 @@ namespace cryptonote MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted) MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted) MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted) - MAP_URI_AUTO_JON2_IF("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES, !m_restricted) + MAP_URI_AUTO_JON2("/get_public_nodes", on_get_public_nodes, COMMAND_RPC_GET_PUBLIC_NODES) MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted) MAP_URI_AUTO_JON2_IF("/set_log_categories", on_set_log_categories, COMMAND_RPC_SET_LOG_CATEGORIES, !m_restricted) @@ -169,6 +174,13 @@ namespace cryptonote MAP_JON_RPC_WE("get_txpool_backlog", on_get_txpool_backlog, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG) MAP_JON_RPC_WE("get_output_distribution", on_get_output_distribution, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION) MAP_JON_RPC_WE_IF("prune_blockchain", on_prune_blockchain, COMMAND_RPC_PRUNE_BLOCKCHAIN, !m_restricted) + MAP_JON_RPC_WE_IF("flush_cache", on_flush_cache, COMMAND_RPC_FLUSH_CACHE, !m_restricted) + MAP_JON_RPC_WE("rpc_access_info", on_rpc_access_info, COMMAND_RPC_ACCESS_INFO) + MAP_JON_RPC_WE("rpc_access_submit_nonce",on_rpc_access_submit_nonce, COMMAND_RPC_ACCESS_SUBMIT_NONCE) + MAP_JON_RPC_WE("rpc_access_pay", on_rpc_access_pay, COMMAND_RPC_ACCESS_PAY) + MAP_JON_RPC_WE_IF("rpc_access_tracking", on_rpc_access_tracking, COMMAND_RPC_ACCESS_TRACKING, !m_restricted) + MAP_JON_RPC_WE_IF("rpc_access_data", on_rpc_access_data, COMMAND_RPC_ACCESS_DATA, !m_restricted) + MAP_JON_RPC_WE_IF("rpc_access_account", on_rpc_access_account, COMMAND_RPC_ACCESS_ACCOUNT, !m_restricted) END_JSON_RPC_MAP() END_URI_MAP2() @@ -236,12 +248,19 @@ namespace cryptonote bool on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_output_distribution(const COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request& req, COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_prune_blockchain(const COMMAND_RPC_PRUNE_BLOCKCHAIN::request& req, COMMAND_RPC_PRUNE_BLOCKCHAIN::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_flush_cache(const COMMAND_RPC_FLUSH_CACHE::request& req, COMMAND_RPC_FLUSH_CACHE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_info(const COMMAND_RPC_ACCESS_INFO::request& req, COMMAND_RPC_ACCESS_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_submit_nonce(const COMMAND_RPC_ACCESS_SUBMIT_NONCE::request& req, COMMAND_RPC_ACCESS_SUBMIT_NONCE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_pay(const COMMAND_RPC_ACCESS_PAY::request& req, COMMAND_RPC_ACCESS_PAY::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_tracking(const COMMAND_RPC_ACCESS_TRACKING::request& req, COMMAND_RPC_ACCESS_TRACKING::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_data(const COMMAND_RPC_ACCESS_DATA::request& req, COMMAND_RPC_ACCESS_DATA::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_rpc_access_account(const COMMAND_RPC_ACCESS_ACCOUNT::request& req, COMMAND_RPC_ACCESS_ACCOUNT::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); //----------------------- private: bool check_core_busy(); bool check_core_ready(); - bool add_host_fail(const connection_context *ctx); + bool add_host_fail(const connection_context *ctx, unsigned int score = 1); //utils uint64_t get_block_reward(const block& blk); @@ -252,6 +271,8 @@ private: enum invoke_http_mode { JON, BIN, JON_RPC }; template <typename COMMAND_TYPE> bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r); + bool get_block_template(const account_public_address &address, const crypto::hash *prev_block, const cryptonote::blobdata &extra_nonce, size_t &reserved_offset, cryptonote::difficulty_type &difficulty, uint64_t &height, uint64_t &expected_reward, block &b, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, epee::json_rpc::error &error_resp); + bool check_payment(const std::string &client, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash); core& m_core; nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >& m_p2p; @@ -260,10 +281,10 @@ private: bool m_should_use_bootstrap_daemon; std::chrono::system_clock::time_point m_bootstrap_height_check_time; bool m_was_bootstrap_ever_used; - network_type m_nettype; bool m_restricted; epee::critical_section m_host_fails_score_lock; std::map<std::string, uint64_t> m_host_fails_score; + std::unique_ptr<rpc_payment> m_rpc_payment; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 2760260f6..855ea854c 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -78,6 +78,7 @@ namespace cryptonote #define CORE_RPC_STATUS_OK "OK" #define CORE_RPC_STATUS_BUSY "BUSY" #define CORE_RPC_STATUS_NOT_MINING "NOT MINING" +#define CORE_RPC_STATUS_PAYMENT_REQUIRED "PAYMENT REQUIRED" // When making *any* change here, bump minor // If the change is incompatible, then bump major and set minor to 0 @@ -91,26 +92,67 @@ namespace cryptonote #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) + struct rpc_request_base + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct rpc_response_base + { + std::string status; + bool untrusted; + + rpc_response_base(): untrusted(false) {} + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(untrusted) + END_KV_SERIALIZE_MAP() + }; + + struct rpc_access_request_base: public rpc_request_base + { + std::string client; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(client) + END_KV_SERIALIZE_MAP() + }; + + struct rpc_access_response_base: public rpc_response_base + { + uint64_t credits; + std::string top_hash; + + rpc_access_response_base(): credits(0) {} + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(credits) + KV_SERIALIZE(top_hash) + END_KV_SERIALIZE_MAP() + }; + struct COMMAND_RPC_GET_HEIGHT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t height; - std::string status; - bool untrusted; std::string hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) KV_SERIALIZE(hash) END_KV_SERIALIZE_MAP() }; @@ -120,13 +162,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCKS_FAST { - struct request_t + struct request_t: public rpc_access_request_base { std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; bool prune; bool no_miner_tx; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(prune) @@ -153,22 +196,19 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<block_complete_entry> blocks; uint64_t start_height; uint64_t current_height; - std::string status; std::vector<block_output_indices> output_indices; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blocks) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) - KV_SERIALIZE(status) KV_SERIALIZE(output_indices) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -176,25 +216,23 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCKS_BY_HEIGHT { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> heights; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(heights) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<block_complete_entry> blocks; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blocks) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -202,23 +240,21 @@ namespace cryptonote struct COMMAND_RPC_GET_ALT_BLOCKS_HASHES { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<std::string> blks_hashes; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(blks_hashes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -226,31 +262,29 @@ namespace cryptonote struct COMMAND_RPC_GET_HASHES_FAST { - struct request_t + struct request_t: public rpc_access_request_base { std::list<crypto::hash> block_ids; //*first 10 blocks id goes sequential, next goes in pow(2,n) offset, like 2, 4, 8, 16, 32, 64 and so on, and the last one is always genesis block */ uint64_t start_height; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(block_ids) KV_SERIALIZE(start_height) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<crypto::hash> m_block_ids; uint64_t start_height; uint64_t current_height; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) KV_SERIALIZE(start_height) KV_SERIALIZE(current_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -288,7 +322,7 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_TRANSACTIONS { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> txs_hashes; bool decode_as_json; @@ -296,6 +330,7 @@ namespace cryptonote bool split; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(txs_hashes) KV_SERIALIZE(decode_as_json) KV_SERIALIZE_OPT(prune, false) @@ -341,7 +376,7 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { // older compatibility stuff std::vector<std::string> txs_as_hex; //transactions blobs as hex (old compat) @@ -352,16 +387,13 @@ namespace cryptonote // new style std::vector<entry> txs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(txs_as_hex) KV_SERIALIZE(txs_as_json) KV_SERIALIZE(txs) KV_SERIALIZE(missed_tx) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -376,27 +408,25 @@ namespace cryptonote SPENT_IN_POOL = 2, }; - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> key_images; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(key_images) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<int> spent_status; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(spent_status) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -405,25 +435,24 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES { - struct request_t + struct request_t: public rpc_access_request_base { crypto::hash txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_VAL_POD_AS_BLOB(txid) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<uint64_t> o_indexes; - std::string status; - bool untrusted; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(o_indexes) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -442,12 +471,13 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUTS_BIN { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<get_outputs_out> outputs; bool get_txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(outputs) KV_SERIALIZE_OPT(get_txid, true) END_KV_SERIALIZE_MAP() @@ -471,16 +501,13 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<outkey> outs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -488,12 +515,13 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_OUTPUTS { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<get_outputs_out> outputs; bool get_txid; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(outputs) KV_SERIALIZE(get_txid) END_KV_SERIALIZE_MAP() @@ -517,16 +545,13 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { std::vector<outkey> outs; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(outs) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -534,13 +559,14 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_SEND_RAW_TX { - struct request_t + struct request_t: public rpc_access_request_base { std::string tx_as_hex; bool do_not_relay; bool do_sanity_checks; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(tx_as_hex) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(do_sanity_checks, true) @@ -549,9 +575,8 @@ namespace cryptonote typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::string reason; bool not_relayed; bool low_mixin; @@ -564,10 +589,9 @@ namespace cryptonote bool not_rct; bool too_few_outputs; bool sanity_check_failed; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(reason) KV_SERIALIZE(not_relayed) KV_SERIALIZE(low_mixin) @@ -580,7 +604,6 @@ namespace cryptonote KV_SERIALIZE(not_rct) KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -588,7 +611,7 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_START_MINING { - struct request_t + struct request_t: public rpc_request_base { std::string miner_address; uint64_t threads_count; @@ -596,6 +619,7 @@ namespace cryptonote bool ignore_battery; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(miner_address) KV_SERIALIZE(threads_count) KV_SERIALIZE(do_background_mining) @@ -604,12 +628,10 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -617,17 +639,16 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_INFO { - struct request_t + struct request_t: public rpc_access_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t height; uint64_t target_height; uint64_t difficulty; @@ -657,7 +678,6 @@ namespace cryptonote uint64_t start_time; uint64_t free_space; bool offline; - bool untrusted; std::string bootstrap_daemon_address; uint64_t height_without_bootstrap; bool was_bootstrap_ever_used; @@ -666,7 +686,7 @@ namespace cryptonote std::string version; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(height) KV_SERIALIZE(target_height) KV_SERIALIZE(difficulty) @@ -696,7 +716,6 @@ namespace cryptonote KV_SERIALIZE(start_time) KV_SERIALIZE(free_space) KV_SERIALIZE(offline) - KV_SERIALIZE(untrusted) KV_SERIALIZE(bootstrap_daemon_address) KV_SERIALIZE(height_without_bootstrap) KV_SERIALIZE(was_bootstrap_ever_used) @@ -712,18 +731,17 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_GET_NET_STATS { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t start_time; uint64_t total_packets_in; uint64_t total_bytes_in; @@ -731,7 +749,7 @@ namespace cryptonote uint64_t total_bytes_out; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(start_time) KV_SERIALIZE(total_packets_in) KV_SERIALIZE(total_bytes_in) @@ -745,21 +763,19 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_STOP_MINING { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -768,18 +784,17 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_MINING_STATUS { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; bool active; uint64_t speed; uint32_t threads_count; @@ -797,7 +812,7 @@ namespace cryptonote uint64_t difficulty_top64; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(active) KV_SERIALIZE(speed) KV_SERIALIZE(threads_count) @@ -821,21 +836,19 @@ namespace cryptonote //----------------------------------------------- struct COMMAND_RPC_SAVE_BC { - struct request_t + struct request_t: public rpc_request_base { - BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -846,14 +859,13 @@ namespace cryptonote { typedef std::list<std::string> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t count; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(count) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -869,7 +881,7 @@ namespace cryptonote struct COMMAND_RPC_GETBLOCKTEMPLATE { - struct request_t + struct request_t: public rpc_request_base { uint64_t reserve_size; //max 255 bytes std::string wallet_address; @@ -877,6 +889,7 @@ namespace cryptonote std::string extra_nonce; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(reserve_size) KV_SERIALIZE(wallet_address) KV_SERIALIZE(prev_block) @@ -885,7 +898,7 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t difficulty; std::string wide_difficulty; @@ -894,14 +907,14 @@ namespace cryptonote uint64_t reserved_offset; uint64_t expected_reward; std::string prev_hash; + uint64_t seed_height; std::string seed_hash; std::string next_seed_hash; blobdata blocktemplate_blob; blobdata blockhashing_blob; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(difficulty) KV_SERIALIZE(wide_difficulty) KV_SERIALIZE(difficulty_top64) @@ -909,10 +922,9 @@ namespace cryptonote KV_SERIALIZE(reserved_offset) KV_SERIALIZE(expected_reward) KV_SERIALIZE(prev_hash) + KV_SERIALIZE(seed_height) KV_SERIALIZE(blocktemplate_blob) KV_SERIALIZE(blockhashing_blob) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) KV_SERIALIZE(seed_hash) KV_SERIALIZE(next_seed_hash) END_KV_SERIALIZE_MAP() @@ -924,12 +936,10 @@ namespace cryptonote { typedef std::vector<std::string> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -937,7 +947,7 @@ namespace cryptonote struct COMMAND_RPC_GENERATEBLOCKS { - struct request_t + struct request_t: public rpc_request_base { uint64_t amount_of_blocks; std::string wallet_address; @@ -945,6 +955,7 @@ namespace cryptonote uint32_t starting_nonce; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(amount_of_blocks) KV_SERIALIZE(wallet_address) KV_SERIALIZE(prev_block) @@ -953,16 +964,15 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint64_t height; std::vector<std::string> blocks; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) KV_SERIALIZE(blocks) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1021,26 +1031,24 @@ namespace cryptonote struct COMMAND_RPC_GET_LAST_BLOCK_HEADER { - struct request_t + struct request_t: public rpc_access_request_base { bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1049,13 +1057,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH { - struct request_t + struct request_t: public rpc_access_request_base { std::string hash; std::vector<std::string> hashes; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(hashes) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1063,18 +1072,15 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; std::vector<block_header_response> block_headers; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) KV_SERIALIZE(block_headers) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1082,28 +1088,26 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(height) KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1111,13 +1115,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK { - struct request_t + struct request_t: public rpc_access_request_base { std::string hash; uint64_t height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(hash) KV_SERIALIZE(height) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1125,24 +1130,21 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; block_header_response block_header; std::string miner_tx_hash; std::vector<std::string> tx_hashes; std::string blob; std::string json; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(block_header) KV_SERIALIZE(miner_tx_hash) KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(status) KV_SERIALIZE(blob) KV_SERIALIZE(json) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1154,19 +1156,20 @@ namespace cryptonote uint32_t ip; uint16_t port; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; uint64_t last_seen; uint32_t pruning_seed; peer() = default; - peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, const std::string &host, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(host), ip(0), port(0), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} - peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, const std::string &host, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(host), ip(0), port(port), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} - peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port) - : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), last_seen(last_seen), pruning_seed(pruning_seed) + peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash) + : id(id), host(epee::string_tools::get_ip_string_from_int32(ip)), ip(ip), port(port), rpc_port(rpc_port), rpc_credits_per_hash(rpc_credits_per_hash), last_seen(last_seen), pruning_seed(pruning_seed) {} BEGIN_KV_SERIALIZE_MAP() @@ -1175,6 +1178,7 @@ namespace cryptonote KV_SERIALIZE(ip) KV_SERIALIZE(port) KV_SERIALIZE_OPT(rpc_port, (uint16_t)0) + KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0) KV_SERIALIZE(last_seen) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) END_KV_SERIALIZE_MAP() @@ -1182,24 +1186,24 @@ namespace cryptonote struct COMMAND_RPC_GET_PEER_LIST { - struct request_t + struct request_t: public rpc_request_base { bool public_only; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(public_only, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<peer> white_list; std::vector<peer> gray_list; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(white_list) KV_SERIALIZE(gray_list) END_KV_SERIALIZE_MAP() @@ -1212,42 +1216,44 @@ namespace cryptonote std::string host; uint64_t last_seen; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; - public_node() = delete; + public_node(): last_seen(0), rpc_port(0), rpc_credits_per_hash(0) {} public_node(const peer &peer) - : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port) + : host(peer.host), last_seen(peer.last_seen), rpc_port(peer.rpc_port), rpc_credits_per_hash(peer.rpc_credits_per_hash) {} BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(host) KV_SERIALIZE(last_seen) KV_SERIALIZE(rpc_port) + KV_SERIALIZE(rpc_credits_per_hash) END_KV_SERIALIZE_MAP() }; struct COMMAND_RPC_GET_PUBLIC_NODES { - struct request_t + struct request_t: public rpc_request_base { bool gray; bool white; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(gray, false) KV_SERIALIZE_OPT(white, true) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<public_node> gray; std::vector<public_node> white; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(gray) KV_SERIALIZE(white) END_KV_SERIALIZE_MAP() @@ -1257,21 +1263,21 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_HASH_RATE { - struct request_t + struct request_t: public rpc_request_base { bool visible; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(visible) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1279,21 +1285,21 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_LEVEL { - struct request_t + struct request_t: public rpc_request_base { int8_t level; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(level) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1301,23 +1307,23 @@ namespace cryptonote struct COMMAND_RPC_SET_LOG_CATEGORIES { - struct request_t + struct request_t: public rpc_request_base { std::string categories; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(categories) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::string categories; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(categories) END_KV_SERIALIZE_MAP() }; @@ -1376,25 +1382,23 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<tx_info> transactions; std::vector<spent_key_image_info> spent_key_images; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(transactions) KV_SERIALIZE(spent_key_images) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1402,23 +1406,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_HASHES_BIN { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<crypto::hash> tx_hashes; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(tx_hashes) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1426,23 +1428,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_HASHES { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<std::string> tx_hashes; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(tx_hashes) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1457,23 +1457,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<tx_backlog_entry> backlog; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(backlog) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1527,23 +1525,21 @@ namespace cryptonote struct COMMAND_RPC_GET_TRANSACTION_POOL_STATS { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; txpool_stats pool_stats; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(pool_stats) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1551,20 +1547,20 @@ namespace cryptonote struct COMMAND_RPC_GET_CONNECTIONS { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::list<connection_info> connections; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(connections) END_KV_SERIALIZE_MAP() }; @@ -1573,13 +1569,14 @@ namespace cryptonote struct COMMAND_RPC_GET_BLOCK_HEADERS_RANGE { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t start_height; uint64_t end_height; bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(start_height) KV_SERIALIZE(end_height) KV_SERIALIZE_OPT(fill_pow_hash, false); @@ -1587,16 +1584,13 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<block_header_response> headers; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(headers) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1631,19 +1625,18 @@ namespace cryptonote struct COMMAND_RPC_STOP_DAEMON { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1651,19 +1644,18 @@ namespace cryptonote struct COMMAND_RPC_FAST_EXIT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1671,25 +1663,23 @@ namespace cryptonote struct COMMAND_RPC_GET_LIMIT { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t limit_up; uint64_t limit_down; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(limit_up) KV_SERIALIZE(limit_down) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1697,26 +1687,26 @@ namespace cryptonote struct COMMAND_RPC_SET_LIMIT { - struct request_t + struct request_t: public rpc_request_base { int64_t limit_down; // all limits (for get and set) are kB/s int64_t limit_up; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(limit_down) KV_SERIALIZE(limit_up) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; int64_t limit_up; int64_t limit_down; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(limit_up) KV_SERIALIZE(limit_down) END_KV_SERIALIZE_MAP() @@ -1726,25 +1716,26 @@ namespace cryptonote struct COMMAND_RPC_OUT_PEERS { - struct request_t + struct request_t: public rpc_request_base { bool set; uint32_t out_peers; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(set, true) KV_SERIALIZE(out_peers) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint32_t out_peers; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(out_peers) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1752,25 +1743,25 @@ namespace cryptonote struct COMMAND_RPC_IN_PEERS { - struct request_t + struct request_t: public rpc_request_base { bool set; uint32_t in_peers; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(set, true) KV_SERIALIZE(in_peers) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { uint32_t in_peers; - std::string status; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(in_peers) - KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1778,17 +1769,18 @@ namespace cryptonote struct COMMAND_RPC_HARD_FORK_INFO { - struct request_t + struct request_t: public rpc_access_request_base { uint8_t version; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(version) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { uint8_t version; bool enabled; @@ -1798,10 +1790,9 @@ namespace cryptonote uint8_t voting; uint32_t state; uint64_t earliest_height; - std::string status; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(version) KV_SERIALIZE(enabled) KV_SERIALIZE(window) @@ -1810,8 +1801,6 @@ namespace cryptonote KV_SERIALIZE(voting) KV_SERIALIZE(state) KV_SERIALIZE(earliest_height) - KV_SERIALIZE(status) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1832,20 +1821,20 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<ban> bans; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(bans) END_KV_SERIALIZE_MAP() }; @@ -1869,22 +1858,21 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct request_t + struct request_t: public rpc_request_base { std::vector<ban> bans; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(bans) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1919,22 +1907,21 @@ namespace cryptonote struct COMMAND_RPC_FLUSH_TRANSACTION_POOL { - struct request_t + struct request_t: public rpc_request_base { std::vector<std::string> txids; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE(txids) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1942,7 +1929,7 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUT_HISTOGRAM { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> amounts; uint64_t min_count; @@ -1951,6 +1938,7 @@ namespace cryptonote uint64_t recent_cutoff; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(amounts); KV_SERIALIZE(min_count); KV_SERIALIZE(max_count); @@ -1979,16 +1967,13 @@ namespace cryptonote entry() {} }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<entry> histogram; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(histogram) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1996,25 +1981,23 @@ namespace cryptonote struct COMMAND_RPC_GET_VERSION { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint32_t version; bool release; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(version) KV_SERIALIZE(release) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2022,26 +2005,26 @@ namespace cryptonote struct COMMAND_RPC_GET_COINBASE_TX_SUM { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t height; uint64_t count; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base); KV_SERIALIZE(height); KV_SERIALIZE(count); END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t emission_amount; uint64_t fee_amount; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(emission_amount) KV_SERIALIZE(fee_amount) END_KV_SERIALIZE_MAP() @@ -2051,28 +2034,26 @@ namespace cryptonote struct COMMAND_RPC_GET_BASE_FEE_ESTIMATE { - struct request_t + struct request_t: public rpc_access_request_base { uint64_t grace_blocks; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(grace_blocks) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t fee; uint64_t quantization_mask; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(fee) KV_SERIALIZE_OPT(quantization_mask, (uint64_t)1) - KV_SERIALIZE(untrusted) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2080,9 +2061,10 @@ namespace cryptonote struct COMMAND_RPC_GET_ALTERNATE_CHAINS { - struct request_t + struct request_t: public rpc_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2110,13 +2092,12 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_response_base { - std::string status; std::vector<chain_info> chains; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(chains) END_KV_SERIALIZE_MAP() }; @@ -2125,21 +2106,21 @@ namespace cryptonote struct COMMAND_RPC_UPDATE { - struct request_t + struct request_t: public rpc_request_base { std::string command; std::string path; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(command); - KV_SERIALIZE(path); + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(command) + KV_SERIALIZE(path) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; bool update; std::string version; std::string user_uri; @@ -2148,7 +2129,7 @@ namespace cryptonote std::string path; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(update) KV_SERIALIZE(version) KV_SERIALIZE(user_uri) @@ -2162,22 +2143,21 @@ namespace cryptonote struct COMMAND_RPC_RELAY_TX { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<std::string> txids; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(txids) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2185,9 +2165,10 @@ namespace cryptonote struct COMMAND_RPC_SYNC_INFO { - struct request_t + struct request_t: public rpc_access_request_base { BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2222,9 +2203,8 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; uint64_t height; uint64_t target_height; uint32_t next_needed_pruning_seed; @@ -2233,7 +2213,7 @@ namespace cryptonote std::string overview; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(height) KV_SERIALIZE(target_height) KV_SERIALIZE(next_needed_pruning_seed) @@ -2247,7 +2227,7 @@ namespace cryptonote struct COMMAND_RPC_GET_OUTPUT_DISTRIBUTION { - struct request_t + struct request_t: public rpc_access_request_base { std::vector<uint64_t> amounts; uint64_t from_height; @@ -2257,6 +2237,7 @@ namespace cryptonote bool compress; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) KV_SERIALIZE(amounts) KV_SERIALIZE_OPT(from_height, (uint64_t)0) KV_SERIALIZE_OPT(to_height, (uint64_t)0) @@ -2309,16 +2290,213 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; - struct response_t + struct response_t: public rpc_access_response_base { - std::string status; std::vector<distribution> distributions; - bool untrusted; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(distributions) - KV_SERIALIZE(untrusted) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_INFO + { + struct request_t: public rpc_access_request_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + std::string hashing_blob; + uint64_t seed_height; + std::string seed_hash; + std::string next_seed_hash; + uint32_t cookie; + uint64_t diff; + uint64_t credits_per_hash_found; + uint64_t height; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + KV_SERIALIZE(hashing_blob) + KV_SERIALIZE(seed_height) + KV_SERIALIZE(seed_hash) + KV_SERIALIZE(next_seed_hash) + KV_SERIALIZE(cookie) + KV_SERIALIZE(diff) + KV_SERIALIZE(credits_per_hash_found) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_SUBMIT_NONCE + { + struct request_t: public rpc_access_request_base + { + uint32_t nonce; + uint32_t cookie; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(nonce) + KV_SERIALIZE(cookie) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_PAY + { + struct request_t: public rpc_access_request_base + { + std::string paying_for; + uint64_t payment; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_request_base) + KV_SERIALIZE(paying_for) + KV_SERIALIZE(payment) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_access_response_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_access_response_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_TRACKING + { + struct request_t: public rpc_request_base + { + bool clear; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(clear) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct entry + { + std::string rpc; + uint64_t count; + uint64_t time; + uint64_t credits; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(rpc) + KV_SERIALIZE(count) + KV_SERIALIZE(time) + KV_SERIALIZE(credits) + END_KV_SERIALIZE_MAP() + }; + + struct response_t: public rpc_response_base + { + std::vector<entry> data; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(data) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_DATA + { + struct request_t: public rpc_request_base + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct entry + { + std::string client; + uint64_t balance; + uint64_t last_update_time; + uint64_t credits_total; + uint64_t credits_used; + uint64_t nonces_good; + uint64_t nonces_stale; + uint64_t nonces_bad; + uint64_t nonces_dupe; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(client) + KV_SERIALIZE(balance) + KV_SERIALIZE(last_update_time) + KV_SERIALIZE(credits_total) + KV_SERIALIZE(credits_used) + KV_SERIALIZE(nonces_good) + KV_SERIALIZE(nonces_stale) + KV_SERIALIZE(nonces_bad) + KV_SERIALIZE(nonces_dupe) + END_KV_SERIALIZE_MAP() + }; + + struct response_t: public rpc_response_base + { + std::list<entry> entries; + uint32_t hashrate; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(entries) + KV_SERIALIZE(hashrate) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_ACCESS_ACCOUNT + { + struct request_t: public rpc_request_base + { + std::string client; + int64_t delta_balance; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(client) + KV_SERIALIZE_OPT(delta_balance, (int64_t)0) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_response_base + { + uint64_t credits; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(credits) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2326,23 +2504,23 @@ namespace cryptonote struct COMMAND_RPC_POP_BLOCKS { - struct request_t + struct request_t: public rpc_request_base { uint64_t nblocks; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(nblocks); + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(nblocks) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; uint64_t height; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(height) END_KV_SERIALIZE_MAP() }; @@ -2351,24 +2529,24 @@ namespace cryptonote struct COMMAND_RPC_PRUNE_BLOCKCHAIN { - struct request_t + struct request_t: public rpc_request_base { bool check; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(check, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { bool pruned; uint32_t pruning_seed; - std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(pruned) KV_SERIALIZE(pruning_seed) END_KV_SERIALIZE_MAP() @@ -2376,4 +2554,27 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_FLUSH_CACHE + { + struct request_t + { + bool bad_txs; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(bad_txs, false) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + } diff --git a/src/rpc/core_rpc_server_error_codes.h b/src/rpc/core_rpc_server_error_codes.h index b13049e61..2fd42f43f 100644 --- a/src/rpc/core_rpc_server_error_codes.h +++ b/src/rpc/core_rpc_server_error_codes.h @@ -43,5 +43,34 @@ #define CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC -11 #define CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS -12 #define CORE_RPC_ERROR_CODE_REGTEST_REQUIRED -13 +#define CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED -14 +#define CORE_RPC_ERROR_CODE_INVALID_CLIENT -15 +#define CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW -16 +#define CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT -17 +#define CORE_RPC_ERROR_CODE_STALE_PAYMENT -18 +static inline const char *get_rpc_server_error_message(int64_t code) +{ + switch (code) + { + case CORE_RPC_ERROR_CODE_WRONG_PARAM: return "Invalid parameter"; + case CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT: return "Height is too large"; + case CORE_RPC_ERROR_CODE_TOO_BIG_RESERVE_SIZE: return "Reserve size is too large"; + case CORE_RPC_ERROR_CODE_WRONG_WALLET_ADDRESS: return "Wrong wallet address"; + case CORE_RPC_ERROR_CODE_INTERNAL_ERROR: return "Internal error"; + case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB: return "Wrong block blob"; + case CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED: return "Block not accepted"; + case CORE_RPC_ERROR_CODE_CORE_BUSY: return "Core is busy"; + case CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB_SIZE: return "Wrong block blob size"; + case CORE_RPC_ERROR_CODE_UNSUPPORTED_RPC: return "Unsupported RPC"; + case CORE_RPC_ERROR_CODE_MINING_TO_SUBADDRESS: return "Mining to subaddress is not supported"; + case CORE_RPC_ERROR_CODE_REGTEST_REQUIRED: return "Regtest mode required"; + case CORE_RPC_ERROR_CODE_PAYMENT_REQUIRED: return "Payment required"; + case CORE_RPC_ERROR_CODE_INVALID_CLIENT: return "Invalid client"; + case CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW: return "Payment too low"; + case CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT: return "Duplicate payment"; + case CORE_RPC_ERROR_CODE_STALE_PAYMENT: return "Stale payment"; + default: MERROR("Unknown error: " << code); return "Unknown error"; + } +} diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 890380dc8..d7e081af3 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -141,7 +141,7 @@ namespace rpc auto& chain = m_core.get_blockchain_storage(); - if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, res.start_height, res.current_height, false)) + if (!chain.find_blockchain_supplement(req.known_hashes, res.hashes, NULL, res.start_height, res.current_height, false)) { res.status = Message::STATUS_FAILED; res.error_details = "Blockchain::find_blockchain_supplement() returned false"; @@ -291,7 +291,7 @@ namespace rpc cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx(tx_blob, tvc, false, false, !relay) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, !relay) || tvc.m_verifivation_failed) { if (tvc.m_verifivation_failed) { diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index 2a43811cf..e64f5f163 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -80,6 +80,7 @@ namespace rpc uint32_t ip; uint16_t port; uint16_t rpc_port; + uint32_t rpc_credits_per_hash; uint64_t last_seen; uint32_t pruning_seed; }; diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 68b33cb8c..0eaa0ef0e 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -92,7 +92,7 @@ namespace cryptonote : rpc_bind_ip({"rpc-bind-ip", rpc_args::tr("Specify IP to bind RPC server"), "127.0.0.1"}) , rpc_bind_ipv6_address({"rpc-bind-ipv6-address", rpc_args::tr("Specify IPv6 address to bind RPC server"), "::1"}) , rpc_use_ipv6({"rpc-use-ipv6", rpc_args::tr("Allow IPv6 for RPC"), false}) - , rpc_require_ipv4({"rpc-require-ipv4", rpc_args::tr("Require successful IPv4 bind for RPC"), true}) + , rpc_ignore_ipv4({"rpc-ignore-ipv4", rpc_args::tr("Ignore unsuccessful IPv4 bind for RPC"), false}) , rpc_login({"rpc-login", rpc_args::tr("Specify username[:password] required for RPC server"), "", true}) , confirm_external_bind({"confirm-external-bind", rpc_args::tr("Confirm rpc-bind-ip value is NOT a loopback (local) IP")}) , rpc_access_control_origins({"rpc-access-control-origins", rpc_args::tr("Specify a comma separated list of origins to allow cross origin resource sharing"), ""}) @@ -113,7 +113,7 @@ namespace cryptonote command_line::add_arg(desc, arg.rpc_bind_ip); command_line::add_arg(desc, arg.rpc_bind_ipv6_address); command_line::add_arg(desc, arg.rpc_use_ipv6); - command_line::add_arg(desc, arg.rpc_require_ipv4); + command_line::add_arg(desc, arg.rpc_ignore_ipv4); command_line::add_arg(desc, arg.rpc_login); command_line::add_arg(desc, arg.confirm_external_bind); command_line::add_arg(desc, arg.rpc_access_control_origins); @@ -135,7 +135,7 @@ namespace cryptonote config.bind_ip = command_line::get_arg(vm, arg.rpc_bind_ip); config.bind_ipv6_address = command_line::get_arg(vm, arg.rpc_bind_ipv6_address); config.use_ipv6 = command_line::get_arg(vm, arg.rpc_use_ipv6); - config.require_ipv4 = command_line::get_arg(vm, arg.rpc_require_ipv4); + config.require_ipv4 = !command_line::get_arg(vm, arg.rpc_ignore_ipv4); if (!config.bind_ip.empty()) { // always parse IP here for error consistency diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index cd154a4d0..bdb9c70d5 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -54,7 +54,7 @@ namespace cryptonote const command_line::arg_descriptor<std::string> rpc_bind_ip; const command_line::arg_descriptor<std::string> rpc_bind_ipv6_address; const command_line::arg_descriptor<bool> rpc_use_ipv6; - const command_line::arg_descriptor<bool> rpc_require_ipv4; + const command_line::arg_descriptor<bool> rpc_ignore_ipv4; const command_line::arg_descriptor<std::string> rpc_login; const command_line::arg_descriptor<bool> confirm_external_bind; const command_line::arg_descriptor<std::string> rpc_access_control_origins; diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp new file mode 100644 index 000000000..0637db728 --- /dev/null +++ b/src/rpc/rpc_payment.cpp @@ -0,0 +1,402 @@ +// Copyright (c) 2018-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/archive/portable_binary_oarchive.hpp> +#include "cryptonote_config.h" +#include "include_base_utils.h" +#include "string_tools.h" +#include "file_io_utils.h" +#include "int-util.h" +#include "common/util.h" +#include "serialization/crypto.h" +#include "common/unordered_containers_boost_serialization.h" +#include "cryptonote_basic/cryptonote_boost_serialization.h" +#include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_basic/difficulty.h" +#include "core_rpc_server_error_codes.h" +#include "rpc_payment.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment" + +#define STALE_THRESHOLD 15 /* seconds */ + +#define PENALTY_FOR_STALE 0 +#define PENALTY_FOR_BAD_HASH 20 +#define PENALTY_FOR_DUPLICATE 20 + +#define DEFAULT_FLUSH_AGE (3600 * 24 * 180) // half a year +#define DEFAULT_ZERO_FLUSH_AGE (60 * 2) // 2 minutes + +#define RPC_PAYMENT_NONCE_TAIL 0x58 + +namespace cryptonote +{ + rpc_payment::client_info::client_info(): + cookie(0), + top(crypto::null_hash), + previous_top(crypto::null_hash), + credits(0), + update_time(time(NULL)), + last_request_timestamp(0), + block_template_update_time(0), + credits_total(0), + credits_used(0), + nonces_good(0), + nonces_stale(0), + nonces_bad(0), + nonces_dupe(0) + { + } + + rpc_payment::rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found): + m_address(address), + m_diff(diff), + m_credits_per_hash_found(credits_per_hash_found), + m_credits_total(0), + m_credits_used(0), + m_nonces_good(0), + m_nonces_stale(0), + m_nonces_bad(0), + m_nonces_dupe(0) + { + } + + uint64_t rpc_payment::balance(const crypto::public_key &client, int64_t delta) + { + client_info &info = m_client_info[client]; // creates if not found + uint64_t credits = info.credits; + if (delta > 0 && credits > std::numeric_limits<uint64_t>::max() - delta) + credits = std::numeric_limits<uint64_t>::max(); + else if (delta < 0 && credits < (uint64_t)-delta) + credits = 0; + else + credits += delta; + if (delta) + MINFO("Client " << client << ": balance change from " << info.credits << " to " << credits); + return info.credits = credits; + } + + bool rpc_payment::pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits) + { + client_info &info = m_client_info[client]; // creates if not found + if (ts < info.last_request_timestamp || (ts == info.last_request_timestamp && !same_ts)) + { + MDEBUG("Invalid ts: " << ts << " <= " << info.last_request_timestamp); + return false; + } + info.last_request_timestamp = ts; + if (info.credits < payment) + { + MDEBUG("Not enough credits: " << info.credits << " < " << payment); + credits = info.credits; + return false; + } + info.credits -= payment; + add64clamp(&info.credits_used, payment); + add64clamp(&m_credits_used, payment); + MDEBUG("client " << client << " paying " << payment << " for " << rpc << ", " << info.credits << " left"); + credits = info.credits; + return true; + } + + bool rpc_payment::get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie) + { + client_info &info = m_client_info[client]; // creates if not found + const uint64_t now = time(NULL); + bool need_template = top != info.top || now >= info.block_template_update_time + STALE_THRESHOLD; + if (need_template) + { + cryptonote::block new_block; + uint64_t new_seed_height; + crypto::hash new_seed_hash; + cryptonote::blobdata extra_nonce("\x42\x42\x42\x42", 4); + if (!get_block_template(extra_nonce, new_block, new_seed_height, new_seed_hash)) + return false; + if(!remove_field_from_tx_extra(new_block.miner_tx.extra, typeid(cryptonote::tx_extra_nonce))) + return false; + char data[33]; + memcpy(data, &client, 32); + data[32] = RPC_PAYMENT_NONCE_TAIL; + crypto::hash hash; + cn_fast_hash(data, sizeof(data), hash); + extra_nonce = cryptonote::blobdata((const char*)&hash, 4); + if(!add_extra_nonce_to_tx_extra(new_block.miner_tx.extra, extra_nonce)) + return false; + info.previous_block = std::move(info.block); + info.block = std::move(new_block); + hashing_blob = get_block_hashing_blob(info.block); + info.previous_hashing_blob = info.hashing_blob; + info.hashing_blob = hashing_blob; + info.previous_top = info.top; + info.previous_seed_height = info.seed_height; + info.seed_height = new_seed_height; + info.previous_seed_hash = info.seed_hash; + info.seed_hash = new_seed_hash; + std::swap(info.previous_payments, info.payments); + info.payments.clear(); + ++info.cookie; + info.block_template_update_time = now; + } + info.top = top; + info.update_time = now; + hashing_blob = info.hashing_blob; + diff = m_diff; + credits_per_hash_found = m_credits_per_hash_found; + credits = info.credits; + seed_height = info.seed_height; + seed_hash = info.seed_hash; + cookie = info.cookie; + return true; + } + + bool rpc_payment::submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale) + { + client_info &info = m_client_info[client]; // creates if not found + if (cookie != info.cookie && cookie != info.cookie - 1) + { + MWARNING("Very stale nonce"); + ++m_nonces_stale; + ++info.nonces_stale; + sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found); + error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT; + error_message = "Very stale payment"; + return false; + } + const bool is_current = cookie == info.cookie; + MINFO("client " << client << " sends nonce: " << nonce << ", " << (is_current ? "current" : "stale")); + std::unordered_set<uint64_t> &payments = is_current ? info.payments : info.previous_payments; + if (!payments.insert(nonce).second) + { + MWARNING("Duplicate nonce " << nonce << " from " << (is_current ? "current" : "previous")); + ++m_nonces_dupe; + ++info.nonces_dupe; + sub64clamp(&info.credits, PENALTY_FOR_DUPLICATE * m_credits_per_hash_found); + error_code = CORE_RPC_ERROR_CODE_DUPLICATE_PAYMENT; + error_message = "Duplicate payment"; + return false; + } + + const uint64_t now = time(NULL); + if (!is_current) + { + if (now > info.update_time + STALE_THRESHOLD) + { + MWARNING("Nonce is stale (top " << top << ", should be " << info.top << " or within " << STALE_THRESHOLD << " seconds"); + ++m_nonces_stale; + ++info.nonces_stale; + sub64clamp(&info.credits, PENALTY_FOR_STALE * m_credits_per_hash_found); + error_code = CORE_RPC_ERROR_CODE_STALE_PAYMENT; + error_message = "stale payment"; + return false; + } + } + + cryptonote::blobdata hashing_blob = is_current ? info.hashing_blob : info.previous_hashing_blob; + if (hashing_blob.size() < 43) + { + // not initialized ? + error_code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_message = "not initialized"; + return false; + } + + block = is_current ? info.block : info.previous_block; + *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(nonce); + if (block.major_version >= RX_BLOCK_VERSION) + { + const uint64_t seed_height = is_current ? info.seed_height : info.previous_seed_height; + const crypto::hash &seed_hash = is_current ? info.seed_hash : info.previous_seed_hash; + const uint64_t height = cryptonote::get_block_height(block); + crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, 0, 0); + } + else + { + const int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0; + crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, cryptonote::get_block_height(block)); + } + if (!check_hash(hash, m_diff)) + { + MWARNING("Payment too low"); + ++m_nonces_bad; + ++info.nonces_bad; + error_code = CORE_RPC_ERROR_CODE_PAYMENT_TOO_LOW; + error_message = "Hash does not meet difficulty (could be wrong PoW hash, or mining at lower difficulty than required, or attempt to defraud)"; + sub64clamp(&info.credits, PENALTY_FOR_BAD_HASH * m_credits_per_hash_found); + return false; + } + + add64clamp(&info.credits, m_credits_per_hash_found); + MINFO("client " << client << " credited for " << m_credits_per_hash_found << ", now " << info.credits << (is_current ? "" : " (close)")); + + m_hashrate[now] += m_diff; + add64clamp(&m_credits_total, m_credits_per_hash_found); + add64clamp(&info.credits_total, m_credits_per_hash_found); + ++m_nonces_good; + ++info.nonces_good; + + credits = info.credits; + block = info.block; + block.nonce = nonce; + stale = !is_current; + return true; + } + + bool rpc_payment::foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const + { + for (std::unordered_map<crypto::public_key, client_info>::const_iterator i = m_client_info.begin(); i != m_client_info.end(); ++i) + { + if (!f(i->first, i->second)) + return false; + } + return true; + } + + bool rpc_payment::load(std::string directory) + { + TRY_ENTRY(); + m_directory = std::move(directory); + std::string state_file_path = directory + "/" + RPC_PAYMENTS_DATA_FILENAME; + MINFO("loading rpc payments data from " << state_file_path); + std::ifstream data; + data.open(state_file_path, std::ios_base::binary | std::ios_base::in); + if (!data.fail()) + { + try + { + boost::archive::portable_binary_iarchive a(data); + a >> *this; + } + catch (const std::exception &e) + { + MERROR("Failed to load RPC payments file: " << e.what()); + m_client_info.clear(); + } + } + else + { + m_client_info.clear(); + } + + CATCH_ENTRY_L0("rpc_payment::load", false); + return true; + } + + bool rpc_payment::store(const std::string &directory_) const + { + TRY_ENTRY(); + const std::string &directory = directory_.empty() ? m_directory : directory_; + MDEBUG("storing rpc payments data to " << directory); + if (!tools::create_directories_if_necessary(directory)) + { + MWARNING("Failed to create data directory: " << directory); + return false; + } + const boost::filesystem::path state_file_path = (boost::filesystem::path(directory) / RPC_PAYMENTS_DATA_FILENAME); + if (boost::filesystem::exists(state_file_path)) + { + std::string state_file_path_old = state_file_path.string() + ".old"; + boost::system::error_code ec; + boost::filesystem::remove(state_file_path_old, ec); + std::error_code e = tools::replace_file(state_file_path.string(), state_file_path_old); + if (e) + MWARNING("Failed to rename " << state_file_path << " to " << state_file_path_old << ": " << e); + } + std::ofstream data; + data.open(state_file_path.string(), std::ios_base::binary | std::ios_base::out | std::ios::trunc); + if (data.fail()) + { + MWARNING("Failed to save RPC payments to file " << state_file_path); + return false; + }; + boost::archive::portable_binary_oarchive a(data); + a << *this; + return true; + CATCH_ENTRY_L0("rpc_payment::store", false); + } + + unsigned int rpc_payment::flush_by_age(time_t seconds) + { + unsigned int count = 0; + const time_t now = time(NULL); + time_t seconds0 = seconds; + if (seconds == 0) + { + seconds = DEFAULT_FLUSH_AGE; + seconds0 = DEFAULT_ZERO_FLUSH_AGE; + } + const time_t threshold = seconds > now ? 0 : now - seconds; + const time_t threshold0 = seconds0 > now ? 0 : now - seconds0; + for (std::unordered_map<crypto::public_key, client_info>::iterator i = m_client_info.begin(); i != m_client_info.end(); ) + { + std::unordered_map<crypto::public_key, client_info>::iterator j = i++; + const time_t t = std::max(j->second.last_request_timestamp, j->second.update_time); + const bool erase = t < ((j->second.credits == 0) ? threshold0 : threshold); + if (erase) + { + MINFO("Erasing " << j->first << " with " << j->second.credits << " credits, inactive for " << (now-t)/86400 << " days"); + m_client_info.erase(j); + ++count; + } + } + return count; + } + + uint64_t rpc_payment::get_hashes(unsigned int seconds) const + { + const uint64_t now = time(NULL); + uint64_t hashes = 0; + for (std::map<uint64_t, uint64_t>::const_reverse_iterator i = m_hashrate.crbegin(); i != m_hashrate.crend(); ++i) + { + if (now > i->first + seconds) + break; + hashes += i->second; + } + return hashes; + } + + void rpc_payment::prune_hashrate(unsigned int seconds) + { + const uint64_t now = time(NULL); + std::map<uint64_t, uint64_t>::iterator i; + for (i = m_hashrate.begin(); i != m_hashrate.end(); ++i) + { + if (now <= i->first + seconds) + break; + } + m_hashrate.erase(m_hashrate.begin(), i); + } + + bool rpc_payment::on_idle() + { + flush_by_age(); + prune_hashrate(3600); + return true; + } +} diff --git a/src/rpc/rpc_payment.h b/src/rpc/rpc_payment.h new file mode 100644 index 000000000..f6832fd34 --- /dev/null +++ b/src/rpc/rpc_payment.h @@ -0,0 +1,146 @@ +// Copyright (c) 2018-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> +#include <unordered_set> +#include <unordered_map> +#include <boost/serialization/version.hpp> +#include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_basic/cryptonote_basic.h" + +namespace cryptonote +{ + class rpc_payment + { + public: + struct client_info + { + cryptonote::block block; + cryptonote::block previous_block; + cryptonote::blobdata hashing_blob; + cryptonote::blobdata previous_hashing_blob; + uint64_t previous_seed_height; + uint64_t seed_height; + crypto::hash previous_seed_hash; + crypto::hash seed_hash; + uint32_t cookie; + crypto::hash top; + crypto::hash previous_top; + uint64_t credits; + std::unordered_set<uint64_t> payments; + std::unordered_set<uint64_t> previous_payments; + uint64_t update_time; + uint64_t last_request_timestamp; + uint64_t block_template_update_time; + uint64_t credits_total; + uint64_t credits_used; + uint64_t nonces_good; + uint64_t nonces_stale; + uint64_t nonces_bad; + uint64_t nonces_dupe; + + client_info(); + + template <class t_archive> + inline void serialize(t_archive &a, const unsigned int ver) + { + a & block; + a & previous_block; + a & hashing_blob; + a & previous_hashing_blob; + a & seed_height; + a & previous_seed_height; + a & seed_hash; + a & previous_seed_hash; + a & cookie; + a & top; + a & previous_top; + a & credits; + a & payments; + a & previous_payments; + a & update_time; + a & last_request_timestamp; + a & block_template_update_time; + a & credits_total; + a & credits_used; + a & nonces_good; + a & nonces_stale; + a & nonces_bad; + a & nonces_dupe; + } + }; + + public: + rpc_payment(const cryptonote::account_public_address &address, uint64_t diff, uint64_t credits_per_hash_found); + uint64_t balance(const crypto::public_key &client, int64_t delta = 0); + bool pay(const crypto::public_key &client, uint64_t ts, uint64_t payment, const std::string &rpc, bool same_ts, uint64_t &credits); + bool get_info(const crypto::public_key &client, const std::function<bool(const cryptonote::blobdata&, cryptonote::block&, uint64_t &seed_height, crypto::hash &seed_hash)> &get_block_template, cryptonote::blobdata &hashing_blob, uint64_t &seed_height, crypto::hash &seed_hash, const crypto::hash &top, uint64_t &diff, uint64_t &credits_per_hash_found, uint64_t &credits, uint32_t &cookie); + bool submit_nonce(const crypto::public_key &client, uint32_t nonce, const crypto::hash &top, int64_t &error_code, std::string &error_message, uint64_t &credits, crypto::hash &hash, cryptonote::block &block, uint32_t cookie, bool &stale); + const cryptonote::account_public_address &get_payment_address() const { return m_address; } + bool foreach(const std::function<bool(const crypto::public_key &client, const client_info &info)> &f) const; + unsigned int flush_by_age(time_t seconds = 0); + uint64_t get_hashes(unsigned int seconds) const; + void prune_hashrate(unsigned int seconds); + bool on_idle(); + + template <class t_archive> + inline void serialize(t_archive &a, const unsigned int ver) + { + a & m_client_info; + a & m_hashrate; + a & m_credits_total; + a & m_credits_used; + a & m_nonces_good; + a & m_nonces_stale; + a & m_nonces_bad; + a & m_nonces_dupe; + } + + bool load(std::string directory); + bool store(const std::string &directory = std::string()) const; + + private: + cryptonote::account_public_address m_address; + uint64_t m_diff; + uint64_t m_credits_per_hash_found; + std::unordered_map<crypto::public_key, client_info> m_client_info; + std::string m_directory; + std::map<uint64_t, uint64_t> m_hashrate; + uint64_t m_credits_total; + uint64_t m_credits_used; + uint64_t m_nonces_good; + uint64_t m_nonces_stale; + uint64_t m_nonces_bad; + uint64_t m_nonces_dupe; + }; +} + +BOOST_CLASS_VERSION(cryptonote::rpc_payment, 0); +BOOST_CLASS_VERSION(cryptonote::rpc_payment::client_info, 0); diff --git a/src/rpc/rpc_payment_costs.h b/src/rpc/rpc_payment_costs.h new file mode 100644 index 000000000..3b27bf286 --- /dev/null +++ b/src/rpc/rpc_payment_costs.h @@ -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 diff --git a/src/rpc/rpc_payment_signature.cpp b/src/rpc/rpc_payment_signature.cpp new file mode 100644 index 000000000..159bb5730 --- /dev/null +++ b/src/rpc/rpc_payment_signature.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2018-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <inttypes.h> +#include <stdlib.h> +#include <chrono> +#include "include_base_utils.h" +#include "string_tools.h" +#include "rpc_payment_signature.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.payment" + +#define TIMESTAMP_LEEWAY (60 * 1000000) /* 60 seconds, in microseconds */ + +namespace cryptonote +{ + std::string make_rpc_payment_signature(const crypto::secret_key &skey) + { + std::string s; + crypto::public_key pkey; + crypto::secret_key_to_public_key(skey, pkey); + crypto::signature sig; + const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); + char ts[17]; + int ret = snprintf(ts, sizeof(ts), "%16.16" PRIx64, now); + CHECK_AND_ASSERT_MES(ret == 16, "", "snprintf failed"); + ts[16] = 0; + CHECK_AND_ASSERT_MES(strlen(ts) == 16, "", "Invalid time conversion"); + crypto::hash hash; + crypto::cn_fast_hash(ts, 16, hash); + crypto::generate_signature(hash, pkey, skey, sig); + s = epee::string_tools::pod_to_hex(pkey) + ts + epee::string_tools::pod_to_hex(sig); + return s; + } + + bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts) + { + if (message.size() != 2 * sizeof(crypto::public_key) + 16 + 2 * sizeof(crypto::signature)) + { + MDEBUG("Bad message size: " << message.size()); + return false; + } + const std::string pkey_string = message.substr(0, 2 * sizeof(crypto::public_key)); + const std::string ts_string = message.substr(2 * sizeof(crypto::public_key), 16); + const std::string signature_string = message.substr(2 * sizeof(crypto::public_key) + 16); + if (!epee::string_tools::hex_to_pod(pkey_string, pkey)) + { + MDEBUG("Bad client id"); + return false; + } + crypto::signature signature; + if (!epee::string_tools::hex_to_pod(signature_string, signature)) + { + MDEBUG("Bad signature"); + return false; + } + crypto::hash hash; + crypto::cn_fast_hash(ts_string.data(), 16, hash); + if (!crypto::check_signature(hash, pkey, signature)) + { + MDEBUG("signature does not verify"); + return false; + } + char *endptr = NULL; + errno = 0; + unsigned long long ull = strtoull(ts_string.c_str(), &endptr, 16); + if (ull == ULLONG_MAX && errno == ERANGE) + { + MDEBUG("bad timestamp"); + return false; + } + ts = ull; + const uint64_t now = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); + if (ts > now + TIMESTAMP_LEEWAY) + { + MDEBUG("Timestamp is in the future"); + return false; + } + return true; + } +} diff --git a/src/rpc/rpc_payment_signature.h b/src/rpc/rpc_payment_signature.h new file mode 100644 index 000000000..4a2fe2ea3 --- /dev/null +++ b/src/rpc/rpc_payment_signature.h @@ -0,0 +1,39 @@ +// Copyright (c) 2018-2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <stdint.h> +#include <string> +#include "crypto/crypto.h" + +namespace cryptonote +{ + std::string make_rpc_payment_signature(const crypto::secret_key &skey); + bool verify_rpc_payment_signature(const std::string &message, crypto::public_key &pkey, uint64_t &ts); +} diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index cc52bde58..ea67209dc 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -571,6 +571,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& in INSERT_INTO_JSON_OBJECT(val, doc, ip, info.ip); INSERT_INTO_JSON_OBJECT(val, doc, port, info.port); INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, info.rpc_port); + INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, info.rpc_credits_per_hash); INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id); @@ -607,6 +608,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& inf GET_FROM_JSON_OBJECT(val, info.ip, ip); GET_FROM_JSON_OBJECT(val, info.port, port); GET_FROM_JSON_OBJECT(val, info.rpc_port, rpc_port); + GET_FROM_JSON_OBJECT(val, info.rpc_credits_per_hash, rpc_credits_per_hash); GET_FROM_JSON_OBJECT(val, info.peer_id, peer_id); @@ -627,6 +629,25 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& inf GET_FROM_JSON_OBJECT(val, info.current_upload, current_upload); } +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_blob_entry& tx, rapidjson::Value& val) +{ + val.SetObject(); + + INSERT_INTO_JSON_OBJECT(val, doc, blob, tx.blob); + INSERT_INTO_JSON_OBJECT(val, doc, prunable_hash, tx.prunable_hash); +} + +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_blob_entry& tx) +{ + if (!val.IsObject()) + { + throw WRONG_TYPE("json object"); + } + + GET_FROM_JSON_OBJECT(val, tx.blob, blob); + GET_FROM_JSON_OBJECT(val, tx.prunable_hash, prunable_hash); +} + void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val) { val.SetObject(); @@ -737,6 +758,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, ra INSERT_INTO_JSON_OBJECT(val, doc, ip, peer.ip); INSERT_INTO_JSON_OBJECT(val, doc, port, peer.port); INSERT_INTO_JSON_OBJECT(val, doc, rpc_port, peer.rpc_port); + INSERT_INTO_JSON_OBJECT(val, doc, rpc_credits_per_hash, peer.rpc_credits_per_hash); INSERT_INTO_JSON_OBJECT(val, doc, last_seen, peer.last_seen); INSERT_INTO_JSON_OBJECT(val, doc, pruning_seed, peer.pruning_seed); } @@ -753,6 +775,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer) GET_FROM_JSON_OBJECT(val, peer.ip, ip); GET_FROM_JSON_OBJECT(val, peer.port, port); GET_FROM_JSON_OBJECT(val, peer.rpc_port, rpc_port); + GET_FROM_JSON_OBJECT(val, peer.rpc_credits_per_hash, rpc_credits_per_hash); GET_FROM_JSON_OBJECT(val, peer.last_seen, last_seen); GET_FROM_JSON_OBJECT(val, peer.pruning_seed, pruning_seed); } diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index c804d148b..5ef75b863 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -221,6 +221,9 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& val, cryptonote::connection_info& info); +void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_blob_entry& tx, rapidjson::Value& val); +void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_blob_entry& tx); + void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index fe384e529..03693a57c 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -58,6 +58,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "rpc/rpc_payment_signature.h" #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" @@ -99,12 +100,17 @@ typedef cryptonote::simple_wallet sw; #define LOCK_IDLE_SCOPE() \ bool auto_refresh_enabled = m_auto_refresh_enabled.load(std::memory_order_relaxed); \ m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ - /* stop any background refresh, and take over */ \ + /* stop any background refresh and other processes, and take over */ \ + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); \ m_wallet->stop(); \ boost::unique_lock<boost::mutex> lock(m_idle_mutex); \ m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ + /* m_idle_mutex is still locked here */ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ + m_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed);; \ + m_rpc_payment_checker.trigger(); \ + m_idle_cond.notify_one(); \ }) #define SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(code) \ @@ -119,23 +125,30 @@ typedef cryptonote::simple_wallet sw; #define LONG_PAYMENT_ID_SUPPORT_CHECK() \ do { \ - if (!m_long_payment_id_support) { \ - fail_msg_writer() << tr("Warning: Long payment IDs are obsolete."); \ - fail_msg_writer() << tr("Long payment IDs are not encrypted on the blockchain, and will harm your privacy."); \ - fail_msg_writer() << tr("Use --long-payment-id-support-bad-for-privacy if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \ - return true; \ - } \ + fail_msg_writer() << tr("Error: Long payment IDs are obsolete."); \ + fail_msg_writer() << tr("Long payment IDs were not encrypted on the blockchain and would harm your privacy."); \ + fail_msg_writer() << tr("If the party you're sending to still requires a long payment ID, please notify them."); \ + return true; \ } while(0) +#define REFRESH_PERIOD 90 // seconds + +#define CREDITS_TARGET 50000 +#define MAX_PAYMENT_DIFF 10000 +#define MIN_PAYMENT_RATE 0.01f // per hash + enum TransferType { Transfer, TransferLocked, }; +static std::string get_human_readable_timespan(std::chrono::seconds seconds); + namespace { const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; const auto arg_wallet_file = wallet_args::arg_wallet_file(); + const auto arg_rpc_client_secret_key = wallet_args::arg_rpc_client_secret_key(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_device = {"generate-from-device", sw::tr("Generate new wallet from device and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; @@ -146,6 +159,7 @@ namespace const command_line::arg_descriptor<std::string> arg_mnemonic_language = {"mnemonic-language", sw::tr("Language for mnemonic"), ""}; const command_line::arg_descriptor<std::string> arg_electrum_seed = {"electrum-seed", sw::tr("Specify Electrum seed for wallet recovery/creation"), ""}; const command_line::arg_descriptor<bool> arg_restore_deterministic_wallet = {"restore-deterministic-wallet", sw::tr("Recover wallet using Electrum-style mnemonic seed"), false}; + const command_line::arg_descriptor<bool> arg_restore_from_seed = {"restore-from-seed", sw::tr("alias for --restore-deterministic-wallet"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; @@ -155,7 +169,6 @@ namespace const command_line::arg_descriptor<bool> arg_create_address_file = {"create-address-file", sw::tr("Create an address file for new wallets"), false}; const command_line::arg_descriptor<std::string> arg_subaddress_lookahead = {"subaddress-lookahead", tools::wallet2::tr("Set subaddress lookahead sizes to <major>:<minor>"), ""}; const command_line::arg_descriptor<bool> arg_use_english_language_names = {"use-english-language-names", sw::tr("Display English language names"), false}; - const command_line::arg_descriptor<bool> arg_long_payment_id_support = {"long-payment-id-support-bad-for-privacy", sw::tr("Support obsolete long (unencrypted) payment ids (using them harms your privacy)"), false}; const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; @@ -249,7 +262,11 @@ namespace const char* USAGE_FROZEN("frozen <key_image>"); const char* USAGE_LOCK("lock"); const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); + const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); + const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); + const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command>]"); @@ -492,22 +509,28 @@ namespace fail_msg_writer() << sw::tr("invalid format for subaddress lookahead; must be <major>:<minor>"); return r; } +} - void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) - { +void simple_wallet::handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon) +{ bool warn_of_possible_attack = !trusted_daemon; try { std::rethrow_exception(e); } - catch (const tools::error::daemon_busy&) + catch (const tools::error::payment_required&) { - fail_msg_writer() << sw::tr("daemon is busy. Please try again later."); + fail_msg_writer() << tr("Payment required, see the 'rpc_payment_info' command"); + m_need_payment = true; } catch (const tools::error::no_connection_to_daemon&) { fail_msg_writer() << sw::tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -604,8 +627,10 @@ namespace if (warn_of_possible_attack) fail_msg_writer() << sw::tr("There was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); - } +} +namespace +{ bool check_file_overwrite(const std::string &filename) { boost::system::error_code errcode; @@ -1910,6 +1935,77 @@ bool simple_wallet::unset_ring(const std::vector<std::string> &args) return true; } +bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + try + { + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + crypto::public_key pkey; + crypto::secret_key_to_public_key(m_wallet->get_rpc_client_secret_key(), pkey); + message_writer() << tr("RPC client ID: ") << pkey; + message_writer() << tr("RPC client secret key: ") << m_wallet->get_rpc_client_secret_key(); + if (!m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (payment_required) + { + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + message_writer() << tr("Using daemon: ") << m_wallet->get_daemon_address(); + message_writer() << tr("Payments required for node use, current credits: ") << credits; + message_writer() << tr("Credits target: ") << target; + uint64_t expected, discrepancy; + m_wallet->credit_report(expected, discrepancy); + message_writer() << tr("Credits spent this session: ") << expected; + if (expected) + message_writer() << tr("Credit discrepancy this session: ") << discrepancy << " (" << 100.0f * discrepancy / expected << "%)"; + float cph = credits_per_hash_found / (float)diff; + message_writer() << tr("Difficulty: ") << diff << ", " << credits_per_hash_found << " " << tr("credits per hash found, ") << cph << " " << tr("credits/hash");; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + bool mining = (now - m_last_rpc_payment_mining_time).total_microseconds() < 1000000; + if (mining) + { + float hash_rate = m_rpc_payment_hash_rate; + if (hash_rate > 0) + { + message_writer() << (boost::format(tr("Mining for payment at %.1f H/s")) % hash_rate).str(); + if (credits < target) + { + std::chrono::seconds seconds((unsigned)((target - credits) / cph / hash_rate)); + std::string target_string = get_human_readable_timespan(seconds); + message_writer() << (boost::format(tr("Estimated time till %u credits target mined: %s")) % target % target_string).str(); + } + } + else + message_writer() << tr("Mining for payment"); + } + else + message_writer() << tr("Not mining"); + } + else + message_writer() << tr("No payment needed for node use"); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + + return true; +} + bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2152,13 +2248,52 @@ bool simple_wallet::net_stats(const std::vector<std::string> &args) return true; } +bool simple_wallet::public_nodes(const std::vector<std::string> &args) +{ + try + { + auto nodes = m_wallet->get_public_nodes(false); + m_claimed_cph.clear(); + if (nodes.empty()) + { + fail_msg_writer() << tr("No known public nodes"); + return true; + } + std::sort(nodes.begin(), nodes.end(), [](const public_node &node0, const public_node &node1) { + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash == 0) + return true; + if (node0.rpc_credits_per_hash && node1.rpc_credits_per_hash) + return node0.rpc_credits_per_hash < node1.rpc_credits_per_hash; + return false; + }); + + const uint64_t now = time(NULL); + message_writer() << boost::format("%32s %12s %16s") % tr("address") % tr("credits/hash") % tr("last_seen"); + for (const auto &node: nodes) + { + const float cph = node.rpc_credits_per_hash / RPC_CREDITS_PER_HASH_SCALE; + char cphs[9]; + snprintf(cphs, sizeof(cphs), "%.3f", cph); + const std::string last_seen = node.last_seen == 0 ? tr("never") : get_human_readable_timespan(std::chrono::seconds(now - node.last_seen)); + std::string host = node.host + ":" + std::to_string(node.rpc_port); + message_writer() << boost::format("%32s %12s %16s") % host % cphs % last_seen; + m_claimed_cph[host] = node.rpc_credits_per_hash; + } + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error retrieving public node list: ") << e.what(); + } + return true; +} + bool simple_wallet::welcome(const std::vector<std::string> &args) { message_writer() << tr("Welcome to Monero, the private cryptocurrency."); message_writer() << ""; message_writer() << tr("Monero, like Bitcoin, is a cryptocurrency. That is, it is digital money."); - message_writer() << tr("Unlike Bitcoin, your Monero transactions and balance stay private, and not visible to the world by default."); - message_writer() << tr("However, you have the option of making those available to select parties, if you choose to."); + message_writer() << tr("Unlike Bitcoin, your Monero transactions and balance stay private and are not visible to the world by default."); + message_writer() << tr("However, you have the option of making those available to select parties if you choose to."); message_writer() << ""; message_writer() << tr("Monero protects your privacy on the blockchain, and while Monero strives to improve all the time,"); message_writer() << tr("no privacy technology can be 100% perfect, Monero included."); @@ -2166,7 +2301,7 @@ bool simple_wallet::welcome(const std::vector<std::string> &args) message_writer() << tr("Flaws in Monero may be discovered in the future, and attacks may be developed to peek under some"); message_writer() << tr("of the layers of privacy Monero provides. Be safe and practice defense in depth."); message_writer() << ""; - message_writer() << tr("Welcome to Monero and financial privacy. For more information, see https://getmonero.org/"); + message_writer() << tr("Welcome to Monero and financial privacy. For more information see https://GetMonero.org"); return true; } @@ -2216,6 +2351,50 @@ bool simple_wallet::cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& return m_wallet->import_key_images(exported_txs, 0, true); } +bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + std::string hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (!m_wallet->get_rpc_payment_info(true, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie)) + { + fail_msg_writer() << tr("Failed to query daemon"); + return true; + } + if (!payment_required) + { + fail_msg_writer() << tr("Daemon does not require payment for RPC access"); + return true; + } + + m_rpc_payment_mining_requested = true; + m_rpc_payment_checker.trigger(); + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + success_msg_writer() << (boost::format(tr("Starting mining for RPC access: diff %llu, %f credits/hash%s")) % diff % cph % (low ? " - this is low" : "")).str(); + success_msg_writer() << tr("Run stop_mining_for_rpc to stop"); + return true; +} + +bool simple_wallet::stop_mining_for_rpc(const std::vector<std::string> &args) +{ + if (!try_connect_to_daemon()) + return true; + + LOCK_IDLE_SCOPE(); + m_rpc_payment_mining_requested = false; + m_last_rpc_payment_mining_time = boost::posix_time::ptime(boost::gregorian::date(1970, 1, 1)); + m_rpc_payment_hash_rate = -1.0f; + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2406,21 +2585,6 @@ bool simple_wallet::set_refresh_type(const std::vector<std::string> &args/* = st return true; } -bool simple_wallet::set_confirm_missing_payment_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) -{ - LONG_PAYMENT_ID_SUPPORT_CHECK(); - - const auto pwd_container = get_and_verify_password(); - if (pwd_container) - { - parse_bool_and_use(args[1], [&](bool r) { - m_wallet->confirm_missing_payment_id(r); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); - }); - } - return true; -} - bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2619,6 +2783,53 @@ bool simple_wallet::set_segregate_pre_fork_outputs(const std::vector<std::string return true; } +bool simple_wallet::set_persistent_rpc_client_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->persistent_rpc_client_id(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); + } + return true; +} + +bool simple_wallet::set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + float threshold; + if (!epee::string_tools::get_xtype_from_string(threshold, args[1]) || threshold < 0.0f) + { + fail_msg_writer() << tr("Invalid threshold"); + return true; + } + m_wallet->auto_mine_for_rpc_payment_threshold(threshold); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_credits_target(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + uint64_t target; + if (!epee::string_tools::get_xtype_from_string(target, args[1])) + { + fail_msg_writer() << tr("Invalid target"); + return true; + } + m_wallet->credits_target(target); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + bool simple_wallet::set_key_reuse_mitigation2(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2677,6 +2888,43 @@ bool simple_wallet::set_ignore_fractional_outputs(const std::vector<std::string> return true; } + +bool simple_wallet::set_ignore_outputs_above(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + uint64_t amount; + if (!cryptonote::parse_amount(amount, args[1])) + { + fail_msg_writer() << tr("Invalid amount"); + return true; + } + if (amount == 0) + amount = MONEY_SUPPLY; + m_wallet->ignore_outputs_above(amount); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + +bool simple_wallet::set_ignore_outputs_below(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + uint64_t amount; + if (!cryptonote::parse_amount(amount, args[1])) + { + fail_msg_writer() << tr("Invalid amount"); + return true; + } + m_wallet->ignore_outputs_below(amount); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + } + return true; +} + bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -2824,6 +3072,12 @@ simple_wallet::simple_wallet() , m_last_activity_time(time(NULL)) , m_locked(false) , m_in_command(false) + , m_need_payment(false) + , m_rpc_payment_mining_requested(false) + , m_last_rpc_payment_mining_time(boost::gregorian::date(1970, 1, 1)) + , m_daemon_rpc_payment_message_displayed(false) + , m_rpc_payment_hash_rate(-1.0f) + , m_suspend_rpc_payment_mining(false) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::on_command, this, &simple_wallet::start_mining, _1), @@ -2987,11 +3241,14 @@ simple_wallet::simple_wallet() " Set this if you are not sure whether you will spend on a key reusing Monero fork later.\n " "subaddress-lookahead <major>:<minor>\n " " Set the lookahead sizes for the subaddress hash table.\n " - " Set this if you are not sure whether you will spend on a key reusing Monero fork later.\n " "segregation-height <n>\n " " Set to the height of a key reusing fork you want to use, 0 to use default.\n " "ignore-fractional-outputs <1|0>\n " " Whether to ignore fractional outputs that result in net loss when spending due to fee.\n " + "ignore-outputs-above <amount>\n " + " Ignore outputs of amount above this threshold when spending. Value 0 is translated to the maximum value (18 million) which disables this filter.\n " + "ignore-outputs-below <amount>\n " + " Ignore outputs of amount below this threshold when spending.\n " "track-uses <1|0>\n " " Whether to keep track of owned outputs uses.\n " "setup-background-mining <1|0>\n " @@ -2999,7 +3256,13 @@ simple_wallet::simple_wallet() "device-name <device_name[:device_spec]>\n " " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " - " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n ")); + " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " + "persistent-client-id <1|0>\n " + " Whether to keep using the same client id for RPC payment over wallet restarts.\n" + "auto-mine-for-rpc-payment-threshold <float>\n " + " Whether to automatically start mining for RPC payment if the daemon requires it.\n" + "credits-target <unsigned int>\n" + " The RPC payment credits balance to target (0 for default).")); m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1), tr("Display the encrypted Electrum-style mnemonic seed.")); @@ -3302,6 +3565,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::net_stats, _1), tr(USAGE_NET_STATS), tr("Prints simple network stats")); + m_cmd_binder.set_handler("public_nodes", + boost::bind(&simple_wallet::public_nodes, this, _1), + tr(USAGE_PUBLIC_NODES), + tr("Lists known public nodes")); m_cmd_binder.set_handler("welcome", boost::bind(&simple_wallet::on_command, this, &simple_wallet::welcome, _1), tr(USAGE_WELCOME), @@ -3310,6 +3577,18 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::version, _1), tr(USAGE_VERSION), tr("Returns version information")); + m_cmd_binder.set_handler("rpc_payment_info", + boost::bind(&simple_wallet::rpc_payment_info, this, _1), + tr(USAGE_RPC_PAYMENT_INFO), + tr("Get info about RPC payments to current node")); + m_cmd_binder.set_handler("start_mining_for_rpc", + boost::bind(&simple_wallet::start_mining_for_rpc, this, _1), + tr(USAGE_START_MINING_FOR_RPC), + tr("Start mining to pay for RPC access")); + m_cmd_binder.set_handler("stop_mining_for_rpc", + boost::bind(&simple_wallet::stop_mining_for_rpc, this, _1), + tr(USAGE_STOP_MINING_FOR_RPC), + tr("Stop mining to pay for RPC access")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1), tr(USAGE_HELP), @@ -3352,7 +3631,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh(); success_msg_writer() << "refresh-type = " << get_refresh_type_name(m_wallet->get_refresh_type()); success_msg_writer() << "priority = " << priority<< " (" << priority_string << ")"; - success_msg_writer() << "confirm-missing-payment-id = " << m_wallet->confirm_missing_payment_id(); success_msg_writer() << "ask-password = " << m_wallet->ask_password() << " (" << ask_password_string << ")"; success_msg_writer() << "unit = " << cryptonote::get_unit(cryptonote::get_default_decimal_point()); success_msg_writer() << "min-outputs-count = " << m_wallet->get_min_output_count(); @@ -3369,6 +3647,8 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "subaddress-lookahead = " << lookahead.first << ":" << lookahead.second; success_msg_writer() << "segregation-height = " << m_wallet->segregation_height(); success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs(); + success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above()); + success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below()); success_msg_writer() << "track-uses = " << m_wallet->track_uses(); success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; success_msg_writer() << "device-name = " << m_wallet->device_name(); @@ -3378,6 +3658,9 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) << " (disabled on Windows)" #endif ; + success_msg_writer() << "persistent-rpc-client-id = " << m_wallet->persistent_rpc_client_id(); + success_msg_writer() << "auto-mine-for-rpc-payment-threshold = " << m_wallet->auto_mine_for_rpc_payment_threshold(); + success_msg_writer() << "credits-target = " << m_wallet->credits_target(); return true; } else @@ -3417,7 +3700,6 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4, or one of ") << join_priority_strings(", ")); - CHECK_SIMPLE_VARIABLE("confirm-missing-payment-id", set_confirm_missing_payment_id, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("ask-password", set_ask_password, tr("0|1|2 (or never|action|decrypt)")); CHECK_SIMPLE_VARIABLE("unit", set_unit, tr("monero, millinero, micronero, nanonero, piconero")); CHECK_SIMPLE_VARIABLE("min-outputs-count", set_min_output_count, tr("unsigned integer")); @@ -3433,11 +3715,16 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("subaddress-lookahead", set_subaddress_lookahead, tr("<major>:<minor>")); CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer")); CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); + CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); CHECK_SIMPLE_VARIABLE("export-format", set_export_format, tr("\"binary\" or \"ascii\"")); + CHECK_SIMPLE_VARIABLE("persistent-rpc-client-id", set_persistent_rpc_client_id, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("auto-mine-for-rpc-payment-threshold", set_auto_mine_for_rpc_payment_threshold, tr("floating point >= 0")); + CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -4202,6 +4489,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } + if (!command_line::is_arg_defaulted(vm, arg_rpc_client_secret_key)) + { + crypto::secret_key rpc_client_secret_key; + if (!epee::string_tools::hex_to_pod(command_line::get_arg(vm, arg_rpc_client_secret_key), rpc_client_secret_key)) + { + fail_msg_writer() << tr("RPC client secret key should be 32 byte in hex format"); + return false; + } + m_wallet->set_rpc_client_secret_key(rpc_client_secret_key); + } + if (!m_wallet->is_trusted_daemon()) { message_writer(console_color_red, true) << (boost::format(tr("Warning: using an untrusted daemon at %s")) % m_wallet->get_daemon_address()).str(); @@ -4232,14 +4530,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (welcome) message_writer(console_color_yellow, true) << tr("If you are new to Monero, type \"welcome\" for a brief overview."); - if (m_long_payment_id_support) - { - message_writer(console_color_red, false) << - tr("WARNING: obsolete long payment IDs are enabled. Sending transactions with those payment IDs are bad for your privacy."); - message_writer(console_color_red, false) << - tr("It is recommended that you do not use them, and ask recipients who ask for one to not endanger your privacy."); - } - m_last_activity_time = time(NULL); return true; } @@ -4264,7 +4554,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json); m_mnemonic_language = command_line::get_arg(vm, arg_mnemonic_language); m_electrum_seed = command_line::get_arg(vm, arg_electrum_seed); - m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); + m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet) || command_line::get_arg(vm, arg_restore_from_seed); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); @@ -4273,7 +4563,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); m_subaddress_lookahead = command_line::get_arg(vm, arg_subaddress_lookahead); m_use_english_language_names = command_line::get_arg(vm, arg_use_english_language_names); - m_long_payment_id_support = command_line::get_arg(vm, arg_long_payment_id_support); m_restoring = !m_generate_from_view_key.empty() || !m_generate_from_spend_key.empty() || !m_generate_from_keys.empty() || @@ -4726,6 +5015,7 @@ bool simple_wallet::close_wallet() if (m_idle_run.load(std::memory_order_relaxed)) { m_idle_run.store(false, std::memory_order_relaxed); + m_suspend_rpc_payment_mining.store(true, std::memory_order_relaxed); m_wallet->stop(); { boost::unique_lock<boost::mutex> lock(m_idle_mutex); @@ -4897,7 +5187,7 @@ void simple_wallet::check_background_mining(const epee::wipeable_string &passwor if (setup == tools::wallet2::BackgroundMiningMaybe) { message_writer() << tr("The daemon is not set up to background mine."); - message_writer() << tr("With background mining enabled, the daemon will mine when idle and not on batttery."); + message_writer() << tr("With background mining enabled, the daemon will mine when idle and not on battery."); message_writer() << tr("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): ")); if (std::cin.eof() || !command_line::is_yes(accepted)) { @@ -4993,6 +5283,40 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph) +{ + try + { + auto i = m_claimed_cph.find(daemon_url); + if (i == m_claimed_cph.end()) + return false; + + claimed_cph = m_claimed_cph[daemon_url]; + bool payment_required; + uint64_t credits, diff, credits_per_hash_found, height, seed_height; + uint32_t cookie; + cryptonote::blobdata hashing_blob; + crypto::hash seed_hash, next_seed_hash; + if (m_wallet->get_rpc_payment_info(false, payment_required, credits, diff, credits_per_hash_found, hashing_blob, height, seed_height, seed_hash, next_seed_hash, cookie) && payment_required) + { + actual_cph = RPC_CREDITS_PER_HASH_SCALE * (credits_per_hash_found / (float)diff); + return true; + } + else + { + fail_msg_writer() << tr("Error checking daemon RPC access prices"); + } + } + catch (const std::exception &e) + { + // can't check + fail_msg_writer() << tr("Error checking daemon RPC access prices: ") << e.what(); + return false; + } + // no record found for this daemon + return false; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::set_daemon(const std::vector<std::string>& args) { std::string daemon_url; @@ -5016,7 +5340,7 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) // If no port has been provided, use the default from config if (!match[3].length()) { - int daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; + uint16_t daemon_port = get_config(m_wallet->nettype()).RPC_DEFAULT_PORT; daemon_url = match[1] + match[2] + std::string(":") + std::to_string(daemon_port); } else { daemon_url = args[0]; @@ -5049,7 +5373,28 @@ bool simple_wallet::set_daemon(const std::vector<std::string>& args) } catch (const std::exception &e) { } } + + if (!try_connect_to_daemon()) + { + fail_msg_writer() << tr("Failed to connect to daemon"); + return true; + } + success_msg_writer() << boost::format("Daemon set to %s, %s") % daemon_url % (m_wallet->is_trusted_daemon() ? tr("trusted") : tr("untrusted")); + + // check whether the daemon's prices match the claim, and disconnect if not, to disincentivize daemons lying + uint32_t actual_cph, claimed_cph; + if (check_daemon_rpc_prices(daemon_url, actual_cph, claimed_cph)) + { + if (actual_cph < claimed_cph) + { + fail_msg_writer() << tr("Daemon RPC credits/hash is less than was claimed. Either this daemon is cheating, or it changed its setup recently."); + fail_msg_writer() << tr("Claimed: ") << claimed_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + fail_msg_writer() << tr("Actual: ") << actual_cph / (float)RPC_CREDITS_PER_HASH_SCALE; + } + } + + m_daemon_rpc_payment_message_displayed = false; } else { fail_msg_writer() << tr("This does not seem to be a valid daemon URL."); } @@ -5104,12 +5449,19 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { crypto::hash payment_id = crypto::null_hash; - if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + crypto::hash8 payment_id8 = crypto::null_hash8; + if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (payment_id8 != crypto::null_hash8) + message_writer() << + tr("NOTE: this transaction uses an encrypted payment ID: consider using subaddresses instead"); + } + else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) message_writer(console_color_red, false) << - (m_long_payment_id_support ? tr("WARNING: this transaction uses an unencrypted payment ID: consider using subaddresses instead.") : tr("WARNING: this transaction uses an unencrypted payment ID: these are obsolete. Support will be withdrawn in the future. Use subaddresses instead.")); + tr("WARNING: this transaction uses an unencrypted payment ID: these are obsolete and ignored. Use subaddresses instead."); } } - if (unlock_time) + if (unlock_time && !cryptonote::is_coinbase(tx)) message_writer() << tr("NOTE: This transaction is locked, see details with: show_transfer ") + epee::string_tools::pod_to_hex(txid); if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -5281,6 +5633,11 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo { ss << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + ss << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::wallet_rpc_error& e) { LOG_ERROR("RPC error: " << e.to_string()); @@ -5312,6 +5669,9 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo fail_msg_writer() << tr("refresh failed: ") << ss.str() << ". " << tr("Blocks received: ") << fetched_blocks; } + // prevent it from triggering the idle screen due to waiting for a foreground refresh + m_last_activity_time = time(NULL); + return true; } //---------------------------------------------------------------------------------------------------- @@ -5604,6 +5964,11 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); } + catch (const tools::error::payment_required&) + { + fail_msg_writer() << tr("payment required."); + m_need_payment = true; + } catch (const tools::error::is_key_image_spent_error&) { fail_msg_writer() << tr("failed to get spent status"); @@ -5707,6 +6072,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending req.outputs[j].index = absolute_offsets[j]; } COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + req.client = cryptonote::make_rpc_payment_signature(m_wallet->get_rpc_client_secret_key()); bool r = m_wallet->invoke_http_bin("/get_outs.bin", req, res); err = interpret_rpc_response(r, res.status); if (!err.empty()) @@ -6062,20 +6428,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri dsts.push_back(de); } - // prompt is there is no payment id and confirmation is required - if (m_long_payment_id_support && !payment_id_seen && m_wallet->confirm_missing_payment_id() && dsts.size() > num_subaddresses) - { - std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay?"), true); - if (std::cin.eof()) - return false; - if (!command_line::is_yes(accepted)) - { - fail_msg_writer() << tr("transaction cancelled."); - - return false; - } - } - SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return false;); try @@ -6637,20 +6989,6 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st payment_id_seen = true; } - // prompt is there is no payment id and confirmation is required - if (m_long_payment_id_support && !payment_id_seen && m_wallet->confirm_missing_payment_id() && !info.is_subaddress) - { - std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay?"), true); - if (std::cin.eof()) - return true; - if (!command_line::is_yes(accepted)) - { - fail_msg_writer() << tr("transaction cancelled."); - - return true; - } - } - SCOPED_WALLET_UNLOCK(); try @@ -6909,22 +7247,6 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) payment_id_seen = true; } - // prompt if there is no payment id and confirmation is required - if (m_long_payment_id_support && !payment_id_seen && m_wallet->confirm_missing_payment_id() && !info.is_subaddress) - { - std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay?"), true); - if (std::cin.eof()) - return true; - if (!command_line::is_yes(accepted)) - { - fail_msg_writer() << tr("transaction cancelled."); - - // would like to return false, because no tx made, but everything else returns true - // and I don't know what returning false might adversely affect. *sigh* - return true; - } - } - SCOPED_WALLET_UNLOCK(); try @@ -8492,6 +8814,7 @@ void simple_wallet::wallet_idle_thread() #endif m_refresh_checker.do_call(boost::bind(&simple_wallet::check_refresh, this)); m_mms_checker.do_call(boost::bind(&simple_wallet::check_mms, this)); + m_rpc_payment_checker.do_call(boost::bind(&simple_wallet::check_rpc_payment, this)); if (!m_idle_run.load(std::memory_order_relaxed)) break; @@ -8545,6 +8868,78 @@ bool simple_wallet::check_mms() return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_rpc_payment() +{ + if (!m_rpc_payment_mining_requested && m_wallet->auto_mine_for_rpc_payment_threshold() == 0.0f) + return true; + + uint64_t target = m_wallet->credits_target(); + if (target == 0) + target = CREDITS_TARGET; + if (m_rpc_payment_mining_requested) + target = std::numeric_limits<uint64_t>::max(); + bool need_payment = m_need_payment || m_rpc_payment_mining_requested || (m_wallet->credits() < target && m_wallet->daemon_requires_payment()); + if (need_payment) + { + const boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::universal_time(); + auto startfunc = [this](uint64_t diff, uint64_t credits_per_hash_found) + { + const float cph = credits_per_hash_found / (float)diff; + bool low = (diff > MAX_PAYMENT_DIFF || cph < MIN_PAYMENT_RATE); + if (credits_per_hash_found > 0 && cph >= m_wallet->auto_mine_for_rpc_payment_threshold()) + { + MINFO(std::to_string(cph) << " credits per hash is >= our threshold (" << m_wallet->auto_mine_for_rpc_payment_threshold() << "), starting mining"); + return true; + } + else if (m_rpc_payment_mining_requested) + { + MINFO("Mining for RPC payment was requested, starting mining"); + return true; + } + else + { + if (!m_daemon_rpc_payment_message_displayed) + { + success_msg_writer() << boost::format(tr("Daemon requests payment at diff %llu, with %f credits/hash%s. Run start_mining_for_rpc to start mining to pay for RPC access, or use another daemon")) % + diff % cph % (low ? " - this is low" : ""); + m_cmd_binder.print_prompt(); + m_daemon_rpc_payment_message_displayed = true; + } + return false; + } + }; + auto contfunc = [&,this](unsigned n_hashes) + { + if (m_suspend_rpc_payment_mining.load(std::memory_order_relaxed)) + return false; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + m_last_rpc_payment_mining_time = now; + if ((now - start_time).total_microseconds() >= 2 * 1000000) + m_rpc_payment_hash_rate = n_hashes / (float)((now - start_time).total_seconds()); + if ((now - start_time).total_microseconds() >= REFRESH_PERIOD * 1000000) + return false; + return true; + }; + auto foundfunc = [this, target](uint64_t credits) + { + m_need_payment = false; + return credits < target; + }; + auto errorfunc = [this](const std::string &error) + { + fail_msg_writer() << tr("Error mining to daemon: ") << error; + m_cmd_binder.print_prompt(); + }; + bool ret = m_wallet->search_for_rpc_payment(target, startfunc, contfunc, foundfunc, errorfunc); + if (!ret) + { + fail_msg_writer() << tr("Failed to start mining for RPC payment"); + m_cmd_binder.print_prompt(); + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- std::string simple_wallet::get_prompt() const { if (m_locked) @@ -9735,6 +10130,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_restore_deterministic_wallet ); + command_line::add_arg(desc_params, arg_restore_from_seed ); command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); @@ -9745,7 +10141,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_create_address_file); command_line::add_arg(desc_params, arg_subaddress_lookahead); command_line::add_arg(desc_params, arg_use_english_language_names); - command_line::add_arg(desc_params, arg_long_payment_id_support); + command_line::add_arg(desc_params, arg_rpc_client_secret_key); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 22659e99e..e8f96ad54 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -144,11 +144,16 @@ namespace cryptonote bool set_subaddress_lookahead(const std::vector<std::string> &args = std::vector<std::string>()); bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_export_format(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); @@ -248,7 +253,11 @@ namespace cryptonote bool thaw(const std::vector<std::string>& args); bool frozen(const std::vector<std::string>& args); bool lock(const std::vector<std::string>& args); + bool rpc_payment_info(const std::vector<std::string> &args); + bool start_mining_for_rpc(const std::vector<std::string> &args); + bool stop_mining_for_rpc(const std::vector<std::string> &args); bool net_stats(const std::vector<std::string>& args); + bool public_nodes(const std::vector<std::string>& args); bool welcome(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args); bool on_unknown_command(const std::vector<std::string>& args); @@ -323,6 +332,11 @@ namespace cryptonote bool check_inactivity(); bool check_refresh(); bool check_mms(); + bool check_rpc_payment(); + + void handle_transfer_exception(const std::exception_ptr &e, bool trusted_daemon); + + bool check_daemon_rpc_prices(const std::string &daemon_url, uint32_t &actual_cph, uint32_t &claimed_cph); //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); @@ -430,8 +444,6 @@ namespace cryptonote std::atomic<bool> m_in_manual_refresh; uint32_t m_current_subaddress_account; - bool m_long_payment_id_support; - std::atomic<time_t> m_last_activity_time; std::atomic<bool> m_locked; std::atomic<bool> m_in_command; @@ -439,7 +451,17 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<1> m_inactivity_checker; epee::math_helper::once_a_time_seconds<90> m_refresh_checker; epee::math_helper::once_a_time_seconds<90> m_mms_checker; + epee::math_helper::once_a_time_seconds<90> m_rpc_payment_checker; + std::atomic<bool> m_need_payment; + boost::posix_time::ptime m_last_rpc_payment_mining_time; + bool m_rpc_payment_mining_requested; + bool m_daemon_rpc_payment_message_displayed; + float m_rpc_payment_hash_rate; + std::atomic<bool> m_suspend_rpc_payment_mining; + + std::unordered_map<std::string, uint32_t> m_claimed_cph; + // MMS mms::message_store& get_message_store() const { return m_wallet->get_message_store(); }; mms::multisig_wallet_state get_multisig_wallet_state() const { return m_wallet->get_multisig_wallet_state(); }; diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index d0fc21f51..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,13 +119,16 @@ if (BUILD_GUI_DEPS) set(libs_to_merge wallet_api wallet + rpc_base multisig + blockchain_db cryptonote_core cryptonote_basic mnemonics common cncrypto device + hardforks ringct ringct_basic checkpoints diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7120485d5..6200c7a1f 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1123,6 +1123,10 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ setStatusError(tr("Failed to load unsigned transactions")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + + return transaction; } // Check tx data and construct confirmation message 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 b85e805de..9b3e7e8b4 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" @@ -137,6 +142,8 @@ using namespace cryptonote; #define DEFAULT_INACTIVITY_LOCK_TIMEOUT 90 // a minute and a half +#define IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION 12 + static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; @@ -383,6 +390,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl { auto parsed = tools::login::parse( command_line::get_arg(vm, opts.daemon_login), false, [password_prompter](bool verify) { + if (!password_prompter) + { + MERROR("Password needed without prompt function"); + return boost::optional<tools::password_container>(); + } return password_prompter("Daemon client password", verify); } ); @@ -1113,7 +1125,6 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_first_refresh_done(false), m_refresh_from_block_height(0), m_explicit_refresh_from_block_height(true), - m_confirm_missing_payment_id(true), m_confirm_non_default_ring_size(true), m_ask_password(AskPasswordToDecrypt), m_min_output_count(0), @@ -1127,16 +1138,20 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_key_reuse_mitigation2(true), m_segregation_height(0), m_ignore_fractional_outputs(true), + m_ignore_outputs_above(MONEY_SUPPLY), + m_ignore_outputs_below(0), 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), @@ -1159,8 +1174,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() @@ -1266,9 +1283,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)); @@ -1793,7 +1817,7 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has } } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) +void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) { PERF_TIMER(process_new_transaction); // In this function, tx (probably) only contains the base information @@ -2285,8 +2309,18 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { - LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id); - MWARNING("Found unencrypted payment ID: these are bad for privacy, consider using subaddresses instead"); + bool ignore = block_version >= IGNORE_LONG_PAYMENT_ID_FROM_BLOCK_VERSION; + if (ignore) + { + LOG_PRINT_L2("Found unencrypted payment ID in tx " << txid << " (ignored)"); + MWARNING("Found OBSOLETE AND IGNORED unencrypted payment ID: these are bad for privacy, use subaddresses instead"); + payment_id = crypto::null_hash; + } + else + { + LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id); + MWARNING("Found unencrypted payment ID: these are bad for privacy, consider using subaddresses instead"); + } } } @@ -2422,7 +2456,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry { TIME_MEASURE_START(miner_tx_handle_time); if (m_refresh_type != RefreshNoCoinbase) - process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.timestamp, true, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, parsed_block.o_indices.indices[0].indices, height, b.major_version, b.timestamp, true, false, false, tx_cache_data[tx_cache_data_offset], output_tracker_cache); ++tx_cache_data_offset; TIME_MEASURE_FINISH(miner_tx_handle_time); @@ -2431,7 +2465,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry THROW_WALLET_EXCEPTION_IF(bche.txs.size() != parsed_block.txes.size(), error::wallet_internal_error, "Wrong amount of transactions for block"); for (size_t idx = 0; idx < b.tx_hashes.size(); ++idx) { - process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.timestamp, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache); + process_new_transaction(b.tx_hashes[idx], parsed_block.txes[idx], parsed_block.o_indices.indices[idx+1].indices, height, b.major_version, b.timestamp, false, false, false, tx_cache_data[tx_cache_data_offset++], output_tracker_cache); } TIME_MEASURE_FINISH(txs_handle_time); m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); @@ -2494,15 +2528,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); @@ -2516,12 +2553,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); @@ -2686,9 +2726,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 { @@ -2734,7 +2775,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks for (size_t j = 0; j < blocks[i].txs.size(); ++j) { tpool.submit(&waiter, [&, i, j](){ - if (!parse_and_validate_tx_base_from_blob(blocks[i].txs[j], parsed_blocks[i].txes[j])) + if (!parse_and_validate_tx_base_from_blob(blocks[i].txs[j].blob, parsed_blocks[i].txes[j])) { boost::unique_lock<boost::mutex> lock(error_lock); error = true; @@ -2747,6 +2788,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks catch(...) { error = true; + exception = std::current_exception(); } } @@ -2793,12 +2835,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 @@ -2940,9 +2985,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) { @@ -2962,7 +3015,7 @@ void wallet2::update_pool_state(bool refreshed) [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen, {}); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, 0, time(NULL), false, true, tx_entry.double_spend_seen, {}); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { @@ -3209,19 +3262,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) { @@ -3272,7 +3328,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 @@ -3291,6 +3350,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; @@ -3382,22 +3447,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) @@ -3467,6 +3529,7 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui THROW_WALLET_EXCEPTION_IF(it_pk == m_pub_keys.end(), error::wallet_internal_error, "public key not found"); m_pub_keys.erase(it_pk); } + transfers_detached = std::distance(it, m_transfers.end()); m_transfers.erase(it, m_transfers.end()); size_t blocks_detached = m_blockchain.size() - height; @@ -3639,9 +3702,6 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint64(m_refresh_from_block_height); json.AddMember("refresh_height", value2, json.GetAllocator()); - value2.SetInt(m_confirm_missing_payment_id ? 1 :0); - json.AddMember("confirm_missing_payment_id", value2, json.GetAllocator()); - value2.SetInt(m_confirm_non_default_ring_size ? 1 :0); json.AddMember("confirm_non_default_ring_size", value2, json.GetAllocator()); @@ -3687,6 +3747,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(m_ignore_fractional_outputs ? 1 : 0); json.AddMember("ignore_fractional_outputs", value2, json.GetAllocator()); + value2.SetUint64(m_ignore_outputs_above); + json.AddMember("ignore_outputs_above", value2, json.GetAllocator()); + + value2.SetUint64(m_ignore_outputs_below); + json.AddMember("ignore_outputs_below", value2, json.GetAllocator()); + value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); @@ -3729,6 +3795,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); @@ -3833,7 +3908,6 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_auto_refresh = true; m_refresh_type = RefreshType::RefreshDefault; m_refresh_from_block_height = 0; - m_confirm_missing_payment_id = true; m_confirm_non_default_ring_size = true; m_ask_password = AskPasswordToDecrypt; cryptonote::set_default_decimal_point(CRYPTONOTE_DISPLAY_DECIMAL_POINT); @@ -3848,6 +3922,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_key_reuse_mitigation2 = true; m_segregation_height = 0; m_ignore_fractional_outputs = true; + m_ignore_outputs_above = MONEY_SUPPLY; + m_ignore_outputs_below = 0; m_track_uses = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; m_setup_background_mining = BackgroundMiningMaybe; @@ -3859,6 +3935,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()) { @@ -3968,8 +4047,6 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_height, uint64_t, Uint64, false, 0); m_refresh_from_block_height = field_refresh_height; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_missing_payment_id, int, Int, false, true); - m_confirm_missing_payment_id = field_confirm_missing_payment_id; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_non_default_ring_size, int, Int, false, true); m_confirm_non_default_ring_size = field_confirm_non_default_ring_size; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, AskPasswordType, Int, false, AskPasswordToDecrypt); @@ -4004,6 +4081,10 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_segregation_height = field_segregation_height; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_fractional_outputs, int, Int, false, true); m_ignore_fractional_outputs = field_ignore_fractional_outputs; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_above, uint64_t, Uint64, false, MONEY_SUPPLY); + m_ignore_outputs_above = field_ignore_outputs_above; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_outputs_below, uint64_t, Uint64, false, 0); + m_ignore_outputs_below = field_ignore_outputs_below; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, inactivity_lock_timeout, uint32_t, Uint, false, DEFAULT_INACTIVITY_LOCK_TIMEOUT); @@ -4065,6 +4146,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 { @@ -5414,6 +5503,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); @@ -5465,10 +5557,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; @@ -5570,6 +5670,11 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string address_file = m_wallet_file + ".address.txt"; r = save_to_file(address_file, m_account.get_public_address_str(m_nettype), true); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file); + // remove old address file + r = boost::filesystem::remove(old_address_file); + if (!r) { + LOG_ERROR("error removing file: " << old_address_file); + } } // remove old wallet file r = boost::filesystem::remove(old_file); @@ -5581,11 +5686,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas if (!r) { LOG_ERROR("error removing file: " << old_keys_file); } - // remove old address file - r = boost::filesystem::remove(old_address_file); - if (!r) { - LOG_ERROR("error removing file: " << old_address_file); - } // remove old message store file if (boost::filesystem::exists(old_mms_file)) { @@ -5825,15 +5925,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)); } @@ -6153,12 +6257,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 { @@ -6168,12 +6273,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) { @@ -6946,7 +7055,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 { @@ -6988,7 +7097,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); @@ -6999,7 +7108,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) { @@ -7015,7 +7124,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) { @@ -7032,7 +7141,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)) @@ -7044,7 +7153,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; @@ -7057,14 +7166,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) @@ -7107,7 +7216,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 @@ -7119,14 +7229,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"); @@ -7343,17 +7457,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"); @@ -7486,11 +7601,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) { @@ -7587,7 +7706,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; @@ -7626,12 +7745,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 @@ -7649,12 +7771,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) @@ -7998,15 +8125,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; @@ -8599,6 +8729,11 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui const transfer_details& td = m_transfers[i]; if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); + continue; + } LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); picks.push_back(i); return picks; @@ -8614,10 +8749,20 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui const transfer_details& td = m_transfers[i]; if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); + continue; + } LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); for (size_t j = i + 1; j < m_transfers.size(); ++j) { const transfer_details& td2 = m_transfers[j]; + if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); + continue; + } if (!is_spent(td2, false) && !td2.m_frozen && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index) { // update our picks if those outputs are less related than any we @@ -9317,11 +9462,16 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const transfer_details& td = m_transfers[i]; if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) { - MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below fractional threshold " << print_money(fractional_threshold)); continue; } if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); + continue; + } const uint32_t index_minor = td.m_subaddr_index.minor; auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; }; if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) @@ -10197,22 +10347,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) @@ -10222,7 +10371,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; @@ -10253,7 +10402,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) @@ -10274,18 +10423,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) @@ -10313,19 +10466,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; } @@ -10446,17 +10602,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"); @@ -10494,16 +10655,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, @@ -10544,16 +10708,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; @@ -10606,16 +10772,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; @@ -10662,16 +10830,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; @@ -10735,16 +10905,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; @@ -10834,11 +11006,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; @@ -10883,11 +11061,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; @@ -11038,11 +11222,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; @@ -11330,22 +11520,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) @@ -11431,7 +11632,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; @@ -11897,15 +12098,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]; @@ -11983,13 +12187,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 @@ -12918,7 +13125,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; @@ -12967,7 +13184,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); @@ -12987,16 +13204,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"); @@ -13168,22 +13388,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); } //---------------------------------------------------------------------------------------------------- @@ -13195,6 +13415,12 @@ bool wallet2::save_to_file(const std::string& path_to_file, const std::string& r } FILE *fp = fopen(path_to_file.c_str(), "w+"); + if (!fp) + { + MERROR("Failed to open wallet file for writing: " << path_to_file << ": " << strerror(errno)); + return false; + } + // Save the result b/c we need to close the fp before returning success/failure. int write_result = PEM_write(fp, ASCII_OUTPUT_MAGIC.c_str(), "", (const unsigned char *) raw.c_str(), raw.length()); fclose(fp); @@ -13329,4 +13555,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 1469b4c00..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; } /*! @@ -1019,8 +1035,6 @@ private: void set_default_priority(uint32_t p) { m_default_priority = p; } bool auto_refresh() const { return m_auto_refresh; } void auto_refresh(bool r) { m_auto_refresh = r; } - bool confirm_missing_payment_id() const { return m_confirm_missing_payment_id; } - void confirm_missing_payment_id(bool always) { m_confirm_missing_payment_id = always; } AskPasswordType ask_password() const { return m_ask_password; } void ask_password(AskPasswordType ask) { m_ask_password = ask; } void set_min_output_count(uint32_t count) { m_min_output_count = count; } @@ -1047,6 +1061,10 @@ private: void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; } bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; } void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } + uint64_t ignore_outputs_above() const { return m_ignore_outputs_above; } + void ignore_outputs_above(uint64_t value) { m_ignore_outputs_above = value; } + uint64_t ignore_outputs_below() const { return m_ignore_outputs_below; } + void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } @@ -1059,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); @@ -1106,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. @@ -1209,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(); @@ -1336,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. @@ -1351,7 +1396,7 @@ private: * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password); - void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); @@ -1361,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); @@ -1377,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); @@ -1433,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; @@ -1496,7 +1544,6 @@ private: // If m_refresh_from_block_height is explicitly set to zero we need this to differentiate it from the case that // m_refresh_from_block_height was defaulted to zero.*/ bool m_explicit_refresh_from_block_height; - bool m_confirm_missing_payment_id; bool m_confirm_non_default_ring_size; AskPasswordType m_ask_password; uint32_t m_min_output_count; @@ -1510,9 +1557,13 @@ private: bool m_key_reuse_mitigation2; uint64_t m_segregation_height; bool m_ignore_fractional_outputs; + uint64_t m_ignore_outputs_above; + uint64_t m_ignore_outputs_below; 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]; @@ -1523,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; @@ -1566,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 0e0221c03..ec21b2897 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -288,7 +288,7 @@ namespace tools if (setup == tools::wallet2::BackgroundMiningMaybe) { MINFO("The daemon is not set up to background mine."); - MINFO("With background mining enabled, the daemon will mine when idle and not on batttery."); + MINFO("With background mining enabled, the daemon will mine when idle and not on battery."); MINFO("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); MINFO("Set setup-background-mining to 1 in monero-wallet-cli to change."); return; @@ -307,7 +307,7 @@ namespace tools return; } - MINFO("Background mining enabled. The daemon will mine when idle and not on batttery."); + MINFO("Background mining enabled. The daemon will mine when idle and not on battery."); } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::not_open(epee::json_rpc::error& er) @@ -793,30 +793,9 @@ namespace tools if (!payment_id.empty()) { - - /* Just to clarify */ - const std::string& payment_id_str = payment_id; - - crypto::hash long_payment_id; - crypto::hash8 short_payment_id; - - /* Parse payment ID */ - if (wallet2::parse_long_payment_id(payment_id_str, long_payment_id)) { - cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, long_payment_id); - } - else { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment id has invalid format: \"" + payment_id_str + "\", expected 64 character string"; - return false; - } - - /* Append Payment ID data into extra */ - if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Something went wrong with payment_id. Please check its format: \"" + payment_id_str + "\", expected 64-character string"; - return false; - } - + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = "Standalone payment IDs are obsolete. Use subaddresses or integrated addresses instead"; + return false; } return true; } @@ -1003,6 +982,13 @@ namespace tools std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); + if (ptx_vector.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE; + er.message = "No transaction created"; + return false; + } + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } @@ -1194,8 +1180,11 @@ namespace tools crypto::hash payment_id; if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { - desc.payment_id = epee::string_tools::pod_to_hex(payment_id8); - has_encrypted_payment_id = true; + if (payment_id8 != crypto::null_hash8) + { + desc.payment_id = epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } } else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { @@ -4334,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); @@ -4382,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); @@ -4480,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"); @@ -4493,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); |