diff options
Diffstat (limited to 'src')
97 files changed, 3333 insertions, 2126 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 1a6a19da5..01faf43c4 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -53,9 +53,7 @@ bool matches_category(relay_method method, relay_category category) noexcept case relay_category::all: return true; case relay_category::relayable: - if (method == relay_method::none) - return false; - return true; + return method != relay_method::none; case relay_category::broadcasted: case relay_category::legacy: break; @@ -65,6 +63,7 @@ bool matches_category(relay_method method, relay_category category) noexcept { default: case relay_method::local: + case relay_method::stem: return false; case relay_method::block: case relay_method::fluff: @@ -80,6 +79,7 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept kept_by_block = 0; do_not_relay = 0; is_local = 0; + dandelionpp_stem = 0; switch (method) { @@ -92,6 +92,9 @@ void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept default: case relay_method::fluff: break; + case relay_method::stem: + dandelionpp_stem = 1; + break; case relay_method::block: kept_by_block = 1; break; @@ -106,9 +109,26 @@ relay_method txpool_tx_meta_t::get_relay_method() const noexcept return relay_method::none; if (is_local) return relay_method::local; + if (dandelionpp_stem) + return relay_method::stem; return relay_method::fluff; } +bool txpool_tx_meta_t::upgrade_relay_method(relay_method method) noexcept +{ + static_assert(relay_method::none < relay_method::local, "bad relay_method value"); + static_assert(relay_method::local < relay_method::stem, "bad relay_method value"); + static_assert(relay_method::stem < relay_method::fluff, "bad relay_method value"); + static_assert(relay_method::fluff < relay_method::block, "bad relay_method value"); + + if (get_relay_method() < method) + { + set_relay_method(method); + return true; + } + return false; +} + const command_line::arg_descriptor<std::string> arg_db_sync_mode = { "db-sync-mode" , "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]." diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index e9fc85803..3e2387da4 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -160,7 +160,7 @@ struct txpool_tx_meta_t uint64_t max_used_block_height; uint64_t last_failed_height; uint64_t receive_time; - uint64_t last_relayed_time; + uint64_t last_relayed_time; //!< If Dandelion++ stem, randomized embargo timestamp. Otherwise, last relayed timestmap. // 112 bytes uint8_t kept_by_block; uint8_t relayed; @@ -168,13 +168,17 @@ struct txpool_tx_meta_t uint8_t double_spend_seen: 1; uint8_t pruned: 1; uint8_t is_local: 1; - uint8_t bf_padding: 5; + uint8_t dandelionpp_stem : 1; + uint8_t bf_padding: 4; uint8_t padding[76]; // till 192 bytes void set_relay_method(relay_method method) noexcept; relay_method get_relay_method() const noexcept; + //! \return True if `get_relay_method()` now returns `method`. + bool upgrade_relay_method(relay_method method) noexcept; + //! See `relay_category` description bool matches(const relay_category category) const noexcept { @@ -1291,6 +1295,25 @@ public: virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const = 0; /** + * @brief fetches a variable number of blocks and transactions from the given height, in canonical blockchain order + * + * The subclass should return the blocks and transactions stored from the one with the given + * height. The number of blocks returned is variable, based on the max_size passed. + * + * @param start_height the height of the first block + * @param min_count the minimum number of blocks to return, if they exist + * @param max_count the maximum number of blocks to return + * @param max_size the maximum size of block/transaction data to return (will be exceeded by one blocks's worth at most, if min_count is met) + * @param blocks the returned block/transaction data + * @param pruned whether to return full or pruned tx data + * @param skip_coinbase whether to return or skip coinbase transactions (they're in blocks regardless) + * @param get_miner_tx_hash whether to calculate and return the miner (coinbase) tx hash + * + * @return true iff the blocks and transactions were found + */ + virtual bool get_blocks_from(uint64_t start_height, size_t min_count, size_t max_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const = 0; + + /** * @brief fetches the prunable transaction blob with the given hash * * The subclass should return the prunable transaction stored which has the given diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 5093015f2..5cec8879d 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1915,6 +1915,7 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::bl // if filtering, make sure those requirements are met before copying blob if (tx_category != relay_category::all) { + RCURSOR(txpool_meta) auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); if (result == MDB_NOTFOUND) return false; @@ -1986,7 +1987,7 @@ bool BlockchainLMDB::prune_worker(int mode, uint32_t pruning_seed) const uint32_t log_stripes = tools::get_pruning_log_stripes(pruning_seed); if (log_stripes && log_stripes != CRYPTONOTE_PRUNING_LOG_STRIPES) throw0(DB_ERROR("Pruning seed not in range")); - pruning_seed = tools::get_pruning_stripe(pruning_seed);; + pruning_seed = tools::get_pruning_stripe(pruning_seed); if (pruning_seed > (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES)) throw0(DB_ERROR("Pruning seed not in range")); check_open(); @@ -3107,6 +3108,104 @@ bool BlockchainLMDB::get_pruned_tx_blobs_from(const crypto::hash& h, size_t coun return true; } +bool BlockchainLMDB::get_blocks_from(uint64_t start_height, size_t min_count, size_t max_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(blocks); + RCURSOR(tx_indices); + RCURSOR(txs_pruned); + if (!pruned) + { + RCURSOR(txs_prunable); + } + + blocks.reserve(std::min<size_t>(max_count, 10000)); // guard against very large max count if only checking bytes + const uint64_t blockchain_height = height(); + uint64_t size = 0; + MDB_val_copy<uint64_t> key(start_height); + MDB_val k, v, val_tx_id; + uint64_t tx_id = ~0; + MDB_cursor_op op = MDB_SET; + for (uint64_t h = start_height; h < blockchain_height && blocks.size() < max_count && (size < max_size || blocks.size() < min_count); ++h) + { + MDB_cursor_op op = h == start_height ? MDB_SET : MDB_NEXT; + int result = mdb_cursor_get(m_cur_blocks, &key, &v, op); + if (result == MDB_NOTFOUND) + throw0(BLOCK_DNE(std::string("Attempt to get block from height ").append(boost::lexical_cast<std::string>(h)).append(" failed -- block not in db").c_str())); + else if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve a block from the db", result).c_str())); + + blocks.resize(blocks.size() + 1); + auto ¤t_block = blocks.back(); + + current_block.first.first.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size); + size += v.mv_size; + + cryptonote::block b; + if (!parse_and_validate_block_from_blob(current_block.first.first, b)) + throw0(DB_ERROR("Invalid block")); + current_block.first.second = get_miner_tx_hash ? cryptonote::get_transaction_hash(b.miner_tx) : crypto::null_hash; + + // get the tx_id for the first tx (the first block's coinbase tx) + if (h == start_height) + { + crypto::hash hash = cryptonote::get_transaction_hash(b.miner_tx); + MDB_val_set(v, hash); + result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve block coinbase transaction from the db: ", result).c_str())); + + const txindex *tip = (const txindex *)v.mv_data; + tx_id = tip->data.tx_id; + val_tx_id.mv_data = &tx_id; + val_tx_id.mv_size = sizeof(tx_id); + } + + if (skip_coinbase) + { + result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &v, op); + if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve transaction data from the db: ", result).c_str())); + if (!pruned) + { + result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, &v, op); + if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve transaction data from the db: ", result).c_str())); + } + } + + op = MDB_NEXT; + + current_block.second.reserve(b.tx_hashes.size()); + for (const auto &tx_hash: b.tx_hashes) + { + // get pruned data + cryptonote::blobdata tx_blob; + result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &v, op); + if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve transaction data from the db: ", result).c_str())); + tx_blob.assign((const char*)v.mv_data, v.mv_size); + + if (!pruned) + { + result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, &v, op); + if (result) + throw0(DB_ERROR(lmdb_error("Error attempting to retrieve transaction data from the db: ", result).c_str())); + tx_blob.append(reinterpret_cast<const char*>(v.mv_data), v.mv_size); + } + current_block.second.push_back(std::make_pair(tx_hash, std::move(tx_blob))); + size += current_block.second.back().second.size(); + } + } + + TXN_POSTFIX_RDONLY(); + + return true; +} + bool BlockchainLMDB::get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); @@ -3271,7 +3370,7 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t& amount, const uint6 else { const pre_rct_outkey *okp = (const pre_rct_outkey *)v.mv_data; - memcpy(&ret, &okp->data, sizeof(pre_rct_output_data_t));; + memcpy(&ret, &okp->data, sizeof(pre_rct_output_data_t)); if (include_commitmemt) ret.commitment = rct::zeroCommit(amount); } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 7c0b4c72c..6ddeed671 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -255,6 +255,7 @@ public: virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const; + virtual bool get_blocks_from(uint64_t start_height, size_t min_count, size_t max_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const; virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const; diff --git a/src/cryptonote_basic/cryptonote_stat_info.h b/src/blockchain_db/locked_txn.h index 4cc1bc764..d14631ddb 100644 --- a/src/cryptonote_basic/cryptonote_stat_info.h +++ b/src/blockchain_db/locked_txn.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-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 @@ -25,30 +25,29 @@ // 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. -// +// // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include "serialization/keyvalue_serialization.h" - namespace cryptonote { - struct core_stat_info_t - { - uint64_t tx_pool_size; - uint64_t blockchain_height; - uint64_t mining_speed; - uint64_t alternative_blocks; - std::string top_block_id_str; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tx_pool_size) - KV_SERIALIZE(blockchain_height) - KV_SERIALIZE(mining_speed) - KV_SERIALIZE(alternative_blocks) - KV_SERIALIZE(top_block_id_str) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<core_stat_info_t> core_stat_info; + // This class is meant to create a batch when none currently exists. + // If a batch exists, it can't be from another thread, since we can + // only be called with the txpool lock taken, and it is held during + // the whole prepare/handle/cleanup incoming block sequence. + class LockedTXN { + public: + LockedTXN(BlockchainDB &db): m_db(db), m_batch(false), m_active(false) { + m_batch = m_db.batch_start(); + m_active = true; + } + void commit() { try { if (m_batch && m_active) { m_db.batch_stop(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::commit filtering exception: " << e.what()); } } + void abort() { try { if (m_batch && m_active) { m_db.batch_abort(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::abort filtering exception: " << e.what()); } } + ~LockedTXN() { abort(); } + private: + BlockchainDB &m_db; + bool m_batch; + bool m_active; + }; } diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 46de38c7e..638dd3b37 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -70,6 +70,7 @@ public: virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const { return false; } + virtual bool get_blocks_from(uint64_t start_height, size_t min_count, size_t max_count, size_t max_size, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata>>>>& blocks, bool pruned, bool skip_coinbase, bool get_miner_tx_hash) const { return false; } virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; } virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; } diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 18204eeee..753bf238c 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -71,6 +71,7 @@ void threadpool::recycle() { } void threadpool::create(unsigned int max_threads) { + const boost::unique_lock<boost::mutex> lock(mutex); boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); max = max_threads ? max_threads : tools::get_max_concurrency(); diff --git a/src/common/util.cpp b/src/common/util.cpp index 57e747837..f1140d1d5 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -66,7 +66,6 @@ using namespace epee; #include "util.h" #include "stack_trace.h" #include "memwipe.h" -#include "cryptonote_config.h" #include "net/http_client.h" // epee::net_utils::... #include "readline_buffer.h" @@ -1074,16 +1073,33 @@ std::string get_nix_version_display_string() { if (seconds < 60) return std::to_string(seconds) + " seconds"; + std::stringstream ss; + ss << std::fixed << std::setprecision(1); if (seconds < 3600) - return std::to_string((uint64_t)(seconds / 60)) + " minutes"; + { + ss << seconds / 60.f; + return ss.str() + " minutes"; + } if (seconds < 3600 * 24) - return std::to_string((uint64_t)(seconds / 3600)) + " hours"; - if (seconds < 3600 * 24 * 30.5) - return std::to_string((uint64_t)(seconds / (3600 * 24))) + " days"; - if (seconds < 3600 * 24 * 365.25) - return std::to_string((uint64_t)(seconds / (3600 * 24 * 30.5))) + " months"; - if (seconds < 3600 * 24 * 365.25 * 100) - return std::to_string((uint64_t)(seconds / (3600 * 24 * 30.5 * 365.25))) + " years"; + { + ss << seconds / 3600.f; + return ss.str() + " hours"; + } + if (seconds < 3600 * 24 * 30.5f) + { + ss << seconds / (3600 * 24.f); + return ss.str() + " days"; + } + if (seconds < 3600 * 24 * 365.25f) + { + ss << seconds / (3600 * 24 * 30.5f); + return ss.str() + " months"; + } + if (seconds < 3600 * 24 * 365.25f * 100) + { + ss << seconds / (3600 * 24 * 365.25f); + return ss.str() + " years"; + } return "a long time"; } @@ -1239,7 +1255,7 @@ std::string get_nix_version_display_string() return get_string_prefix_by_width(s, 999999999).second; }; - std::vector<std::pair<std::string, size_t>> split_string_by_width(const std::string &s, size_t columns) + std::vector<std::pair<std::string, size_t>> split_line_by_width(const std::string &s, size_t columns) { std::vector<std::string> words; std::vector<std::pair<std::string, size_t>> lines; @@ -1279,4 +1295,97 @@ std::string get_nix_version_display_string() return lines; } + // Calculate a "sync weight" over ranges of blocks in the blockchain, suitable for + // calculating sync time estimates + uint64_t cumulative_block_sync_weight(cryptonote::network_type nettype, uint64_t start_block, uint64_t num_blocks) + { + if (nettype != cryptonote::MAINNET) + { + // No detailed data available except for Mainnet: Give back the number of blocks + // as a very simple and non-varying block sync weight for ranges of Testnet and + // Stagenet blocks + return num_blocks; + } + + // The following is a table of average blocks sizes in bytes over the Monero mainnet + // blockchain, where the block size is averaged over ranges of 10,000 blocks + // (about 2 weeks worth of blocks each). + // The first array entry of 442 thus means "The average byte size of the blocks + // 0 .. 9,999 is 442". The info "block_size" from the "get_block_header_by_height" + // RPC call was used for calculating this. This table (and the whole mechanism + // of calculating a "sync weight") is most important when estimating times for + // syncing from scratch. Without it the fast progress through the (in comparison) + // rather small blocks in the early blockchain) would lead to vastly underestimated + // total sync times. + // It's no big problem for estimates that this table will, over time, and if not + // updated, miss larger and larger parts at the top of the blockchain, as long + // as block size averages there do not differ wildly. + // Without time-consuming tests it's hard to say how much the estimates would + // improve if one would not only take block sizes into account, but also varying + // verification times i.e. the different CPU effort needed for the different + // transaction types (pre / post RingCT, pre / post Bulletproofs). + // Testnet and Stagenet are neglected here because of their much smaller + // importance. + static const uint32_t average_block_sizes[] = + { + 442, 1211, 1445, 1763, 2272, 8217, 5603, 9999, 16358, 10805, 5290, 4362, + 4325, 5584, 4515, 5008, 4789, 5196, 7660, 3829, 6034, 2925, 3762, 2545, + 2437, 2553, 2167, 2761, 2015, 1969, 2350, 1731, 2367, 2078, 2026, 3518, + 2214, 1908, 1780, 1640, 1976, 1647, 1921, 1716, 1895, 2150, 2419, 2451, + 2147, 2327, 2251, 1644, 1750, 1481, 1570, 1524, 1562, 1668, 1386, 1494, + 1637, 1880, 1431, 1472, 1637, 1363, 1762, 1597, 1999, 1564, 1341, 1388, + 1530, 1476, 1617, 1488, 1368, 1906, 1403, 1695, 1535, 1598, 1318, 1234, + 1358, 1406, 1698, 1554, 1591, 1758, 1426, 2389, 1946, 1533, 1308, 2701, + 1525, 1653, 3580, 1889, 2913, 8164, 5154, 3762, 3356, 4360, 3589, 4844, + 4232, 3781, 3882, 5924, 10790, 7185, 7442, 8214, 8509, 7484, 6939, 7391, + 8210, 15572, 39680, 44810, 53873, 54639, 68227, 63428, 62386, 68504, + 83073, 103858, 117573, 98089, 96793, 102337, 94714, 129568, 251584, + 132026, 94579, 94516, 95722, 106495, 121824, 153983, 162338, 136608, + 137104, 109872, 91114, 84757, 96339, 74251, 94314, 143216, 155837, + 129968, 120201, 109913, 101588, 97332, 104611, 95310, 93419, 113345, + 100743, 92152, 57565, 22533, 37564, 21823, 19980, 18277, 18402, 14344, + 12142, 15842, 13677, 17631, 18294, 22270, 41422, 39296, 36688, 33512, + 33831, 27582, 22276, 27516, 27317, 25505, 24426, 20566, 23045, 26766, + 28185, 26169, 27011, + 28642 // Blocks 1,990,000 to 1,999,999 in December 2019 + }; + const uint64_t block_range_size = 10000; + + uint64_t num_block_sizes = sizeof(average_block_sizes) / sizeof(average_block_sizes[0]); + uint64_t weight = 0; + uint64_t table_index = start_block / block_range_size; + for (;;) { + if (num_blocks == 0) + { + break; + } + if (table_index >= num_block_sizes) + { + // Take all blocks beyond our table as having the size of the blocks + // in the last table entry i.e. in the most recent known block range + weight += num_blocks * average_block_sizes[num_block_sizes - 1]; + break; + } + uint64_t portion_size = std::min(num_blocks, block_range_size - start_block % block_range_size); + weight += portion_size * average_block_sizes[table_index]; + table_index++; + num_blocks -= portion_size; + start_block += portion_size; + } + return weight; + } + + std::vector<std::pair<std::string, size_t>> split_string_by_width(const std::string &s, size_t columns) + { + std::vector<std::string> lines; + std::vector<std::pair<std::string, size_t>> all_lines; + boost::split(lines, s, boost::is_any_of("\n"), boost::token_compress_on); + for (const auto &e: lines) + { + std::vector<std::pair<std::string, size_t>> new_lines = split_line_by_width(e, columns); + for (auto &l: new_lines) + all_lines.push_back(std::move(l)); + } + return all_lines; + } } diff --git a/src/common/util.h b/src/common/util.h index b794d7908..25137ab64 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -46,6 +46,7 @@ #endif #include "crypto/hash.h" +#include "cryptonote_config.h" /*! \brief Various Tools * @@ -252,4 +253,6 @@ namespace tools void clear_screen(); std::vector<std::pair<std::string, size_t>> split_string_by_width(const std::string &s, size_t columns); + + uint64_t cumulative_block_sync_weight(cryptonote::network_type nettype, uint64_t start_block, uint64_t num_blocks); } diff --git a/src/crypto/duration.h b/src/crypto/duration.h new file mode 100644 index 000000000..493874288 --- /dev/null +++ b/src/crypto/duration.h @@ -0,0 +1,70 @@ +// Copyright (c) 2020, 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 <chrono> +#include "crypto/crypto.h" + +namespace crypto +{ + //! Generate poisson distributed values in discrete `D` time units. + template<typename D> + struct random_poisson_duration + { + using result_type = D; //!< std::chrono::duration time unit precision + using rep = typename result_type::rep; //!< Type used to represent duration value + + //! \param average for generated durations + explicit random_poisson_duration(result_type average) + : dist(average.count() < 0 ? 0 : average.count()) + {} + + //! Generate a crypto-secure random duration + result_type operator()() + { + crypto::random_device rand{}; + return result_type{dist(rand)}; + } + + private: + std::poisson_distribution<rep> dist; + }; + + /* A custom duration is used for subsecond precision because of the + variance. If 5000 milliseconds is given, 95% of the values fall between + 4859ms-5141ms in 1ms increments (not enough time variance). Providing 1/4 + seconds would yield 95% of the values between 3s-7.25s in 1/4s + increments. */ + + //! Generate random durations with 1 second precision + using random_poisson_seconds = random_poisson_duration<std::chrono::seconds>; + //! Generate random duration with 1/4 second precision + using random_poisson_subseconds = + random_poisson_duration<std::chrono::duration<std::chrono::milliseconds::rep, std::ratio<1, 4>>>; +} diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index a7a459ad3..606c67336 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -62,6 +62,7 @@ static CTHR_MUTEX_TYPE rx_dataset_mutex = CTHR_MUTEX_INIT; static rx_state rx_s[2] = {{CTHR_MUTEX_INIT,{0},0,0},{CTHR_MUTEX_INIT,{0},0,0}}; static randomx_dataset *rx_dataset; +static int rx_dataset_nomem; static uint64_t rx_dataset_height; static THREADV randomx_vm *rx_vm = NULL; @@ -246,20 +247,25 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch } if (miners) { CTHR_MUTEX_LOCK(rx_dataset_mutex); - if (rx_dataset == NULL) { - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); + if (!rx_dataset_nomem) { if (rx_dataset == NULL) { - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); + if (rx_dataset == NULL) { + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); + } + if (rx_dataset != NULL) + rx_initdata(rx_sp->rs_cache, miners, seedheight); } - if (rx_dataset != NULL) - rx_initdata(rx_sp->rs_cache, miners, seedheight); } if (rx_dataset != NULL) flags |= RANDOMX_FLAG_FULL_MEM; else { miners = 0; - mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset for miner"); + if (!rx_dataset_nomem) { + rx_dataset_nomem = 1; + mwarning(RX_LOGCAT, "Couldn't allocate RandomX dataset for miner"); + } } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } @@ -278,6 +284,10 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch CTHR_MUTEX_LOCK(rx_dataset_mutex); if (rx_dataset != NULL && rx_dataset_height != seedheight) rx_initdata(cache, miners, seedheight); + else if (rx_dataset == NULL) { + /* this is a no-op if the cache hasn't changed */ + randomx_vm_set_cache(rx_vm, rx_sp->rs_cache); + } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } else { /* this is a no-op if the cache hasn't changed */ @@ -309,5 +319,6 @@ void rx_stop_mining(void) { rx_dataset = NULL; randomx_release_dataset(rd); } + rx_dataset_nomem = 0; CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index 5bb56e083..59040d8a2 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -54,7 +54,6 @@ set(cryptonote_basic_private_headers cryptonote_basic_impl.h cryptonote_boost_serialization.h cryptonote_format_utils.h - cryptonote_stat_info.h difficulty.h hardfork.h miner.h diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index a9aec163b..02eca289e 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -40,13 +40,11 @@ extern "C" } #include "cryptonote_basic_impl.h" #include "cryptonote_format_utils.h" +#include "cryptonote_config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "account" -#define KEYS_ENCRYPTION_SALT 'k' - - using namespace std; DISABLE_VS_WARNINGS(4244 4345) @@ -69,7 +67,7 @@ DISABLE_VS_WARNINGS(4244 4345) static_assert(sizeof(base_key) == sizeof(crypto::hash), "chacha key and hash should be the same size"); epee::mlocked<tools::scrubbed_arr<char, sizeof(base_key)+1>> data; memcpy(data.data(), &base_key, sizeof(base_key)); - data[sizeof(base_key)] = KEYS_ENCRYPTION_SALT; + data[sizeof(base_key)] = config::HASH_KEY_MEMORY; crypto::generate_chacha_key(data.data(), sizeof(data), key, 1); } //----------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 138cf49f4..b3400abc7 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -44,8 +44,6 @@ using namespace epee; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "cn" -#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d - // #define ENABLE_HASH_CASH_INTEGRITY_CHECK using namespace crypto; @@ -128,6 +126,20 @@ namespace cryptonote namespace cryptonote { //--------------------------------------------------------------- + void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h, hw::device &hwdev) + { + hwdev.get_transaction_prefix_hash(tx,h); + } + + //--------------------------------------------------------------- + crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx, hw::device &hwdev) + { + crypto::hash h = null_hash; + get_transaction_prefix_hash(tx, h, hwdev); + return h; + } + + //--------------------------------------------------------------- void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h) { std::ostringstream s; @@ -996,17 +1008,31 @@ namespace cryptonote } } //--------------------------------------------------------------- - std::string print_money(uint64_t amount, unsigned int decimal_point) + static void insert_money_decimal_point(std::string &s, unsigned int decimal_point) { if (decimal_point == (unsigned int)-1) decimal_point = default_decimal_point; - std::string s = std::to_string(amount); if(s.size() < decimal_point+1) { s.insert(0, decimal_point+1 - s.size(), '0'); } if (decimal_point > 0) s.insert(s.size() - decimal_point, "."); + } + //--------------------------------------------------------------- + std::string print_money(uint64_t amount, unsigned int decimal_point) + { + std::string s = std::to_string(amount); + insert_money_decimal_point(s, decimal_point); + return s; + } + //--------------------------------------------------------------- + std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point) + { + std::stringstream ss; + ss << amount; + std::string s = ss.str(); + insert_money_decimal_point(s, decimal_point); return s; } //--------------------------------------------------------------- diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 29e4def64..d1b24d950 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -38,6 +38,7 @@ #include "crypto/crypto.h" #include "crypto/hash.h" #include <unordered_map> +#include <boost/multiprecision/cpp_int.hpp> namespace epee { @@ -47,6 +48,8 @@ namespace epee namespace cryptonote { //--------------------------------------------------------------- + void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h, hw::device &hwdev); + crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx, hw::device &hwdev); void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h); crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx); bool parse_and_validate_tx_prefix_from_blob(const blobdata& tx_blob, transaction_prefix& tx); @@ -139,6 +142,7 @@ namespace cryptonote unsigned int get_default_decimal_point(); std::string get_unit(unsigned int decimal_point = -1); std::string print_money(uint64_t amount, unsigned int decimal_point = -1); + std::string print_money(const boost::multiprecision::uint128_t &amount, unsigned int decimal_point = -1); //--------------------------------------------------------------- template<class t_object> bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index f5f663464..ec5f604a5 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -29,6 +29,9 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once + +#include "cryptonote_protocol/enums.h" + namespace cryptonote { /************************************************************************/ @@ -36,7 +39,9 @@ namespace cryptonote /************************************************************************/ struct tx_verification_context { - bool m_should_be_relayed; + static_assert(unsigned(relay_method::none) == 0, "default m_relay initialization is not to relay_method::none"); + + relay_method m_relay; // gives indication on how tx should be relayed (if at all) bool m_verifivation_failed; //bad tx, should drop connection bool m_verifivation_impossible; //the transaction is related with an alternative blockchain bool m_added_to_pool; @@ -47,7 +52,6 @@ namespace cryptonote bool m_too_big; bool m_overspend; bool m_fee_too_low; - bool m_not_rct; bool m_too_few_outputs; }; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 134b630f7..81dc15dee 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -102,7 +102,12 @@ #define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week -#define CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE 5 // seconds +#define CRYPTONOTE_DANDELIONPP_STEMS 2 // number of outgoing stem connections per epoch +#define CRYPTONOTE_DANDELIONPP_FLUFF_PROBABILITY 10 // out of 100 +#define CRYPTONOTE_DANDELIONPP_MIN_EPOCH 10 // minutes +#define CRYPTONOTE_DANDELIONPP_EPOCH_RANGE 30 // seconds +#define CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE 5 // seconds average for poisson distributed fluff flush +#define CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE 173 // seconds (see tx_pool.cpp for more info) // see src/cryptonote_protocol/levin_notify.cpp #define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes @@ -144,8 +149,6 @@ #define RPC_IP_FAILS_BEFORE_BLOCK 3 -#define ALLOW_DEBUG_COMMANDS - #define CRYPTONOTE_NAME "bitmonero" #define CRYPTONOTE_POOLDATA_FILENAME "poolstate.bin" #define CRYPTONOTE_BLOCKCHAINDATA_FILENAME "data.mdb" @@ -193,7 +196,6 @@ namespace config uint8_t const FEE_CALCULATION_MAX_RETRIES = 10; uint64_t const DEFAULT_DUST_THRESHOLD = ((uint64_t)2000000000); // 2 * pow(10, 9) uint64_t const BASE_REWARD_CLAMP_THRESHOLD = ((uint64_t)100000000); // pow(10, 8) - std::string const P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY = "0000000000000000000000000000000000000000000000000000000000000000"; uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18; uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19; @@ -207,6 +209,17 @@ namespace config std::string const GENESIS_TX = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; uint32_t const GENESIS_NONCE = 10000; + // Hash domain separators + const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; + const char HASH_KEY_RINGDB[] = "ringdsb"; + const char HASH_KEY_SUBADDRESS[] = "SubAddr"; + const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; + const unsigned char HASH_KEY_WALLET = 0x8c; + const unsigned char HASH_KEY_WALLET_CACHE = 0x8d; + const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; + const unsigned char HASH_KEY_MEMORY = 'k'; + const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + namespace testnet { uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 78893fdf2..2571e4203 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -928,7 +928,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, for (auto& bl : original_chain) { block_verification_context bvc = {}; - bool r = handle_block_to_main_chain(bl, bvc); + bool r = handle_block_to_main_chain(bl, bvc, false); CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC! failed to add (again) block while chain switching during the rollback!"); } @@ -979,7 +979,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> block_verification_context bvc = {}; // add block to main chain - bool r = handle_block_to_main_chain(bei.bl, bvc); + bool r = handle_block_to_main_chain(bei.bl, bvc, false); // if adding block to main chain failed, rollback to previous state and // return false @@ -1044,6 +1044,11 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info> reorg_notify->notify("%s", std::to_string(split_height).c_str(), "%h", std::to_string(m_db->height()).c_str(), "%n", std::to_string(m_db->height() - split_height).c_str(), "%d", std::to_string(discarded_blocks).c_str(), NULL); + std::shared_ptr<tools::Notify> block_notify = m_block_notify; + if (block_notify) + for (const auto &bei: alt_chain) + block_notify->notify("%s", epee::string_tools::pod_to_hex(get_block_hash(bei.bl)).c_str(), NULL); + MGINFO_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db->height()); return true; } @@ -2488,38 +2493,10 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons } db_rtxn_guard rtxn_guard(m_db); - total_height = get_current_blockchain_height(); - size_t count = 0, size = 0; blocks.reserve(std::min(std::min(max_count, (size_t)10000), (size_t)(total_height - start_height))); - for(uint64_t i = start_height; i < total_height && count < max_count && (size < FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE || count < 3); i++, count++) - { - blocks.resize(blocks.size()+1); - blocks.back().first.first = m_db->get_block_blob_from_height(i); - block b; - CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first.first, b), false, "internal error, invalid block"); - blocks.back().first.second = get_miner_tx_hash ? cryptonote::get_transaction_hash(b.miner_tx) : crypto::null_hash; - std::vector<cryptonote::blobdata> txs; - if (pruned) - { - CHECK_AND_ASSERT_MES(m_db->get_pruned_tx_blobs_from(b.tx_hashes.front(), b.tx_hashes.size(), txs), false, "Failed to retrieve all transactions needed"); - } - else - { - std::vector<crypto::hash> mis; - get_transactions_blobs(b.tx_hashes, txs, mis, pruned); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); - } - size += blocks.back().first.first.size(); - for (const auto &t: txs) - size += t.size(); + CHECK_AND_ASSERT_MES(m_db->get_blocks_from(start_height, 3, max_count, FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE, blocks, pruned, true, get_miner_tx_hash), + false, "Error getting blocks"); - CHECK_AND_ASSERT_MES(txs.size() == b.tx_hashes.size(), false, "mismatched sizes of b.tx_hashes and txs"); - blocks.back().second.reserve(txs.size()); - for (size_t i = 0; i < txs.size(); ++i) - { - blocks.back().second.push_back(std::make_pair(b.tx_hashes[i], std::move(txs[i]))); - } - } return true; } //------------------------------------------------------------------ @@ -2541,6 +2518,13 @@ bool Blockchain::add_block_as_invalid(const block_extended_info& bei, const cryp return true; } //------------------------------------------------------------------ +void Blockchain::flush_invalid_blocks() +{ + LOG_PRINT_L3("Blockchain::" << __func__); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + m_invalid_blocks.clear(); +} +//------------------------------------------------------------------ bool Blockchain::have_block(const crypto::hash& id) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -2567,11 +2551,11 @@ bool Blockchain::have_block(const crypto::hash& id) const return false; } //------------------------------------------------------------------ -bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc) +bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify/* = true*/) { LOG_PRINT_L3("Blockchain::" << __func__); crypto::hash id = get_block_hash(bl); - return handle_block_to_main_chain(bl, id, bvc); + return handle_block_to_main_chain(bl, id, bvc, notify); } //------------------------------------------------------------------ size_t Blockchain::get_total_transactions() const @@ -3416,7 +3400,7 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const if (version >= HF_VERSION_PER_BYTE_FEE) { const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - uint64_t fee_per_byte = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? m_long_term_effective_median_block_weight : median, version); + uint64_t fee_per_byte = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? std::min<uint64_t>(median, m_long_term_effective_median_block_weight) : median, version); MDEBUG("Using " << print_money(fee_per_byte) << "/byte fee"); needed_fee = tx_weight * fee_per_byte; // quantize fee up to 8 decimals @@ -3474,7 +3458,7 @@ uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0; uint64_t base_reward; - if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) + if (!get_block_reward(m_current_block_cumul_weight_limit / 2, 1, already_generated_coins, base_reward, version)) { MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); base_reward = BLOCK_REWARD_OVERESTIMATE; @@ -3680,7 +3664,7 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids) // Needs to validate the block and acquire each transaction from the // transaction mem_pool, then pass the block and transactions to // m_db->add_block() -bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) +bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify/* = true*/) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -4060,9 +4044,12 @@ leave: get_difficulty_for_next_block(); // just to cache it invalidate_block_template_cache(); - std::shared_ptr<tools::Notify> block_notify = m_block_notify; - if (block_notify) - block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL); + if (notify) + { + std::shared_ptr<tools::Notify> block_notify = m_block_notify; + if (block_notify) + block_notify->notify("%s", epee::string_tools::pod_to_hex(id).c_str(), NULL); + } return true; } @@ -5169,12 +5156,12 @@ bool Blockchain::for_all_transactions(std::function<bool(const crypto::hash&, co bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const { - return m_db->for_all_outputs(f);; + return m_db->for_all_outputs(f); } bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t height)> f) const { - return m_db->for_all_outputs(amount, f);; + return m_db->for_all_outputs(amount, f); } void Blockchain::invalidate_block_template_cache() diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 0aecdcb57..3a89cc5df 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -1017,6 +1017,11 @@ namespace cryptonote */ bool has_block_weights(uint64_t height, uint64_t nblocks) const; + /** + * @brief flush the invalid blocks set + */ + void flush_invalid_blocks(); + #ifndef IN_UNIT_TESTS private: #endif @@ -1207,10 +1212,11 @@ namespace cryptonote * * @param bl the block to be added * @param bvc metadata concerning the block's validity + * @param notify if set to true, sends new block notification on success * * @return true if the block was added successfully, otherwise false */ - bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); + bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify = true); /** * @brief validate and add a new block to the end of the blockchain @@ -1222,10 +1228,11 @@ namespace cryptonote * @param bl the block to be added * @param id the hash of the block * @param bvc metadata concerning the block's validity + * @param notify if set to true, sends new block notification on success * * @return true if the block was added successfully, otherwise false */ - bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); + bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify = true); /** * @brief validate and add a new block to an alternate blockchain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 5c8f59291..3ff3c77e2 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -428,9 +428,9 @@ namespace cryptonote return m_blockchain_storage.get_split_transactions_blobs(txs_ids, txs, missed_txs); } //----------------------------------------------------------------------------------------------- - bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const + bool core::get_txpool_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive_txes) const { - m_mempool.get_transaction_backlog(backlog); + m_mempool.get_transaction_backlog(backlog, include_sensitive_txes); return true; } //----------------------------------------------------------------------------------------------- @@ -1056,17 +1056,6 @@ namespace cryptonote return handle_incoming_txs({std::addressof(tx_blob), 1}, {std::addressof(tvc), 1}, tx_relay, relayed); } //----------------------------------------------------------------------------------------------- - bool core::get_stat_info(core_stat_info& st_inf) const - { - st_inf.mining_speed = m_miner.get_speed(); - st_inf.alternative_blocks = m_blockchain_storage.get_alternative_blocks_count(); - st_inf.blockchain_height = m_blockchain_storage.get_current_blockchain_height(); - st_inf.tx_pool_size = m_mempool.get_transactions_count(); - st_inf.top_block_id_str = epee::string_tools::pod_to_hex(m_blockchain_storage.get_tail_id()); - return true; - } - - //----------------------------------------------------------------------------------------------- bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const { if(!tx.vin.size()) @@ -1175,10 +1164,10 @@ namespace cryptonote return m_mempool.check_for_key_images(key_im, spent); } //----------------------------------------------------------------------------------------------- - std::pair<uint64_t, uint64_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) + std::pair<boost::multiprecision::uint128_t, boost::multiprecision::uint128_t> core::get_coinbase_tx_sum(const uint64_t start_offset, const size_t count) { - uint64_t emission_amount = 0; - uint64_t total_fee_amount = 0; + boost::multiprecision::uint128_t emission_amount = 0; + boost::multiprecision::uint128_t total_fee_amount = 0; if (count) { const uint64_t end = start_offset + count - 1; @@ -1200,7 +1189,7 @@ namespace cryptonote }); } - return std::pair<uint64_t, uint64_t>(emission_amount, total_fee_amount); + return std::pair<boost::multiprecision::uint128_t, boost::multiprecision::uint128_t>(emission_amount, total_fee_amount); } //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const @@ -1295,6 +1284,7 @@ namespace cryptonote break; case relay_method::block: case relay_method::fluff: + case relay_method::stem: public_req.txs.push_back(std::move(std::get<1>(tx))); break; } @@ -1306,9 +1296,9 @@ namespace cryptonote re-relaying public and private _should_ be acceptable here. */ const boost::uuids::uuid source = boost::uuids::nil_uuid(); if (!public_req.txs.empty()) - get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_); + get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_, relay_method::fluff); if (!private_req.txs.empty()) - get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid); + get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid, relay_method::local); } return true; } @@ -1554,9 +1544,9 @@ namespace cryptonote return m_blockchain_storage.get_db().get_block_cumulative_difficulty(height); } //----------------------------------------------------------------------------------------------- - size_t core::get_pool_transactions_count() const + size_t core::get_pool_transactions_count(bool include_sensitive_txes) const { - return m_mempool.get_transactions_count(); + return m_mempool.get_transactions_count(include_sensitive_txes); } //----------------------------------------------------------------------------------------------- bool core::have_block(const crypto::hash& id) const @@ -1918,7 +1908,7 @@ namespace cryptonote MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); if (p < threshold) { - MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck."); + MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack, or your computer's time is off. Or it could be just sheer bad luck."); std::shared_ptr<tools::Notify> block_rate_notify = m_block_rate_notify; if (block_rate_notify) @@ -1942,6 +1932,16 @@ namespace cryptonote bad_semantics_txes_lock.unlock(); } //----------------------------------------------------------------------------------------------- + void core::flush_invalid_blocks() + { + m_blockchain_storage.flush_invalid_blocks(); + } + //----------------------------------------------------------------------------------------------- + bool core::get_txpool_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes) + { + return m_mempool.get_complement(hashes, txes); + } + //----------------------------------------------------------------------------------------------- bool core::update_blockchain_pruning() { return m_blockchain_storage.update_blockchain_pruning(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 55efb566c..255645efc 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -45,7 +45,6 @@ #include "blockchain.h" #include "cryptonote_basic/miner.h" #include "cryptonote_basic/connection_context.h" -#include "cryptonote_basic/cryptonote_stat_info.h" #include "warnings.h" #include "crypto/hash.h" #include "span.h" @@ -470,10 +469,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_txpool_backlog + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_txpool_backlog */ - bool get_txpool_backlog(std::vector<tx_backlog_entry>& backlog) const; + bool get_txpool_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_transactions @@ -515,10 +515,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions_count + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_transactions_count */ - size_t get_pool_transactions_count() const; + size_t get_pool_transactions_count(bool include_sensitive_txes = false) const; /** * @copydoc Blockchain::get_total_transactions @@ -556,15 +557,6 @@ namespace cryptonote bool 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; /** - * @brief gets some stats about the daemon - * - * @param st_inf return-by-reference container for the stats requested - * - * @return true - */ - bool get_stat_info(core_stat_info& st_inf) const; - - /** * @copydoc Blockchain::get_tx_outputs_gindexs * * @note see Blockchain::get_tx_outputs_gindexs @@ -765,7 +757,7 @@ namespace cryptonote * * @return the number of blocks to sync in one go */ - std::pair<uint64_t, uint64_t> get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); + std::pair<boost::multiprecision::uint128_t, boost::multiprecision::uint128_t> get_coinbase_tx_sum(const uint64_t start_offset, const size_t count); /** * @brief get the network type we're on @@ -859,6 +851,20 @@ namespace cryptonote */ void flush_bad_txs_cache(); + /** + * @brief flushes the invalid block cache + */ + void flush_invalid_blocks(); + + /** + * @brief returns the set of transactions in the txpool which are not in the argument + * + * @param hashes hashes of transactions to exclude from the result + * + * @return true iff success, false otherwise + */ + bool get_txpool_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes); + private: /** diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index f50fc61a5..3dd29dd1b 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -142,7 +142,7 @@ namespace cryptonote uint64_t summary_amounts = 0; for (size_t no = 0; no < out_amounts.size(); no++) { - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation);; + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); crypto::public_key out_eph_public_key = AUTO_VAL_INIT(out_eph_public_key); bool r = crypto::generate_key_derivation(miner_address.m_view_public_key, txkey.sec, derivation); CHECK_AND_ASSERT_MES(r, false, "while creating outs: failed to generate_key_derivation(" << miner_address.m_view_public_key << ", " << txkey.sec << ")"); @@ -590,7 +590,7 @@ namespace cryptonote tx.vout[i].amount = 0; crypto::hash tx_prefix_hash; - get_transaction_prefix_hash(tx, tx_prefix_hash); + get_transaction_prefix_hash(tx, tx_prefix_hash, hwdev); rct::ctkeyV outSk; if (use_simple_rct) tx.rct_signatures = rct::genRctSimple(rct::hash2rct(tx_prefix_hash), inSk, destinations, inamounts, outamounts, amount_in - amount_out, mixRing, amount_keys, msout ? &kLRki : NULL, msout, index, outSk, rct_config, hwdev); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 1bc475879..469319824 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -38,6 +38,7 @@ #include "cryptonote_basic/cryptonote_boost_serialization.h" #include "cryptonote_config.h" #include "blockchain.h" +#include "blockchain_db/locked_txn.h" #include "blockchain_db/blockchain_db.h" #include "common/boost_serialization_helper.h" #include "int-util.h" @@ -45,6 +46,7 @@ #include "warnings.h" #include "common/perf_timer.h" #include "crypto/hash.h" +#include "crypto/duration.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "txpool" @@ -57,6 +59,29 @@ namespace cryptonote { namespace { + /*! The Dandelion++ has formula for calculating the average embargo timeout: + (-k*(k-1)*hop)/(2*log(1-ep)) + where k is the number of hops before this node and ep is the probability + that one of the k hops hits their embargo timer, and hop is the average + time taken between hops. So decreasing ep will make it more probable + that "this" node is the first to expire the embargo timer. Increasing k + will increase the number of nodes that will be "hidden" as a prior + recipient of the tx. + + As example, k=5 and ep=0.1 means "this" embargo timer has a 90% + probability of being the first to expire amongst 5 nodes that saw the + tx before "this" one. These values are independent to the fluff + probability, but setting a low k with a low p (fluff probability) is + not ideal since a blackhole is more likely to reveal earlier nodes in + the chain. + + This value was calculated with k=10, ep=0.10, and hop = 175 ms. A + testrun from a recent Intel laptop took ~80ms to + receive+parse+proces+send transaction. At least 50ms will be added to + the latency if crossing an ocean. So 175ms is the fudge factor for + a single hop with 173s being the embargo timer. */ + constexpr const std::chrono::seconds dandelionpp_embargo_average{CRYPTONOTE_DANDELIONPP_EMBARGO_AVERAGE}; + //TODO: constants such as these should at least be in the header, // but probably somewhere more accessible to the rest of the // codebase. As it stands, it is at best nontrivial to test @@ -88,25 +113,6 @@ namespace cryptonote else return get_min_block_weight(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } - - // This class is meant to create a batch when none currently exists. - // If a batch exists, it can't be from another thread, since we can - // only be called with the txpool lock taken, and it is held during - // the whole prepare/handle/cleanup incoming block sequence. - class LockedTXN { - public: - LockedTXN(Blockchain &b): m_blockchain(b), m_batch(false), m_active(false) { - m_batch = m_blockchain.get_db().batch_start(); - m_active = true; - } - void commit() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_stop(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::commit filtering exception: " << e.what()); } } - void abort() { try { if (m_batch && m_active) { m_blockchain.get_db().batch_abort(); m_active = false; } } catch (const std::exception &e) { MWARNING("LockedTXN::abort filtering exception: " << e.what()); } } - ~LockedTXN() { abort(); } - private: - Blockchain &m_blockchain; - bool m_batch; - bool m_active; - }; } //--------------------------------------------------------------------------------- //--------------------------------------------------------------------------------- @@ -204,7 +210,7 @@ namespace cryptonote // TODO: Investigate why not? if(!kept_by_block) { - if(have_tx_keyimges_as_spent(tx)) + if(have_tx_keyimges_as_spent(tx, id)) { mark_double_spend(tx); LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images"); @@ -247,7 +253,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.set_relay_method(tx_relay); - meta.double_spend_seen = have_tx_keyimges_as_spent(tx); + meta.double_spend_seen = have_tx_keyimges_as_spent(tx, id); meta.pruned = tx.pruned; meta.bf_padding = 0; memset(meta.padding, 0, sizeof(meta.padding)); @@ -256,7 +262,7 @@ namespace cryptonote if (kept_by_block) m_parsed_tx_cache.insert(std::make_pair(id, tx)); CRITICAL_REGION_LOCAL1(m_blockchain); - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); if (!insert_key_images(tx, id, tx_relay)) return false; @@ -280,34 +286,51 @@ namespace cryptonote } }else { - //update transactions container - meta.weight = tx_weight; - meta.fee = fee; - meta.max_used_block_id = max_used_block_id; - meta.max_used_block_height = max_used_block_height; - meta.last_failed_height = 0; - meta.last_failed_id = null_hash; - meta.receive_time = receive_time; - meta.last_relayed_time = time(NULL); - meta.relayed = relayed; - meta.set_relay_method(tx_relay); - meta.double_spend_seen = false; - meta.pruned = tx.pruned; - meta.bf_padding = 0; - memset(meta.padding, 0, sizeof(meta.padding)); - try { if (kept_by_block) m_parsed_tx_cache.insert(std::make_pair(id, tx)); CRITICAL_REGION_LOCAL1(m_blockchain); - LockedTXN lock(m_blockchain); - m_blockchain.remove_txpool_tx(id); - if (!insert_key_images(tx, id, tx_relay)) - return false; + LockedTXN lock(m_blockchain.get_db()); + + const bool existing_tx = m_blockchain.get_txpool_tx_meta(id, meta); + if (existing_tx) + { + /* If Dandelion++ loop. Do not use txes in the `local` state in the + loop detection - txes in that state should be outgoing over i2p/tor + then routed back via public dandelion++ stem. Pretend to be + another stem node in that situation, a loop over the public + network hasn't been hit yet. */ + if (tx_relay == relay_method::stem && meta.dandelionpp_stem) + tx_relay = relay_method::fluff; + } + else + meta.set_relay_method(relay_method::none); + + if (meta.upgrade_relay_method(tx_relay) || !existing_tx) // synchronize with embargo timer or stem/fluff out-of-order messages + { + //update transactions container + meta.last_relayed_time = std::numeric_limits<decltype(meta.last_relayed_time)>::max(); + meta.receive_time = receive_time; + meta.weight = tx_weight; + meta.fee = fee; + meta.max_used_block_id = max_used_block_id; + meta.max_used_block_height = max_used_block_height; + meta.last_failed_height = 0; + meta.last_failed_id = null_hash; + meta.relayed = relayed; + meta.double_spend_seen = false; + meta.pruned = tx.pruned; + meta.bf_padding = 0; + memset(meta.padding, 0, sizeof(meta.padding)); + + if (!insert_key_images(tx, id, tx_relay)) + return false; - m_blockchain.add_txpool_tx(id, blob, meta); - m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); + m_blockchain.remove_txpool_tx(id); + m_blockchain.add_txpool_tx(id, blob, meta); + 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) @@ -317,8 +340,9 @@ namespace cryptonote } tvc.m_added_to_pool = true; - if(meta.fee > 0 && tx_relay != relay_method::none) - tvc.m_should_be_relayed = true; + static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero"); + if(meta.fee > 0) + tvc.m_relay = tx_relay; } tvc.m_verifivation_failed = false; @@ -362,7 +386,7 @@ namespace cryptonote if (bytes == 0) bytes = m_txpool_max_weight; CRITICAL_REGION_LOCAL1(m_blockchain); - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); bool changed = false; // this will never remove the first one, but we don't care @@ -422,27 +446,18 @@ namespace cryptonote CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; - /* If any existing key-image in the set is publicly visible AND this is - not forcibly "kept_by_block", then fail (duplicate key image). If all - existing key images are supposed to be hidden, we silently allow so - that the node doesn't leak knowledge of a local/stem tx. */ - bool visible = false; + // Only allow multiple txes per key-image if kept-by-block. Only allow + // the same txid if going from local/stem->fluff. + if (tx_relay != relay_method::block) { - for (const crypto::hash& other_id : kei_image_set) - visible |= m_blockchain.txpool_tx_matches_category(other_id, relay_category::legacy); - } - - CHECK_AND_ASSERT_MES(!visible, false, "internal error: tx_relay=" << unsigned(tx_relay) + const bool one_txid = + (kei_image_set.empty() || (kei_image_set.size() == 1 && *(kei_image_set.cbegin()) == id)); + CHECK_AND_ASSERT_MES(one_txid, false, "internal error: tx_relay=" << unsigned(tx_relay) << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL << "tx_id=" << id); + } - /* If adding a tx (hash) that already exists, fail only if the tx has - been publicly "broadcast" previously. This way, when a private tx is - received for the first time from a remote node, "this" node will - respond as-if it were seen for the first time. LMDB does the - "hard-check" on key-images, so the effect is overwriting the existing - tx_pool metadata and "first seen" time. */ const bool new_or_previously_private = kei_image_set.insert(id).second || !m_blockchain.txpool_tx_matches_category(id, relay_category::legacy); @@ -494,7 +509,7 @@ namespace cryptonote try { - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(id, meta)) { @@ -549,7 +564,7 @@ namespace cryptonote try { - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(txid, meta)) { @@ -580,7 +595,7 @@ namespace cryptonote 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.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; td.relayed = meta.relayed; td.do_not_relay = meta.do_not_relay; td.double_spend_seen = meta.double_spend_seen; @@ -594,6 +609,39 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + bool tx_memory_pool::get_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes) const + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + + m_blockchain.for_all_txpool_txes([this, &hashes, &txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata*) { + const auto tx_relay_method = meta.get_relay_method(); + if (tx_relay_method != relay_method::block && tx_relay_method != relay_method::fluff) + return true; + const auto i = std::find(hashes.begin(), hashes.end(), txid); + if (i == hashes.end()) + { + cryptonote::blobdata bd; + try + { + if (!m_blockchain.get_txpool_tx_blob(txid, bd, cryptonote::relay_category::broadcasted)) + { + MERROR("Failed to get blob for txpool transaction " << txid); + return true; + } + txes.emplace_back(std::move(bd)); + } + catch (const std::exception &e) + { + MERROR("Failed to get blob for txpool transaction " << txid << ": " << e.what()); + return true; + } + } + return true; + }, false); + return true; + } + //--------------------------------------------------------------------------------- void tx_memory_pool::on_idle() { m_remove_stuck_tx_interval.do_call([this](){return remove_stuck_transactions();}); @@ -638,7 +686,7 @@ namespace cryptonote if (!remove.empty()) { - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); for (const std::pair<crypto::hash, uint64_t> &entry: remove) { const crypto::hash &txid = entry.first; @@ -680,8 +728,13 @@ 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.pruned && 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) { + if (!meta.dandelionpp_stem && now - meta.last_relayed_time <= get_relay_delay(now, meta.receive_time)) + return true; + if (meta.dandelionpp_stem && meta.last_relayed_time < now) // for dandelion++ stem, this value is the embargo timeout + return true; + // 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 // flushed txes to be re-added when received from a node which was just about to flush it @@ -706,10 +759,12 @@ namespace cryptonote //--------------------------------------------------------------------------------- void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method) { + crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average}; + const auto now = std::chrono::system_clock::now(); + CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - const time_t now = time(NULL); - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); for (const auto& hash : hashes) { try @@ -717,9 +772,15 @@ namespace cryptonote txpool_tx_meta_t meta; if (m_blockchain.get_txpool_tx_meta(hash, meta)) { + // txes can be received as "stem" or "fluff" in either order + meta.upgrade_relay_method(method); meta.relayed = true; - meta.last_relayed_time = now; - meta.set_relay_method(method); + + if (meta.dandelionpp_stem) + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now + embargo_duration()); + else + meta.last_relayed_time = std::chrono::system_clock::to_time_t(now); + m_blockchain.update_txpool_tx(hash, meta); } } @@ -904,7 +965,7 @@ namespace cryptonote txi.receive_time = include_sensitive_data ? meta.receive_time : 0; txi.relayed = meta.relayed; // In restricted mode we do not include this data: - txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0; + txi.last_relayed_time = (include_sensitive_data && !meta.dandelionpp_stem) ? meta.last_relayed_time : 0; txi.do_not_relay = meta.do_not_relay; txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(std::move(txi)); @@ -956,7 +1017,7 @@ namespace cryptonote txi.last_failed_block_hash = meta.last_failed_id; txi.receive_time = meta.receive_time; txi.relayed = meta.relayed; - txi.last_relayed_time = meta.last_relayed_time; + txi.last_relayed_time = meta.dandelionpp_stem ? 0 : meta.last_relayed_time; txi.do_not_relay = meta.do_not_relay; txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(txi); @@ -1037,30 +1098,32 @@ namespace cryptonote return m_blockchain.get_db().txpool_has_tx(id, tx_category); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const + bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail - if(have_tx_keyimg_as_spent(tokey_in.k_image)) + if(have_tx_keyimg_as_spent(tokey_in.k_image, txid)) return true; } return false; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const + bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - bool spent = false; const auto found = m_spent_key_images.find(key_im); - if (found != m_spent_key_images.end()) + if (found != m_spent_key_images.end() && !found->second.empty()) { - for (const crypto::hash& tx_hash : found->second) - spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted); + // If another tx is using the key image, always return as spent. + // See `insert_key_images`. + if (1 < found->second.size() || *(found->second.cbegin()) != txid) + return true; + return m_blockchain.txpool_tx_matches_category(txid, relay_category::broadcasted); } - return spent; + return false; } //--------------------------------------------------------------------------------- void tx_memory_pool::lock() const @@ -1186,7 +1249,7 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); bool changed = false; - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); for(size_t i = 0; i!= tx.vin.size(); i++) { CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void()); @@ -1268,7 +1331,11 @@ namespace cryptonote fee = 0; //baseline empty block - get_block_reward(median_weight, total_weight, already_generated_coins, best_coinbase, version); + if (!get_block_reward(median_weight, total_weight, already_generated_coins, best_coinbase, version)) + { + MERROR("Failed to get block reward for empty block"); + return false; + } size_t max_total_weight_pre_v5 = (130 * median_weight) / 100 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; @@ -1278,7 +1345,7 @@ namespace cryptonote LOG_PRINT_L2("Filling block template, median weight " << median_weight << ", " << m_txs_by_fee_and_receive_time.size() << " txes in the pool"); - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); auto sorted_it = m_txs_by_fee_and_receive_time.begin(); for (; sorted_it != m_txs_by_fee_and_receive_time.end(); ++sorted_it) @@ -1415,7 +1482,7 @@ namespace cryptonote size_t n_removed = 0; if (!remove.empty()) { - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); for (const crypto::hash &txid: remove) { try @@ -1495,7 +1562,7 @@ namespace cryptonote } if (!remove.empty()) { - LockedTXN lock(m_blockchain); + LockedTXN lock(m_blockchain.get_db()); for (const auto &txid: remove) { try diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index f716440ad..292d427e2 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -441,6 +441,11 @@ namespace cryptonote */ bool get_transaction_info(const crypto::hash &txid, tx_details &td) const; + /** + * @brief get transactions not in the passed set + */ + bool get_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes) const; + private: /** @@ -465,10 +470,11 @@ namespace cryptonote * @brief check if a transaction in the pool has a given spent key image * * @param key_im the spent key image to look for + * @param txid hash of the new transaction where `key_im` was seen. * * @return true if the spent key image is present, otherwise false */ - bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const; + bool have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const; /** * @brief check if any spent key image in a transaction is in the pool @@ -479,10 +485,11 @@ namespace cryptonote * @note see tx_pool::have_tx_keyimg_as_spent * * @param tx the transaction to check spent key images of + * @param txid hash of `tx`. * * @return true if any spent key images are present in the pool, otherwise false */ - bool have_tx_keyimges_as_spent(const transaction& tx) const; + bool have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const; /** * @brief forget a transaction's spent key images diff --git a/src/cryptonote_core/tx_sanity_check.cpp b/src/cryptonote_core/tx_sanity_check.cpp index 03cbb5c26..e99982def 100644 --- a/src/cryptonote_core/tx_sanity_check.cpp +++ b/src/cryptonote_core/tx_sanity_check.cpp @@ -28,7 +28,7 @@ #include <stdint.h> #include <vector> -#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "blockchain.h" #include "tx_sanity_check.h" @@ -39,7 +39,7 @@ namespace cryptonote { -bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob) +bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available) { cryptonote::transaction tx; @@ -70,14 +70,18 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob n_indices += in_to_key.key_offsets.size(); } + return tx_sanity_check(rct_indices, n_indices, rct_outs_available); +} + +bool tx_sanity_check(const std::set<uint64_t> &rct_indices, size_t n_indices, uint64_t rct_outs_available) +{ if (n_indices <= 10) { MDEBUG("n_indices is only " << n_indices << ", not checking"); return true; } - uint64_t n_available = blockchain.get_num_mature_outputs(0); - if (n_available < 10000) + if (rct_outs_available < 10000) return true; if (rct_indices.size() < n_indices * 8 / 10) @@ -88,9 +92,9 @@ bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob std::vector<uint64_t> offsets(rct_indices.begin(), rct_indices.end()); uint64_t median = epee::misc_utils::median(offsets); - if (median < n_available * 6 / 10) + if (median < rct_outs_available * 6 / 10) { - MERROR("median offset index is too low (median is " << median << " out of total " << n_available << "offsets). Transactions should contain a higher fraction of recent outputs."); + MERROR("median offset index is too low (median is " << median << " out of total " << rct_outs_available << "offsets). Transactions should contain a higher fraction of recent outputs."); return false; } diff --git a/src/cryptonote_core/tx_sanity_check.h b/src/cryptonote_core/tx_sanity_check.h index c12d1b0b1..4a469462f 100644 --- a/src/cryptonote_core/tx_sanity_check.h +++ b/src/cryptonote_core/tx_sanity_check.h @@ -26,11 +26,11 @@ // 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 <set> #include "cryptonote_basic/blobdatatype.h" namespace cryptonote { - class Blockchain; - - bool tx_sanity_check(Blockchain &blockchain, const cryptonote::blobdata &tx_blob); + bool tx_sanity_check(const cryptonote::blobdata &tx_blob, uint64_t rct_outs_available); + bool tx_sanity_check(const std::set<uint64_t> &rct_indices, size_t n_indices, uint64_t rct_outs_available); } diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 201001c8e..76b57afd3 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -197,10 +197,12 @@ namespace cryptonote { std::vector<blobdata> txs; std::string _; // padding + bool dandelionpp_fluff; //zero initialization defaults to stem mode BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(txs) KV_SERIALIZE(_) + KV_SERIALIZE_OPT(dandelionpp_fluff, true) // backwards compatible mode is fluff END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -257,7 +259,10 @@ namespace cryptonote BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(current_height) KV_SERIALIZE(cumulative_difficulty) - KV_SERIALIZE(cumulative_difficulty_top64) + if (is_store) + KV_SERIALIZE(cumulative_difficulty_top64) + else + KV_SERIALIZE_OPT(cumulative_difficulty_top64, (uint64_t)0) KV_SERIALIZE_VAL_POD_AS_BLOB(top_id) KV_SERIALIZE_OPT(top_version, (uint8_t)0) KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0) @@ -298,7 +303,10 @@ namespace cryptonote KV_SERIALIZE(start_height) KV_SERIALIZE(total_height) KV_SERIALIZE(cumulative_difficulty) - KV_SERIALIZE(cumulative_difficulty_top64) + if (is_store) + KV_SERIALIZE(cumulative_difficulty_top64) + else + KV_SERIALIZE_OPT(cumulative_difficulty_top64, (uint64_t)0) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_weights) END_KV_SERIALIZE_MAP() @@ -347,5 +355,23 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; }; + + /************************************************************************/ + /* */ + /************************************************************************/ + struct NOTIFY_GET_TXPOOL_COMPLEMENT + { + const static int ID = BC_COMMANDS_POOL_BASE + 10; + + struct request_t + { + std::vector<crypto::hash> hashes; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(hashes) + 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 ddbd45a61..e2ad3727f 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -45,7 +45,6 @@ #include "block_queue.h" #include "common/perf_timer.h" #include "cryptonote_basic/connection_context.h" -#include "cryptonote_basic/cryptonote_stat_info.h" #include <boost/circular_buffer.hpp> PUSH_WARNINGS @@ -77,7 +76,6 @@ namespace cryptonote { public: typedef cryptonote_connection_context connection_context; - typedef core_stat_info stat_info; typedef t_cryptonote_protocol_handler<t_core> cryptonote_protocol_handler; typedef CORE_SYNC_DATA payload_type; @@ -92,6 +90,7 @@ namespace cryptonote HANDLE_NOTIFY_T2(NOTIFY_RESPONSE_CHAIN_ENTRY, &cryptonote_protocol_handler::handle_response_chain_entry) HANDLE_NOTIFY_T2(NOTIFY_NEW_FLUFFY_BLOCK, &cryptonote_protocol_handler::handle_notify_new_fluffy_block) HANDLE_NOTIFY_T2(NOTIFY_REQUEST_FLUFFY_MISSING_TX, &cryptonote_protocol_handler::handle_request_fluffy_missing_tx) + HANDLE_NOTIFY_T2(NOTIFY_GET_TXPOOL_COMPLEMENT, &cryptonote_protocol_handler::handle_notify_get_txpool_complement) END_INVOKE_MAP2() bool on_idle(); @@ -102,7 +101,6 @@ namespace cryptonote bool process_payload_sync_data(const CORE_SYNC_DATA& hshd, cryptonote_connection_context& context, bool is_inital); bool get_payload_sync_data(blobdata& data); bool get_payload_sync_data(CORE_SYNC_DATA& hshd); - bool get_stat_info(core_stat_info& stat_inf); bool on_callback(cryptonote_connection_context& context); t_core& get_core(){return m_core;} bool is_synchronized(){return m_synchronized;} @@ -127,10 +125,11 @@ namespace cryptonote int handle_response_chain_entry(int command, NOTIFY_RESPONSE_CHAIN_ENTRY::request& arg, cryptonote_connection_context& context); int handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context); int handle_request_fluffy_missing_tx(int command, NOTIFY_REQUEST_FLUFFY_MISSING_TX::request& arg, cryptonote_connection_context& context); + int handle_notify_get_txpool_complement(int command, NOTIFY_GET_TXPOOL_COMPLEMENT::request& arg, cryptonote_connection_context& context); //----------------- i_bc_protocol_layout --------------------------------------- virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context); - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone); + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay); //---------------------------------------------------------------------------------- //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context); bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe); @@ -147,6 +146,7 @@ namespace cryptonote int try_add_next_blocks(cryptonote_connection_context &context); void notify_new_stripe(cryptonote_connection_context &context, uint32_t stripe); void skip_unneeded_hashes(cryptonote_connection_context& context, bool check_block_queue) const; + bool request_txpool_complement(cryptonote_connection_context &context); t_core& m_core; @@ -156,6 +156,7 @@ namespace cryptonote std::atomic<bool> m_synchronized; std::atomic<bool> m_stopping; std::atomic<bool> m_no_sync; + std::atomic<bool> m_ask_for_txpool_complement; boost::mutex m_sync_lock; block_queue m_block_queue; epee::math_helper::once_a_time_seconds<30> m_idle_peer_kicker; @@ -169,6 +170,14 @@ namespace cryptonote size_t m_block_download_max_size; bool m_sync_pruned_blocks; + // Values for sync time estimates + boost::posix_time::ptime m_sync_start_time; + boost::posix_time::ptime m_period_start_time; + uint64_t m_sync_start_height; + uint64_t m_period_start_height; + uint64_t get_estimated_remaining_sync_seconds(uint64_t current_blockchain_height, uint64_t target_blockchain_height); + std::string get_periodic_sync_estimate(uint64_t current_blockchain_height, uint64_t target_blockchain_height); + boost::mutex m_buffer_mutex; double get_avg_block_size(); boost::circular_buffer<size_t> m_avg_buffer = boost::circular_buffer<size_t>(10); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index a7bf0c283..f8e032fde 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -43,6 +43,7 @@ #include "profile_tools.h" #include "net/network_throttle-detail.hpp" #include "common/pruning.h" +#include "common/util.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.cn" @@ -83,6 +84,7 @@ namespace cryptonote m_p2p(p_net_layout), m_syncronized_connections_count(0), m_synchronized(offline), + m_ask_for_txpool_complement(true), m_stopping(false), m_no_sync(false) @@ -154,12 +156,6 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::get_stat_info(core_stat_info& stat_inf) - { - return m_core.get_stat_info(stat_inf); - } - //------------------------------------------------------------------------------------------------------------------------ - template<class t_core> void t_cryptonote_protocol_handler<t_core>::log_connections() { std::stringstream ss; @@ -312,7 +308,7 @@ namespace cryptonote if (version >= 6 && version != hshd.top_version) { if (version < hshd.top_version && version == m_core.get_ideal_hard_fork_version()) - MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version that we think (" << + MCLOG_RED(el::Level::Warning, "global", context << " peer claims higher version than we think (" << (unsigned)hshd.top_version << " for " << (hshd.current_height - 1) << " instead of " << (unsigned)version << ") - we may be forked from the network and a software upgrade may be needed"); return false; @@ -344,7 +340,7 @@ namespace cryptonote if(m_core.have_block(hshd.top_id)) { context.m_state = cryptonote_connection_context::state_normal; - if(is_inital && target == m_core.get_current_blockchain_height()) + if(is_inital && hshd.current_height >= target && target == m_core.get_current_blockchain_height()) on_connection_synchronized(); return true; } @@ -367,7 +363,7 @@ namespace cryptonote uint64_t last_block_v1 = m_core.get_nettype() == TESTNET ? 624633 : m_core.get_nettype() == MAINNET ? 1009826 : (uint64_t)-1; uint64_t diff_v2 = max_block_height > last_block_v1 ? std::min(abs_diff, max_block_height - last_block_v1) : 0; MCLOG(is_inital ? el::Level::Info : el::Level::Debug, "global", el::Color::Yellow, context << "Sync data returned a new top block candidate: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height - << " [Your node is " << abs_diff << " blocks (" << ((abs_diff - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " + << " [Your node is " << abs_diff << " blocks (" << tools::get_human_readable_timespan((abs_diff - diff_v2) * DIFFICULTY_TARGET_V1 + diff_v2 * DIFFICULTY_TARGET_V2) << ") " << (0 <= diff ? std::string("behind") : std::string("ahead")) << "] " << ENDL << "SYNCHRONIZATION started"); if (hshd.current_height >= m_core.get_current_blockchain_height() + 5) // don't switch to unsafe mode just for a few blocks @@ -885,6 +881,34 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> + int t_cryptonote_protocol_handler<t_core>::handle_notify_get_txpool_complement(int command, NOTIFY_GET_TXPOOL_COMPLEMENT::request& arg, cryptonote_connection_context& context) + { + MLOG_P2P_MESSAGE("Received NOTIFY_GET_TXPOOL_COMPLEMENT (" << arg.hashes.size() << " txes)"); + + std::vector<std::pair<cryptonote::blobdata, block>> local_blocks; + std::vector<cryptonote::blobdata> local_txs; + + std::vector<cryptonote::blobdata> txes; + if (!m_core.get_txpool_complement(arg.hashes, txes)) + { + LOG_ERROR_CCONTEXT("failed to get txpool complement"); + return 1; + } + + NOTIFY_NEW_TRANSACTIONS::request new_txes; + new_txes.txs = std::move(txes); + + MLOG_P2P_MESSAGE + ( + "-->>NOTIFY_NEW_TRANSACTIONS: " + << ", txs.size()=" << new_txes.txs.size() + ); + + post_notify<NOTIFY_NEW_TRANSACTIONS>(new_txes, context); + return 1; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context) { MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)"); @@ -903,29 +927,60 @@ namespace cryptonote return 1; } - std::vector<cryptonote::blobdata> newtxs; - newtxs.reserve(arg.txs.size()); - for (size_t i = 0; i < arg.txs.size(); ++i) + relay_method tx_relay; + std::vector<blobdata> stem_txs{}; + std::vector<blobdata> fluff_txs{}; + if (arg.dandelionpp_fluff) { - cryptonote::tx_verification_context tvc{}; - m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, relay_method::fluff, true); - if(tvc.m_verifivation_failed) + tx_relay = relay_method::fluff; + fluff_txs.reserve(arg.txs.size()); + } + else + { + tx_relay = relay_method::stem; + stem_txs.reserve(arg.txs.size()); + } + + for (auto& tx : arg.txs) + { + tx_verification_context tvc{}; + if (!m_core.handle_incoming_tx({tx, crypto::null_hash}, tvc, tx_relay, true)) { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); drop_connection(context, false, false); return 1; } - if(tvc.m_should_be_relayed) - newtxs.push_back(std::move(arg.txs[i])); + + switch (tvc.m_relay) + { + case relay_method::local: + case relay_method::stem: + stem_txs.push_back(std::move(tx)); + break; + case relay_method::block: + case relay_method::fluff: + fluff_txs.push_back(std::move(tx)); + break; + default: + case relay_method::none: + break; + } } - arg.txs = std::move(newtxs); - if(arg.txs.size()) + if (!stem_txs.empty()) { //TODO: add announce usage here - relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone()); + arg.dandelionpp_fluff = false; + arg.txs = std::move(stem_txs); + relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone(), relay_method::stem); + } + if (!fluff_txs.empty()) + { + //TODO: add announce usage here + arg.dandelionpp_fluff = true; + arg.txs = std::move(fluff_txs); + relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone(), relay_method::fluff); } - return 1; } //------------------------------------------------------------------------------------------------------------------------ @@ -1158,6 +1213,55 @@ namespace cryptonote return 1; } + // Get an estimate for the remaining sync time from given current to target blockchain height, in seconds + template<class t_core> + uint64_t t_cryptonote_protocol_handler<t_core>::get_estimated_remaining_sync_seconds(uint64_t current_blockchain_height, uint64_t target_blockchain_height) + { + // The average sync speed varies so much, even averaged over quite long time periods like 10 minutes, + // that using some sliding window would be difficult to implement without often leading to bad estimates. + // The simplest strategy - always average sync speed over the maximum available interval i.e. since sync + // started at all (from "m_sync_start_time" and "m_sync_start_height") - gives already useful results + // and seems to be quite robust. Some quite special cases like "Internet connection suddenly becoming + // much faster after syncing already a long time, and staying fast" are not well supported however. + + if (target_blockchain_height <= current_blockchain_height) + { + // Syncing stuck, or other special circumstance: Avoid errors, simply give back 0 + return 0; + } + + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + const boost::posix_time::time_duration sync_time = now - m_sync_start_time; + cryptonote::network_type nettype = m_core.get_nettype(); + + // Don't simply use remaining number of blocks for the estimate but "sync weight" as provided by + // "cumulative_block_sync_weight" which knows about strongly varying Monero mainnet block sizes + uint64_t synced_weight = tools::cumulative_block_sync_weight(nettype, m_sync_start_height, current_blockchain_height - m_sync_start_height); + float us_per_weight = (float)sync_time.total_microseconds() / (float)synced_weight; + uint64_t remaining_weight = tools::cumulative_block_sync_weight(nettype, current_blockchain_height, target_blockchain_height - current_blockchain_height); + float remaining_us = us_per_weight * (float)remaining_weight; + return (uint64_t)(remaining_us / 1e6); + } + + // Return a textual remaining sync time estimate, or the empty string if waiting period not yet over + template<class t_core> + std::string t_cryptonote_protocol_handler<t_core>::get_periodic_sync_estimate(uint64_t current_blockchain_height, uint64_t target_blockchain_height) + { + std::string text = ""; + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + boost::posix_time::time_duration period_sync_time = now - m_period_start_time; + if (period_sync_time > boost::posix_time::minutes(2)) + { + // Period is over, time to report another estimate + uint64_t remaining_seconds = get_estimated_remaining_sync_seconds(current_blockchain_height, target_blockchain_height); + text = tools::get_human_readable_timespan(remaining_seconds); + + // Start the new period + m_period_start_time = now; + } + return text; + } + template<class t_core> int t_cryptonote_protocol_handler<t_core>::try_add_next_blocks(cryptonote_connection_context& context) { @@ -1186,6 +1290,9 @@ namespace cryptonote if (!starting) m_last_add_end_time = tools::get_tick_count(); }); + m_sync_start_time = boost::posix_time::microsec_clock::universal_time(); + m_sync_start_height = m_core.get_current_blockchain_height(); + m_period_start_time = m_sync_start_time; while (1) { @@ -1436,7 +1543,16 @@ namespace cryptonote if (completion_percent == 100) // never show 100% if not actually up to date completion_percent = 99; progress_message = " (" + std::to_string(completion_percent) + "%, " - + std::to_string(target_blockchain_height - current_blockchain_height) + " left)"; + + std::to_string(target_blockchain_height - current_blockchain_height) + " left"; + std::string time_message = get_periodic_sync_estimate(current_blockchain_height, target_blockchain_height); + if (!time_message.empty()) + { + uint64_t total_blocks_to_sync = target_blockchain_height - m_sync_start_height; + uint64_t total_blocks_synced = current_blockchain_height - m_sync_start_height; + progress_message += ", " + std::to_string(total_blocks_synced * 100 / total_blocks_to_sync) + "% of total synced"; + progress_message += ", estimated " + time_message + " left"; + } + progress_message += ")"; } const uint32_t previous_stripe = tools::get_pruning_stripe(previous_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); const uint32_t current_stripe = tools::get_pruning_stripe(current_blockchain_height, target_blockchain_height, CRYPTONOTE_PRUNING_LOG_STRIPES); @@ -1671,9 +1787,9 @@ skip: const float max_multiplier = 10.f; const float min_multiplier = 1.25f; float multiplier = max_multiplier; - if (dt/1e6 >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY) + if (dt >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY) { - multiplier = max_multiplier - (dt/1e6-REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY) * (max_multiplier - min_multiplier) / (REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD - REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY); + multiplier = max_multiplier - (dt-REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY) * (max_multiplier - min_multiplier) / (REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD - REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY); multiplier = std::min(max_multiplier, std::max(min_multiplier, multiplier)); } if (dl_speed * .8f > ctx.m_current_speed_down * multiplier) @@ -2174,8 +2290,26 @@ skip: bool t_cryptonote_protocol_handler<t_core>::on_connection_synchronized() { bool val_expected = false; - if(!m_core.is_within_compiled_block_hash_area(m_core.get_current_blockchain_height()) && m_synchronized.compare_exchange_strong(val_expected, true)) + uint64_t current_blockchain_height = m_core.get_current_blockchain_height(); + if(!m_core.is_within_compiled_block_hash_area(current_blockchain_height) && m_synchronized.compare_exchange_strong(val_expected, true)) { + if ((current_blockchain_height > m_sync_start_height) && (m_sync_spans_downloaded > 0)) + { + uint64_t synced_blocks = current_blockchain_height - m_sync_start_height; + // Report only after syncing an "interesting" number of blocks: + if (synced_blocks > 20) + { + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + uint64_t synced_seconds = (now - m_sync_start_time).total_seconds(); + if (synced_seconds == 0) + { + synced_seconds = 1; + } + float blocks_per_second = (1000 * synced_blocks / synced_seconds) / 1000.0f; + MGINFO_YELLOW("Synced " << synced_blocks << " blocks in " + << tools::get_human_readable_timespan(synced_seconds) << " (" << blocks_per_second << " blocks per second)"); + } + } MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << "You are now synchronized with the network. You may now start monero-wallet-cli." << ENDL << ENDL @@ -2201,6 +2335,27 @@ skip: } m_core.safesyncmode(true); m_p2p->clear_used_stripe_peers(); + + // ask for txpool complement from any suitable node if we did not yet + val_expected = true; + if (m_ask_for_txpool_complement.compare_exchange_strong(val_expected, false)) + { + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool + { + if(context.m_state < cryptonote_connection_context::state_synchronizing) + { + MDEBUG(context << "not ready, ignoring"); + return true; + } + if (!request_txpool_complement(context)) + { + MERROR(context << "Failed to request txpool complement"); + return true; + } + return false; + }); + } + return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -2343,14 +2498,29 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone) + bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay) { /* Push all outgoing transactions to this function. The behavior needs to identify how the transaction is going to be relayed, and then update the local mempool before doing the relay. The code was already updating the DB twice on received transactions - it is difficult to workaround this due to the internal design. */ - return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core) != epee::net_utils::zone::invalid; + return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core, tx_relay) != epee::net_utils::zone::invalid; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> + bool t_cryptonote_protocol_handler<t_core>::request_txpool_complement(cryptonote_connection_context &context) + { + NOTIFY_GET_TXPOOL_COMPLEMENT::request r = {}; + if (!m_core.get_pool_transaction_hashes(r.hashes, false)) + { + MERROR("Failed to get txpool hashes"); + return false; + } + MLOG_P2P_MESSAGE("-->>NOTIFY_GET_TXPOOL_COMPLEMENT: hashes.size()=" << r.hashes.size() ); + post_notify<NOTIFY_GET_TXPOOL_COMPLEMENT>(r, context); + MLOG_PEER_STATE("requesting txpool complement"); + return true; } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> @@ -2463,7 +2633,10 @@ skip: MINFO("Target height decreasing from " << previous_target << " to " << target); m_core.set_target_blockchain_height(target); if (target == 0 && context.m_state > cryptonote_connection_context::state_before_handshake && !m_stopping) + { MCWARNING("global", "monerod is now disconnected from the network"); + m_ask_for_txpool_complement = true; + } } m_block_queue.flush_spans(context.m_connection_id, false); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index 978a9ebf3..11184299d 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -41,7 +41,7 @@ namespace cryptonote struct i_cryptonote_protocol { virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0; - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)=0; + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay)=0; //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0; }; @@ -54,7 +54,7 @@ namespace cryptonote { return false; } - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone) + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone, relay_method tx_relay) { return false; } diff --git a/src/cryptonote_protocol/enums.h b/src/cryptonote_protocol/enums.h index 2ec622d94..a3a52b83f 100644 --- a/src/cryptonote_protocol/enums.h +++ b/src/cryptonote_protocol/enums.h @@ -37,7 +37,8 @@ namespace cryptonote { none = 0, //!< Received via RPC with `do_not_relay` set local, //!< Received via RPC; trying to send over i2p/tor, etc. - block, //!< Received in block, takes precedence over others - fluff //!< Received/sent over public networks + stem, //!< Received/send over network using Dandelion++ stem + fluff, //!< Received/sent over network using Dandelion++ fluff + block //!< Received in block, takes precedence over others }; } diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index e45c34e02..127801092 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -30,6 +30,7 @@ #include <boost/asio/steady_timer.hpp> #include <boost/system/system_error.hpp> +#include <boost/uuid/uuid_io.hpp> #include <chrono> #include <deque> #include <stdexcept> @@ -38,12 +39,17 @@ #include "common/expect.h" #include "common/varint.h" #include "cryptonote_config.h" -#include "crypto/random.h" +#include "crypto/crypto.h" +#include "crypto/duration.h" #include "cryptonote_basic/connection_context.h" +#include "cryptonote_core/i_core_events.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "net/dandelionpp.h" #include "p2p/net_node.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "net.p2p.tx" + namespace { int get_command_from_message(const cryptonote::blobdata &msg) @@ -58,11 +64,14 @@ namespace levin { namespace { - constexpr std::size_t connection_id_reserve_size = 100; + constexpr const std::size_t connection_id_reserve_size = 100; constexpr const std::chrono::minutes noise_min_epoch{CRYPTONOTE_NOISE_MIN_EPOCH}; constexpr const std::chrono::seconds noise_epoch_range{CRYPTONOTE_NOISE_EPOCH_RANGE}; + constexpr const std::chrono::minutes dandelionpp_min_epoch{CRYPTONOTE_DANDELIONPP_MIN_EPOCH}; + constexpr const std::chrono::seconds dandelionpp_epoch_range{CRYPTONOTE_DANDELIONPP_EPOCH_RANGE}; + constexpr const std::chrono::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY}; constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE}; @@ -80,22 +89,8 @@ namespace levin connections (Dandelion++ makes similar assumptions in its stem algorithm). The randomization yields 95% values between 1s-4s in 1/4s increments. */ - constexpr const fluff_stepsize fluff_average_out{fluff_stepsize{fluff_average_in} / 2}; - - class random_poisson - { - std::poisson_distribution<fluff_stepsize::rep> dist; - public: - explicit random_poisson(fluff_stepsize average) - : dist(average.count() < 0 ? 0 : average.count()) - {} - - fluff_stepsize operator()() - { - crypto::random_device rand{}; - return fluff_stepsize{dist(rand)}; - } - }; + using fluff_duration = crypto::random_poisson_subseconds::result_type; + constexpr const fluff_duration fluff_average_out{fluff_duration{fluff_average_in} / 2}; /*! Select a randomized duration from 0 to `range`. The precision will be to the systems `steady_clock`. As an example, supplying 3 seconds to this @@ -129,10 +124,11 @@ namespace levin return outs; } - std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad) + std::string make_tx_payload(std::vector<blobdata>&& txs, const bool pad, const bool fluff) { NOTIFY_NEW_TRANSACTIONS::request request{}; request.txs = std::move(txs); + request.dandelionpp_fluff = fluff; if (pad) { @@ -169,9 +165,9 @@ namespace levin return fullBlob; } - bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad) + bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad, const bool fluff) { - const cryptonote::blobdata blob = make_tx_payload(std::move(txs), pad); + const cryptonote::blobdata blob = make_tx_payload(std::move(txs), pad, fluff); p2p.for_connection(destination, [&blob](detail::p2p_context& context) { on_levin_traffic(context, true, true, false, blob.size(), get_command_from_message(blob)); return true; @@ -248,7 +244,8 @@ namespace levin flush_time(std::chrono::steady_clock::time_point::max()), connection_count(0), is_public(is_public), - pad_txs(pad_txs) + pad_txs(pad_txs), + fluffing(false) { for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count) channels.emplace_back(io_service); @@ -265,6 +262,7 @@ namespace levin 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 const bool pad_txs; //!< Pad txs to the next boundary for privacy + bool fluffing; //!< Zone is in Dandelion++ fluff epoch }; } // detail @@ -298,6 +296,8 @@ namespace levin if (!channel.connection.is_nil()) channel.queue.push_back(std::move(message_)); + else if (destination_ == 0 && zone_->connection_count == 0) + MWARNING("Unable to send transaction(s) over anonymity network - no available outbound connections"); } }; @@ -357,8 +357,12 @@ namespace levin return true; }); + // Always send txs in stem mode over i2p/tor, see comments in `send_txs` below. for (auto& connection : connections) - make_payload_send_txs(*zone_->p2p, std::move(connection.first), connection.second, zone_->pad_txs); + { + std::sort(connection.first.begin(), connection.first.end()); // don't leak receive order + make_payload_send_txs(*zone_->p2p, std::move(connection.first), connection.second, zone_->pad_txs, zone_->is_public); + } if (next_flush != std::chrono::steady_clock::time_point::max()) fluff_flush::queue(std::move(zone_), next_flush); @@ -379,34 +383,48 @@ namespace levin void operator()() { - if (!zone_ || !zone_->p2p || txs_.empty()) + run(std::move(zone_), epee::to_span(txs_), source_); + } + + static void run(std::shared_ptr<detail::zone> zone, epee::span<const blobdata> txs, const boost::uuids::uuid& source) + { + if (!zone || !zone->p2p || txs.empty()) return; - assert(zone_->strand.running_in_this_thread()); + assert(zone->strand.running_in_this_thread()); const auto now = std::chrono::steady_clock::now(); auto next_flush = std::chrono::steady_clock::time_point::max(); - random_poisson in_duration(fluff_average_in); - random_poisson out_duration(fluff_average_out); + crypto::random_poisson_subseconds in_duration(fluff_average_in); + crypto::random_poisson_subseconds out_duration(fluff_average_out); + + + MDEBUG("Queueing " << txs.size() << " transaction(s) for Dandelion++ fluffing"); - zone_->p2p->foreach_connection([this, now, &in_duration, &out_duration, &next_flush] (detail::p2p_context& context) + bool available = false; + zone->p2p->foreach_connection([txs, now, &zone, &source, &in_duration, &out_duration, &next_flush, &available] (detail::p2p_context& context) { - if (this->source_ != context.m_connection_id && (this->zone_->is_public || !context.m_is_income)) + // When i2p/tor, only fluff to outbound connections + if (source != context.m_connection_id && (zone->is_public || !context.m_is_income)) { + available = true; if (context.fluff_txs.empty()) context.flush_time = now + (context.m_is_income ? in_duration() : out_duration()); next_flush = std::min(next_flush, context.flush_time); - context.fluff_txs.reserve(context.fluff_txs.size() + this->txs_.size()); - for (const blobdata& tx : this->txs_) + context.fluff_txs.reserve(context.fluff_txs.size() + txs.size()); + for (const blobdata& tx : txs) context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns) } return true; }); - if (next_flush < zone_->flush_time) - fluff_flush::queue(std::move(zone_), next_flush); + if (!available) + MWARNING("Unable to send transaction(s), no available connections"); + + if (next_flush < zone->flush_time) + fluff_flush::queue(std::move(zone), next_flush); } }; @@ -458,6 +476,11 @@ namespace levin assert(zone->strand.running_in_this_thread()); zone->connection_count = zone->map.size(); + + // only noise uses the "noise channels", only update when enabled + if (zone->noise.empty()) + return; + for (auto id = zone->map.begin(); id != zone->map.end(); ++id) { const std::size_t i = id - zone->map.begin(); @@ -466,26 +489,75 @@ namespace levin } //! \pre Called within `zone_->strand`. + static void run(std::shared_ptr<detail::zone> zone, std::vector<boost::uuids::uuid> out_connections) + { + if (!zone) + return; + + assert(zone->strand.running_in_this_thread()); + if (zone->map.update(std::move(out_connections))) + post(std::move(zone)); + } + + //! \pre Called within `zone_->strand`. void operator()() { - if (!zone_) + run(std::move(zone_), std::move(out_connections_)); + } + }; + + //! Checks fluff status for this node, and then does stem or fluff for txes + struct dandelionpp_notify + { + std::shared_ptr<detail::zone> zone_; + i_core_events* core_; + std::vector<blobdata> txs_; + boost::uuids::uuid source_; + + //! \pre Called in `zone_->strand` + void operator()() + { + if (!zone_ || !core_ || txs_.empty()) return; - assert(zone_->strand.running_in_this_thread()); - if (zone_->map.update(std::move(out_connections_))) - post(std::move(zone_)); + if (zone_->fluffing) + { + core_->on_transactions_relayed(epee::to_span(txs_), relay_method::fluff); + fluff_notify::run(std::move(zone_), epee::to_span(txs_), source_); + } + else // forward tx in stem + { + core_->on_transactions_relayed(epee::to_span(txs_), relay_method::stem); + for (int tries = 2; 0 < tries; tries--) + { + const boost::uuids::uuid destination = zone_->map.get_stem(source_); + if (!destination.is_nil() && make_payload_send_txs(*zone_->p2p, std::vector<blobdata>{txs_}, destination, zone_->pad_txs, false)) + { + /* Source is intentionally omitted in debug log for privacy - a + nil uuid indicates source is that node. */ + MDEBUG("Sent " << txs_.size() << " transaction(s) to " << destination << " using Dandelion++ stem"); + return; + } + + // connection list may be outdated, try again + update_channels::run(zone_, get_out_connections(*zone_->p2p)); + } + + MERROR("Unable to send transaction(s) via Dandelion++ stem"); + } } }; - //! Swaps out noise channels entirely; new epoch start. + //! Swaps out noise/dandelionpp channels entirely; new epoch start. class change_channels { std::shared_ptr<detail::zone> zone_; net::dandelionpp::connection_map map_; // Requires manual copy constructor + bool fluffing_; public: - explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map) - : zone_(std::move(zone)), map_(std::move(map)) + explicit change_channels(std::shared_ptr<detail::zone> zone, net::dandelionpp::connection_map map, const bool fluffing) + : zone_(std::move(zone)), map_(std::move(map)), fluffing_(fluffing) {} change_channels(change_channels&&) = default; @@ -497,11 +569,15 @@ namespace levin void operator()() { if (!zone_) - return + return; assert(zone_->strand.running_in_this_thread()); + if (zone_->is_public) + MDEBUG("Starting new Dandelion++ epoch: " << (fluffing_ ? "fluff" : "stem")); + zone_->map = std::move(map_); + zone_->fluffing = fluffing_; update_channels::post(std::move(zone_)); } }; @@ -564,9 +640,12 @@ namespace levin { channel.active = nullptr; channel.connection = boost::uuids::nil_uuid(); - zone_->strand.post( - update_channels{zone_, get_out_connections(*zone_->p2p)} - ); + + auto connections = get_out_connections(*zone_->p2p); + if (connections.empty()) + MWARNING("Lost all outbound connections to anonymity network - currently unable to send transaction(s)"); + + zone_->strand.post(update_channels{zone_, std::move(connections)}); } } @@ -592,9 +671,10 @@ namespace levin if (error && error != boost::system::errc::operation_canceled) throw boost::system::system_error{error, "start_epoch timer failed"}; + const bool fluffing = crypto::rand_idx(unsigned(100)) < CRYPTONOTE_DANDELIONPP_FLUFF_PROBABILITY; const auto start = std::chrono::steady_clock::now(); zone_->strand.dispatch( - change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}} + change_channels{zone_, net::dandelionpp::connection_map{get_out_connections(*(zone_->p2p)), count_}, fluffing} ); detail::zone& alias = *zone_; @@ -610,10 +690,16 @@ namespace levin if (!zone_->p2p) throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"}; - if (!zone_->noise.empty()) + const bool noise_enabled = !zone_->noise.empty(); + if (noise_enabled || is_public) { const auto now = std::chrono::steady_clock::now(); - start_epoch{zone_, noise_min_epoch, noise_epoch_range, CRYPTONOTE_NOISE_CHANNELS}(); + const auto min_epoch = noise_enabled ? noise_min_epoch : dandelionpp_min_epoch; + const auto epoch_range = noise_enabled ? noise_epoch_range : dandelionpp_epoch_range; + const std::size_t out_count = noise_enabled ? CRYPTONOTE_NOISE_CHANNELS : CRYPTONOTE_DANDELIONPP_STEMS; + + start_epoch{zone_, min_epoch, epoch_range, out_count}(); + for (std::size_t channel = 0; channel < zone_->channels.size(); ++channel) send_noise::wait(now, zone_, channel); } @@ -663,7 +749,7 @@ namespace levin zone_->flush_txs.cancel(); } - bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source) + bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, i_core_events& core, relay_method tx_relay) { if (txs.empty()) return true; @@ -671,6 +757,17 @@ namespace levin if (!zone_) return false; + /* If noise is enabled in a zone, it always takes precedence. The technique + provides good protection against ISP adversaries, but not sybil + adversaries. Noise is currently only enabled over I2P/Tor - those + networks provide protection against sybil attacks (we only send to + outgoing connections). + + If noise is disabled, Dandelion++ is used for public networks only. + Dandelion++ over I2P/Tor should be an interesting case to investigate, + but the mempool/stempool needs to know the zone a tx originated from to + work properly. */ + if (!zone_->noise.empty() && !zone_->channels.empty()) { // covert send in "noise" channel @@ -678,8 +775,17 @@ namespace levin CRYPTONOTE_MAX_FRAGMENTS * CRYPTONOTE_NOISE_BYTES <= LEVIN_DEFAULT_MAX_PACKET_SIZE, "most nodes will reject this fragment setting" ); - // padding is not useful when using noise mode - const std::string payload = make_tx_payload(std::move(txs), false); + if (tx_relay == relay_method::stem) + { + MWARNING("Dandelion++ stem not supported over noise networks"); + tx_relay = relay_method::local; // do not put into stempool embargo (hopefully not there already!). + } + + core.on_transactions_relayed(epee::to_span(txs), tx_relay); + + // Padding is not useful when using noise mode. Send as stem so receiver + // forwards in Dandelion++ mode. + const std::string payload = make_tx_payload(std::move(txs), false, false); epee::byte_slice message = epee::levin::make_fragmented_notify( zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload) ); @@ -698,9 +804,31 @@ namespace levin } else { - zone_->strand.dispatch(fluff_notify{zone_, std::move(txs), source}); + switch (tx_relay) + { + default: + case relay_method::none: + case relay_method::block: + return false; + case relay_method::stem: + tx_relay = relay_method::fluff; // don't set stempool embargo when skipping to fluff + /* fallthrough */ + case relay_method::local: + if (zone_->is_public) + { + // this will change a local tx to stem or fluff ... + zone_->strand.dispatch( + dandelionpp_notify{zone_, std::addressof(core), std::move(txs), source} + ); + break; + } + /* fallthrough */ + case relay_method::fluff: + core.on_transactions_relayed(epee::to_span(txs), tx_relay); + zone_->strand.dispatch(fluff_notify{zone_, std::move(txs), source}); + break; + } } - return true; } } // levin diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index ce652d933..641f1f956 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -35,6 +35,7 @@ #include "byte_slice.h" #include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_protocol/enums.h" #include "cryptonote_protocol/fwd.h" #include "net/enums.h" #include "span.h" @@ -122,7 +123,7 @@ namespace levin particular stem. \return True iff the notification is queued for sending. */ - bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source); + bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, i_core_events& core, relay_method tx_relay); }; } // levin } // net diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index ed6d3af01..3a4081e39 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -857,13 +857,27 @@ bool t_command_parser_executor::set_bootstrap_daemon(const std::vector<std::stri bool t_command_parser_executor::flush_cache(const std::vector<std::string>& args) { + bool bad_txs = false, bad_blocks = false; + std::string arg; + if (args.empty()) goto show_list; - if (args[0] == "bad-txs") - return m_executor.flush_cache(true); + + for (size_t i = 0; i < args.size(); ++i) + { + arg = args[i]; + if (arg == "bad-txs") + bad_txs = true; + else if (arg == "bad-blocks") + bad_blocks = true; + else + goto show_list; + } + return m_executor.flush_cache(bad_txs, bad_blocks); show_list: - std::cout << "Cache type needed: bad-txs" << std::endl; + std::cout << "Invalid cache type: " << arg << std::endl; + std::cout << "Cache types: bad-txs bad-blocks" << std::endl; return true; } diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 8ec690631..7fae77c30 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -325,7 +325,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "flush_cache" , std::bind(&t_command_parser_executor::flush_cache, &m_parser, p::_1) - , "flush_cache bad-txs" + , "flush_cache [bad-txs] [bad-blocks]" , "Flush the specified cache(s)." ); } diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 8fa983fe5..3e25636d8 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -69,7 +69,7 @@ uint16_t parse_public_rpc_port(const po::variables_map &vm) const auto &restricted_rpc_port = cryptonote::core_rpc_server::arg_rpc_restricted_bind_port; if (!command_line::is_arg_defaulted(vm, restricted_rpc_port)) { - rpc_port_str = command_line::get_arg(vm, restricted_rpc_port);; + rpc_port_str = command_line::get_arg(vm, restricted_rpc_port); } else if (command_line::get_arg(vm, cryptonote::core_rpc_server::arg_restricted_rpc)) { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 1ca728e39..034d49918 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -695,11 +695,11 @@ bool t_rpc_command_executor::print_net_stats() if (m_is_rpc) { - if (!m_rpc_client->json_rpc_request(net_stats_req, net_stats_res, "get_net_stats", fail_message.c_str())) + if (!m_rpc_client->rpc_request(net_stats_req, net_stats_res, "/get_net_stats", fail_message.c_str())) { return true; } - if (!m_rpc_client->json_rpc_request(limit_req, limit_res, "get_limit", fail_message.c_str())) + if (!m_rpc_client->rpc_request(limit_req, limit_res, "/get_limit", fail_message.c_str())) { return true; } @@ -1699,15 +1699,19 @@ bool t_rpc_command_executor::print_bans() } } - for (auto i = res.bans.begin(); i != res.bans.end(); ++i) + if (!res.bans.empty()) { - tools::msg_writer() << i->host << " banned for " << i->seconds << " seconds"; + for (auto i = res.bans.begin(); i != res.bans.end(); ++i) + { + tools::msg_writer() << i->host << " banned for " << i->seconds << " seconds"; + } } + else + tools::msg_writer() << "No IPs are banned"; return true; } - bool t_rpc_command_executor::ban(const std::string &address, time_t seconds) { cryptonote::COMMAND_RPC_SETBANS::request req; @@ -1905,9 +1909,9 @@ bool t_rpc_command_executor::print_coinbase_tx_sum(uint64_t height, uint64_t cou tools::msg_writer() << "Sum of coinbase transactions between block heights [" << height << ", " << (height + count) << ") is " - << cryptonote::print_money(res.emission_amount + res.fee_amount) << " " - << "consisting of " << cryptonote::print_money(res.emission_amount) - << " in emissions, and " << cryptonote::print_money(res.fee_amount) << " in fees"; + << cryptonote::print_money(boost::multiprecision::uint128_t(res.wide_emission_amount) + boost::multiprecision::uint128_t(res.wide_fee_amount)) << " " + << "consisting of " << cryptonote::print_money(boost::multiprecision::uint128_t(res.wide_emission_amount)) + << " in emissions, and " << cryptonote::print_money(boost::multiprecision::uint128_t(res.wide_fee_amount)) << " in fees"; return true; } @@ -2427,7 +2431,7 @@ bool t_rpc_command_executor::set_bootstrap_daemon( return true; } -bool t_rpc_command_executor::flush_cache(bool bad_txs) +bool t_rpc_command_executor::flush_cache(bool bad_txs, bool bad_blocks) { cryptonote::COMMAND_RPC_FLUSH_CACHE::request req; cryptonote::COMMAND_RPC_FLUSH_CACHE::response res; @@ -2435,6 +2439,7 @@ bool t_rpc_command_executor::flush_cache(bool bad_txs) epee::json_rpc::error error_resp; req.bad_txs = bad_txs; + req.bad_blocks = bad_blocks; if (m_is_rpc) { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 1754ce32e..66ada6f1f 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -172,7 +172,7 @@ public: bool rpc_payments(); - bool flush_cache(bool bad_txs); + bool flush_cache(bool bad_txs, bool invalid_blocks); }; } // namespace daemonize diff --git a/src/device/device.hpp b/src/device/device.hpp index 215e97eb6..3d9ea0449 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -56,6 +56,7 @@ namespace cryptonote struct subaddress_index; struct tx_destination_entry; struct keypair; + class transaction_prefix; } namespace hw { @@ -203,6 +204,8 @@ namespace hw { virtual bool open_tx(crypto::secret_key &tx_key) = 0; + virtual void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) = 0; + virtual bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) = 0; bool decrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) { diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp index dc06ce237..47156cbce 100644 --- a/src/device/device_default.cpp +++ b/src/device/device_default.cpp @@ -36,9 +36,7 @@ #include "cryptonote_basic/subaddress_index.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "ringct/rctOps.h" - -#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d -#define CHACHA8_KEY_TAIL 0x8c +#include "cryptonote_config.h" namespace hw { @@ -107,7 +105,7 @@ namespace hw { epee::mlocked<tools::scrubbed_arr<char, sizeof(view_key) + sizeof(spend_key) + 1>> data; memcpy(data.data(), &view_key, sizeof(view_key)); memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key)); - data[sizeof(data) - 1] = CHACHA8_KEY_TAIL; + data[sizeof(data) - 1] = config::HASH_KEY_WALLET; crypto::generate_chacha_key(data.data(), sizeof(data), key, kdf_rounds); return true; } @@ -196,14 +194,13 @@ namespace hw { } crypto::secret_key device_default::get_subaddress_secret_key(const crypto::secret_key &a, const cryptonote::subaddress_index &index) { - const char prefix[] = "SubAddr"; - char data[sizeof(prefix) + sizeof(crypto::secret_key) + 2 * sizeof(uint32_t)]; - memcpy(data, prefix, sizeof(prefix)); - memcpy(data + sizeof(prefix), &a, sizeof(crypto::secret_key)); + char data[sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key) + 2 * sizeof(uint32_t)]; + memcpy(data, config::HASH_KEY_SUBADDRESS, sizeof(config::HASH_KEY_SUBADDRESS)); + memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS), &a, sizeof(crypto::secret_key)); uint32_t idx = SWAP32LE(index.major); - memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &idx, sizeof(uint32_t)); + memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key), &idx, sizeof(uint32_t)); idx = SWAP32LE(index.minor); - memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key) + sizeof(uint32_t), &idx, sizeof(uint32_t)); + memcpy(data + sizeof(config::HASH_KEY_SUBADDRESS) + sizeof(crypto::secret_key) + sizeof(uint32_t), &idx, sizeof(uint32_t)); crypto::secret_key m; crypto::hash_to_scalar(data, sizeof(data), m); return m; @@ -284,6 +281,10 @@ namespace hw { return true; } + void device_default::get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) { + cryptonote::get_transaction_prefix_hash(tx, h); + } + bool device_default::generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, @@ -344,7 +345,7 @@ namespace hw { return false; memcpy(data, &derivation, 32); - data[32] = ENCRYPTED_PAYMENT_ID_TAIL; + data[32] = config::HASH_KEY_ENCRYPTED_PAYMENT_ID; cn_fast_hash(data, 33, hash); for (size_t b = 0; b < 8; ++b) diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 5252d4129..64cad78b0 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -112,6 +112,7 @@ namespace hw { crypto::signature &sig) override; bool open_tx(crypto::secret_key &tx_key) override; + void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override; bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override; diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index 72f4c3bdb..840529c38 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -44,8 +44,20 @@ namespace hw { static std::string safe_hid_error(hid_device *hwdev) { if (hwdev) { - const char* error_str = (const char*)hid_error(hwdev); - return std::string(error_str == nullptr ? "Unknown error" : error_str); + const wchar_t* error_wstr = hid_error(hwdev); + if (error_wstr == nullptr) + { + return "Unknown error"; + } + std::mbstate_t state{}; + const size_t len_symbols = std::wcsrtombs(nullptr, &error_wstr, 0, &state); + if (len_symbols == static_cast<std::size_t>(-1)) + { + return "Failed to convert wide char error"; + } + std::string error_str(len_symbols + 1, 0); + std::wcsrtombs(&error_str[0], &error_wstr, error_str.size(), &state); + return error_str; } return std::string("NULL device"); } diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index 49f54e5a5..222a84d3f 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -55,7 +55,10 @@ namespace hw { } #define TRACKD MTRACE("hw") - #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(mask))==(ok), "Wrong Device Status : SW=" << std::hex << (sw) << " (EXPECT=" << std::hex << (ok) << ", MASK=" << std::hex << (mask) << ")") ; + #define ASSERT_SW(sw,ok,msk) CHECK_AND_ASSERT_THROW_MES(((sw)&(mask))==(ok), \ + "Wrong Device Status: " << "0x" << std::hex << (sw) << " (" << Status::to_string(sw) << "), " << \ + "EXPECTED 0x" << std::hex << (ok) << " (" << Status::to_string(ok) << "), " << \ + "MASK 0x" << std::hex << (mask)); #define ASSERT_T0(exp) CHECK_AND_ASSERT_THROW_MES(exp, "Protocol assert failure: "#exp ) ; #define ASSERT_X(exp,msg) CHECK_AND_ASSERT_THROW_MES(exp, msg); @@ -64,6 +67,71 @@ namespace hw { crypto::secret_key dbg_spendkey; #endif + struct Status + { + unsigned int code; + const char *string; + + constexpr operator unsigned int() const + { + return this->code; + } + + static const char *to_string(unsigned int code); + }; + + // Must be sorted in ascending order by the code + #define LEDGER_STATUS(status) {status, #status} + constexpr Status status_codes[] = { + LEDGER_STATUS(SW_BYTES_REMAINING_00), + LEDGER_STATUS(SW_WARNING_STATE_UNCHANGED), + LEDGER_STATUS(SW_STATE_TERMINATED), + LEDGER_STATUS(SW_MORE_DATA_AVAILABLE), + LEDGER_STATUS(SW_WRONG_LENGTH), + LEDGER_STATUS(SW_LOGICAL_CHANNEL_NOT_SUPPORTED), + LEDGER_STATUS(SW_SECURE_MESSAGING_NOT_SUPPORTED), + LEDGER_STATUS(SW_LAST_COMMAND_EXPECTED), + LEDGER_STATUS(SW_COMMAND_CHAINING_NOT_SUPPORTED), + LEDGER_STATUS(SW_SECURITY_LOAD_KEY), + LEDGER_STATUS(SW_SECURITY_COMMITMENT_CONTROL), + LEDGER_STATUS(SW_SECURITY_AMOUNT_CHAIN_CONTROL), + LEDGER_STATUS(SW_SECURITY_COMMITMENT_CHAIN_CONTROL), + LEDGER_STATUS(SW_SECURITY_OUTKEYS_CHAIN_CONTROL), + LEDGER_STATUS(SW_SECURITY_MAXOUTPUT_REACHED), + LEDGER_STATUS(SW_SECURITY_TRUSTED_INPUT), + LEDGER_STATUS(SW_CLIENT_NOT_SUPPORTED), + LEDGER_STATUS(SW_SECURITY_STATUS_NOT_SATISFIED), + LEDGER_STATUS(SW_FILE_INVALID), + LEDGER_STATUS(SW_PIN_BLOCKED), + LEDGER_STATUS(SW_DATA_INVALID), + LEDGER_STATUS(SW_CONDITIONS_NOT_SATISFIED), + LEDGER_STATUS(SW_COMMAND_NOT_ALLOWED), + LEDGER_STATUS(SW_APPLET_SELECT_FAILED), + LEDGER_STATUS(SW_WRONG_DATA), + LEDGER_STATUS(SW_FUNC_NOT_SUPPORTED), + LEDGER_STATUS(SW_FILE_NOT_FOUND), + LEDGER_STATUS(SW_RECORD_NOT_FOUND), + LEDGER_STATUS(SW_FILE_FULL), + LEDGER_STATUS(SW_INCORRECT_P1P2), + LEDGER_STATUS(SW_REFERENCED_DATA_NOT_FOUND), + LEDGER_STATUS(SW_WRONG_P1P2), + LEDGER_STATUS(SW_CORRECT_LENGTH_00), + LEDGER_STATUS(SW_INS_NOT_SUPPORTED), + LEDGER_STATUS(SW_CLA_NOT_SUPPORTED), + LEDGER_STATUS(SW_UNKNOWN), + LEDGER_STATUS(SW_OK), + LEDGER_STATUS(SW_ALGORITHM_UNSUPPORTED) + }; + + const char *Status::to_string(unsigned int code) + { + constexpr size_t status_codes_size = sizeof(status_codes) / sizeof(status_codes[0]); + constexpr const Status *status_codes_end = &status_codes[status_codes_size]; + + const Status *item = std::lower_bound(&status_codes[0], status_codes_end, code); + return (item == status_codes_end || code < *item) ? "UNKNOWN" : item->string; + } + /* ===================================================================== */ /* === hmacmap ==== */ /* ===================================================================== */ @@ -191,7 +259,7 @@ namespace hw { static int device_id = 0; - #define PROTOCOL_VERSION 2 + #define PROTOCOL_VERSION 3 #define INS_NONE 0x00 #define INS_RESET 0x02 @@ -228,6 +296,7 @@ namespace hw { #define INS_BLIND 0x78 #define INS_UNBLIND 0x7A #define INS_GEN_TXOUT_KEYS 0x7B + #define INS_PREFIX_HASH 0x7D #define INS_VALIDATE 0x7C #define INS_MLSAG 0x7E #define INS_CLOSE_TX 0x80 @@ -1346,6 +1415,81 @@ namespace hw { return true; } + void device_ledger::get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) { + AUTO_LOCK_CMD(); + + int pref_length = 0, pref_offset = 0, offset = 0; + + #ifdef DEBUG_HWDEVICE + crypto::hash h_x; + this->controle_device->get_transaction_prefix_hash(tx,h_x); + MDEBUG("get_transaction_prefix_hash [[IN]] h_x/1 "<<h_x); + #endif + + std::ostringstream s_x; + binary_archive<true> a_x(s_x); + CHECK_AND_ASSERT_THROW_MES(::serialization::serialize(a_x, const_cast<cryptonote::transaction_prefix&>(tx)), + "unable to serialize transaction prefix"); + pref_length = s_x.str().size(); + //auto pref = std::make_unique<unsigned char[]>(pref_length); + auto uprt_pref = std::unique_ptr<unsigned char[]>{ new unsigned char[pref_length] }; + unsigned char* pref = uprt_pref.get(); + memmove(pref, s_x.str().data(), pref_length); + + offset = set_command_header_noopt(INS_PREFIX_HASH,1); + pref_offset = 0; + unsigned char v; + + //version as varint + do { + v = pref[pref_offset]; + this->buffer_send[offset] = v; + offset += 1; + pref_offset += 1; + } while (v&0x80); + + //locktime as var int + do { + v = pref[pref_offset]; + this->buffer_send[offset] = v; + offset += 1; + pref_offset += 1; + } while (v&0x80); + + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange_wait_on_input(); + + //hash remains + int cnt = 0; + while (pref_offset < pref_length) { + int len; + cnt++; + offset = set_command_header(INS_PREFIX_HASH,2,cnt); + len = pref_length - pref_offset; + //options + if (len > (BUFFER_SEND_SIZE-7)) { + len = BUFFER_SEND_SIZE-7; + this->buffer_send[offset] = 0x80; + } else { + this->buffer_send[offset] = 0x00; + } + offset += 1; + //send chunk + memmove(&this->buffer_send[offset], pref+pref_offset, len); + offset += len; + pref_offset += len; + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + } + memmove(h.data, &this->buffer_recv[0], 32); + + #ifdef DEBUG_HWDEVICE + hw::ledger::check8("prefix_hash", "h", h_x.data, h.data); + #endif + } + bool device_ledger::encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) { AUTO_LOCK_CMD(); diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 05a26143a..070162cbc 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -58,6 +58,46 @@ namespace hw { #ifdef WITH_DEVICE_LEDGER + // Origin: https://github.com/LedgerHQ/ledger-app-monero/blob/master/src/monero_types.h + #define SW_BYTES_REMAINING_00 0x6100 + #define SW_WARNING_STATE_UNCHANGED 0x6200 + #define SW_STATE_TERMINATED 0x6285 + #define SW_MORE_DATA_AVAILABLE 0x6310 + #define SW_WRONG_LENGTH 0x6700 + #define SW_LOGICAL_CHANNEL_NOT_SUPPORTED 0x6881 + #define SW_SECURE_MESSAGING_NOT_SUPPORTED 0x6882 + #define SW_LAST_COMMAND_EXPECTED 0x6883 + #define SW_COMMAND_CHAINING_NOT_SUPPORTED 0x6884 + #define SW_SECURITY_LOAD_KEY 0x6900 + #define SW_SECURITY_COMMITMENT_CONTROL 0x6911 + #define SW_SECURITY_AMOUNT_CHAIN_CONTROL 0x6912 + #define SW_SECURITY_COMMITMENT_CHAIN_CONTROL 0x6913 + #define SW_SECURITY_OUTKEYS_CHAIN_CONTROL 0x6914 + #define SW_SECURITY_MAXOUTPUT_REACHED 0x6915 + #define SW_SECURITY_TRUSTED_INPUT 0x6916 + #define SW_CLIENT_NOT_SUPPORTED 0x6930 + #define SW_SECURITY_STATUS_NOT_SATISFIED 0x6982 + #define SW_FILE_INVALID 0x6983 + #define SW_PIN_BLOCKED 0x6983 + #define SW_DATA_INVALID 0x6984 + #define SW_CONDITIONS_NOT_SATISFIED 0x6985 + #define SW_COMMAND_NOT_ALLOWED 0x6986 + #define SW_APPLET_SELECT_FAILED 0x6999 + #define SW_WRONG_DATA 0x6a80 + #define SW_FUNC_NOT_SUPPORTED 0x6a81 + #define SW_FILE_NOT_FOUND 0x6a82 + #define SW_RECORD_NOT_FOUND 0x6a83 + #define SW_FILE_FULL 0x6a84 + #define SW_INCORRECT_P1P2 0x6a86 + #define SW_REFERENCED_DATA_NOT_FOUND 0x6a88 + #define SW_WRONG_P1P2 0x6b00 + #define SW_CORRECT_LENGTH_00 0x6c00 + #define SW_INS_NOT_SUPPORTED 0x6d00 + #define SW_CLA_NOT_SUPPORTED 0x6e00 + #define SW_UNKNOWN 0x6f00 + #define SW_OK 0x9000 + #define SW_ALGORITHM_UNSUPPORTED 0x9484 + namespace { bool apdu_verbose =true; } @@ -128,8 +168,8 @@ namespace hw { unsigned int id; void logCMD(void); void logRESP(void); - unsigned int exchange(unsigned int ok=0x9000, unsigned int mask=0xFFFF); - unsigned int exchange_wait_on_input(unsigned int ok=0x9000, unsigned int mask=0xFFFF); + unsigned int exchange(unsigned int ok=SW_OK, unsigned int mask=0xFFFF); + unsigned int exchange_wait_on_input(unsigned int ok=SW_OK, unsigned int mask=0xFFFF); void reset_buffer(void); int set_command_header(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00); int set_command_header_noopt(unsigned char ins, unsigned char p1 = 0x00, unsigned char p2 = 0x00); @@ -235,6 +275,8 @@ namespace hw { bool open_tx(crypto::secret_key &tx_key) override; + void get_transaction_prefix_hash(const cryptonote::transaction_prefix& tx, crypto::hash& h) override; + bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override; rct::key genCommitmentMask(const rct::key &amount_key) override; diff --git a/src/multisig/multisig.cpp b/src/multisig/multisig.cpp index 14df4d554..999894db0 100644 --- a/src/multisig/multisig.cpp +++ b/src/multisig/multisig.cpp @@ -33,19 +33,22 @@ #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "multisig.h" +#include "cryptonote_config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "multisig" using namespace std; -static const rct::key multisig_salt = { {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }; - namespace cryptonote { //----------------------------------------------------------------- crypto::secret_key get_multisig_blinded_secret_key(const crypto::secret_key &key) { + rct::key multisig_salt; + static_assert(sizeof(rct::key) == sizeof(config::HASH_KEY_MULTISIG), "Hash domain separator is an unexpected size"); + memcpy(multisig_salt.bytes, config::HASH_KEY_MULTISIG, sizeof(rct::key)); + rct::keyV data; data.reserve(2); data.push_back(rct::sk2rct(key)); diff --git a/src/net/i2p_address.cpp b/src/net/i2p_address.cpp index cba829d3f..f4cc75fee 100644 --- a/src/net/i2p_address.cpp +++ b/src/net/i2p_address.cpp @@ -171,7 +171,8 @@ namespace net bool i2p_address::less(const i2p_address& rhs) const noexcept { - return std::strcmp(host_str(), rhs.host_str()) < 0 || port() < rhs.port(); + int res = std::strcmp(host_str(), rhs.host_str()); + return res < 0 || (res == 0 && port() < rhs.port()); } bool i2p_address::is_same_host(const i2p_address& rhs) const noexcept diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index 904a9a0fc..4414861e7 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -173,7 +173,8 @@ namespace net bool tor_address::less(const tor_address& rhs) const noexcept { - return std::strcmp(host_str(), rhs.host_str()) < 0 || port() < rhs.port(); + int res = std::strcmp(host_str(), rhs.host_str()); + return res < 0 || (res == 0 && port() < rhs.port()); } bool tor_address::is_same_host(const tor_address& rhs) const noexcept diff --git a/src/net/zmq.cpp b/src/net/zmq.cpp index d02a22983..7ea80b907 100644 --- a/src/net/zmq.cpp +++ b/src/net/zmq.cpp @@ -33,6 +33,8 @@ #include <limits> #include <utility> +#include "byte_slice.h" + namespace net { namespace zmq @@ -183,6 +185,22 @@ namespace zmq { return retry_op(zmq_send, socket, payload.data(), payload.size(), flags); } + + expect<void> send(epee::byte_slice&& payload, void* socket, int flags) noexcept + { + void* const data = const_cast<std::uint8_t*>(payload.data()); + const std::size_t size = payload.size(); + auto buffer = payload.take_buffer(); // clears `payload` from callee + + zmq_msg_t msg{}; + MONERO_ZMQ_CHECK(zmq_msg_init_data(std::addressof(msg), data, size, epee::release_byte_slice::call, buffer.get())); + buffer.release(); // zmq will now decrement byte_slice ref-count + + expect<void> sent = retry_op(zmq_msg_send, std::addressof(msg), socket, flags); + if (!sent) // beware if removing `noexcept` from this function - possible leak here + zmq_msg_close(std::addressof(msg)); + return sent; + } } // zmq } // net diff --git a/src/net/zmq.h b/src/net/zmq.h index c6a7fd743..65560b62e 100644 --- a/src/net/zmq.h +++ b/src/net/zmq.h @@ -53,6 +53,11 @@ #define MONERO_ZMQ_THROW(msg) \ MONERO_THROW( ::net::zmq::get_error_code(), msg ) +namespace epee +{ + class byte_slice; +} + namespace net { namespace zmq @@ -132,5 +137,24 @@ namespace zmq \param flags See `zmq_send` for possible flags. \return `success()` if sent, otherwise ZMQ error. */ expect<void> send(epee::span<const std::uint8_t> payload, void* socket, int flags = 0) noexcept; + + /*! Sends `payload` on `socket`. Blocks until the entire message is queued + for sending, or until `zmq_term` is called on the `zmq_context` + associated with `socket`. If the context is terminated, + `make_error_code(ETERM)` is returned. + + \note This will automatically retry on `EINTR`, so exiting on + interrupts requires context termination. + \note If non-blocking behavior is requested on `socket` or by `flags`, + then `net::zmq::make_error_code(EAGAIN)` will be returned if this + would block. + + \param payload sent as one message on `socket`. + \param socket Handle created with `zmq_socket`. + \param flags See `zmq_msg_send` for possible flags. + + \post `payload.emtpy()` - ownership is transferred to zmq. + \return `success()` if sent, otherwise ZMQ error. */ + expect<void> send(epee::byte_slice&& payload, void* socket, int flags = 0) noexcept; } // zmq } // net diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 0e9c1c942..5337106dd 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -123,6 +123,7 @@ namespace nodetool peerid_type peer_id; uint32_t support_flags; bool m_in_timedsync; + std::set<epee::net_utils::network_address> sent_addresses; }; template<class t_payload_net_handler> @@ -300,8 +301,6 @@ namespace nodetool bool islimitup=false; bool islimitdown=false; - typedef COMMAND_REQUEST_STAT_INFO_T<typename t_payload_net_handler::stat_info> COMMAND_REQUEST_STAT_INFO; - CHAIN_LEVIN_INVOKE_MAP2(p2p_connection_context); //move levin_commands_handler interface invoke(...) callbacks into invoke map CHAIN_LEVIN_NOTIFY_MAP2(p2p_connection_context); //move levin_commands_handler interface notify(...) callbacks into nothing @@ -312,11 +311,6 @@ namespace nodetool HANDLE_INVOKE_T2(COMMAND_HANDSHAKE, &node_server::handle_handshake) HANDLE_INVOKE_T2(COMMAND_TIMED_SYNC, &node_server::handle_timed_sync) HANDLE_INVOKE_T2(COMMAND_PING, &node_server::handle_ping) -#ifdef ALLOW_DEBUG_COMMANDS - HANDLE_INVOKE_T2(COMMAND_REQUEST_STAT_INFO, &node_server::handle_get_stat_info) - HANDLE_INVOKE_T2(COMMAND_REQUEST_NETWORK_STATE, &node_server::handle_get_network_state) - HANDLE_INVOKE_T2(COMMAND_REQUEST_PEER_ID, &node_server::handle_get_peer_id) -#endif HANDLE_INVOKE_T2(COMMAND_REQUEST_SUPPORT_FLAGS, &node_server::handle_get_support_flags) CHAIN_INVOKE_MAP_TO_OBJ_FORCE_CONTEXT(m_payload_handler, typename t_payload_net_handler::connection_context&) END_INVOKE_MAP2() @@ -327,17 +321,11 @@ namespace nodetool int handle_handshake(int command, typename COMMAND_HANDSHAKE::request& arg, typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context); int handle_timed_sync(int command, typename COMMAND_TIMED_SYNC::request& arg, typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context); int handle_ping(int command, COMMAND_PING::request& arg, COMMAND_PING::response& rsp, p2p_connection_context& context); -#ifdef ALLOW_DEBUG_COMMANDS - int handle_get_stat_info(int command, typename COMMAND_REQUEST_STAT_INFO::request& arg, typename COMMAND_REQUEST_STAT_INFO::response& rsp, p2p_connection_context& context); - int handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context); - int handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context); -#endif int handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context); bool init_config(); bool make_default_peer_id(); bool make_default_config(); bool store_config(); - bool check_trust(const proof_of_trust& tr, epee::net_utils::zone zone_type); //----------------- levin_commands_handler ------------------------------------------------------------- @@ -346,7 +334,7 @@ namespace nodetool virtual void callback(p2p_connection_context& context); //----------------- i_p2p_endpoint ------------------------------------------------------------- virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections); - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core); + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay); virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context); virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context); virtual bool drop_connection(const epee::net_utils::connection_context_base& context); @@ -362,7 +350,7 @@ namespace nodetool const boost::program_options::variables_map& vm ); bool idle_worker(); - bool handle_remote_peerlist(const std::vector<peerlist_entry>& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context); + bool handle_remote_peerlist(const std::vector<peerlist_entry>& peerlist, const epee::net_utils::connection_context_base& context); bool get_local_node_data(basic_node_data& node_data, const network_zone& zone); //bool get_local_handshake_data(handshake_data& hshd); @@ -392,7 +380,7 @@ namespace nodetool bool try_ping(basic_node_data& node_data, p2p_connection_context& context, const t_callback &cb); bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); bool make_expected_connections_count(network_zone& zone, PeerType peer_type, size_t expected_connections); - void cache_connect_fail_info(const epee::net_utils::network_address& addr); + void record_addr_failed(const epee::net_utils::network_address& addr); bool is_addr_recently_failed(const epee::net_utils::network_address& addr); bool is_priority_node(const epee::net_utils::network_address& na); std::set<std::string> get_seed_nodes(cryptonote::network_type nettype) const; @@ -413,7 +401,6 @@ namespace nodetool bool set_rate_limit(const boost::program_options::variables_map& vm, int64_t limit); bool has_too_many_connections(const epee::net_utils::network_address &address); - uint64_t get_connections_count(); size_t get_incoming_connections_count(); size_t get_incoming_connections_count(network_zone&); size_t get_outgoing_connections_count(); @@ -477,9 +464,6 @@ namespace nodetool epee::math_helper::once_a_time_seconds<60> m_gray_peerlist_housekeeping_interval; epee::math_helper::once_a_time_seconds<3600, false> m_incoming_connections_interval; -#ifdef ALLOW_DEBUG_COMMANDS - uint64_t m_last_stat_request_time; -#endif std::list<epee::net_utils::network_address> m_priority_peers; std::vector<epee::net_utils::network_address> m_exclusive_peers; std::vector<epee::net_utils::network_address> m_seed_nodes; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 08bc76d26..9ff2627d0 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -628,6 +628,8 @@ namespace nodetool full_addrs.insert("195.154.123.123:18080"); full_addrs.insert("212.83.172.165:18080"); full_addrs.insert("192.110.160.146:18080"); + full_addrs.insert("88.198.163.90:18080"); + full_addrs.insert("95.217.25.101:18080"); } return full_addrs; } @@ -813,7 +815,6 @@ namespace nodetool //only in case if we really sure that we have external visible ip m_have_address = true; - m_last_stat_request_time = 0; //configure self @@ -940,15 +941,6 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - uint64_t node_server<t_payload_net_handler>::get_connections_count() - { - std::uint64_t count = 0; - for (auto& zone : m_network_zones) - count += zone.second.m_net_server.get_config_object().get_connections_count(); - return count; - } - //----------------------------------------------------------------------------------- - template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::deinit() { kill(); @@ -1023,15 +1015,18 @@ namespace nodetool epee::simple_event ev; std::atomic<bool> hsh_result(false); + bool timeout = false; bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(), - [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) + [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_, &timeout](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) { epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();}); if(code < 0) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE invoke failed. (" << code << ", " << epee::levin::get_err_descr(code) << ")"); + if (code == LEVIN_ERROR_CONNECTION_TIMEDOUT || code == LEVIN_ERROR_CONNECTION_DESTROYED) + timeout = true; return; } @@ -1041,7 +1036,7 @@ namespace nodetool return; } - if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.node_data.local_time, context)) + if(!handle_remote_peerlist(rsp.local_peerlist_new, context)) { LOG_WARNING_CC(context, "COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); add_host_fail(context.m_remote_address); @@ -1060,17 +1055,15 @@ namespace nodetool pi = context.peer_id = rsp.node_data.peer_id; context.m_rpc_port = rsp.node_data.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); + network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone()); + 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) + if(rsp.node_data.peer_id == zone.m_config.m_peer_id) { - if(rsp.node_data.peer_id == zone.second.m_config.m_peer_id) - { - LOG_DEBUG_CC(context, "Connection to self detected, dropping connection"); - hsh_result = false; - return; - } + LOG_DEBUG_CC(context, "Connection to self detected, dropping connection"); + hsh_result = false; + return; } LOG_INFO_CC(context, "New connection handshaked, pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed)); LOG_DEBUG_CC(context, " COMMAND_HANDSHAKE INVOKED OK"); @@ -1089,7 +1082,8 @@ namespace nodetool if(!hsh_result) { LOG_WARNING_CC(context_, "COMMAND_HANDSHAKE Failed"); - m_network_zones.at(context_.m_remote_address.get_zone()).m_net_server.get_config_object().close(context_.m_connection_id); + if (!timeout) + zone.m_net_server.get_config_object().close(context_.m_connection_id); } else if (!just_take_peerlist) { @@ -1119,7 +1113,7 @@ namespace nodetool return; } - if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.local_time, context)) + if(!handle_remote_peerlist(rsp.local_peerlist_new, context)) { LOG_WARNING_CC(context, "COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); m_network_zones.at(context.m_remote_address.get_zone()).m_net_server.get_config_object().close(context.m_connection_id ); @@ -1262,7 +1256,7 @@ namespace nodetool bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, bool(con), "Connect failed to " << na.str() /*<< ", try " << try_count*/); - //m_peerlist.set_peer_unreachable(pe); + record_addr_failed(na); return false; } @@ -1276,7 +1270,7 @@ namespace nodetool LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << na.str() /*<< ", try " << try_count*/); - zone.m_net_server.get_config_object().close(con->m_connection_id); + record_addr_failed(na); return false; } @@ -1327,6 +1321,7 @@ namespace nodetool bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, p2p_connection_context{}, "Connect failed to " << na.str()); + record_addr_failed(na); return false; } @@ -1338,7 +1333,7 @@ namespace nodetool bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, *con, "Failed to HANDSHAKE with peer " << na.str()); - zone.m_net_server.get_config_object().close(con->m_connection_id); + record_addr_failed(na); return false; } @@ -1353,6 +1348,13 @@ namespace nodetool //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + void node_server<t_payload_net_handler>::record_addr_failed(const epee::net_utils::network_address& addr) + { + CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); + m_conn_fails_cache[addr.host_str()] = time(NULL); + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::is_addr_recently_failed(const epee::net_utils::network_address& addr) { CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); @@ -1434,10 +1436,10 @@ namespace nodetool std::deque<size_t> filtered; const size_t limit = use_white_list ? 20 : std::numeric_limits<size_t>::max(); - size_t idx = 0, skipped = 0; for (int step = 0; step < 2; ++step) { bool skip_duplicate_class_B = step == 0; + size_t idx = 0, skipped = 0; zone.m_peerlist.foreach (use_white_list, [&classB, &filtered, &idx, &skipped, skip_duplicate_class_B, limit, next_needed_pruning_stripe](const peerlist_entry &pe){ if (filtered.size() >= limit) return false; @@ -1543,6 +1545,7 @@ namespace nodetool return true; size_t try_count = 0; + bool is_connected_to_at_least_one_seed_node = false; size_t current_index = crypto::rand_idx(m_seed_nodes.size()); const net_server& server = m_network_zones.at(epee::net_utils::zone::public_).m_net_server; while(true) @@ -1550,21 +1553,25 @@ namespace nodetool if(server.is_stop_signal_sent()) return false; - if(try_to_connect_and_handshake_with_new_peer(m_seed_nodes[current_index], true)) + peerlist_entry pe_seed{}; + pe_seed.adr = m_seed_nodes[current_index]; + if (is_peer_used(pe_seed)) + is_connected_to_at_least_one_seed_node = true; + else if (try_to_connect_and_handshake_with_new_peer(m_seed_nodes[current_index], true)) break; if(++try_count > m_seed_nodes.size()) { if (!m_fallback_seed_nodes_added) { MWARNING("Failed to connect to any of seed peers, trying fallback seeds"); - current_index = m_seed_nodes.size(); + current_index = m_seed_nodes.size() - 1; for (const auto &peer: get_seed_nodes(m_nettype)) { MDEBUG("Fallback seed node: " << peer); append_net_address(m_seed_nodes, peer, cryptonote::get_config(m_nettype).P2P_DEFAULT_PORT); } m_fallback_seed_nodes_added = true; - if (current_index == m_seed_nodes.size()) + if (current_index == m_seed_nodes.size() - 1) { MWARNING("No fallback seeds, continuing without seeds"); break; @@ -1573,7 +1580,8 @@ namespace nodetool } else { - MWARNING("Failed to connect to any of seed peers, continuing without seeds"); + if (!is_connected_to_at_least_one_seed_node) + MWARNING("Failed to connect to any of seed peers, continuing without seeds"); break; } } @@ -1894,7 +1902,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::handle_remote_peerlist(const std::vector<peerlist_entry>& peerlist, time_t local_time, const epee::net_utils::connection_context_base& context) + bool node_server<t_payload_net_handler>::handle_remote_peerlist(const std::vector<peerlist_entry>& peerlist, const epee::net_utils::connection_context_base& context) { std::vector<peerlist_entry> peerlist_ = peerlist; if(!sanitize_peerlist(peerlist_)) @@ -1911,16 +1919,13 @@ namespace nodetool } LOG_DEBUG_CC(context, "REMOTE PEERLIST: remote peerlist size=" << peerlist_.size()); - LOG_DEBUG_CC(context, "REMOTE PEERLIST: " << ENDL << print_peerlist_to_string(peerlist_)); - return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_); + LOG_TRACE_CC(context, "REMOTE PEERLIST: " << ENDL << print_peerlist_to_string(peerlist_)); + return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_, [this](const peerlist_entry &pe) { return !is_addr_recently_failed(pe.adr); }); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::get_local_node_data(basic_node_data& node_data, const network_zone& zone) { - time_t local_time; - time(&local_time); - node_data.local_time = local_time; // \TODO This can be an identifying value across zones (public internet to tor/i2p) ... node_data.peer_id = zone.m_config.m_peer_id; if(!m_hide_my_port && zone.m_can_pingback) node_data.my_port = m_external_port ? m_external_port : m_listening_port; @@ -1932,91 +1937,6 @@ namespace nodetool return true; } //----------------------------------------------------------------------------------- -#ifdef ALLOW_DEBUG_COMMANDS - template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::check_trust(const proof_of_trust& tr, const epee::net_utils::zone zone_type) - { - uint64_t local_time = time(NULL); - uint64_t time_delata = local_time > tr.time ? local_time - tr.time: tr.time - local_time; - if(time_delata > 24*60*60 ) - { - MWARNING("check_trust failed to check time conditions, local_time=" << local_time << ", proof_time=" << tr.time); - return false; - } - if(m_last_stat_request_time >= tr.time ) - { - MWARNING("check_trust failed to check time conditions, last_stat_request_time=" << m_last_stat_request_time << ", proof_time=" << tr.time); - return false; - } - - const network_zone& zone = m_network_zones.at(zone_type); - if(zone.m_config.m_peer_id != tr.peer_id) - { - MWARNING("check_trust failed: peer_id mismatch (passed " << tr.peer_id << ", expected " << peerid_to_string(zone.m_config.m_peer_id) << ")"); - return false; - } - crypto::public_key pk = AUTO_VAL_INIT(pk); - epee::string_tools::hex_to_pod(::config::P2P_REMOTE_DEBUG_TRUSTED_PUB_KEY, pk); - crypto::hash h = get_proof_of_trust_hash(tr); - if(!crypto::check_signature(h, pk, tr.sign)) - { - MWARNING("check_trust failed: sign check failed"); - return false; - } - //update last request time - m_last_stat_request_time = tr.time; - return true; - } - //----------------------------------------------------------------------------------- - template<class t_payload_net_handler> - int node_server<t_payload_net_handler>::handle_get_stat_info(int command, typename COMMAND_REQUEST_STAT_INFO::request& arg, typename COMMAND_REQUEST_STAT_INFO::response& rsp, p2p_connection_context& context) - { - if(!check_trust(arg.tr, context.m_remote_address.get_zone())) - { - drop_connection(context); - return 1; - } - rsp.connections_count = get_connections_count(); - rsp.incoming_connections_count = rsp.connections_count - get_outgoing_connections_count(); - rsp.version = MONERO_VERSION_FULL; - rsp.os_version = tools::get_os_version_string(); - m_payload_handler.get_stat_info(rsp.payload_info); - return 1; - } - //----------------------------------------------------------------------------------- - template<class t_payload_net_handler> - int node_server<t_payload_net_handler>::handle_get_network_state(int command, COMMAND_REQUEST_NETWORK_STATE::request& arg, COMMAND_REQUEST_NETWORK_STATE::response& rsp, p2p_connection_context& context) - { - if(!check_trust(arg.tr, context.m_remote_address.get_zone())) - { - drop_connection(context); - return 1; - } - m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) - { - connection_entry ce; - ce.adr = cntxt.m_remote_address; - ce.id = cntxt.peer_id; - ce.is_income = cntxt.m_is_income; - rsp.connections_list.push_back(ce); - return true; - }); - - network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone()); - zone.m_peerlist.get_peerlist(rsp.local_peerlist_gray, rsp.local_peerlist_white); - rsp.my_id = zone.m_config.m_peer_id; - rsp.local_time = time(NULL); - return 1; - } - //----------------------------------------------------------------------------------- - template<class t_payload_net_handler> - int node_server<t_payload_net_handler>::handle_get_peer_id(int command, COMMAND_REQUEST_PEER_ID::request& arg, COMMAND_REQUEST_PEER_ID::response& rsp, p2p_connection_context& context) - { - rsp.my_id = m_network_zones.at(context.m_remote_address.get_zone()).m_config.m_peer_id; - return 1; - } -#endif - //----------------------------------------------------------------------------------- template<class t_payload_net_handler> int node_server<t_payload_net_handler>::handle_get_support_flags(int command, COMMAND_REQUEST_SUPPORT_FLAGS::request& arg, COMMAND_REQUEST_SUPPORT_FLAGS::response& rsp, p2p_connection_context& context) { @@ -2056,18 +1976,13 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core) + epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, const cryptonote::relay_method tx_relay) { namespace enet = epee::net_utils; - const auto send = [&txs, &source, &core] (std::pair<const enet::zone, network_zone>& network) + const auto send = [&txs, &source, &core, tx_relay] (std::pair<const enet::zone, network_zone>& network) { - const bool is_public = (network.first == enet::zone::public_); - const cryptonote::relay_method tx_relay = is_public ? - cryptonote::relay_method::fluff : cryptonote::relay_method::local; - - core.on_transactions_relayed(epee::to_span(txs), tx_relay); - if (network.second.m_notifier.send_txs(std::move(txs), source)) + if (network.second.m_notifier.send_txs(std::move(txs), source, core, tx_relay)) return network.first; return enet::zone::invalid; }; @@ -2291,12 +2206,20 @@ namespace nodetool } //fill response - rsp.local_time = time(NULL); - const epee::net_utils::zone zone_type = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(zone_type); - zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); + std::vector<peerlist_entry> local_peerlist_new; + zone.m_peerlist.get_peerlist_head(local_peerlist_new, true, P2P_DEFAULT_PEERS_IN_HANDSHAKE); + + //only include out peers we did not already send + rsp.local_peerlist_new.reserve(local_peerlist_new.size()); + for (auto &pe: local_peerlist_new) + { + if (!context.sent_addresses.insert(pe.adr).second) + continue; + rsp.local_peerlist_new.push_back(std::move(pe)); + } m_payload_handler.get_payload_sync_data(rsp.payload_data); /* Tor/I2P nodes receiving connections via forwarding (from tor/i2p daemon) @@ -2418,6 +2341,8 @@ namespace nodetool //fill response zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); + for (const auto &e: rsp.local_peerlist_new) + context.sent_addresses.insert(e.adr); get_local_node_data(rsp.node_data, zone); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index ed88aa28c..6a6100e0c 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -50,7 +50,7 @@ namespace nodetool struct i_p2p_endpoint { virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)=0; - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core)=0; + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay)=0; virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; @@ -75,7 +75,7 @@ namespace nodetool { return false; } - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core) + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core, cryptonote::relay_method tx_relay) { return epee::net_utils::zone::invalid; } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index 58b704f73..300181bbb 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -43,6 +43,7 @@ #include <boost/range/adaptor/reversed.hpp> +#include "crypto/crypto.h" #include "cryptonote_config.h" #include "net/enums.h" #include "net/local_ip.h" @@ -101,7 +102,7 @@ namespace nodetool bool init(peerlist_types&& peers, bool allow_local_ip); size_t get_white_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_white.size();} size_t get_gray_peers_count(){CRITICAL_REGION_LOCAL(m_peerlist_lock); return m_peers_gray.size();} - bool merge_peerlist(const std::vector<peerlist_entry>& outer_bs); + bool merge_peerlist(const std::vector<peerlist_entry>& outer_bs, const std::function<bool(const peerlist_entry&)> &f = NULL); bool get_peerlist_head(std::vector<peerlist_entry>& bs_head, bool anonymize, uint32_t depth = P2P_DEFAULT_PEERS_IN_HANDSHAKE); void get_peerlist(std::vector<peerlist_entry>& pl_gray, std::vector<peerlist_entry>& pl_white); void get_peerlist(peerlist_types& peers); @@ -112,7 +113,6 @@ namespace nodetool 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, 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); bool remove_from_peer_gray(const peerlist_entry& pe); @@ -213,12 +213,13 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::merge_peerlist(const std::vector<peerlist_entry>& outer_bs) + bool peerlist_manager::merge_peerlist(const std::vector<peerlist_entry>& outer_bs, const std::function<bool(const peerlist_entry&)> &f) { CRITICAL_REGION_LOCAL(m_peerlist_lock); for(const peerlist_entry& be: outer_bs) { - append_with_peer_gray(be); + if (!f || f(be)) + append_with_peer_gray(be); } // delete extra elements trim_gray_peerlist(); @@ -269,19 +270,19 @@ namespace nodetool peers_indexed::index<by_time>::type& by_time_index=m_peers_white.get<by_time>(); uint32_t cnt = 0; - // picks a random set of peers within the first 120%, rather than a set of the first 100%. + // picks a random set of peers within the whole set, rather pick the first depth elements. // The intent is that if someone asks twice, they can't easily tell: // - this address was not in the first list, but is in the second, so the only way this can be // is if its last_seen was recently reset, so this means the target node recently had a new // connection to that address // - this address was in the first list, and not in the second, which means either the address - // was moved to the gray list (if it's not accessibe, which the attacker can check if + // was moved to the gray list (if it's not accessible, which the attacker can check if // the address accepts incoming connections) or it was the oldest to still fit in the 250 items, // so its last_seen is old. // // See Cao, Tong et al. "Exploring the Monero Peer-to-Peer Network". https://eprint.iacr.org/2019/411 // - const uint32_t pick_depth = anonymize ? depth + depth / 5 : depth; + const uint32_t pick_depth = anonymize ? m_peers_white.size() : depth; bs_head.reserve(pick_depth); for(const peers_indexed::value_type& vl: boost::adaptors::reverse(by_time_index)) { diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 393bddd05..609661871 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -40,9 +40,6 @@ #include "string_tools.h" #include "time_helper.h" #include "cryptonote_config.h" -#ifdef ALLOW_DEBUG_COMMANDS -#include "crypto/crypto.h" -#endif namespace nodetool { @@ -82,8 +79,7 @@ namespace nodetool BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(adr) KV_SERIALIZE(id) - if (!is_store || this_ref.last_seen != 0) - KV_SERIALIZE_OPT(last_seen, (int64_t)0) + 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) @@ -166,7 +162,6 @@ namespace nodetool struct basic_node_data { uuid network_id; - uint64_t local_time; uint32_t my_port; uint16_t rpc_port; uint32_t rpc_credits_per_hash; @@ -175,7 +170,6 @@ namespace nodetool BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(network_id) KV_SERIALIZE(peer_id) - 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) @@ -214,35 +208,7 @@ namespace nodetool BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(node_data) KV_SERIALIZE(payload_data) - if (is_store) - { - // saving: save both, so old and new peers can understand it - KV_SERIALIZE(local_peerlist_new) - std::vector<peerlist_entry_base<network_address_old>> local_peerlist; - for (const auto &p: this_ref.local_peerlist_new) - { - if (p.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) - { - 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, p.rpc_credits_per_hash})); - } - else - MDEBUG("Not including in legacy peer list: " << p.adr.str()); - } - epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); - } - else - { - // loading: load old list only if there is no new one - if (!epee::serialization::selector<is_store>::serialize(this_ref.local_peerlist_new, stg, hparent_section, "local_peerlist_new")) - { - 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, p.rpc_credits_per_hash})); - } - } + KV_SERIALIZE(local_peerlist_new) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -268,42 +234,12 @@ namespace nodetool struct response_t { - uint64_t local_time; t_playload_type payload_data; std::vector<peerlist_entry> local_peerlist_new; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(local_time) KV_SERIALIZE(payload_data) - if (is_store) - { - // saving: save both, so old and new peers can understand it - KV_SERIALIZE(local_peerlist_new) - std::vector<peerlist_entry_base<network_address_old>> local_peerlist; - for (const auto &p: this_ref.local_peerlist_new) - { - if (p.adr.get_type_id() == epee::net_utils::ipv4_network_address::get_type_id()) - { - 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})); - } - else - MDEBUG("Not including in legacy peer list: " << p.adr.str()); - } - epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); - } - else - { - // loading: load old list only if there is no new one - if (!epee::serialization::selector<is_store>::serialize(this_ref.local_peerlist_new, stg, hparent_section, "local_peerlist_new")) - { - 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})); - } - } + KV_SERIALIZE(local_peerlist_new) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -347,117 +283,6 @@ namespace nodetool }; -#ifdef ALLOW_DEBUG_COMMANDS - //These commands are considered as insecure, and made in debug purposes for a limited lifetime. - //Anyone who feel unsafe with this commands can disable the ALLOW_GET_STAT_COMMAND macro. - - struct proof_of_trust - { - peerid_type peer_id; - uint64_t time; - crypto::signature sign; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(peer_id) - KV_SERIALIZE(time) - KV_SERIALIZE_VAL_POD_AS_BLOB(sign) - END_KV_SERIALIZE_MAP() - }; - - - template<class payload_stat_info> - struct COMMAND_REQUEST_STAT_INFO_T - { - const static int ID = P2P_COMMANDS_POOL_BASE + 4; - - struct request_t - { - proof_of_trust tr; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tr) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::string version; - std::string os_version; - uint64_t connections_count; - uint64_t incoming_connections_count; - payload_stat_info payload_info; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(version) - KV_SERIALIZE(os_version) - KV_SERIALIZE(connections_count) - KV_SERIALIZE(incoming_connections_count) - KV_SERIALIZE(payload_info) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - - /************************************************************************/ - /* */ - /************************************************************************/ - struct COMMAND_REQUEST_NETWORK_STATE - { - const static int ID = P2P_COMMANDS_POOL_BASE + 5; - - struct request_t - { - proof_of_trust tr; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tr) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - std::vector<peerlist_entry> local_peerlist_white; - std::vector<peerlist_entry> local_peerlist_gray; - std::vector<connection_entry> connections_list; - peerid_type my_id; - uint64_t local_time; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(local_peerlist_white) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(local_peerlist_gray) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(connections_list) - KV_SERIALIZE(my_id) - KV_SERIALIZE(local_time) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - - /************************************************************************/ - /* */ - /************************************************************************/ - struct COMMAND_REQUEST_PEER_ID - { - const static int ID = P2P_COMMANDS_POOL_BASE + 6; - - struct request_t - { - BEGIN_KV_SERIALIZE_MAP() - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<request_t> request; - - struct response_t - { - peerid_type my_id; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(my_id) - END_KV_SERIALIZE_MAP() - }; - typedef epee::misc_utils::struct_init<response_t> response; - }; - /************************************************************************/ /* */ /************************************************************************/ @@ -482,16 +307,4 @@ namespace nodetool }; typedef epee::misc_utils::struct_init<response_t> response; }; - -#endif - - - inline crypto::hash get_proof_of_trust_hash(const nodetool::proof_of_trust& pot) - { - std::string s; - s.append(reinterpret_cast<const char*>(&pot.peer_id), sizeof(pot.peer_id)); - s.append(reinterpret_cast<const char*>(&pot.time), sizeof(pot.time)); - return crypto::cn_fast_hash(s.data(), s.size()); - } - } diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index 80ecc5593..2ff88c6e7 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -100,8 +100,8 @@ static inline bool is_reduced(const rct::key &scalar) static rct::key get_exponent(const rct::key &base, size_t idx) { - static const std::string salt("bulletproof"); - std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + salt + tools::get_varint_data(idx); + static const std::string domain_separator(config::HASH_KEY_BULLETPROOF_EXPONENT); + std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + domain_separator + tools::get_varint_data(idx); rct::key e; ge_p3 e_p3; rct::hash_to_p3(e_p3, rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size()))); @@ -173,7 +173,7 @@ static rct::key cross_vector_exponent8(size_t size, const std::vector<ge_p3> &A, multiexp_data.resize(size*2 + (!!extra_point)); for (size_t i = 0; i < size; ++i) { - sc_mul(multiexp_data[i*2].scalar.bytes, a[ao+i].bytes, INV_EIGHT.bytes);; + sc_mul(multiexp_data[i*2].scalar.bytes, a[ao+i].bytes, INV_EIGHT.bytes); multiexp_data[i*2].point = A[Ao+i]; sc_mul(multiexp_data[i*2+1].scalar.bytes, b[bo+i].bytes, INV_EIGHT.bytes); if (scale) diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index 65d88b57e..fe5e5a85b 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -35,6 +35,7 @@ set(rpc_base_sources set(rpc_sources bootstrap_daemon.cpp + bootstrap_node_selector.cpp core_rpc_server.cpp rpc_payment.cpp rpc_version_str.cpp diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index c97b2c95a..6a0833f19 100644 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -2,6 +2,8 @@ #include <stdexcept> +#include <boost/thread/locks.hpp> + #include "crypto/crypto.h" #include "cryptonote_core/cryptonote_core.h" #include "misc_log_ex.h" @@ -12,15 +14,22 @@ namespace cryptonote { - bootstrap_daemon::bootstrap_daemon(std::function<boost::optional<std::string>()> get_next_public_node) - : m_get_next_public_node(get_next_public_node) + bootstrap_daemon::bootstrap_daemon( + std::function<std::map<std::string, bool>()> get_public_nodes, + bool rpc_payment_enabled) + : m_selector(new bootstrap_node::selector_auto(std::move(get_public_nodes))) + , m_rpc_payment_enabled(rpc_payment_enabled) { } - bootstrap_daemon::bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials) - : bootstrap_daemon(nullptr) + bootstrap_daemon::bootstrap_daemon( + const std::string &address, + boost::optional<epee::net_utils::http::login> credentials, + bool rpc_payment_enabled) + : m_selector(nullptr) + , m_rpc_payment_enabled(rpc_payment_enabled) { - if (!set_server(address, credentials)) + if (!set_server(address, std::move(credentials))) { throw std::runtime_error("invalid bootstrap daemon address or credentials"); } @@ -54,11 +63,16 @@ namespace cryptonote return res.height; } - bool bootstrap_daemon::handle_result(bool success) + bool bootstrap_daemon::handle_result(bool success, const std::string &status) { - if (!success && m_get_next_public_node) + const bool failed = !success || (!m_rpc_payment_enabled && status == CORE_RPC_STATUS_PAYMENT_REQUIRED); + if (failed && m_selector) { + const std::string current_address = address(); m_http_client.disconnect(); + + const boost::unique_lock<boost::mutex> lock(m_selector_mutex); + m_selector->handle_result(current_address, !failed); } return success; @@ -79,14 +93,18 @@ namespace cryptonote bool bootstrap_daemon::switch_server_if_needed() { - if (!m_get_next_public_node || m_http_client.is_connected()) + if (m_http_client.is_connected() || !m_selector) { return true; } - const boost::optional<std::string> address = m_get_next_public_node(); - if (address) { - return set_server(*address); + boost::optional<bootstrap_node::node_info> node; + { + const boost::unique_lock<boost::mutex> lock(m_selector_mutex); + node = m_selector->next_node(); + } + if (node) { + return set_server(node->address, node->credentials); } return false; diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index 6276b1b21..bedc255b5 100644 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -1,26 +1,34 @@ #pragma once #include <functional> -#include <vector> +#include <map> #include <boost/optional/optional.hpp> +#include <boost/thread/mutex.hpp> #include <boost/utility/string_ref.hpp> #include "net/http_client.h" #include "storages/http_abstract_invoke.h" +#include "bootstrap_node_selector.h" + namespace cryptonote { class bootstrap_daemon { public: - 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); + bootstrap_daemon( + std::function<std::map<std::string, bool>()> get_public_nodes, + bool rpc_payment_enabled); + bootstrap_daemon( + const std::string &address, + boost::optional<epee::net_utils::http::login> credentials, + bool rpc_payment_enabled); std::string address() const noexcept; boost::optional<uint64_t> get_height(); - bool handle_result(bool success); + bool handle_result(bool success, const std::string &status); template <class t_request, class t_response> bool invoke_http_json(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct) @@ -30,7 +38,8 @@ namespace cryptonote return false; } - return handle_result(epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client)); + const bool result = epee::net_utils::invoke_http_json(uri, out_struct, result_struct, m_http_client); + return handle_result(result, result_struct.status); } template <class t_request, class t_response> @@ -41,7 +50,8 @@ namespace cryptonote return false; } - return handle_result(epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client)); + const bool result = epee::net_utils::invoke_http_bin(uri, out_struct, result_struct, m_http_client); + return handle_result(result, result_struct.status); } template <class t_request, class t_response> @@ -52,7 +62,13 @@ namespace cryptonote return false; } - return handle_result(epee::net_utils::invoke_http_json_rpc("/json_rpc", std::string(command_name.begin(), command_name.end()), out_struct, result_struct, m_http_client)); + const bool result = epee::net_utils::invoke_http_json_rpc( + "/json_rpc", + std::string(command_name.begin(), command_name.end()), + out_struct, + result_struct, + m_http_client); + return handle_result(result, result_struct.status); } private: @@ -61,7 +77,9 @@ namespace cryptonote private: epee::net_utils::http::http_simple_client m_http_client; - std::function<boost::optional<std::string>()> m_get_next_public_node; + const bool m_rpc_payment_enabled; + const std::unique_ptr<bootstrap_node::selector> m_selector; + boost::mutex m_selector_mutex; }; } diff --git a/src/rpc/bootstrap_node_selector.cpp b/src/rpc/bootstrap_node_selector.cpp new file mode 100644 index 000000000..34845060e --- /dev/null +++ b/src/rpc/bootstrap_node_selector.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2020, 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 "bootstrap_node_selector.h" + +#include "crypto/crypto.h" + +namespace cryptonote +{ +namespace bootstrap_node +{ + + void selector_auto::node::handle_result(bool success) + { + if (!success) + { + fails = std::min(std::numeric_limits<size_t>::max() - 2, fails) + 2; + } + else + { + fails = std::max(std::numeric_limits<size_t>::min() + 2, fails) - 2; + } + } + + void selector_auto::handle_result(const std::string &address, bool success) + { + auto &nodes_by_address = m_nodes.get<by_address>(); + const auto it = nodes_by_address.find(address); + if (it != nodes_by_address.end()) + { + nodes_by_address.modify(it, [success](node &entry) { + entry.handle_result(success); + }); + } + } + + boost::optional<node_info> selector_auto::next_node() + { + if (!has_at_least_one_good_node()) + { + append_new_nodes(); + } + + if (m_nodes.empty()) + { + return {}; + } + + auto node = m_nodes.get<by_fails>().begin(); + const size_t count = std::distance(node, m_nodes.get<by_fails>().upper_bound(node->fails)); + std::advance(node, crypto::rand_idx(count)); + + return {{node->address, {}}}; + } + + bool selector_auto::has_at_least_one_good_node() const + { + return !m_nodes.empty() && m_nodes.get<by_fails>().begin()->fails == 0; + } + + void selector_auto::append_new_nodes() + { + bool updated = false; + + for (const auto &node : m_get_nodes()) + { + const auto &address = node.first; + const auto &white = node.second; + const size_t initial_score = white ? 0 : 1; + updated |= m_nodes.get<by_address>().insert({address, initial_score}).second; + } + + if (updated) + { + truncate(); + } + } + + void selector_auto::truncate() + { + const size_t total = m_nodes.size(); + if (total > m_max_nodes) + { + auto &nodes_by_fails = m_nodes.get<by_fails>(); + auto from = nodes_by_fails.rbegin(); + std::advance(from, total - m_max_nodes); + nodes_by_fails.erase(from.base(), nodes_by_fails.end()); + } + } + +} +} diff --git a/src/rpc/bootstrap_node_selector.h b/src/rpc/bootstrap_node_selector.h new file mode 100644 index 000000000..fc993719b --- /dev/null +++ b/src/rpc/bootstrap_node_selector.h @@ -0,0 +1,103 @@ +// Copyright (c) 2020, 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 <functional> +#include <limits> +#include <map> +#include <string> +#include <utility> + +#include <boost/multi_index_container.hpp> +#include <boost/multi_index/member.hpp> +#include <boost/multi_index/ordered_index.hpp> +#include <boost/optional/optional.hpp> + +#include "net/http_client.h" + +namespace cryptonote +{ +namespace bootstrap_node +{ + + struct node_info + { + std::string address; + boost::optional<epee::net_utils::http::login> credentials; + }; + + struct selector + { + virtual void handle_result(const std::string &address, bool success) = 0; + virtual boost::optional<node_info> next_node() = 0; + }; + + class selector_auto : public selector + { + public: + selector_auto(std::function<std::map<std::string, bool>()> get_nodes, size_t max_nodes = 1000) + : m_get_nodes(std::move(get_nodes)) + , m_max_nodes(max_nodes) + {} + + void handle_result(const std::string &address, bool success) final; + boost::optional<node_info> next_node() final; + + private: + bool has_at_least_one_good_node() const; + void append_new_nodes(); + void truncate(); + + private: + struct node + { + std::string address; + size_t fails; + + void handle_result(bool success); + }; + + struct by_address {}; + struct by_fails {}; + + typedef boost::multi_index_container< + node, + boost::multi_index::indexed_by< + boost::multi_index::ordered_unique<boost::multi_index::tag<by_address>, boost::multi_index::member<node, std::string, &node::address>>, + boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_fails>, boost::multi_index::member<node, size_t, &node::fails>> + > + > nodes_list; + + const std::function<std::map<std::string, bool>()> m_get_nodes; + const size_t m_max_nodes; + nodes_list m_nodes; + }; + +} +} diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 409c8a01c..9b0eeb1f1 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -125,11 +125,16 @@ namespace return (value + quantum - 1) / quantum * quantum; } + void store_128(boost::multiprecision::uint128_t value, uint64_t &slow64, std::string &swide, uint64_t &stop64) + { + slow64 = (value & 0xffffffffffffffff).convert_to<uint64_t>(); + swide = cryptonote::hex(value); + stop64 = ((value >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); + } + void store_difficulty(cryptonote::difficulty_type difficulty, uint64_t &sdiff, std::string &swdiff, uint64_t &stop64) { - sdiff = (difficulty & 0xffffffffffffffff).convert_to<uint64_t>(); - swdiff = cryptonote::hex(difficulty); - stop64 = ((difficulty >> 64) & 0xffffffffffffffff).convert_to<uint64_t>(); + store_128(difficulty, sdiff, swdiff, stop64); } } @@ -148,6 +153,7 @@ namespace cryptonote 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); + command_line::add_arg(desc, arg_rpc_payment_allow_free_loopback); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -157,6 +163,8 @@ namespace cryptonote : m_core(cr) , m_p2p(p2p) , m_was_bootstrap_ever_used(false) + , disable_rpc_ban(false) + , m_rpc_payment_allow_free_loopback(false) {} //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const std::string &username_password) @@ -170,7 +178,7 @@ namespace cryptonote return set_bootstrap_daemon(address, credentials); } //------------------------------------------------------------------------------------------------------------------------------ - boost::optional<std::string> core_rpc_server::get_random_public_node() + std::map<std::string, bool> core_rpc_server::get_public_nodes(uint32_t credits_per_hash_threshold/* = 0*/) { COMMAND_RPC_GET_PUBLIC_NODES::request request; COMMAND_RPC_GET_PUBLIC_NODES::response response; @@ -179,47 +187,51 @@ namespace cryptonote request.white = true; if (!on_get_public_nodes(request, response) || response.status != CORE_RPC_STATUS_OK) { - return boost::none; - } - - const auto get_random_node_address = [](const std::vector<public_node>& public_nodes) -> std::string { - const auto& random_node = public_nodes[crypto::rand_idx(public_nodes.size())]; - const auto address = random_node.host + ":" + std::to_string(random_node.rpc_port); - return address; - }; - - if (!response.white.empty()) - { - return get_random_node_address(response.white); + return {}; } - MDEBUG("No white public node found, checking gray peers"); + std::map<std::string, bool> result; - if (!response.gray.empty()) - { - return get_random_node_address(response.gray); - } + const auto append = [&result, &credits_per_hash_threshold](const std::vector<public_node> &nodes, bool white) { + for (const auto &node : nodes) + { + const bool rpc_payment_enabled = credits_per_hash_threshold > 0; + const bool node_rpc_payment_enabled = node.rpc_credits_per_hash > 0; + if (!node_rpc_payment_enabled || + (rpc_payment_enabled && node.rpc_credits_per_hash >= credits_per_hash_threshold)) + { + result.insert(std::make_pair(node.host + ":" + std::to_string(node.rpc_port), white)); + } + } + }; - MERROR("Failed to find any suitable public node"); + append(response.white, true); + append(response.gray, false); - return boost::none; + return result; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials) { boost::unique_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); + constexpr const uint32_t credits_per_hash_threshold = 0; + constexpr const bool rpc_payment_enabled = credits_per_hash_threshold != 0; + if (address.empty()) { m_bootstrap_daemon.reset(nullptr); } else if (address == "auto") { - m_bootstrap_daemon.reset(new bootstrap_daemon([this]{ return get_random_public_node(); })); + auto get_nodes = [this, credits_per_hash_threshold]() { + return get_public_nodes(credits_per_hash_threshold); + }; + m_bootstrap_daemon.reset(new bootstrap_daemon(std::move(get_nodes), rpc_payment_enabled)); } else { - m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials)); + m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials, rpc_payment_enabled)); } m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr; @@ -247,6 +259,7 @@ namespace cryptonote if (!rpc_config) return false; + disable_rpc_ban = rpc_config->disable_rpc_ban; std::string address = command_line::get_arg(vm, arg_rpc_payment_address); if (!address.empty() && allow_rpc_payment) { @@ -273,6 +286,7 @@ namespace cryptonote MERROR("Payments difficulty and/or payments credits are 0, but a payment address was given"); return false; } + m_rpc_payment_allow_free_loopback = command_line::get_arg(vm, arg_rpc_payment_allow_free_loopback); 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)); @@ -346,7 +360,7 @@ namespace cryptonote #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) +#define CHECK_PAYMENT_MIN1(req, res, payment, same_ts) do { if (!ctx || (m_rpc_payment_allow_free_loopback && ctx->m_remote_address.is_loopback())) 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() { @@ -359,7 +373,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::add_host_fail(const connection_context *ctx, unsigned int score) { - if(!ctx || !ctx->m_remote_address.is_blockable()) + if(!ctx || !ctx->m_remote_address.is_blockable() || disable_rpc_ban) return false; CRITICAL_REGION_LOCAL(m_host_fails_score_lock); @@ -424,7 +438,7 @@ namespace cryptonote store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64); res.target = m_core.get_blockchain_storage().get_difficulty_target(); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase - res.tx_pool_size = m_core.get_pool_transactions_count(); + res.tx_pool_size = m_core.get_pool_transactions_count(!restricted); res.alt_blocks_count = restricted ? 0 : m_core.get_blockchain_storage().get_alternative_blocks_count(); uint64_t total_conn = restricted ? 0 : m_p2p.get_public_connections_count(); res.outgoing_connections_count = restricted ? 0 : m_p2p.get_public_outgoing_connections_count(); @@ -544,7 +558,7 @@ namespace cryptonote CHECK_PAYMENT_SAME_TS(req, res, bs.size() * COST_PER_BLOCK); - size_t pruned_size = 0, unpruned_size = 0, ntxes = 0; + size_t size = 0, ntxes = 0; res.blocks.reserve(bs.size()); res.output_indices.reserve(bs.size()); for(auto& bd: bs) @@ -552,8 +566,7 @@ namespace cryptonote 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(); + size += bd.first.first.size(); res.output_indices.push_back(COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices()); ntxes += bd.second.size(); res.output_indices.back().indices.reserve(1 + bd.second.size()); @@ -562,11 +575,10 @@ namespace cryptonote res.blocks.back().txs.reserve(bd.second.size()); 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), crypto::null_hash}); i->second.clear(); i->second.shrink_to_fit(); - pruned_size += res.blocks.back().txs.back().blob.size(); + size += res.blocks.back().txs.back().blob.size(); } const size_t n_txes_to_lookup = bd.second.size() + (req.no_miner_tx ? 0 : 1); @@ -589,7 +601,7 @@ namespace cryptonote } } - MDEBUG("on_get_blocks: " << bs.size() << " blocks, " << ntxes << " txes, pruned size " << pruned_size << ", unpruned size " << unpruned_size); + MDEBUG("on_get_blocks: " << bs.size() << " blocks, " << ntxes << " txes, size " << size); res.status = CORE_RPC_STATUS_OK; return true; } @@ -783,6 +795,9 @@ namespace cryptonote CHECK_PAYMENT_MIN1(req, res, req.txs_hashes.size() * COST_PER_TX, false); + const bool restricted = m_restricted && ctx; + const bool request_has_rpc_origin = ctx != NULL; + std::vector<crypto::hash> vh; for(const auto& tx_hex_str: req.txs_hashes) { @@ -817,7 +832,7 @@ namespace cryptonote { std::vector<tx_info> pool_tx_info; std::vector<spent_key_image_info> pool_key_image_info; - bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info); + bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info, !request_has_rpc_origin || !restricted); if(r) { // sort to match original request @@ -1095,7 +1110,7 @@ namespace cryptonote return true; } - if (req.do_sanity_checks && !cryptonote::tx_sanity_check(m_core.get_blockchain_storage(), tx_blob)) + if (req.do_sanity_checks && !cryptonote::tx_sanity_check(tx_blob, m_core.get_blockchain_storage().get_num_mature_outputs(0))) { res.status = "Failed"; res.reason = "Sanity check failed"; @@ -1104,6 +1119,8 @@ namespace cryptonote } res.sanity_check_failed = false; + const bool restricted = m_restricted && ctx; + tx_verification_context tvc{}; if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (req.do_not_relay ? relay_method::none : relay_method::local), false) || tvc.m_verifivation_failed) { @@ -1123,8 +1140,6 @@ namespace cryptonote add_reason(reason, "overspend"); if ((res.fee_too_low = tvc.m_fee_too_low)) add_reason(reason, "fee too low"); - if ((res.not_rct = tvc.m_not_rct)) - add_reason(reason, "tx is not ringct"); if ((res.too_few_outputs = tvc.m_too_few_outputs)) add_reason(reason, "too few outputs"); const std::string punctuation = reason.empty() ? "" : ": "; @@ -1139,7 +1154,7 @@ namespace cryptonote return true; } - if(!tvc.m_should_be_relayed) + if(tvc.m_relay == relay_method::none) { LOG_PRINT_L0("[on_send_raw_tx]: tx accepted, but not relayed"); res.reason = "Not relayed"; @@ -1149,8 +1164,8 @@ namespace cryptonote } NOTIFY_NEW_TRANSACTIONS::request r; - r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); + r.txs.push_back(std::move(tx_blob)); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = CORE_RPC_STATUS_OK; return true; @@ -1410,12 +1425,13 @@ namespace cryptonote const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; + const bool allow_sensitive = !request_has_rpc_origin || !restricted; - size_t n_txes = m_core.get_pool_transactions_count(); + size_t n_txes = m_core.get_pool_transactions_count(allow_sensitive); 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); + m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, allow_sensitive); for (tx_info& txi : res.transactions) txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); } @@ -1435,12 +1451,13 @@ namespace cryptonote const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; + const bool allow_sensitive = !request_has_rpc_origin || !restricted; - size_t n_txes = m_core.get_pool_transactions_count(); + size_t n_txes = m_core.get_pool_transactions_count(allow_sensitive); 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); + m_core.get_pool_transaction_hashes(res.tx_hashes, allow_sensitive); } res.status = CORE_RPC_STATUS_OK; @@ -1458,13 +1475,14 @@ namespace cryptonote const bool restricted = m_restricted && ctx; const bool request_has_rpc_origin = ctx != NULL; + const bool allow_sensitive = !request_has_rpc_origin || !restricted; - size_t n_txes = m_core.get_pool_transactions_count(); + size_t n_txes = m_core.get_pool_transactions_count(allow_sensitive); 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); + m_core.get_pool_transaction_hashes(tx_hashes, allow_sensitive); 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)); @@ -1932,7 +1950,7 @@ namespace cryptonote if (*bootstrap_daemon_height < target_height) { MINFO("Bootstrap daemon is out of sync"); - return m_bootstrap_daemon->handle_result(false); + return m_bootstrap_daemon->handle_result(false, {}); } uint64_t top_height = m_core.get_current_blockchain_height(); @@ -2211,6 +2229,7 @@ namespace cryptonote error_resp.message = "Internal error: can't produce valid response."; return false; } + res.miner_tx_hash = res.block_header.miner_tx_hash; 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])); @@ -2497,9 +2516,9 @@ namespace cryptonote 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; + std::pair<boost::multiprecision::uint128_t, boost::multiprecision::uint128_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); + store_128(amounts.first, res.emission_amount, res.wide_emission_amount, res.emission_amount_top64); + store_128(amounts.second, res.fee_amount, res.wide_fee_amount, res.fee_amount_top64); res.status = CORE_RPC_STATUS_OK; return true; } @@ -2762,8 +2781,8 @@ namespace cryptonote if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy)) { NOTIFY_NEW_TRANSACTIONS::request r; - r.txs.push_back(txblob); - m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); + r.txs.push_back(std::move(txblob)); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes } else @@ -3014,6 +3033,8 @@ namespace cryptonote RPC_TRACKER(flush_cache); if (req.bad_txs) m_core.flush_bad_txs_cache(); + if (req.bad_blocks) + m_core.flush_invalid_blocks(); res.status = CORE_RPC_STATUS_OK; return true; } @@ -3247,4 +3268,10 @@ namespace cryptonote , "Restrict RPC to clients sending micropayment, yields that many credits per payment" , DEFAULT_PAYMENT_CREDITS_PER_HASH }; + + const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_payment_allow_free_loopback = { + "rpc-payment-allow-free-loopback" + , "Allow free access from the loopback address (ie, the local host)" + , false + }; } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index fbcffd120..3c404bbd8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -75,6 +75,7 @@ namespace cryptonote 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; + static const command_line::arg_descriptor<bool> arg_rpc_payment_allow_free_loopback; typedef epee::net_utils::connection_context_base connection_context; @@ -266,7 +267,7 @@ private: //utils uint64_t get_block_reward(const block& blk); bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash); - boost::optional<std::string> get_random_public_node(); + std::map<std::string, bool> get_public_nodes(uint32_t credits_per_hash_threshold = 0); bool set_bootstrap_daemon(const std::string &address, const std::string &username_password); bool set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials); enum invoke_http_mode { JON, BIN, JON_RPC }; @@ -286,6 +287,8 @@ private: 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; + bool disable_rpc_ban; + bool m_rpc_payment_allow_free_loopback; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 12b042c7e..a3c187c24 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -588,7 +588,6 @@ namespace cryptonote bool too_big; bool overspend; bool fee_too_low; - bool not_rct; bool too_few_outputs; bool sanity_check_failed; @@ -603,7 +602,6 @@ namespace cryptonote KV_SERIALIZE(too_big) KV_SERIALIZE(overspend) KV_SERIALIZE(fee_too_low) - KV_SERIALIZE(not_rct) KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) END_KV_SERIALIZE_MAP() @@ -2023,12 +2021,20 @@ namespace cryptonote struct response_t: public rpc_access_response_base { uint64_t emission_amount; + std::string wide_emission_amount; + uint64_t emission_amount_top64; uint64_t fee_amount; + std::string wide_fee_amount; + uint64_t fee_amount_top64; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) KV_SERIALIZE(emission_amount) + KV_SERIALIZE(wide_emission_amount) + KV_SERIALIZE(emission_amount_top64) KV_SERIALIZE(fee_amount) + KV_SERIALIZE(wide_fee_amount) + KV_SERIALIZE(fee_amount_top64) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2561,10 +2567,12 @@ namespace cryptonote struct request_t: public rpc_request_base { bool bad_txs; + bool bad_blocks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(bad_txs, false) + KV_SERIALIZE_OPT(bad_blocks, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 24800ff20..de0510fec 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -28,7 +28,12 @@ #include "daemon_handler.h" +#include <algorithm> +#include <cstring> +#include <stdexcept> + #include <boost/uuid/nil_generator.hpp> +#include <boost/utility/string_ref.hpp> // likely included by daemon_handler.h's includes, // but including here for clarity #include "cryptonote_core/cryptonote_core.h" @@ -42,6 +47,74 @@ namespace cryptonote namespace rpc { + namespace + { + using handler_function = epee::byte_slice(DaemonHandler& handler, const rapidjson::Value& id, const rapidjson::Value& msg); + struct handler_map + { + const char* method_name; + handler_function* call; + }; + + bool operator<(const handler_map& lhs, const handler_map& rhs) noexcept + { + return std::strcmp(lhs.method_name, rhs.method_name) < 0; + } + + bool operator<(const handler_map& lhs, const std::string& rhs) noexcept + { + return std::strcmp(lhs.method_name, rhs.c_str()) < 0; + } + + template<typename Message> + epee::byte_slice handle_message(DaemonHandler& handler, const rapidjson::Value& id, const rapidjson::Value& parameters) + { + typename Message::Request request{}; + request.fromJson(parameters); + + typename Message::Response response{}; + handler.handle(request, response); + return FullMessage::getResponse(response, id); + } + + constexpr const handler_map handlers[] = + { + {u8"get_block_hash", handle_message<GetBlockHash>}, + {u8"get_block_header_by_hash", handle_message<GetBlockHeaderByHash>}, + {u8"get_block_header_by_height", handle_message<GetBlockHeaderByHeight>}, + {u8"get_block_headers_by_height", handle_message<GetBlockHeadersByHeight>}, + {u8"get_blocks_fast", handle_message<GetBlocksFast>}, + {u8"get_dynamic_fee_estimate", handle_message<GetFeeEstimate>}, + {u8"get_hashes_fast", handle_message<GetHashesFast>}, + {u8"get_height", handle_message<GetHeight>}, + {u8"get_info", handle_message<GetInfo>}, + {u8"get_last_block_header", handle_message<GetLastBlockHeader>}, + {u8"get_output_distribution", handle_message<GetOutputDistribution>}, + {u8"get_output_histogram", handle_message<GetOutputHistogram>}, + {u8"get_output_keys", handle_message<GetOutputKeys>}, + {u8"get_peer_list", handle_message<GetPeerList>}, + {u8"get_rpc_version", handle_message<GetRPCVersion>}, + {u8"get_transaction_pool", handle_message<GetTransactionPool>}, + {u8"get_transactions", handle_message<GetTransactions>}, + {u8"get_tx_global_output_indices", handle_message<GetTxGlobalOutputIndices>}, + {u8"hard_fork_info", handle_message<HardForkInfo>}, + {u8"key_images_spent", handle_message<KeyImagesSpent>}, + {u8"mining_status", handle_message<MiningStatus>}, + {u8"save_bc", handle_message<SaveBC>}, + {u8"send_raw_tx", handle_message<SendRawTxHex>}, + {u8"set_log_level", handle_message<SetLogLevel>}, + {u8"start_mining", handle_message<StartMining>}, + {u8"stop_mining", handle_message<StopMining>} + }; + } // anonymous + + DaemonHandler::DaemonHandler(cryptonote::core& c, t_p2p& p2p) + : m_core(c), m_p2p(p2p) + { + const auto last_sorted = std::is_sorted_until(std::begin(handlers), std::end(handlers)); + if (last_sorted != std::end(handlers)) + throw std::logic_error{std::string{"ZMQ JSON-RPC handlers map is not properly sorted, see "} + last_sorted->method_name}; + } void DaemonHandler::handle(const GetHeight::Request& req, GetHeight::Response& res) { @@ -277,10 +350,10 @@ namespace rpc res.error_details = "Invalid hex"; return; } - handleTxBlob(tx_blob, req.relay, res); + handleTxBlob(std::move(tx_blob), req.relay, res); } - void DaemonHandler::handleTxBlob(const std::string& tx_blob, bool relay, SendRawTx::Response& res) + void DaemonHandler::handleTxBlob(std::string&& tx_blob, bool relay, SendRawTx::Response& res) { if (!m_p2p.get_payload_object().is_synchronized()) { @@ -338,11 +411,6 @@ namespace rpc if (!res.error_details.empty()) res.error_details += " and "; res.error_details += "fee too low"; } - if (tvc.m_not_rct) - { - if (!res.error_details.empty()) res.error_details += " and "; - res.error_details += "tx is not ringct"; - } if (tvc.m_too_few_outputs) { if (!res.error_details.empty()) res.error_details += " and "; @@ -356,7 +424,7 @@ namespace rpc return; } - if(!tvc.m_should_be_relayed || !relay) + if(tvc.m_relay == relay_method::none || !relay) { MERROR("[SendRawTx]: tx accepted, but not relayed"); res.error_details = "Not relayed"; @@ -367,8 +435,8 @@ namespace rpc } NOTIFY_NEW_TRANSACTIONS::request r; - r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); + r.txs.push_back(std::move(tx_blob)); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid, relay_method::local); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = Message::STATUS_OK; @@ -836,72 +904,28 @@ namespace rpc return true; } - std::string DaemonHandler::handle(const std::string& request) + epee::byte_slice DaemonHandler::handle(const std::string& request) { MDEBUG("Handling RPC request: " << request); - Message* resp_message = NULL; - try { FullMessage req_full(request, true); - rapidjson::Value& req_json = req_full.getMessage(); - const std::string request_type = req_full.getRequestType(); - - // create correct Message subclass and call handle() on it - REQ_RESP_TYPES_MACRO(request_type, GetHeight, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetBlocksFast, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetHashesFast, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetTransactions, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, KeyImagesSpent, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetTxGlobalOutputIndices, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, SendRawTx, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, SendRawTxHex, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetInfo, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, StartMining, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, StopMining, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, MiningStatus, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, SaveBC, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetBlockHash, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetLastBlockHeader, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHash, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetBlockHeaderByHeight, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetBlockHeadersByHeight, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetPeerList, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, SetLogLevel, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetTransactionPool, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, HardForkInfo, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetOutputHistogram, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetOutputKeys, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetRPCVersion, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetFeeEstimate, req_json, resp_message, handle); - REQ_RESP_TYPES_MACRO(request_type, GetOutputDistribution, req_json, resp_message, handle); - - // if none of the request types matches - if (resp_message == NULL) - { + const auto matched_handler = std::lower_bound(std::begin(handlers), std::end(handlers), request_type); + if (matched_handler == std::end(handlers) || matched_handler->method_name != request_type) return BAD_REQUEST(request_type, req_full.getID()); - } - - FullMessage resp_full = FullMessage::responseMessage(resp_message, req_full.getID()); - const std::string response = resp_full.getJson(); - delete resp_message; - resp_message = NULL; + epee::byte_slice response = matched_handler->call(*this, req_full.getID(), req_full.getMessage()); - MDEBUG("Returning RPC response: " << response); + const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; + MDEBUG("Returning RPC response: " << response_view); return response; } catch (const std::exception& e) { - if (resp_message) - { - delete resp_message; - } - return BAD_JSON(e.what()); } } diff --git a/src/rpc/daemon_handler.h b/src/rpc/daemon_handler.h index 34723f676..b797b1155 100644 --- a/src/rpc/daemon_handler.h +++ b/src/rpc/daemon_handler.h @@ -28,6 +28,7 @@ #pragma once +#include "byte_slice.h" #include "daemon_messages.h" #include "daemon_rpc_version.h" #include "rpc_handler.h" @@ -50,7 +51,7 @@ class DaemonHandler : public RpcHandler { public: - DaemonHandler(cryptonote::core& c, t_p2p& p2p) : m_core(c), m_p2p(p2p) { } + DaemonHandler(cryptonote::core& c, t_p2p& p2p); ~DaemonHandler() { } @@ -132,13 +133,13 @@ class DaemonHandler : public RpcHandler void handle(const GetOutputDistribution::Request& req, GetOutputDistribution::Response& res); - std::string handle(const std::string& request); + epee::byte_slice handle(const std::string& request) override final; private: bool getBlockHeaderByHash(const crypto::hash& hash_in, cryptonote::rpc::BlockHeaderResponse& response); - void handleTxBlob(const std::string& tx_blob, bool relay, SendRawTx::Response& res); + void handleTxBlob(std::string&& tx_blob, bool relay, SendRawTx::Response& res); cryptonote::core& m_core; t_p2p& m_p2p; diff --git a/src/rpc/daemon_messages.cpp b/src/rpc/daemon_messages.cpp index cf0f5ece1..5c179408e 100644 --- a/src/rpc/daemon_messages.cpp +++ b/src/rpc/daemon_messages.cpp @@ -34,99 +34,47 @@ namespace cryptonote namespace rpc { +void GetHeight::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -const char* const GetHeight::name = "get_height"; -const char* const GetBlocksFast::name = "get_blocks_fast"; -const char* const GetHashesFast::name = "get_hashes_fast"; -const char* const GetTransactions::name = "get_transactions"; -const char* const KeyImagesSpent::name = "key_images_spent"; -const char* const GetTxGlobalOutputIndices::name = "get_tx_global_output_indices"; -const char* const SendRawTx::name = "send_raw_tx"; -const char* const SendRawTxHex::name = "send_raw_tx_hex"; -const char* const StartMining::name = "start_mining"; -const char* const StopMining::name = "stop_mining"; -const char* const MiningStatus::name = "mining_status"; -const char* const GetInfo::name = "get_info"; -const char* const SaveBC::name = "save_bc"; -const char* const GetBlockHash::name = "get_block_hash"; -const char* const GetLastBlockHeader::name = "get_last_block_header"; -const char* const GetBlockHeaderByHash::name = "get_block_header_by_hash"; -const char* const GetBlockHeaderByHeight::name = "get_block_header_by_height"; -const char* const GetBlockHeadersByHeight::name = "get_block_headers_by_height"; -const char* const GetPeerList::name = "get_peer_list"; -const char* const SetLogLevel::name = "set_log_level"; -const char* const GetTransactionPool::name = "get_transaction_pool"; -const char* const HardForkInfo::name = "hard_fork_info"; -const char* const GetOutputHistogram::name = "get_output_histogram"; -const char* const GetOutputKeys::name = "get_output_keys"; -const char* const GetRPCVersion::name = "get_rpc_version"; -const char* const GetFeeEstimate::name = "get_dynamic_fee_estimate"; -const char* const GetOutputDistribution::name = "get_output_distribution"; - - - - -rapidjson::Value GetHeight::Request::toJson(rapidjson::Document& doc) const +void GetHeight::Request::fromJson(const rapidjson::Value& val) { - return Message::toJson(doc); } -void GetHeight::Request::fromJson(rapidjson::Value& val) +void GetHeight::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { + INSERT_INTO_JSON_OBJECT(dest, height, height); } -rapidjson::Value GetHeight::Response::toJson(rapidjson::Document& doc) const -{ - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - val.AddMember("height", height, al); - - return val; -} - -void GetHeight::Response::fromJson(rapidjson::Value& val) +void GetHeight::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, height, height); } -rapidjson::Value GetBlocksFast::Request::toJson(rapidjson::Document& doc) const +void GetBlocksFast::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - INSERT_INTO_JSON_OBJECT(val, doc, block_ids, block_ids); - val.AddMember("start_height", start_height, al); - val.AddMember("prune", prune, al); - - return val; + INSERT_INTO_JSON_OBJECT(dest, block_ids, block_ids); + INSERT_INTO_JSON_OBJECT(dest, start_height, start_height); + INSERT_INTO_JSON_OBJECT(dest, prune, prune); } -void GetBlocksFast::Request::fromJson(rapidjson::Value& val) +void GetBlocksFast::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, block_ids, block_ids); GET_FROM_JSON_OBJECT(val, start_height, start_height); GET_FROM_JSON_OBJECT(val, prune, prune); } -rapidjson::Value GetBlocksFast::Response::toJson(rapidjson::Document& doc) const +void GetBlocksFast::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - INSERT_INTO_JSON_OBJECT(val, doc, blocks, blocks); - val.AddMember("start_height", start_height, al); - val.AddMember("current_height", current_height, al); - INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); - - return val; + INSERT_INTO_JSON_OBJECT(dest, blocks, blocks); + INSERT_INTO_JSON_OBJECT(dest, start_height, start_height); + INSERT_INTO_JSON_OBJECT(dest, current_height, current_height); + INSERT_INTO_JSON_OBJECT(dest, output_indices, output_indices); } -void GetBlocksFast::Response::fromJson(rapidjson::Value& val) +void GetBlocksFast::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, blocks, blocks); GET_FROM_JSON_OBJECT(val, start_height, start_height); @@ -135,38 +83,26 @@ void GetBlocksFast::Response::fromJson(rapidjson::Value& val) } -rapidjson::Value GetHashesFast::Request::toJson(rapidjson::Document& doc) const +void GetHashesFast::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - INSERT_INTO_JSON_OBJECT(val, doc, known_hashes, known_hashes); - val.AddMember("start_height", start_height, al); - - return val; + INSERT_INTO_JSON_OBJECT(dest, known_hashes, known_hashes); + INSERT_INTO_JSON_OBJECT(dest, start_height, start_height); } -void GetHashesFast::Request::fromJson(rapidjson::Value& val) +void GetHashesFast::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, known_hashes, known_hashes); GET_FROM_JSON_OBJECT(val, start_height, start_height); } -rapidjson::Value GetHashesFast::Response::toJson(rapidjson::Document& doc) const +void GetHashesFast::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - INSERT_INTO_JSON_OBJECT(val, doc, hashes, hashes); - val.AddMember("start_height", start_height, al); - val.AddMember("current_height", current_height, al); - - return val; + INSERT_INTO_JSON_OBJECT(dest, hashes, hashes); + INSERT_INTO_JSON_OBJECT(dest, start_height, start_height); + INSERT_INTO_JSON_OBJECT(dest, current_height, current_height); } -void GetHashesFast::Response::fromJson(rapidjson::Value& val) +void GetHashesFast::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, hashes, hashes); GET_FROM_JSON_OBJECT(val, start_height, start_height); @@ -174,154 +110,114 @@ void GetHashesFast::Response::fromJson(rapidjson::Value& val) } -rapidjson::Value GetTransactions::Request::toJson(rapidjson::Document& doc) const +void GetTransactions::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, tx_hashes); - - return val; + INSERT_INTO_JSON_OBJECT(dest, tx_hashes, tx_hashes); } -void GetTransactions::Request::fromJson(rapidjson::Value& val) +void GetTransactions::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, tx_hashes, tx_hashes); } -rapidjson::Value GetTransactions::Response::toJson(rapidjson::Document& doc) const +void GetTransactions::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - rapidjson::Value val(rapidjson::kObjectType); - - INSERT_INTO_JSON_OBJECT(val, doc, txs, txs); - INSERT_INTO_JSON_OBJECT(val, doc, missed_hashes, missed_hashes); - - return val; + INSERT_INTO_JSON_OBJECT(dest, txs, txs); + INSERT_INTO_JSON_OBJECT(dest, missed_hashes, missed_hashes); } -void GetTransactions::Response::fromJson(rapidjson::Value& val) +void GetTransactions::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, txs, txs); GET_FROM_JSON_OBJECT(val, missed_hashes, missed_hashes); } -rapidjson::Value KeyImagesSpent::Request::toJson(rapidjson::Document& doc) const +void KeyImagesSpent::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); - - return val; + INSERT_INTO_JSON_OBJECT(dest, key_images, key_images); } -void KeyImagesSpent::Request::fromJson(rapidjson::Value& val) +void KeyImagesSpent::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, key_images, key_images); } -rapidjson::Value KeyImagesSpent::Response::toJson(rapidjson::Document& doc) const +void KeyImagesSpent::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, spent_status, spent_status); - - return val; + INSERT_INTO_JSON_OBJECT(dest, spent_status, spent_status); } -void KeyImagesSpent::Response::fromJson(rapidjson::Value& val) +void KeyImagesSpent::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, spent_status, spent_status); } -rapidjson::Value GetTxGlobalOutputIndices::Request::toJson(rapidjson::Document& doc) const +void GetTxGlobalOutputIndices::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx_hash); - - return val; + INSERT_INTO_JSON_OBJECT(dest, tx_hash, tx_hash); } -void GetTxGlobalOutputIndices::Request::fromJson(rapidjson::Value& val) +void GetTxGlobalOutputIndices::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, tx_hash, tx_hash); } -rapidjson::Value GetTxGlobalOutputIndices::Response::toJson(rapidjson::Document& doc) const +void GetTxGlobalOutputIndices::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, output_indices, output_indices); - - return val; + INSERT_INTO_JSON_OBJECT(dest, output_indices, output_indices); } -void GetTxGlobalOutputIndices::Response::fromJson(rapidjson::Value& val) +void GetTxGlobalOutputIndices::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, output_indices, output_indices); } -rapidjson::Value SendRawTx::Request::toJson(rapidjson::Document& doc) const +void SendRawTx::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, tx, tx); - INSERT_INTO_JSON_OBJECT(val, doc, relay, relay); - - return val; + INSERT_INTO_JSON_OBJECT(dest, tx, tx); + INSERT_INTO_JSON_OBJECT(dest, relay, relay); } -void SendRawTx::Request::fromJson(rapidjson::Value& val) +void SendRawTx::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, tx, tx); GET_FROM_JSON_OBJECT(val, relay, relay); } -rapidjson::Value SendRawTx::Response::toJson(rapidjson::Document& doc) const +void SendRawTx::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, relayed, relayed); - - return val; + INSERT_INTO_JSON_OBJECT(dest, relayed, relayed); } -void SendRawTx::Response::fromJson(rapidjson::Value& val) +void SendRawTx::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, relayed, relayed); } -rapidjson::Value SendRawTxHex::Request::toJson(rapidjson::Document& doc) const +void SendRawTxHex::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, tx_as_hex, tx_as_hex); - INSERT_INTO_JSON_OBJECT(val, doc, relay, relay); - - return val; + INSERT_INTO_JSON_OBJECT(dest, tx_as_hex, tx_as_hex); + INSERT_INTO_JSON_OBJECT(dest, relay, relay); } -void SendRawTxHex::Request::fromJson(rapidjson::Value& val) +void SendRawTxHex::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, tx_as_hex, tx_as_hex); GET_FROM_JSON_OBJECT(val, relay, relay); } -rapidjson::Value StartMining::Request::toJson(rapidjson::Document& doc) const +void StartMining::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, miner_address, miner_address); - INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); - INSERT_INTO_JSON_OBJECT(val, doc, do_background_mining, do_background_mining); - INSERT_INTO_JSON_OBJECT(val, doc, ignore_battery, ignore_battery); - - return val; + INSERT_INTO_JSON_OBJECT(dest, miner_address, miner_address); + INSERT_INTO_JSON_OBJECT(dest, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(dest, do_background_mining, do_background_mining); + INSERT_INTO_JSON_OBJECT(dest, ignore_battery, ignore_battery); } -void StartMining::Request::fromJson(rapidjson::Value& val) +void StartMining::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, miner_address, miner_address); GET_FROM_JSON_OBJECT(val, threads_count, threads_count); @@ -329,58 +225,46 @@ void StartMining::Request::fromJson(rapidjson::Value& val) GET_FROM_JSON_OBJECT(val, ignore_battery, ignore_battery); } -rapidjson::Value StartMining::Response::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void StartMining::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void StartMining::Response::fromJson(rapidjson::Value& val) +void StartMining::Response::fromJson(const rapidjson::Value& val) { } -rapidjson::Value StopMining::Request::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void StopMining::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void StopMining::Request::fromJson(rapidjson::Value& val) +void StopMining::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value StopMining::Response::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void StopMining::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void StopMining::Response::fromJson(rapidjson::Value& val) +void StopMining::Response::fromJson(const rapidjson::Value& val) { } -rapidjson::Value MiningStatus::Request::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void MiningStatus::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void MiningStatus::Request::fromJson(rapidjson::Value& val) +void MiningStatus::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value MiningStatus::Response::toJson(rapidjson::Document& doc) const +void MiningStatus::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, active, active); - INSERT_INTO_JSON_OBJECT(val, doc, speed, speed); - INSERT_INTO_JSON_OBJECT(val, doc, threads_count, threads_count); - INSERT_INTO_JSON_OBJECT(val, doc, address, address); - INSERT_INTO_JSON_OBJECT(val, doc, is_background_mining_enabled, is_background_mining_enabled); - - return val; + INSERT_INTO_JSON_OBJECT(dest, active, active); + INSERT_INTO_JSON_OBJECT(dest, speed, speed); + INSERT_INTO_JSON_OBJECT(dest, threads_count, threads_count); + INSERT_INTO_JSON_OBJECT(dest, address, address); + INSERT_INTO_JSON_OBJECT(dest, is_background_mining_enabled, is_background_mining_enabled); } -void MiningStatus::Response::fromJson(rapidjson::Value& val) +void MiningStatus::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, active, active); GET_FROM_JSON_OBJECT(val, speed, speed); @@ -390,318 +274,230 @@ void MiningStatus::Response::fromJson(rapidjson::Value& val) } -rapidjson::Value GetInfo::Request::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void GetInfo::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void GetInfo::Request::fromJson(rapidjson::Value& val) +void GetInfo::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetInfo::Response::toJson(rapidjson::Document& doc) const +void GetInfo::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, info, info); - - return val; + INSERT_INTO_JSON_OBJECT(dest, info, info); } -void GetInfo::Response::fromJson(rapidjson::Value& val) +void GetInfo::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, info, info); } -rapidjson::Value SaveBC::Request::toJson(rapidjson::Document& doc) const -{ - auto val = Message::toJson(doc); +void SaveBC::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} - return val; -} - -void SaveBC::Request::fromJson(rapidjson::Value& val) +void SaveBC::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value SaveBC::Response::toJson(rapidjson::Document& doc) const -{ - auto val = Message::toJson(doc); +void SaveBC::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} - return val; -} - -void SaveBC::Response::fromJson(rapidjson::Value& val) +void SaveBC::Response::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetBlockHash::Request::toJson(rapidjson::Document& doc) const +void GetBlockHash::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, height, height); - - return val; + INSERT_INTO_JSON_OBJECT(dest, height, height); } -void GetBlockHash::Request::fromJson(rapidjson::Value& val) +void GetBlockHash::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, height, height); } -rapidjson::Value GetBlockHash::Response::toJson(rapidjson::Document& doc) const +void GetBlockHash::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); - - return val; + INSERT_INTO_JSON_OBJECT(dest, hash, hash); } -void GetBlockHash::Response::fromJson(rapidjson::Value& val) +void GetBlockHash::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, hash, hash); } -rapidjson::Value GetLastBlockHeader::Request::toJson(rapidjson::Document& doc) const -{ - auto val = Message::toJson(doc); - - return val; -} +void GetLastBlockHeader::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void GetLastBlockHeader::Request::fromJson(rapidjson::Value& val) +void GetLastBlockHeader::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetLastBlockHeader::Response::toJson(rapidjson::Document& doc) const +void GetLastBlockHeader::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, header, header); - - return val; + INSERT_INTO_JSON_OBJECT(dest, header, header); } -void GetLastBlockHeader::Response::fromJson(rapidjson::Value& val) +void GetLastBlockHeader::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, header, header); } -rapidjson::Value GetBlockHeaderByHash::Request::toJson(rapidjson::Document& doc) const +void GetBlockHeaderByHash::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, hash, hash); - - return val; + INSERT_INTO_JSON_OBJECT(dest, hash, hash); } -void GetBlockHeaderByHash::Request::fromJson(rapidjson::Value& val) +void GetBlockHeaderByHash::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, hash, hash); } -rapidjson::Value GetBlockHeaderByHash::Response::toJson(rapidjson::Document& doc) const +void GetBlockHeaderByHash::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, header, header); - - return val; + INSERT_INTO_JSON_OBJECT(dest, header, header); } -void GetBlockHeaderByHash::Response::fromJson(rapidjson::Value& val) +void GetBlockHeaderByHash::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, header, header); } -rapidjson::Value GetBlockHeaderByHeight::Request::toJson(rapidjson::Document& doc) const +void GetBlockHeaderByHeight::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, height, height); - - return val; + INSERT_INTO_JSON_OBJECT(dest, height, height); } -void GetBlockHeaderByHeight::Request::fromJson(rapidjson::Value& val) +void GetBlockHeaderByHeight::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, height, height); } -rapidjson::Value GetBlockHeaderByHeight::Response::toJson(rapidjson::Document& doc) const +void GetBlockHeaderByHeight::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, header, header); - - return val; + INSERT_INTO_JSON_OBJECT(dest, header, header); } -void GetBlockHeaderByHeight::Response::fromJson(rapidjson::Value& val) +void GetBlockHeaderByHeight::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, header, header); } -rapidjson::Value GetBlockHeadersByHeight::Request::toJson(rapidjson::Document& doc) const +void GetBlockHeadersByHeight::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, heights, heights); - - return val; + INSERT_INTO_JSON_OBJECT(dest, heights, heights); } -void GetBlockHeadersByHeight::Request::fromJson(rapidjson::Value& val) +void GetBlockHeadersByHeight::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, heights, heights); } -rapidjson::Value GetBlockHeadersByHeight::Response::toJson(rapidjson::Document& doc) const +void GetBlockHeadersByHeight::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, headers, headers); - - return val; + INSERT_INTO_JSON_OBJECT(dest, headers, headers); } -void GetBlockHeadersByHeight::Response::fromJson(rapidjson::Value& val) +void GetBlockHeadersByHeight::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, headers, headers); } -rapidjson::Value GetPeerList::Request::toJson(rapidjson::Document& doc) const -{ - auto val = Message::toJson(doc); +void GetPeerList::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} - return val; -} - -void GetPeerList::Request::fromJson(rapidjson::Value& val) +void GetPeerList::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetPeerList::Response::toJson(rapidjson::Document& doc) const +void GetPeerList::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, white_list, white_list); - INSERT_INTO_JSON_OBJECT(val, doc, gray_list, gray_list); - - return val; + INSERT_INTO_JSON_OBJECT(dest, white_list, white_list); + INSERT_INTO_JSON_OBJECT(dest, gray_list, gray_list); } -void GetPeerList::Response::fromJson(rapidjson::Value& val) +void GetPeerList::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, white_list, white_list); GET_FROM_JSON_OBJECT(val, gray_list, gray_list); } -rapidjson::Value SetLogLevel::Request::toJson(rapidjson::Document& doc) const +void SetLogLevel::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - auto& al = doc.GetAllocator(); - - val.AddMember("level", level, al); - - return val; + INSERT_INTO_JSON_OBJECT(dest, level, level); } -void SetLogLevel::Request::fromJson(rapidjson::Value& val) +void SetLogLevel::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, level, level); } -rapidjson::Value SetLogLevel::Response::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void SetLogLevel::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void SetLogLevel::Response::fromJson(rapidjson::Value& val) +void SetLogLevel::Response::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetTransactionPool::Request::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void GetTransactionPool::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void GetTransactionPool::Request::fromJson(rapidjson::Value& val) +void GetTransactionPool::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetTransactionPool::Response::toJson(rapidjson::Document& doc) const +void GetTransactionPool::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, transactions, transactions); - INSERT_INTO_JSON_OBJECT(val, doc, key_images, key_images); - - return val; + INSERT_INTO_JSON_OBJECT(dest, transactions, transactions); + INSERT_INTO_JSON_OBJECT(dest, key_images, key_images); } -void GetTransactionPool::Response::fromJson(rapidjson::Value& val) +void GetTransactionPool::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, transactions, transactions); GET_FROM_JSON_OBJECT(val, key_images, key_images); } -rapidjson::Value HardForkInfo::Request::toJson(rapidjson::Document& doc) const +void HardForkInfo::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, version, version); - - return val; + INSERT_INTO_JSON_OBJECT(dest, version, version); } -void HardForkInfo::Request::fromJson(rapidjson::Value& val) +void HardForkInfo::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, version, version); } -rapidjson::Value HardForkInfo::Response::toJson(rapidjson::Document& doc) const +void HardForkInfo::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, info, info); - - return val; + INSERT_INTO_JSON_OBJECT(dest, info, info); } -void HardForkInfo::Response::fromJson(rapidjson::Value& val) +void HardForkInfo::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, info, info); } -rapidjson::Value GetOutputHistogram::Request::toJson(rapidjson::Document& doc) const +void GetOutputHistogram::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); - INSERT_INTO_JSON_OBJECT(val, doc, min_count, min_count); - INSERT_INTO_JSON_OBJECT(val, doc, max_count, max_count); - INSERT_INTO_JSON_OBJECT(val, doc, unlocked, unlocked); - INSERT_INTO_JSON_OBJECT(val, doc, recent_cutoff, recent_cutoff); - - return val; + INSERT_INTO_JSON_OBJECT(dest, amounts, amounts); + INSERT_INTO_JSON_OBJECT(dest, min_count, min_count); + INSERT_INTO_JSON_OBJECT(dest, max_count, max_count); + INSERT_INTO_JSON_OBJECT(dest, unlocked, unlocked); + INSERT_INTO_JSON_OBJECT(dest, recent_cutoff, recent_cutoff); } -void GetOutputHistogram::Request::fromJson(rapidjson::Value& val) +void GetOutputHistogram::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, amounts, amounts); GET_FROM_JSON_OBJECT(val, min_count, min_count); @@ -710,100 +506,74 @@ void GetOutputHistogram::Request::fromJson(rapidjson::Value& val) GET_FROM_JSON_OBJECT(val, recent_cutoff, recent_cutoff); } -rapidjson::Value GetOutputHistogram::Response::toJson(rapidjson::Document& doc) const +void GetOutputHistogram::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, histogram, histogram); - - return val; + INSERT_INTO_JSON_OBJECT(dest, histogram, histogram); } -void GetOutputHistogram::Response::fromJson(rapidjson::Value& val) +void GetOutputHistogram::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, histogram, histogram); } -rapidjson::Value GetOutputKeys::Request::toJson(rapidjson::Document& doc) const +void GetOutputKeys::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, outputs, outputs); - - return val; + INSERT_INTO_JSON_OBJECT(dest, outputs, outputs); } -void GetOutputKeys::Request::fromJson(rapidjson::Value& val) +void GetOutputKeys::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, outputs, outputs); } -rapidjson::Value GetOutputKeys::Response::toJson(rapidjson::Document& doc) const +void GetOutputKeys::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, keys, keys); - - return val; + INSERT_INTO_JSON_OBJECT(dest, keys, keys); } -void GetOutputKeys::Response::fromJson(rapidjson::Value& val) +void GetOutputKeys::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, keys, keys); } -rapidjson::Value GetRPCVersion::Request::toJson(rapidjson::Document& doc) const -{ - return Message::toJson(doc); -} +void GetRPCVersion::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const +{} -void GetRPCVersion::Request::fromJson(rapidjson::Value& val) +void GetRPCVersion::Request::fromJson(const rapidjson::Value& val) { } -rapidjson::Value GetRPCVersion::Response::toJson(rapidjson::Document& doc) const +void GetRPCVersion::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, version, version); - - return val; + INSERT_INTO_JSON_OBJECT(dest, version, version); } -void GetRPCVersion::Response::fromJson(rapidjson::Value& val) +void GetRPCVersion::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, version, version); } -rapidjson::Value GetFeeEstimate::Request::toJson(rapidjson::Document& doc) const +void GetFeeEstimate::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, num_grace_blocks, num_grace_blocks); - - return val; + INSERT_INTO_JSON_OBJECT(dest, num_grace_blocks, num_grace_blocks); } -void GetFeeEstimate::Request::fromJson(rapidjson::Value& val) +void GetFeeEstimate::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, num_grace_blocks, num_grace_blocks); } -rapidjson::Value GetFeeEstimate::Response::toJson(rapidjson::Document& doc) const +void GetFeeEstimate::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, estimated_base_fee, estimated_base_fee); - INSERT_INTO_JSON_OBJECT(val, doc, fee_mask, fee_mask); - INSERT_INTO_JSON_OBJECT(val, doc, size_scale, size_scale); - INSERT_INTO_JSON_OBJECT(val, doc, hard_fork_version, hard_fork_version); - - return val; + INSERT_INTO_JSON_OBJECT(dest, estimated_base_fee, estimated_base_fee); + INSERT_INTO_JSON_OBJECT(dest, fee_mask, fee_mask); + INSERT_INTO_JSON_OBJECT(dest, size_scale, size_scale); + INSERT_INTO_JSON_OBJECT(dest, hard_fork_version, hard_fork_version); } -void GetFeeEstimate::Response::fromJson(rapidjson::Value& val) +void GetFeeEstimate::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, estimated_base_fee, estimated_base_fee); GET_FROM_JSON_OBJECT(val, fee_mask, fee_mask); @@ -811,19 +581,15 @@ void GetFeeEstimate::Response::fromJson(rapidjson::Value& val) GET_FROM_JSON_OBJECT(val, hard_fork_version, hard_fork_version); } -rapidjson::Value GetOutputDistribution::Request::toJson(rapidjson::Document& doc) const +void GetOutputDistribution::Request::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, amounts, amounts); - INSERT_INTO_JSON_OBJECT(val, doc, from_height, from_height); - INSERT_INTO_JSON_OBJECT(val, doc, to_height, to_height); - INSERT_INTO_JSON_OBJECT(val, doc, cumulative, cumulative); - - return val; + INSERT_INTO_JSON_OBJECT(dest, amounts, amounts); + INSERT_INTO_JSON_OBJECT(dest, from_height, from_height); + INSERT_INTO_JSON_OBJECT(dest, to_height, to_height); + INSERT_INTO_JSON_OBJECT(dest, cumulative, cumulative); } -void GetOutputDistribution::Request::fromJson(rapidjson::Value& val) +void GetOutputDistribution::Request::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, amounts, amounts); GET_FROM_JSON_OBJECT(val, from_height, from_height); @@ -831,17 +597,13 @@ void GetOutputDistribution::Request::fromJson(rapidjson::Value& val) GET_FROM_JSON_OBJECT(val, cumulative, cumulative); } -rapidjson::Value GetOutputDistribution::Response::toJson(rapidjson::Document& doc) const +void GetOutputDistribution::Response::doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - auto val = Message::toJson(doc); - - INSERT_INTO_JSON_OBJECT(val, doc, status, status); - INSERT_INTO_JSON_OBJECT(val, doc, distributions, distributions); - - return val; + INSERT_INTO_JSON_OBJECT(dest, status, status); + INSERT_INTO_JSON_OBJECT(dest, distributions, distributions); } -void GetOutputDistribution::Response::fromJson(rapidjson::Value& val) +void GetOutputDistribution::Response::fromJson(const rapidjson::Value& val) { GET_FROM_JSON_OBJECT(val, status, status); GET_FROM_JSON_OBJECT(val, distributions, distributions); diff --git a/src/rpc/daemon_messages.h b/src/rpc/daemon_messages.h index c0d9aed0a..bb5059cdc 100644 --- a/src/rpc/daemon_messages.h +++ b/src/rpc/daemon_messages.h @@ -28,6 +28,8 @@ #pragma once +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> #include <unordered_map> #include <vector> @@ -40,26 +42,25 @@ #define BEGIN_RPC_MESSAGE_CLASS(classname) \ class classname \ { \ - public: \ - static const char* const name; + public: #define BEGIN_RPC_MESSAGE_REQUEST \ - class Request : public Message \ + class Request final : public Message \ { \ public: \ Request() { } \ ~Request() { } \ - rapidjson::Value toJson(rapidjson::Document& doc) const; \ - void fromJson(rapidjson::Value& val); + void doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const override final; \ + void fromJson(const rapidjson::Value& val) override final; #define BEGIN_RPC_MESSAGE_RESPONSE \ - class Response : public Message \ + class Response final : public Message \ { \ public: \ Response() { } \ ~Response() { } \ - rapidjson::Value toJson(rapidjson::Document& doc) const; \ - void fromJson(rapidjson::Value& val); + void doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const override final; \ + void fromJson(const rapidjson::Value& val) override final; #define END_RPC_MESSAGE_REQUEST }; #define END_RPC_MESSAGE_RESPONSE }; diff --git a/src/rpc/message.cpp b/src/rpc/message.cpp index 158b58005..0d8983cb1 100644 --- a/src/rpc/message.cpp +++ b/src/rpc/message.cpp @@ -27,12 +27,10 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "message.h" + #include "daemon_rpc_version.h" #include "serialization/json_object.h" -#include "rapidjson/writer.h" -#include "rapidjson/stringbuffer.h" - namespace cryptonote { @@ -52,60 +50,33 @@ constexpr const char id_field[] = "id"; constexpr const char method_field[] = "method"; constexpr const char params_field[] = "params"; constexpr const char result_field[] = "result"; -} -rapidjson::Value Message::toJson(rapidjson::Document& doc) const +const rapidjson::Value& get_method_field(const rapidjson::Value& src) { - rapidjson::Value val(rapidjson::kObjectType); - - auto& al = doc.GetAllocator(); - - val.AddMember("status", rapidjson::StringRef(status.c_str()), al); - val.AddMember("error_details", rapidjson::StringRef(error_details.c_str()), al); - INSERT_INTO_JSON_OBJECT(val, doc, rpc_version, DAEMON_RPC_VERSION_ZMQ); - - return val; + const auto member = src.FindMember(method_field); + if (member == src.MemberEnd()) + throw cryptonote::json::MISSING_KEY{method_field}; + if (!member->value.IsString()) + throw cryptonote::json::WRONG_TYPE{"Expected string"}; + return member->value; } - -void Message::fromJson(rapidjson::Value& val) -{ - GET_FROM_JSON_OBJECT(val, status, status); - GET_FROM_JSON_OBJECT(val, error_details, error_details); - GET_FROM_JSON_OBJECT(val, rpc_version, rpc_version); } - -FullMessage::FullMessage(const std::string& request, Message* message) +void Message::toJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const { - doc.SetObject(); - - doc.AddMember(method_field, rapidjson::StringRef(request.c_str()), doc.GetAllocator()); - doc.AddMember(params_field, message->toJson(doc), doc.GetAllocator()); - - // required by JSON-RPC 2.0 spec - doc.AddMember("jsonrpc", rapidjson::Value("2.0"), doc.GetAllocator()); + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, status, status); + INSERT_INTO_JSON_OBJECT(dest, error_details, error_details); + INSERT_INTO_JSON_OBJECT(dest, rpc_version, DAEMON_RPC_VERSION_ZMQ); + doToJson(dest); + dest.EndObject(); } -FullMessage::FullMessage(Message* message) +void Message::fromJson(const rapidjson::Value& val) { - doc.SetObject(); - - // required by JSON-RPC 2.0 spec - doc.AddMember("jsonrpc", "2.0", doc.GetAllocator()); - - if (message->status == Message::STATUS_OK) - { - doc.AddMember(result_field, message->toJson(doc), doc.GetAllocator()); - } - else - { - cryptonote::rpc::error err; - - err.error_str = message->status; - err.message = message->error_details; - - INSERT_INTO_JSON_OBJECT(doc, doc, error, err); - } + GET_FROM_JSON_OBJECT(val, status, status); + GET_FROM_JSON_OBJECT(val, error_details, error_details); + GET_FROM_JSON_OBJECT(val, rpc_version, rpc_version); } FullMessage::FullMessage(const std::string& json_string, bool request) @@ -120,7 +91,7 @@ FullMessage::FullMessage(const std::string& json_string, bool request) if (request) { - OBJECT_HAS_MEMBER_OR_THROW(doc, method_field) + get_method_field(doc); // throws on errors OBJECT_HAS_MEMBER_OR_THROW(doc, params_field) } else @@ -132,30 +103,12 @@ FullMessage::FullMessage(const std::string& json_string, bool request) } } -std::string FullMessage::getJson() -{ - - if (!doc.HasMember(id_field)) - { - doc.AddMember(id_field, rapidjson::Value("unused"), doc.GetAllocator()); - } - - rapidjson::StringBuffer buf; - - rapidjson::Writer<rapidjson::StringBuffer> writer(buf); - - doc.Accept(writer); - - return std::string(buf.GetString(), buf.GetSize()); -} - std::string FullMessage::getRequestType() const { - OBJECT_HAS_MEMBER_OR_THROW(doc, method_field) - return doc[method_field].GetString(); + return get_method_field(doc).GetString(); } -rapidjson::Value& FullMessage::getMessage() +const rapidjson::Value& FullMessage::getMessage() const { if (doc.HasMember(params_field)) { @@ -174,30 +127,15 @@ rapidjson::Value& FullMessage::getMessage() rapidjson::Value FullMessage::getMessageCopy() { - rapidjson::Value& val = getMessage(); - - return rapidjson::Value(val, doc.GetAllocator()); + return rapidjson::Value(getMessage(), doc.GetAllocator()); } -rapidjson::Value& FullMessage::getID() +const rapidjson::Value& FullMessage::getID() const { OBJECT_HAS_MEMBER_OR_THROW(doc, id_field) return doc[id_field]; } -void FullMessage::setID(rapidjson::Value& id) -{ - auto itr = doc.FindMember(id_field); - if (itr != doc.MemberEnd()) - { - itr->value = id; - } - else - { - doc.AddMember(id_field, id, doc.GetAllocator()); - } -} - cryptonote::rpc::error FullMessage::getError() { cryptonote::rpc::error err; @@ -211,82 +149,89 @@ cryptonote::rpc::error FullMessage::getError() return err; } -FullMessage FullMessage::requestMessage(const std::string& request, Message* message) +epee::byte_slice FullMessage::getRequest(const std::string& request, const Message& message, const unsigned id) { - return FullMessage(request, message); -} + rapidjson::StringBuffer buffer; + { + rapidjson::Writer<rapidjson::StringBuffer> dest{buffer}; -FullMessage FullMessage::requestMessage(const std::string& request, Message* message, rapidjson::Value& id) -{ - auto mes = requestMessage(request, message); - mes.setID(id); - return mes; -} + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, jsonrpc, (boost::string_ref{"2.0", 3})); -FullMessage FullMessage::responseMessage(Message* message) -{ - return FullMessage(message); -} + dest.Key(id_field); + json::toJsonValue(dest, id); -FullMessage FullMessage::responseMessage(Message* message, rapidjson::Value& id) -{ - auto mes = responseMessage(message); - mes.setID(id); - return mes; + dest.Key(method_field); + json::toJsonValue(dest, request); + + dest.Key(params_field); + message.toJson(dest); + + dest.EndObject(); + + if (!dest.IsComplete()) + throw std::logic_error{"Invalid JSON tree generated"}; + } + return epee::byte_slice{{buffer.GetString(), buffer.GetSize()}}; } -FullMessage* FullMessage::timeoutMessage() + +epee::byte_slice FullMessage::getResponse(const Message& message, const rapidjson::Value& id) { - auto *full_message = new FullMessage(); + rapidjson::StringBuffer buffer; + { + rapidjson::Writer<rapidjson::StringBuffer> dest{buffer}; - auto& doc = full_message->doc; - auto& al = full_message->doc.GetAllocator(); + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, jsonrpc, (boost::string_ref{"2.0", 3})); - doc.SetObject(); + dest.Key(id_field); + json::toJsonValue(dest, id); - // required by JSON-RPC 2.0 spec - doc.AddMember("jsonrpc", "2.0", al); + if (message.status == Message::STATUS_OK) + { + dest.Key(result_field); + message.toJson(dest); + } + else + { + cryptonote::rpc::error err; - cryptonote::rpc::error err; + err.error_str = message.status; + err.message = message.error_details; - err.error_str = "RPC request timed out."; - INSERT_INTO_JSON_OBJECT(doc, doc, err, err); + INSERT_INTO_JSON_OBJECT(dest, error, err); + } + dest.EndObject(); - return full_message; + if (!dest.IsComplete()) + throw std::logic_error{"Invalid JSON tree generated"}; + } + return epee::byte_slice{{buffer.GetString(), buffer.GetSize()}}; } // convenience functions for bad input -std::string BAD_REQUEST(const std::string& request) +epee::byte_slice BAD_REQUEST(const std::string& request) { - Message fail; - fail.status = Message::STATUS_BAD_REQUEST; - fail.error_details = std::string("\"") + request + "\" is not a valid request."; - - FullMessage fail_response = FullMessage::responseMessage(&fail); - - return fail_response.getJson(); + rapidjson::Value invalid; + return BAD_REQUEST(request, invalid); } -std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id) +epee::byte_slice BAD_REQUEST(const std::string& request, const rapidjson::Value& id) { Message fail; fail.status = Message::STATUS_BAD_REQUEST; fail.error_details = std::string("\"") + request + "\" is not a valid request."; - - FullMessage fail_response = FullMessage::responseMessage(&fail, id); - - return fail_response.getJson(); + return FullMessage::getResponse(fail, id); } -std::string BAD_JSON(const std::string& error_details) +epee::byte_slice BAD_JSON(const std::string& error_details) { + rapidjson::Value invalid; Message fail; fail.status = Message::STATUS_BAD_JSON; fail.error_details = error_details; - - FullMessage fail_response = FullMessage::responseMessage(&fail); - - return fail_response.getJson(); + return FullMessage::getResponse(fail, invalid); } diff --git a/src/rpc/message.h b/src/rpc/message.h index 2b7b61ab3..8156e232c 100644 --- a/src/rpc/message.h +++ b/src/rpc/message.h @@ -28,27 +28,13 @@ #pragma once -#include "rapidjson/document.h" -#include "rpc/message_data_structs.h" +#include <rapidjson/document.h> +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> #include <string> -/* I normally hate using macros, but in this case it would be untenably - * verbose to not use a macro. This macro saves the trouble of explicitly - * writing the below if block for every single RPC call. - */ -#define REQ_RESP_TYPES_MACRO( runtime_str, type, reqjson, resp_message_ptr, handler) \ - \ - if (runtime_str == type::name) \ - { \ - type::Request reqvar; \ - type::Response *respvar = new type::Response(); \ - \ - reqvar.fromJson(reqjson); \ - \ - handler(reqvar, *respvar); \ - \ - resp_message_ptr = respvar; \ - } +#include "byte_slice.h" +#include "rpc/message_data_structs.h" namespace cryptonote { @@ -58,6 +44,9 @@ namespace rpc class Message { + virtual void doToJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const + {} + public: static const char* STATUS_OK; static const char* STATUS_RETRY; @@ -69,9 +58,9 @@ namespace rpc virtual ~Message() { } - virtual rapidjson::Value toJson(rapidjson::Document& doc) const; + void toJson(rapidjson::Writer<rapidjson::StringBuffer>& dest) const; - virtual void fromJson(rapidjson::Value& val); + virtual void fromJson(const rapidjson::Value& val); std::string status; std::string error_details; @@ -87,27 +76,18 @@ namespace rpc FullMessage(const std::string& json_string, bool request=false); - std::string getJson(); - std::string getRequestType() const; - rapidjson::Value& getMessage(); + const rapidjson::Value& getMessage() const; rapidjson::Value getMessageCopy(); - rapidjson::Value& getID(); - - void setID(rapidjson::Value& id); + const rapidjson::Value& getID() const; cryptonote::rpc::error getError(); - static FullMessage requestMessage(const std::string& request, Message* message); - static FullMessage requestMessage(const std::string& request, Message* message, rapidjson::Value& id); - - static FullMessage responseMessage(Message* message); - static FullMessage responseMessage(Message* message, rapidjson::Value& id); - - static FullMessage* timeoutMessage(); + static epee::byte_slice getRequest(const std::string& request, const Message& message, unsigned id); + static epee::byte_slice getResponse(const Message& message, const rapidjson::Value& id); private: FullMessage() = default; @@ -120,10 +100,10 @@ namespace rpc // convenience functions for bad input - std::string BAD_REQUEST(const std::string& request); - std::string BAD_REQUEST(const std::string& request, rapidjson::Value& id); + epee::byte_slice BAD_REQUEST(const std::string& request); + epee::byte_slice BAD_REQUEST(const std::string& request, const rapidjson::Value& id); - std::string BAD_JSON(const std::string& error_details); + epee::byte_slice BAD_JSON(const std::string& error_details); } // namespace rpc diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 0eaa0ef0e..9153e76ea 100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019, The Monero Project +// Copyright (c) 2014-2020, The Monero Project // // All rights reserved. // @@ -51,7 +51,7 @@ namespace cryptonote const std::vector<std::string> ssl_allowed_fingerprints = command_line::get_arg(vm, arg.rpc_ssl_allowed_fingerprints); std::vector<std::vector<uint8_t>> allowed_fingerprints{ ssl_allowed_fingerprints.size() }; - std::transform(ssl_allowed_fingerprints.begin(), ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + std::transform(ssl_allowed_fingerprints.begin(), ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex_locale::to_vector); for (const auto &fpr: allowed_fingerprints) { if (fpr.size() != SSL_FINGERPRINT_SIZE) @@ -103,6 +103,7 @@ namespace cryptonote , rpc_ssl_allowed_fingerprints({"rpc-ssl-allowed-fingerprints", rpc_args::tr("List of certificate fingerprints to allow")}) , rpc_ssl_allow_chained({"rpc-ssl-allow-chained", rpc_args::tr("Allow user (via --rpc-ssl-certificates) chain certificates"), false}) , rpc_ssl_allow_any_cert({"rpc-ssl-allow-any-cert", rpc_args::tr("Allow any peer certificate"), false}) + , disable_rpc_ban({"disable-rpc-ban", rpc_args::tr("Do not ban hosts on RPC errors"), false, false}) {} const char* rpc_args::tr(const char* str) { return i18n_translate(str, "cryptonote::rpc_args"); } @@ -123,6 +124,7 @@ namespace cryptonote command_line::add_arg(desc, arg.rpc_ssl_ca_certificates); command_line::add_arg(desc, arg.rpc_ssl_allowed_fingerprints); command_line::add_arg(desc, arg.rpc_ssl_allow_chained); + command_line::add_arg(desc, arg.disable_rpc_ban); if (any_cert_option) command_line::add_arg(desc, arg.rpc_ssl_allow_any_cert); } @@ -136,6 +138,7 @@ namespace cryptonote 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_ignore_ipv4); + config.disable_rpc_ban = command_line::get_arg(vm, arg.disable_rpc_ban); 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 bdb9c70d5..ac6eb2744 100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h @@ -65,6 +65,7 @@ namespace cryptonote const command_line::arg_descriptor<std::vector<std::string>> rpc_ssl_allowed_fingerprints; const command_line::arg_descriptor<bool> rpc_ssl_allow_chained; const command_line::arg_descriptor<bool> rpc_ssl_allow_any_cert; + const command_line::arg_descriptor<bool> disable_rpc_ban; }; // `allow_any_cert` bool toggles `--rpc-ssl-allow-any-cert` configuration @@ -85,5 +86,6 @@ namespace cryptonote std::vector<std::string> access_control_origins; boost::optional<tools::login> login; // currently `boost::none` if unspecified by user epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + bool disable_rpc_ban = false; }; } diff --git a/src/rpc/rpc_handler.h b/src/rpc/rpc_handler.h index b81983d28..9a1c3fc12 100644 --- a/src/rpc/rpc_handler.h +++ b/src/rpc/rpc_handler.h @@ -32,6 +32,7 @@ #include <cstdint> #include <string> #include <vector> +#include "byte_slice.h" #include "crypto/hash.h" namespace cryptonote @@ -54,7 +55,7 @@ class RpcHandler RpcHandler() { } virtual ~RpcHandler() { } - virtual std::string handle(const std::string& request) = 0; + virtual epee::byte_slice handle(const std::string& request) = 0; static boost::optional<output_distribution_data> get_output_distribution(const std::function<bool(uint64_t, uint64_t, uint64_t, uint64_t&, std::vector<uint64_t>&, uint64_t&)> &f, uint64_t amount, uint64_t from_height, uint64_t to_height, const std::function<crypto::hash(uint64_t)> &get_hash, bool cumulative, uint64_t blockchain_height); diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index b363c27b2..2b9c19f57 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -54,8 +54,6 @@ #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(): @@ -147,7 +145,7 @@ namespace cryptonote return false; char data[33]; memcpy(data, &client, 32); - data[32] = RPC_PAYMENT_NONCE_TAIL; + data[32] = config::HASH_KEY_RPC_PAYMENT_NONCE; crypto::hash hash; cn_fast_hash(data, sizeof(data), hash); extra_nonce = cryptonote::blobdata((const char*)&hash, 4); diff --git a/src/rpc/rpc_payment_signature.cpp b/src/rpc/rpc_payment_signature.cpp index 2e8b54b4f..559f3a1e9 100644 --- a/src/rpc/rpc_payment_signature.cpp +++ b/src/rpc/rpc_payment_signature.cpp @@ -102,6 +102,11 @@ namespace cryptonote MDEBUG("Timestamp is in the future"); return false; } + if (ts < now - TIMESTAMP_LEEWAY) + { + MDEBUG("Timestamp is too old"); + return false; + } return true; } } diff --git a/src/rpc/zmq_server.cpp b/src/rpc/zmq_server.cpp index 1ee55673e..91e4751d1 100644 --- a/src/rpc/zmq_server.cpp +++ b/src/rpc/zmq_server.cpp @@ -28,10 +28,13 @@ #include "zmq_server.h" +#include <boost/utility/string_ref.hpp> #include <chrono> #include <cstdint> #include <system_error> +#include "byte_slice.h" + namespace cryptonote { @@ -73,10 +76,11 @@ void ZmqServer::serve() { const std::string message = MONERO_UNWRAP(net::zmq::receive(socket.get())); MDEBUG("Received RPC request: \"" << message << "\""); - const std::string& response = handler.handle(message); + epee::byte_slice response = handler.handle(message); - MONERO_UNWRAP(net::zmq::send(epee::strspan<std::uint8_t>(response), socket.get())); - MDEBUG("Sent RPC reply: \"" << response << "\""); + const boost::string_ref response_view{reinterpret_cast<const char*>(response.data()), response.size()}; + MDEBUG("Sending RPC reply: \"" << response_view << "\""); + MONERO_UNWRAP(net::zmq::send(std::move(response), socket.get())); } } catch (const std::system_error& e) diff --git a/src/serialization/CMakeLists.txt b/src/serialization/CMakeLists.txt index 28b775a37..0b231f156 100644 --- a/src/serialization/CMakeLists.txt +++ b/src/serialization/CMakeLists.txt @@ -44,6 +44,7 @@ target_link_libraries(serialization LINK_PRIVATE cryptonote_core cryptonote_protocol + epee ${Boost_CHRONO_LIBRARY} ${Boost_REGEX_LIBRARY} ${Boost_SYSTEM_LIBRARY} diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index ea67209dc..f20fd181a 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2019, The Monero Project +// Copyright (c) 2016-2020, The Monero Project // // All rights reserved. // @@ -32,7 +32,11 @@ #include <boost/variant/apply_visitor.hpp> #include <limits> #include <type_traits> -#include "string_tools.h" + +// drop macro from windows.h +#ifdef GetObject + #undef GetObject +#endif namespace cryptonote { @@ -109,9 +113,27 @@ namespace } } -void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val) +void read_hex(const rapidjson::Value& val, epee::span<std::uint8_t> dest) +{ + if (!val.IsString()) + { + throw WRONG_TYPE("string"); + } + + if (!epee::from_hex::to_buffer(dest, {val.GetString(), val.Size()})) + { + throw BAD_INPUT(); + } +} + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rapidjson::Value& src) +{ + src.Accept(dest); +} + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const boost::string_ref i) { - val = rapidjson::Value(i.c_str(), doc.GetAllocator()); + dest.String(i.data(), i.size()); } void fromJsonValue(const rapidjson::Value& val, std::string& str) @@ -124,9 +146,9 @@ void fromJsonValue(const rapidjson::Value& val, std::string& str) str = val.GetString(); } -void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, bool i) { - val.SetBool(i); + dest.Bool(i); } void fromJsonValue(const rapidjson::Value& val, bool& b) @@ -163,9 +185,9 @@ void fromJsonValue(const rapidjson::Value& val, short& i) to_int(val, i); } -void toJsonValue(rapidjson::Document& doc, const unsigned int i, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const unsigned int i) { - val = rapidjson::Value(i); + dest.Uint(i); } void fromJsonValue(const rapidjson::Value& val, unsigned int& i) @@ -173,9 +195,9 @@ void fromJsonValue(const rapidjson::Value& val, unsigned int& i) to_uint(val, i); } -void toJsonValue(rapidjson::Document& doc, const int i, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const int i) { - val = rapidjson::Value(i); + dest.Int(i); } void fromJsonValue(const rapidjson::Value& val, int& i) @@ -183,10 +205,10 @@ void fromJsonValue(const rapidjson::Value& val, int& i) to_int(val, i); } -void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const unsigned long long i) { - static_assert(!precision_loss<unsigned long long, std::uint64_t>(), "precision loss"); - val = rapidjson::Value(std::uint64_t(i)); + static_assert(!precision_loss<unsigned long long, std::uint64_t>(), "bad uint64 conversion"); + dest.Uint64(i); } void fromJsonValue(const rapidjson::Value& val, unsigned long long& i) @@ -194,10 +216,10 @@ void fromJsonValue(const rapidjson::Value& val, unsigned long long& i) to_uint64(val, i); } -void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const long long i) { - static_assert(!precision_loss<long long, std::int64_t>(), "precision loss"); - val = rapidjson::Value(std::int64_t(i)); + static_assert(!precision_loss<long long, std::int64_t>(), "bad int64 conversion"); + dest.Int64(i); } void fromJsonValue(const rapidjson::Value& val, long long& i) @@ -215,17 +237,19 @@ void fromJsonValue(const rapidjson::Value& val, long& i) to_int64(val, i); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::transaction& tx) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, version, tx.version); - INSERT_INTO_JSON_OBJECT(val, doc, unlock_time, tx.unlock_time); - INSERT_INTO_JSON_OBJECT(val, doc, inputs, tx.vin); - INSERT_INTO_JSON_OBJECT(val, doc, outputs, tx.vout); - INSERT_INTO_JSON_OBJECT(val, doc, extra, tx.extra); - INSERT_INTO_JSON_OBJECT(val, doc, signatures, tx.signatures); - INSERT_INTO_JSON_OBJECT(val, doc, ringct, tx.rct_signatures); + INSERT_INTO_JSON_OBJECT(dest, version, tx.version); + INSERT_INTO_JSON_OBJECT(dest, unlock_time, tx.unlock_time); + INSERT_INTO_JSON_OBJECT(dest, inputs, tx.vin); + INSERT_INTO_JSON_OBJECT(dest, outputs, tx.vout); + INSERT_INTO_JSON_OBJECT(dest, extra, tx.extra); + INSERT_INTO_JSON_OBJECT(dest, signatures, tx.signatures); + INSERT_INTO_JSON_OBJECT(dest, ringct, tx.rct_signatures); + + dest.EndObject(); } @@ -245,17 +269,19 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx) GET_FROM_JSON_OBJECT(val, tx.rct_signatures, ringct); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::block& b) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, major_version, b.major_version); + INSERT_INTO_JSON_OBJECT(dest, minor_version, b.minor_version); + INSERT_INTO_JSON_OBJECT(dest, timestamp, b.timestamp); + INSERT_INTO_JSON_OBJECT(dest, prev_id, b.prev_id); + INSERT_INTO_JSON_OBJECT(dest, nonce, b.nonce); + INSERT_INTO_JSON_OBJECT(dest, miner_tx, b.miner_tx); + INSERT_INTO_JSON_OBJECT(dest, tx_hashes, b.tx_hashes); - INSERT_INTO_JSON_OBJECT(val, doc, major_version, b.major_version); - INSERT_INTO_JSON_OBJECT(val, doc, minor_version, b.minor_version); - INSERT_INTO_JSON_OBJECT(val, doc, timestamp, b.timestamp); - INSERT_INTO_JSON_OBJECT(val, doc, prev_id, b.prev_id); - INSERT_INTO_JSON_OBJECT(val, doc, nonce, b.nonce); - INSERT_INTO_JSON_OBJECT(val, doc, miner_tx, b.miner_tx); - INSERT_INTO_JSON_OBJECT(val, doc, tx_hashes, b.tx_hashes); + dest.EndObject(); } @@ -275,35 +301,34 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b) GET_FROM_JSON_OBJECT(val, b.tx_hashes, tx_hashes); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_v& txin) { - val.SetObject(); - + dest.StartObject(); struct add_input { using result_type = void; - rapidjson::Document& doc; - rapidjson::Value& val; + rapidjson::Writer<rapidjson::StringBuffer>& dest; void operator()(cryptonote::txin_to_key const& input) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_key, input); + INSERT_INTO_JSON_OBJECT(dest, to_key, input); } void operator()(cryptonote::txin_gen const& input) const { - INSERT_INTO_JSON_OBJECT(val, doc, gen, input); + INSERT_INTO_JSON_OBJECT(dest, gen, input); } void operator()(cryptonote::txin_to_script const& input) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_script, input); + INSERT_INTO_JSON_OBJECT(dest, to_script, input); } void operator()(cryptonote::txin_to_scripthash const& input) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_scripthash, input); + INSERT_INTO_JSON_OBJECT(dest, to_scripthash, input); } }; - boost::apply_visitor(add_input{doc, val}, txin); + boost::apply_visitor(add_input{dest}, txin); + dest.EndObject(); } @@ -348,13 +373,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin) } } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_gen& txin) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, height, txin.height); -} + INSERT_INTO_JSON_OBJECT(dest, height, txin.height); + dest.EndObject(); +} void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin) { @@ -366,13 +392,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin) GET_FROM_JSON_OBJECT(val, txin.height, height); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_script& txin) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(dest, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(dest, sigset, txin.sigset); - INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); - INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); - INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); + dest.EndObject(); } @@ -388,14 +416,17 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val) + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_scripthash& txin) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, prev, txin.prev); - INSERT_INTO_JSON_OBJECT(val, doc, prevout, txin.prevout); - INSERT_INTO_JSON_OBJECT(val, doc, script, txin.script); - INSERT_INTO_JSON_OBJECT(val, doc, sigset, txin.sigset); + INSERT_INTO_JSON_OBJECT(dest, prev, txin.prev); + INSERT_INTO_JSON_OBJECT(dest, prevout, txin.prevout); + INSERT_INTO_JSON_OBJECT(dest, script, txin.script); + INSERT_INTO_JSON_OBJECT(dest, sigset, txin.sigset); + + dest.EndObject(); } @@ -412,15 +443,16 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& GET_FROM_JSON_OBJECT(val, txin.sigset, sigset); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_key& txin) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, amount, txin.amount); - INSERT_INTO_JSON_OBJECT(val, doc, key_offsets, txin.key_offsets); - INSERT_INTO_JSON_OBJECT(val, doc, key_image, txin.k_image); -} + INSERT_INTO_JSON_OBJECT(dest, amount, txin.amount); + INSERT_INTO_JSON_OBJECT(dest, key_offsets, txin.key_offsets); + INSERT_INTO_JSON_OBJECT(dest, key_image, txin.k_image); + dest.EndObject(); +} void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin) { @@ -434,14 +466,16 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin) GET_FROM_JSON_OBJECT(val, txin.k_image, key_image); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val) + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_script& txout) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, keys, txout.keys); - INSERT_INTO_JSON_OBJECT(val, doc, script, txout.script); -} + INSERT_INTO_JSON_OBJECT(dest, keys, txout.keys); + INSERT_INTO_JSON_OBJECT(dest, script, txout.script); + dest.EndObject(); +} void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout) { @@ -454,13 +488,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txo GET_FROM_JSON_OBJECT(val, txout.script, script); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val) + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_scripthash& txout) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, hash, txout.hash); -} + INSERT_INTO_JSON_OBJECT(dest, hash, txout.hash); + dest.EndObject(); +} void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout) { @@ -472,13 +508,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& GET_FROM_JSON_OBJECT(val, txout.hash, hash); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val) + +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_key& txout) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, key, txout.key); -} + INSERT_INTO_JSON_OBJECT(dest, key, txout.key); + dest.EndObject(); +} void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) { @@ -490,33 +528,32 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout) GET_FROM_JSON_OBJECT(val, txout.key, key); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::tx_out& txout) { - val.SetObject(); - - INSERT_INTO_JSON_OBJECT(val, doc, amount, txout.amount); + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, amount, txout.amount); struct add_output { using result_type = void; - rapidjson::Document& doc; - rapidjson::Value& val; + rapidjson::Writer<rapidjson::StringBuffer>& dest; void operator()(cryptonote::txout_to_key const& output) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_key, output); + INSERT_INTO_JSON_OBJECT(dest, to_key, output); } void operator()(cryptonote::txout_to_script const& output) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_script, output); + INSERT_INTO_JSON_OBJECT(dest, to_script, output); } void operator()(cryptonote::txout_to_scripthash const& output) const { - INSERT_INTO_JSON_OBJECT(val, doc, to_scripthash, output); + INSERT_INTO_JSON_OBJECT(dest, to_scripthash, output); } }; - boost::apply_visitor(add_output{doc, val}, txout.target); + boost::apply_visitor(add_output{dest}, txout.target); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) @@ -559,37 +596,39 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout) } } -void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::connection_info& info) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, incoming, info.incoming); + INSERT_INTO_JSON_OBJECT(dest, localhost, info.localhost); + INSERT_INTO_JSON_OBJECT(dest, local_ip, info.local_ip); + INSERT_INTO_JSON_OBJECT(dest, address_type, info.address_type); - INSERT_INTO_JSON_OBJECT(val, doc, incoming, info.incoming); - INSERT_INTO_JSON_OBJECT(val, doc, localhost, info.localhost); - INSERT_INTO_JSON_OBJECT(val, doc, local_ip, info.local_ip); - INSERT_INTO_JSON_OBJECT(val, doc, address_type, info.address_type); + INSERT_INTO_JSON_OBJECT(dest, ip, info.ip); + INSERT_INTO_JSON_OBJECT(dest, port, info.port); + INSERT_INTO_JSON_OBJECT(dest, rpc_port, info.rpc_port); + INSERT_INTO_JSON_OBJECT(dest, rpc_credits_per_hash, info.rpc_credits_per_hash); - 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(dest, peer_id, info.peer_id); - INSERT_INTO_JSON_OBJECT(val, doc, peer_id, info.peer_id); + INSERT_INTO_JSON_OBJECT(dest, recv_count, info.recv_count); + INSERT_INTO_JSON_OBJECT(dest, recv_idle_time, info.recv_idle_time); - INSERT_INTO_JSON_OBJECT(val, doc, recv_count, info.recv_count); - INSERT_INTO_JSON_OBJECT(val, doc, recv_idle_time, info.recv_idle_time); + INSERT_INTO_JSON_OBJECT(dest, send_count, info.send_count); + INSERT_INTO_JSON_OBJECT(dest, send_idle_time, info.send_idle_time); - INSERT_INTO_JSON_OBJECT(val, doc, send_count, info.send_count); - INSERT_INTO_JSON_OBJECT(val, doc, send_idle_time, info.send_idle_time); + INSERT_INTO_JSON_OBJECT(dest, state, info.state); - INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); + INSERT_INTO_JSON_OBJECT(dest, live_time, info.live_time); - INSERT_INTO_JSON_OBJECT(val, doc, live_time, info.live_time); + INSERT_INTO_JSON_OBJECT(dest, avg_download, info.avg_download); + INSERT_INTO_JSON_OBJECT(dest, current_download, info.current_download); - INSERT_INTO_JSON_OBJECT(val, doc, avg_download, info.avg_download); - INSERT_INTO_JSON_OBJECT(val, doc, current_download, info.current_download); + INSERT_INTO_JSON_OBJECT(dest, avg_upload, info.avg_upload); + INSERT_INTO_JSON_OBJECT(dest, current_upload, info.current_upload); - INSERT_INTO_JSON_OBJECT(val, doc, avg_upload, info.avg_upload); - INSERT_INTO_JSON_OBJECT(val, doc, current_upload, info.current_upload); + dest.EndObject(); } @@ -629,12 +668,14 @@ 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) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::tx_blob_entry& tx) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, blob, tx.blob); - INSERT_INTO_JSON_OBJECT(val, doc, prunable_hash, tx.prunable_hash); + INSERT_INTO_JSON_OBJECT(dest, blob, tx.blob); + INSERT_INTO_JSON_OBJECT(dest, prunable_hash, tx.prunable_hash); + + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_blob_entry& tx) @@ -648,12 +689,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_blob_entry& tx) GET_FROM_JSON_OBJECT(val, tx.prunable_hash, prunable_hash); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::block_complete_entry& blk, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::block_complete_entry& blk) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, block, blk.block); + INSERT_INTO_JSON_OBJECT(dest, transactions, blk.txs); - INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); - INSERT_INTO_JSON_OBJECT(val, doc, transactions, blk.txs); + dest.EndObject(); } @@ -668,12 +711,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry GET_FROM_JSON_OBJECT(val, blk.txs, transactions); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::block_with_transactions& blk) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, block, blk.block); - INSERT_INTO_JSON_OBJECT(val, doc, transactions, blk.transactions); + INSERT_INTO_JSON_OBJECT(dest, block, blk.block); + INSERT_INTO_JSON_OBJECT(dest, transactions, blk.transactions); + + dest.EndObject(); } @@ -688,13 +733,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_tran GET_FROM_JSON_OBJECT(val, blk.transactions, transactions); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::transaction_info& tx_info) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, height, tx_info.height); + INSERT_INTO_JSON_OBJECT(dest, in_pool, tx_info.in_pool); + INSERT_INTO_JSON_OBJECT(dest, transaction, tx_info.transaction); - INSERT_INTO_JSON_OBJECT(val, doc, height, tx_info.height); - INSERT_INTO_JSON_OBJECT(val, doc, in_pool, tx_info.in_pool); - INSERT_INTO_JSON_OBJECT(val, doc, transaction, tx_info.transaction); + dest.EndObject(); } @@ -710,12 +757,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_inf GET_FROM_JSON_OBJECT(val, tx_info.transaction, transaction); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_key_and_amount_index& out) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, amount_index, out.amount_index); - INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); + INSERT_INTO_JSON_OBJECT(dest, amount_index, out.amount_index); + INSERT_INTO_JSON_OBJECT(dest, key, out.key); + + dest.EndObject(); } @@ -730,12 +779,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_ GET_FROM_JSON_OBJECT(val, out.key, key); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::amount_with_random_outputs& out) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, amount, out.amount); + INSERT_INTO_JSON_OBJECT(dest, outputs, out.outputs); - INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); - INSERT_INTO_JSON_OBJECT(val, doc, outputs, out.outputs); + dest.EndObject(); } @@ -750,17 +801,19 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_ran GET_FROM_JSON_OBJECT(val, out.outputs, outputs); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::peer& peer) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, id, peer.id); + INSERT_INTO_JSON_OBJECT(dest, ip, peer.ip); + INSERT_INTO_JSON_OBJECT(dest, port, peer.port); + INSERT_INTO_JSON_OBJECT(dest, rpc_port, peer.rpc_port); + INSERT_INTO_JSON_OBJECT(dest, rpc_credits_per_hash, peer.rpc_credits_per_hash); + INSERT_INTO_JSON_OBJECT(dest, last_seen, peer.last_seen); + INSERT_INTO_JSON_OBJECT(dest, pruning_seed, peer.pruning_seed); - INSERT_INTO_JSON_OBJECT(val, doc, id, peer.id); - 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); + dest.EndObject(); } @@ -780,25 +833,27 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer) GET_FROM_JSON_OBJECT(val, peer.pruning_seed, pruning_seed); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::tx_in_pool& tx) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, tx, tx.tx); - INSERT_INTO_JSON_OBJECT(val, doc, tx_hash, tx.tx_hash); - INSERT_INTO_JSON_OBJECT(val, doc, blob_size, tx.blob_size); - INSERT_INTO_JSON_OBJECT(val, doc, weight, tx.weight); - INSERT_INTO_JSON_OBJECT(val, doc, fee, tx.fee); - INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_hash, tx.max_used_block_hash); - INSERT_INTO_JSON_OBJECT(val, doc, max_used_block_height, tx.max_used_block_height); - INSERT_INTO_JSON_OBJECT(val, doc, kept_by_block, tx.kept_by_block); - INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_hash, tx.last_failed_block_hash); - INSERT_INTO_JSON_OBJECT(val, doc, last_failed_block_height, tx.last_failed_block_height); - INSERT_INTO_JSON_OBJECT(val, doc, receive_time, tx.receive_time); - INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time); - INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed); - INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay); - INSERT_INTO_JSON_OBJECT(val, doc, double_spend_seen, tx.double_spend_seen); + INSERT_INTO_JSON_OBJECT(dest, tx, tx.tx); + INSERT_INTO_JSON_OBJECT(dest, tx_hash, tx.tx_hash); + INSERT_INTO_JSON_OBJECT(dest, blob_size, tx.blob_size); + INSERT_INTO_JSON_OBJECT(dest, weight, tx.weight); + INSERT_INTO_JSON_OBJECT(dest, fee, tx.fee); + INSERT_INTO_JSON_OBJECT(dest, max_used_block_hash, tx.max_used_block_hash); + INSERT_INTO_JSON_OBJECT(dest, max_used_block_height, tx.max_used_block_height); + INSERT_INTO_JSON_OBJECT(dest, kept_by_block, tx.kept_by_block); + INSERT_INTO_JSON_OBJECT(dest, last_failed_block_hash, tx.last_failed_block_hash); + INSERT_INTO_JSON_OBJECT(dest, last_failed_block_height, tx.last_failed_block_height); + INSERT_INTO_JSON_OBJECT(dest, receive_time, tx.receive_time); + INSERT_INTO_JSON_OBJECT(dest, last_relayed_time, tx.last_relayed_time); + INSERT_INTO_JSON_OBJECT(dest, relayed, tx.relayed); + INSERT_INTO_JSON_OBJECT(dest, do_not_relay, tx.do_not_relay); + INSERT_INTO_JSON_OBJECT(dest, double_spend_seen, tx.double_spend_seen); + + dest.EndObject(); } @@ -825,18 +880,20 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx) GET_FROM_JSON_OBJECT(val, tx.double_spend_seen, double_spend_seen); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::hard_fork_info& info) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, version, info.version); + INSERT_INTO_JSON_OBJECT(dest, enabled, info.enabled); + INSERT_INTO_JSON_OBJECT(dest, window, info.window); + INSERT_INTO_JSON_OBJECT(dest, votes, info.votes); + INSERT_INTO_JSON_OBJECT(dest, threshold, info.threshold); + INSERT_INTO_JSON_OBJECT(dest, voting, info.voting); + INSERT_INTO_JSON_OBJECT(dest, state, info.state); + INSERT_INTO_JSON_OBJECT(dest, earliest_height, info.earliest_height); - INSERT_INTO_JSON_OBJECT(val, doc, version, info.version); - INSERT_INTO_JSON_OBJECT(val, doc, enabled, info.enabled); - INSERT_INTO_JSON_OBJECT(val, doc, window, info.window); - INSERT_INTO_JSON_OBJECT(val, doc, votes, info.votes); - INSERT_INTO_JSON_OBJECT(val, doc, threshold, info.threshold); - INSERT_INTO_JSON_OBJECT(val, doc, voting, info.voting); - INSERT_INTO_JSON_OBJECT(val, doc, state, info.state); - INSERT_INTO_JSON_OBJECT(val, doc, earliest_height, info.earliest_height); + dest.EndObject(); } @@ -857,14 +914,16 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& GET_FROM_JSON_OBJECT(val, info.earliest_height, earliest_height); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_amount_count& out) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); - INSERT_INTO_JSON_OBJECT(val, doc, total_count, out.total_count); - INSERT_INTO_JSON_OBJECT(val, doc, unlocked_count, out.unlocked_count); - INSERT_INTO_JSON_OBJECT(val, doc, recent_count, out.recent_count); + INSERT_INTO_JSON_OBJECT(dest, amount, out.amount); + INSERT_INTO_JSON_OBJECT(dest, total_count, out.total_count); + INSERT_INTO_JSON_OBJECT(dest, unlocked_count, out.unlocked_count); + INSERT_INTO_JSON_OBJECT(dest, recent_count, out.recent_count); + + dest.EndObject(); } @@ -881,12 +940,14 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_c GET_FROM_JSON_OBJECT(val, out.recent_count, recent_count); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_amount_and_index& out) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, amount, out.amount); + INSERT_INTO_JSON_OBJECT(dest, index, out.index); - INSERT_INTO_JSON_OBJECT(val, doc, amount, out.amount); - INSERT_INTO_JSON_OBJECT(val, doc, index, out.index); + dest.EndObject(); } @@ -901,13 +962,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_a GET_FROM_JSON_OBJECT(val, out.index, index); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_key_mask_unlocked& out) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, key, out.key); - INSERT_INTO_JSON_OBJECT(val, doc, mask, out.mask); - INSERT_INTO_JSON_OBJECT(val, doc, unlocked, out.unlocked); + INSERT_INTO_JSON_OBJECT(dest, key, out.key); + INSERT_INTO_JSON_OBJECT(dest, mask, out.mask); + INSERT_INTO_JSON_OBJECT(dest, unlocked, out.unlocked); + + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out) @@ -922,13 +985,15 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask GET_FROM_JSON_OBJECT(val, out.unlocked, unlocked); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::error& err) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, code, err.code); + INSERT_INTO_JSON_OBJECT(dest, error_str, err.error_str); + INSERT_INTO_JSON_OBJECT(dest, message, err.message); - INSERT_INTO_JSON_OBJECT(val, doc, code, err.code); - INSERT_INTO_JSON_OBJECT(val, doc, error_str, err.error_str); - INSERT_INTO_JSON_OBJECT(val, doc, message, err.message); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error) @@ -943,20 +1008,22 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error) GET_FROM_JSON_OBJECT(val, error.message, message); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::BlockHeaderResponse& response) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, major_version, response.major_version); + INSERT_INTO_JSON_OBJECT(dest, minor_version, response.minor_version); + INSERT_INTO_JSON_OBJECT(dest, timestamp, response.timestamp); + INSERT_INTO_JSON_OBJECT(dest, prev_id, response.prev_id); + INSERT_INTO_JSON_OBJECT(dest, nonce, response.nonce); + INSERT_INTO_JSON_OBJECT(dest, height, response.height); + INSERT_INTO_JSON_OBJECT(dest, depth, response.depth); + INSERT_INTO_JSON_OBJECT(dest, hash, response.hash); + INSERT_INTO_JSON_OBJECT(dest, difficulty, response.difficulty); + INSERT_INTO_JSON_OBJECT(dest, reward, response.reward); - INSERT_INTO_JSON_OBJECT(val, doc, major_version, response.major_version); - INSERT_INTO_JSON_OBJECT(val, doc, minor_version, response.minor_version); - INSERT_INTO_JSON_OBJECT(val, doc, timestamp, response.timestamp); - INSERT_INTO_JSON_OBJECT(val, doc, prev_id, response.prev_id); - INSERT_INTO_JSON_OBJECT(val, doc, nonce, response.nonce); - INSERT_INTO_JSON_OBJECT(val, doc, height, response.height); - INSERT_INTO_JSON_OBJECT(val, doc, depth, response.depth); - INSERT_INTO_JSON_OBJECT(val, doc, hash, response.hash); - INSERT_INTO_JSON_OBJECT(val, doc, difficulty, response.difficulty); - INSERT_INTO_JSON_OBJECT(val, doc, reward, response.reward); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response) @@ -978,34 +1045,36 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResp GET_FROM_JSON_OBJECT(val, response.reward, reward); } -void toJsonValue(rapidjson::Document& doc, const rct::rctSig& sig, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::rctSig& sig) { using boost::adaptors::transform; - val.SetObject(); + dest.StartObject(); const auto just_mask = [] (rct::ctkey const& key) -> rct::key const& { return key.mask; }; - INSERT_INTO_JSON_OBJECT(val, doc, type, sig.type); - INSERT_INTO_JSON_OBJECT(val, doc, encrypted, sig.ecdhInfo); - INSERT_INTO_JSON_OBJECT(val, doc, commitments, transform(sig.outPk, just_mask)); - INSERT_INTO_JSON_OBJECT(val, doc, fee, sig.txnFee); + INSERT_INTO_JSON_OBJECT(dest, type, sig.type); + INSERT_INTO_JSON_OBJECT(dest, encrypted, sig.ecdhInfo); + INSERT_INTO_JSON_OBJECT(dest, commitments, transform(sig.outPk, just_mask)); + INSERT_INTO_JSON_OBJECT(dest, fee, sig.txnFee); // prunable { - rapidjson::Value prunable; - prunable.SetObject(); + dest.Key("prunable"); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(prunable, doc, range_proofs, sig.p.rangeSigs); - INSERT_INTO_JSON_OBJECT(prunable, doc, bulletproofs, sig.p.bulletproofs); - INSERT_INTO_JSON_OBJECT(prunable, doc, mlsags, sig.p.MGs); - INSERT_INTO_JSON_OBJECT(prunable, doc, pseudo_outs, sig.get_pseudo_outs()); + INSERT_INTO_JSON_OBJECT(dest, range_proofs, sig.p.rangeSigs); + INSERT_INTO_JSON_OBJECT(dest, bulletproofs, sig.p.bulletproofs); + INSERT_INTO_JSON_OBJECT(dest, mlsags, sig.p.MGs); + INSERT_INTO_JSON_OBJECT(dest, pseudo_outs, sig.get_pseudo_outs()); - val.AddMember("prunable", prunable, doc.GetAllocator()); + dest.EndObject(); } + + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) @@ -1046,12 +1115,12 @@ void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig) } } -void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::ecdhTuple& tuple) { - val.SetObject(); - - INSERT_INTO_JSON_OBJECT(val, doc, mask, tuple.mask); - INSERT_INTO_JSON_OBJECT(val, doc, amount, tuple.amount); + dest.StartObject(); + INSERT_INTO_JSON_OBJECT(dest, mask, tuple.mask); + INSERT_INTO_JSON_OBJECT(dest, amount, tuple.amount); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple) @@ -1065,14 +1134,14 @@ void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple) GET_FROM_JSON_OBJECT(val, tuple.amount, amount); } -void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::rangeSig& sig) { - val.SetObject(); + dest.StartObject(); - INSERT_INTO_JSON_OBJECT(val, doc, asig, sig.asig); + INSERT_INTO_JSON_OBJECT(dest, asig, sig.asig); + INSERT_INTO_JSON_OBJECT(dest, Ci, epee::span<const rct::key>{sig.Ci}); - std::vector<rct::key> keyVector(sig.Ci, std::end(sig.Ci)); - INSERT_INTO_JSON_OBJECT(val, doc, Ci, keyVector); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig) @@ -1102,22 +1171,24 @@ void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig) } } -void toJsonValue(rapidjson::Document& doc, const rct::Bulletproof& p, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::Bulletproof& p) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, V, p.V); + INSERT_INTO_JSON_OBJECT(dest, A, p.A); + INSERT_INTO_JSON_OBJECT(dest, S, p.S); + INSERT_INTO_JSON_OBJECT(dest, T1, p.T1); + INSERT_INTO_JSON_OBJECT(dest, T2, p.T2); + INSERT_INTO_JSON_OBJECT(dest, taux, p.taux); + INSERT_INTO_JSON_OBJECT(dest, mu, p.mu); + INSERT_INTO_JSON_OBJECT(dest, L, p.L); + INSERT_INTO_JSON_OBJECT(dest, R, p.R); + INSERT_INTO_JSON_OBJECT(dest, a, p.a); + INSERT_INTO_JSON_OBJECT(dest, b, p.b); + INSERT_INTO_JSON_OBJECT(dest, t, p.t); - INSERT_INTO_JSON_OBJECT(val, doc, V, p.V); - INSERT_INTO_JSON_OBJECT(val, doc, A, p.A); - INSERT_INTO_JSON_OBJECT(val, doc, S, p.S); - INSERT_INTO_JSON_OBJECT(val, doc, T1, p.T1); - INSERT_INTO_JSON_OBJECT(val, doc, T2, p.T2); - INSERT_INTO_JSON_OBJECT(val, doc, taux, p.taux); - INSERT_INTO_JSON_OBJECT(val, doc, mu, p.mu); - INSERT_INTO_JSON_OBJECT(val, doc, L, p.L); - INSERT_INTO_JSON_OBJECT(val, doc, R, p.R); - INSERT_INTO_JSON_OBJECT(val, doc, a, p.a); - INSERT_INTO_JSON_OBJECT(val, doc, b, p.b); - INSERT_INTO_JSON_OBJECT(val, doc, t, p.t); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p) @@ -1141,17 +1212,15 @@ void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p) GET_FROM_JSON_OBJECT(val, p.t, t); } -void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::boroSig& sig) { - val.SetObject(); + dest.StartObject(); - std::vector<rct::key> keyVector(sig.s0, std::end(sig.s0)); - INSERT_INTO_JSON_OBJECT(val, doc, s0, keyVector); + INSERT_INTO_JSON_OBJECT(dest, s0, epee::span<const rct::key>{sig.s0}); + INSERT_INTO_JSON_OBJECT(dest, s1, epee::span<const rct::key>{sig.s1}); + INSERT_INTO_JSON_OBJECT(dest, ee, sig.ee); - keyVector.assign(sig.s1, std::end(sig.s1)); - INSERT_INTO_JSON_OBJECT(val, doc, s1, keyVector); - - INSERT_INTO_JSON_OBJECT(val, doc, ee, sig.ee); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig) @@ -1188,12 +1257,14 @@ void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig) GET_FROM_JSON_OBJECT(val, sig.ee, ee); } -void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::mgSig& sig) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, ss, sig.ss); + INSERT_INTO_JSON_OBJECT(dest, cc, sig.cc); - INSERT_INTO_JSON_OBJECT(val, doc, ss, sig.ss); - INSERT_INTO_JSON_OBJECT(val, doc, cc, sig.cc); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig) @@ -1207,32 +1278,34 @@ void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig) GET_FROM_JSON_OBJECT(val, sig.cc, cc); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val) -{ - val.SetObject(); - - INSERT_INTO_JSON_OBJECT(val, doc, height, info.height); - INSERT_INTO_JSON_OBJECT(val, doc, target_height, info.target_height); - INSERT_INTO_JSON_OBJECT(val, doc, difficulty, info.difficulty); - INSERT_INTO_JSON_OBJECT(val, doc, target, info.target); - INSERT_INTO_JSON_OBJECT(val, doc, tx_count, info.tx_count); - INSERT_INTO_JSON_OBJECT(val, doc, tx_pool_size, info.tx_pool_size); - INSERT_INTO_JSON_OBJECT(val, doc, alt_blocks_count, info.alt_blocks_count); - INSERT_INTO_JSON_OBJECT(val, doc, outgoing_connections_count, info.outgoing_connections_count); - INSERT_INTO_JSON_OBJECT(val, doc, incoming_connections_count, info.incoming_connections_count); - INSERT_INTO_JSON_OBJECT(val, doc, white_peerlist_size, info.white_peerlist_size); - INSERT_INTO_JSON_OBJECT(val, doc, grey_peerlist_size, info.grey_peerlist_size); - INSERT_INTO_JSON_OBJECT(val, doc, mainnet, info.mainnet); - INSERT_INTO_JSON_OBJECT(val, doc, testnet, info.testnet); - INSERT_INTO_JSON_OBJECT(val, doc, stagenet, info.stagenet); - INSERT_INTO_JSON_OBJECT(val, doc, nettype, info.nettype); - INSERT_INTO_JSON_OBJECT(val, doc, top_block_hash, info.top_block_hash); - INSERT_INTO_JSON_OBJECT(val, doc, cumulative_difficulty, info.cumulative_difficulty); - INSERT_INTO_JSON_OBJECT(val, doc, block_size_limit, info.block_size_limit); - INSERT_INTO_JSON_OBJECT(val, doc, block_weight_limit, info.block_weight_limit); - INSERT_INTO_JSON_OBJECT(val, doc, block_size_median, info.block_size_median); - INSERT_INTO_JSON_OBJECT(val, doc, block_weight_median, info.block_weight_median); - INSERT_INTO_JSON_OBJECT(val, doc, start_time, info.start_time); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::DaemonInfo& info) +{ + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, height, info.height); + INSERT_INTO_JSON_OBJECT(dest, target_height, info.target_height); + INSERT_INTO_JSON_OBJECT(dest, difficulty, info.difficulty); + INSERT_INTO_JSON_OBJECT(dest, target, info.target); + INSERT_INTO_JSON_OBJECT(dest, tx_count, info.tx_count); + INSERT_INTO_JSON_OBJECT(dest, tx_pool_size, info.tx_pool_size); + INSERT_INTO_JSON_OBJECT(dest, alt_blocks_count, info.alt_blocks_count); + INSERT_INTO_JSON_OBJECT(dest, outgoing_connections_count, info.outgoing_connections_count); + INSERT_INTO_JSON_OBJECT(dest, incoming_connections_count, info.incoming_connections_count); + INSERT_INTO_JSON_OBJECT(dest, white_peerlist_size, info.white_peerlist_size); + INSERT_INTO_JSON_OBJECT(dest, grey_peerlist_size, info.grey_peerlist_size); + INSERT_INTO_JSON_OBJECT(dest, mainnet, info.mainnet); + INSERT_INTO_JSON_OBJECT(dest, testnet, info.testnet); + INSERT_INTO_JSON_OBJECT(dest, stagenet, info.stagenet); + INSERT_INTO_JSON_OBJECT(dest, nettype, info.nettype); + INSERT_INTO_JSON_OBJECT(dest, top_block_hash, info.top_block_hash); + INSERT_INTO_JSON_OBJECT(dest, cumulative_difficulty, info.cumulative_difficulty); + INSERT_INTO_JSON_OBJECT(dest, block_size_limit, info.block_size_limit); + INSERT_INTO_JSON_OBJECT(dest, block_weight_limit, info.block_weight_limit); + INSERT_INTO_JSON_OBJECT(dest, block_size_median, info.block_size_median); + INSERT_INTO_JSON_OBJECT(dest, block_weight_median, info.block_weight_median); + INSERT_INTO_JSON_OBJECT(dest, start_time, info.start_time); + + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info) @@ -1266,14 +1339,16 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& inf GET_FROM_JSON_OBJECT(val, info.start_time, start_time); } -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_distribution& dist, rapidjson::Value& val) +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_distribution& dist) { - val.SetObject(); + dest.StartObject(); + + INSERT_INTO_JSON_OBJECT(dest, distribution, dist.data.distribution); + INSERT_INTO_JSON_OBJECT(dest, amount, dist.amount); + INSERT_INTO_JSON_OBJECT(dest, start_height, dist.data.start_height); + INSERT_INTO_JSON_OBJECT(dest, base, dist.data.base); - INSERT_INTO_JSON_OBJECT(val, doc, distribution, dist.data.distribution); - INSERT_INTO_JSON_OBJECT(val, doc, amount, dist.amount); - INSERT_INTO_JSON_OBJECT(val, doc, start_height, dist.data.start_height); - INSERT_INTO_JSON_OBJECT(val, doc, base, dist.data.base); + dest.EndObject(); } void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribution& dist) diff --git a/src/serialization/json_object.h b/src/serialization/json_object.h index 5ef75b863..664b539b5 100644 --- a/src/serialization/json_object.h +++ b/src/serialization/json_object.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016-2019, The Monero Project +// Copyright (c) 2016-2020, The Monero Project // // All rights reserved. // @@ -28,12 +28,18 @@ #pragma once -#include "string_tools.h" -#include "rapidjson/document.h" +#include <boost/utility/string_ref.hpp> +#include <cstring> +#include <rapidjson/document.h> +#include <rapidjson/stringbuffer.h> +#include <rapidjson/writer.h> + #include "cryptonote_basic/cryptonote_basic.h" #include "rpc/message_data_structs.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" #include "common/sfinae_helpers.h" +#include "hex.h" +#include "span.h" #define OBJECT_HAS_MEMBER_OR_THROW(val, key) \ do \ @@ -44,10 +50,9 @@ } \ } while (0); -#define INSERT_INTO_JSON_OBJECT(jsonVal, doc, key, source) \ - rapidjson::Value key##Val; \ - cryptonote::json::toJsonValue(doc, source, key##Val); \ - jsonVal.AddMember(#key, key##Val, doc.GetAllocator()); +#define INSERT_INTO_JSON_OBJECT(dest, key, source) \ + dest.Key(#key, sizeof(#key) - 1); \ + cryptonote::json::toJsonValue(dest, source); #define GET_FROM_JSON_OBJECT(source, dst, key) \ OBJECT_HAS_MEMBER_OR_THROW(source, #key) \ @@ -114,35 +119,41 @@ inline constexpr bool is_to_hex() return std::is_pod<Type>() && !std::is_integral<Type>(); } +void read_hex(const rapidjson::Value& val, epee::span<std::uint8_t> dest); + +// POD to json key +template <class Type> +inline typename std::enable_if<is_to_hex<Type>()>::type toJsonKey(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Type& pod) +{ + const auto hex = epee::to_hex::array(pod); + dest.Key(hex.data(), hex.size()); +} // POD to json value template <class Type> -typename std::enable_if<is_to_hex<Type>()>::type toJsonValue(rapidjson::Document& doc, const Type& pod, rapidjson::Value& value) +inline typename std::enable_if<is_to_hex<Type>()>::type toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Type& pod) { - value = rapidjson::Value(epee::string_tools::pod_to_hex(pod).c_str(), doc.GetAllocator()); + const auto hex = epee::to_hex::array(pod); + dest.String(hex.data(), hex.size()); } template <class Type> -typename std::enable_if<is_to_hex<Type>()>::type fromJsonValue(const rapidjson::Value& val, Type& t) +inline typename std::enable_if<is_to_hex<Type>()>::type fromJsonValue(const rapidjson::Value& val, Type& t) { - if (!val.IsString()) - { - throw WRONG_TYPE("string"); - } + static_assert(std::is_standard_layout<Type>(), "expected standard layout type"); + json::read_hex(val, epee::as_mut_byte_span(t)); +} - //TODO: handle failure to convert hex string to POD type - bool success = epee::string_tools::hex_to_pod(val.GetString(), t); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rapidjson::Value& src); - if (!success) - { - throw BAD_INPUT(); - } +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, boost::string_ref i); +inline void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const std::string& i) +{ + toJsonValue(dest, boost::string_ref{i}); } - -void toJsonValue(rapidjson::Document& doc, const std::string& i, rapidjson::Value& val); void fromJsonValue(const rapidjson::Value& val, std::string& str); -void toJsonValue(rapidjson::Document& doc, bool i, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, bool i); void fromJsonValue(const rapidjson::Value& val, bool& b); // integers overloads for toJsonValue are not needed for standard promotions @@ -157,144 +168,144 @@ void fromJsonValue(const rapidjson::Value& val, unsigned short& i); void fromJsonValue(const rapidjson::Value& val, short& i); -void toJsonValue(rapidjson::Document& doc, const unsigned i, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const unsigned i); void fromJsonValue(const rapidjson::Value& val, unsigned& i); -void toJsonValue(rapidjson::Document& doc, const int, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const int); void fromJsonValue(const rapidjson::Value& val, int& i); -void toJsonValue(rapidjson::Document& doc, const unsigned long long i, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const unsigned long long i); void fromJsonValue(const rapidjson::Value& val, unsigned long long& i); -void toJsonValue(rapidjson::Document& doc, const long long i, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const long long i); void fromJsonValue(const rapidjson::Value& val, long long& i); -inline void toJsonValue(rapidjson::Document& doc, const unsigned long i, rapidjson::Value& val) { - toJsonValue(doc, static_cast<unsigned long long>(i), val); +inline void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const unsigned long i) { + toJsonValue(dest, static_cast<unsigned long long>(i)); } void fromJsonValue(const rapidjson::Value& val, unsigned long& i); -inline void toJsonValue(rapidjson::Document& doc, const long i, rapidjson::Value& val) { - toJsonValue(doc, static_cast<long long>(i), val); +inline void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const long i) { + toJsonValue(dest, static_cast<long long>(i)); } void fromJsonValue(const rapidjson::Value& val, long& i); // end integers -void toJsonValue(rapidjson::Document& doc, const cryptonote::transaction& tx, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::transaction& tx); void fromJsonValue(const rapidjson::Value& val, cryptonote::transaction& tx); -void toJsonValue(rapidjson::Document& doc, const cryptonote::block& b, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::block& b); void fromJsonValue(const rapidjson::Value& val, cryptonote::block& b); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_v& txin, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_v& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_v& txin); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_gen& txin, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_gen& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_gen& txin); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_script& txin, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_script& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_script& txin); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_scripthash& txin, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_scripthash& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_scripthash& txin); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txin_to_key& txin, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txin_to_key& txin); void fromJsonValue(const rapidjson::Value& val, cryptonote::txin_to_key& txin); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_target_v& txout, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_target_v& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_target_v& txout); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_script& txout, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_script& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_script& txout); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_scripthash& txout, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_scripthash& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_scripthash& txout); -void toJsonValue(rapidjson::Document& doc, const cryptonote::txout_to_key& txout, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::txout_to_key& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::txout_to_key& txout); -void toJsonValue(rapidjson::Document& doc, const cryptonote::tx_out& txout, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::tx_out& txout); void fromJsonValue(const rapidjson::Value& val, cryptonote::tx_out& txout); -void toJsonValue(rapidjson::Document& doc, const cryptonote::connection_info& info, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::connection_info& info); 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 toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::tx_blob_entry& tx); 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 toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::block_complete_entry& blk); void fromJsonValue(const rapidjson::Value& val, cryptonote::block_complete_entry& blk); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::block_with_transactions& blk, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::block_with_transactions& blk); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::block_with_transactions& blk); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::transaction_info& tx_info, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::transaction_info& tx_info); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::transaction_info& tx_info); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_and_amount_index& out, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_key_and_amount_index& out); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_and_amount_index& out); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::amount_with_random_outputs& out, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::amount_with_random_outputs& out); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::amount_with_random_outputs& out); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::peer& peer, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::peer& peer); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::peer& peer); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::tx_in_pool& tx); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::hard_fork_info& info); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::hard_fork_info& info); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_count& out, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_amount_count& out); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_count& out); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_amount_and_index& out, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_amount_and_index& out); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_amount_and_index& out); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_key_mask_unlocked& out, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_key_mask_unlocked& out); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_key_mask_unlocked& out); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::error& err, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::error& err); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::error& error); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::BlockHeaderResponse& response, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::BlockHeaderResponse& response); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::BlockHeaderResponse& response); -void toJsonValue(rapidjson::Document& doc, const rct::rctSig& i, rapidjson::Value& val); -void fromJsonValue(const rapidjson::Value& i, rct::rctSig& sig); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::rctSig& i); +void fromJsonValue(const rapidjson::Value& val, rct::rctSig& sig); -void toJsonValue(rapidjson::Document& doc, const rct::ecdhTuple& tuple, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::ecdhTuple& tuple); void fromJsonValue(const rapidjson::Value& val, rct::ecdhTuple& tuple); -void toJsonValue(rapidjson::Document& doc, const rct::rangeSig& sig, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::rangeSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::rangeSig& sig); -void toJsonValue(rapidjson::Document& doc, const rct::Bulletproof& p, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::Bulletproof& p); void fromJsonValue(const rapidjson::Value& val, rct::Bulletproof& p); -void toJsonValue(rapidjson::Document& doc, const rct::boroSig& sig, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::boroSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::boroSig& sig); -void toJsonValue(rapidjson::Document& doc, const rct::mgSig& sig, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const rct::mgSig& sig); void fromJsonValue(const rapidjson::Value& val, rct::mgSig& sig); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::DaemonInfo& info, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::DaemonInfo& info); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::DaemonInfo& info); -void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::output_distribution& dist, rapidjson::Value& val); +void toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const cryptonote::rpc::output_distribution& dist); void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::output_distribution& dist); template <typename Map> -typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val); +typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Map& map); template <typename Map> typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map); template <typename Vec> -typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val); +typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Vec &vec); template <typename Vec> typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec); @@ -304,24 +315,22 @@ typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJson // unfortunately because of how templates work they have to be here. template <typename Map> -typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Document& doc, const Map& map, rapidjson::Value& val) +inline typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Map& map) { - val.SetObject(); - - auto& al = doc.GetAllocator(); + using key_type = typename Map::key_type; + static_assert(std::is_same<std::string, key_type>() || is_to_hex<key_type>(), "invalid map key type"); + dest.StartObject(); for (const auto& i : map) { - rapidjson::Value k; - rapidjson::Value m; - toJsonValue(doc, i.first, k); - toJsonValue(doc, i.second, m); - val.AddMember(k, m, al); + toJsonKey(dest, i.first); + toJsonValue(dest, i.second); } + dest.EndObject(); } template <typename Map> -typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map) +inline typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonValue(const rapidjson::Value& val, Map& map) { if (!val.IsObject()) { @@ -342,20 +351,16 @@ typename std::enable_if<sfinae::is_map_like<Map>::value, void>::type fromJsonVal } template <typename Vec> -typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Document& doc, const Vec &vec, rapidjson::Value& val) +inline typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type toJsonValue(rapidjson::Writer<rapidjson::StringBuffer>& dest, const Vec &vec) { - val.SetArray(); - + dest.StartArray(); for (const auto& t : vec) - { - rapidjson::Value v; - toJsonValue(doc, t, v); - val.PushBack(v, doc.GetAllocator()); - } + toJsonValue(dest, t); + dest.EndArray(vec.size()); } template <typename Vec> -typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec) +inline typename std::enable_if<sfinae::is_vector_like<Vec>::value, void>::type fromJsonValue(const rapidjson::Value& val, Vec& vec) { if (!val.IsArray()) { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d96ca925e..a86e01104 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -108,7 +108,7 @@ typedef cryptonote::simple_wallet sw; 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_suspend_rpc_payment_mining.store(false, std::memory_order_relaxed); \ m_rpc_payment_checker.trigger(); \ m_idle_cond.notify_one(); \ }) @@ -180,8 +180,8 @@ namespace const char* USAGE_PAYMENT_ID("payment_id"); const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"); const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]"); - const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]"); - const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]"); + const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]"); + const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_BELOW("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_SINGLE("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id (obsolete)>]"); const char* USAGE_DONATE("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id (obsolete)>]"); @@ -194,7 +194,7 @@ namespace " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" " account untag <account_index_1> [<account_index_2> ...]\n" " account tag_description <tag_name> <description>"); - const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> | device [<index>]]"); + const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> | device [<index>] | one-off <account> <subaddress>]"); const char* USAGE_INTEGRATED_ADDRESS("integrated_address [device] [<payment_id> | <address>]"); const char* USAGE_ADDRESS_BOOK("address_book [(add (<address>|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"); const char* USAGE_SET_VARIABLE("set <option> [<value>]"); @@ -1691,7 +1691,7 @@ bool simple_wallet::print_ring(const std::vector<std::string> &args) rings.push_back({key_image, ring}); else if (!m_wallet->get_rings(txid, rings)) { - fail_msg_writer() << tr("Key image either not spent, or spent with mixin 0"); + fail_msg_writer() << tr("Key image either not spent, or spent with ring size 1"); return true; } @@ -1963,7 +1963,7 @@ bool simple_wallet::rpc_payment_info(const std::vector<std::string> &args) 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");; + 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) @@ -3138,13 +3138,13 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("locked_sweep_all", boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_sweep_all,_1), tr(USAGE_LOCKED_SWEEP_ALL), - tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability.")); + tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" or \"index=all\" is specified, the wallet sweeps outputs received by those or all address indices, respectively. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability.")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_unmixable, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr(USAGE_SWEEP_ALL), - tr("Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs.")); + tr("Send all unlocked balance to an address. If the parameter \"index=<N1>[,<N2>,...]\" or \"index=all\" is specified, the wallet sweeps outputs received by those or all address indices, respectively. If omitted, the wallet randomly chooses an address index to be used. If the parameter \"outputs=<N>\" is specified and N > 0, wallet splits the transaction into N even outputs.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_below, _1), tr(USAGE_SWEEP_BELOW), @@ -3181,7 +3181,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_address, _1), tr(USAGE_ADDRESS), - tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the wallet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text.")); + tr("If no arguments are specified or <index> is specified, the wallet shows the default or specified address. If \"all\" is specified, the wallet shows all the existing addresses in the currently selected account. If \"new \" is specified, the wallet creates a new address with the provided label text (which can be empty). If \"label\" is specified, the wallet sets the label of the address specified by <index> to the provided label text. If \"one-off\" is specified, the address for the specified index is generated and displayed, and remembered by the wallet")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::on_command, this, &simple_wallet::print_integrated_address, _1), tr(USAGE_INTEGRATED_ADDRESS), @@ -5199,8 +5199,11 @@ void simple_wallet::check_background_mining(const epee::wipeable_string &passwor if (is_background_mining_enabled) { // already active, nice - m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); - m_wallet->rewrite(m_wallet_file, password); + if (setup == tools::wallet2::BackgroundMiningMaybe) + { + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); + m_wallet->rewrite(m_wallet_file, password); + } start_background_mining(); return; } @@ -5223,6 +5226,11 @@ void simple_wallet::check_background_mining(const epee::wipeable_string &passwor m_wallet->rewrite(m_wallet_file, password); start_background_mining(); } + else + { + // the setting is already enabled, and the daemon is not mining yet, so start it + start_background_mining(); + } } //---------------------------------------------------------------------------------------------------- bool simple_wallet::start_mining(const std::vector<std::string>& args) @@ -5584,6 +5592,14 @@ boost::optional<epee::wipeable_string> simple_wallet::on_device_passphrase_reque //---------------------------------------------------------------------------------------------------- void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money) { + const uint64_t rfbh = m_wallet->get_refresh_from_block_height(); + std::string err; + const uint64_t dh = m_wallet->get_daemon_blockchain_height(err); + if (err.empty() && rfbh > dh) + { + message_writer(console_color_yellow, false) << tr("The wallet's refresh-from-block-height setting is higher than the daemon's height: this may mean your wallet will skip over transactions"); + } + // Key image sync after the first refresh if (!m_wallet->get_account().get_device().has_tx_cold_sign() || m_wallet->get_account().get_device().has_ki_live_refresh()) { return; @@ -5853,8 +5869,14 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args if (uses) { std::vector<uint64_t> heights; - for (const auto &e: td.m_uses) heights.push_back(e.first); - const std::pair<std::string, std::string> line = show_outputs_line(heights, blockchain_height, td.m_spent_height); + uint64_t idx = 0; + for (const auto &e: td.m_uses) + { + heights.push_back(e.first); + if (e.first < td.m_spent_height) + ++idx; + } + const std::pair<std::string, std::string> line = show_outputs_line(heights, blockchain_height, idx); extra_string += std::string("\n ") + tr("Used at heights: ") + line.first + "\n " + line.second; } message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << @@ -6023,7 +6045,7 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- -std::pair<std::string, std::string> simple_wallet::show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height) const +std::pair<std::string, std::string> simple_wallet::show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_idx) const { std::stringstream ostr; @@ -6031,7 +6053,7 @@ std::pair<std::string, std::string> simple_wallet::show_outputs_line(const std:: blockchain_height = std::max(blockchain_height, h); for (size_t j = 0; j < heights.size(); ++j) - ostr << (heights[j] == highlight_height ? " *" : " ") << heights[j]; + ostr << (j == highlight_idx ? " *" : " ") << heights[j]; // visualize the distribution, using the code by moneroexamples onion-monero-viewer const uint64_t resolution = 79; @@ -6041,20 +6063,23 @@ std::pair<std::string, std::string> simple_wallet::show_outputs_line(const std:: uint64_t pos = (heights[j] * resolution) / blockchain_height; ring_str[pos] = 'o'; } - if (highlight_height < blockchain_height) + if (highlight_idx < heights.size() && heights[highlight_idx] < blockchain_height) { - uint64_t pos = (highlight_height * resolution) / blockchain_height; + uint64_t pos = (heights[highlight_idx] * resolution) / blockchain_height; ring_str[pos] = '*'; } return std::make_pair(ostr.str(), ring_str); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr) +bool simple_wallet::process_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr, bool verbose) { uint32_t version; if (!try_connect_to_daemon(false, &version)) + { + fail_msg_writer() << tr("failed to connect to daemon"); return false; + } // available for RPC version 1.4 or higher if (version < MAKE_CORE_RPC_VERSION(1, 4)) return true; @@ -6070,7 +6095,8 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending { const cryptonote::transaction& tx = ptx_vector[n].tx; const tools::wallet2::tx_construction_data& construction_data = ptx_vector[n].construction_data; - ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx); + if (verbose) + ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx); // for each input std::vector<uint64_t> spent_key_height(tx.vin.size()); std::vector<crypto::hash> spent_key_txid (tx.vin.size()); @@ -6091,7 +6117,8 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending } const cryptonote::tx_source_entry& source = *sptr; - ostr << boost::format(tr("\nInput %llu/%llu (%s): amount=%s")) % (i + 1) % tx.vin.size() % epee::string_tools::pod_to_hex(in_key.k_image) % print_money(source.amount); + if (verbose) + ostr << boost::format(tr("\nInput %llu/%llu (%s): amount=%s")) % (i + 1) % tx.vin.size() % epee::string_tools::pod_to_hex(in_key.k_image) % print_money(source.amount); // convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets); // get block heights from which those ring member keys originated @@ -6103,6 +6130,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.get_txid = true; 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); @@ -6120,19 +6148,18 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending return false; } } - ostr << tr("\nOriginating block heights: "); + if (verbose) + ostr << tr("\nOriginating block heights: "); spent_key_height[i] = res.outs[source.real_output].height; spent_key_txid [i] = res.outs[source.real_output].txid; std::vector<uint64_t> heights(absolute_offsets.size(), 0); - uint64_t highlight_height = std::numeric_limits<uint64_t>::max(); for (size_t j = 0; j < absolute_offsets.size(); ++j) { heights[j] = res.outs[j].height; - if (j == source.real_output) - highlight_height = heights[j]; } - std::pair<std::string, std::string> ring_str = show_outputs_line(heights, blockchain_height, highlight_height); - ostr << ring_str.first << tr("\n|") << ring_str.second << tr("|\n"); + std::pair<std::string, std::string> ring_str = show_outputs_line(heights, blockchain_height, source.real_output); + if (verbose) + ostr << ring_str.first << tr("\n|") << ring_str.second << tr("|\n"); } // warn if rings contain keys originating from the same tx or temporally very close block heights bool are_keys_from_same_tx = false; @@ -6151,7 +6178,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending ostr << tr("\nWarning: Some input keys being spent are from ") << (are_keys_from_same_tx ? tr("the same transaction") : tr("blocks that are temporally very close")) - << tr(", which can break the anonymity of ring signature. Make sure this is intentional!"); + << tr(", which can break the anonymity of ring signatures. Make sure this is intentional!"); } ostr << ENDL; } @@ -6206,7 +6233,7 @@ void simple_wallet::check_for_inactivity_lock(bool user) m_in_command = true; if (!user) { - const std::string speech = tr("I locked your Monero wallet to protect you while you were away"); + const std::string speech = tr("I locked your Monero wallet to protect you while you were away\nsee \"help_advanced set\" to configure/disable"); std::vector<std::pair<std::string, size_t>> lines = tools::split_string_by_width(speech, 45); size_t max_len = 0; @@ -6592,11 +6619,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri float days = locked_blocks / 720.0f; prompt << boost::format(tr(".\nThis transaction (including %s change) will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % cryptonote::print_money(change) % ((unsigned long long)unlock_block) % days; } - if (m_wallet->print_ring_members()) - { - if (!print_ring_members(ptx_vector, prompt)) - return false; - } + if (!process_ring_members(ptx_vector, prompt, m_wallet->print_ring_members())) + return false; bool default_ring_size = true; for (const auto &ptx: ptx_vector) { @@ -6860,7 +6884,12 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st std::set<uint32_t> subaddr_indices; if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") { - if (!parse_subaddress_indices(local_args[0], subaddr_indices)) + if (local_args[0] == "index=all") + { + for (uint32_t i = 0; i < m_wallet->get_num_subaddresses(m_current_subaddress_account); ++i) + subaddr_indices.insert(i); + } + else if (!parse_subaddress_indices(local_args[0], subaddr_indices)) { print_usage(); return true; @@ -7047,7 +7076,7 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st if (subaddr_indices.size() > 1) prompt << tr("WARNING: Outputs of multiple addresses are being used together, which might potentially compromise your privacy.\n"); } - if (m_wallet->print_ring_members() && !print_ring_members(ptx_vector, prompt)) + if (!process_ring_members(ptx_vector, prompt, m_wallet->print_ring_members())) return true; if (ptx_vector.size() > 1) { prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay?")) % @@ -7291,7 +7320,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) uint64_t total_fee = ptx_vector[0].fee; uint64_t total_sent = m_wallet->get_transfer_details(ptx_vector[0].selected_transfers.front()).amount(); std::ostringstream prompt; - if (!print_ring_members(ptx_vector, prompt)) + if (!process_ring_members(ptx_vector, prompt, m_wallet->print_ring_members())) return true; prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay?")) % print_money(total_sent) % @@ -8373,7 +8402,7 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec m_in_manual_refresh.store(true, std::memory_order_relaxed); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); @@ -8803,6 +8832,8 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) } } + m_in_manual_refresh.store(true, std::memory_order_relaxed); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); return refresh_main(start_height, reset_type, true); } //---------------------------------------------------------------------------------------------------- @@ -9263,6 +9294,24 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: print_address_sub(m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1); m_wallet->device_show_address(m_current_subaddress_account, m_wallet->get_num_subaddresses(m_current_subaddress_account) - 1, boost::none); } + else if (local_args[0] == "one-off") + { + local_args.erase(local_args.begin()); + std::string label; + if (local_args.size() != 2) + { + fail_msg_writer() << tr("Expected exactly two arguments for index"); + return true; + } + uint32_t major, minor; + if (!epee::string_tools::get_xtype_from_string(major, local_args[0]) || !epee::string_tools::get_xtype_from_string(minor, local_args[1])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[0] << " " << local_args[1]; + return true; + } + m_wallet->create_one_off_subaddress({major, minor}); + success_msg_writer() << boost::format(tr("Address at %u %u: %s")) % major % minor % m_wallet->get_subaddress_as_str({major, minor}); + } else if (local_args.size() >= 2 && local_args[0] == "label") { if (!epee::string_tools::get_xtype_from_string(index, local_args[1])) @@ -10036,7 +10085,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) try { - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index e401f5fda..4ba2793e0 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -270,12 +270,12 @@ namespace cryptonote bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message = std::string()); bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs); bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs); - bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); + bool process_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr, bool verbose); std::string get_prompt() const; bool print_seed(bool encrypted); void key_images_sync_intern(); void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money); - std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits<uint64_t>::max()) const; + std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_idx = std::numeric_limits<uint64_t>::max()) const; bool freeze_thaw(const std::vector<std::string>& args, bool freeze); bool prompt_if_old(const std::vector<tools::wallet2::pending_tx> &ptx_vector); bool on_command(bool (simple_wallet::*cmd)(const std::vector<std::string>&), const std::vector<std::string> &args); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 6200c7a1f..4612b0397 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -725,7 +725,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &seed) return recover(path, "", seed); } -bool WalletImpl::recover(const std::string &path, const std::string &password, const std::string &seed) +bool WalletImpl::recover(const std::string &path, const std::string &password, const std::string &seed, const std::string &seed_offset/* = {}*/) { clearStatus(); m_errorString.clear(); @@ -743,6 +743,10 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c setStatusError(tr("Electrum-style word list failed verification")); return false; } + if (!seed_offset.empty()) + { + recovery_key = cryptonote::decrypt_key(recovery_key, seed_offset); + } if (old_language == crypto::ElectrumWords::old_language_name) old_language = Language::English().get_language_name(); @@ -1671,6 +1675,26 @@ void WalletImpl::disposeTransaction(PendingTransaction *t) delete t; } +uint64_t WalletImpl::estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const +{ + const size_t pubkey_size = 33; + const size_t encrypted_paymentid_size = 11; + const size_t extra_size = pubkey_size + encrypted_paymentid_size; + + return m_wallet->estimate_fee( + m_wallet->use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0), + m_wallet->use_fork_rules(4, 0), + 1, + m_wallet->get_min_ring_size() - 1, + destinations.size() + 1, + extra_size, + m_wallet->use_fork_rules(8, 0), + m_wallet->get_base_fee(), + m_wallet->get_fee_multiplier(m_wallet->adjust_priority(static_cast<uint32_t>(priority))), + m_wallet->get_fee_quantization_mask()); +} + TransactionHistory *WalletImpl::history() { return m_history.get(); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 331bf4b38..66eeb0e73 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -60,7 +60,7 @@ public: const std::string &language) const override; bool open(const std::string &path, const std::string &password); bool recover(const std::string &path,const std::string &password, - const std::string &seed); + const std::string &seed, const std::string &seed_offset = {}); bool recoverFromKeysWithPassword(const std::string &path, const std::string &password, const std::string &language, @@ -166,6 +166,8 @@ public: bool importKeyImages(const std::string &filename) override; virtual void disposeTransaction(PendingTransaction * t) override; + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const override; virtual TransactionHistory * history() override; virtual AddressBook * addressBook() override; virtual Subaddress * subaddress() override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index e543a115b..6309724a4 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -879,6 +879,14 @@ struct Wallet */ virtual void disposeTransaction(PendingTransaction * t) = 0; + /*! + * \brief Estimates transaction fee. + * \param destinations Vector consisting of <address, amount> pairs. + * \return Estimated fee. + */ + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const = 0; + /*! * \brief exportKeyImages - exports key images to file * \param filename @@ -1085,10 +1093,12 @@ struct WalletManager * \param nettype Network type * \param restoreHeight restore from start height * \param kdf_rounds Number of rounds for key derivation function + * \param seed_offset Seed offset passphrase (optional) * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, - NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1) = 0; + NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1, + const std::string &seed_offset = {}) = 0; Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, bool testnet = false, uint64_t restoreHeight = 0) // deprecated { @@ -1283,7 +1293,11 @@ struct WalletManager virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; //! checks for an update and returns version, hash and url - static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, std::string subdir); + static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates( + const std::string &software, + std::string subdir, + const char *buildtag = nullptr, + const char *current_version = nullptr); }; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index d589dcc75..8d7541cea 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -93,13 +93,14 @@ Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight, - uint64_t kdf_rounds) + uint64_t kdf_rounds, + const std::string &seed_offset/* = {}*/) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } - wallet->recover(path, password, mnemonic); + wallet->recover(path, password, mnemonic, seed_offset); return wallet; } @@ -341,22 +342,30 @@ std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool return addresses.front(); } -std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, std::string subdir) +std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates( + const std::string &software, + std::string subdir, + const char *buildtag/* = nullptr*/, + const char *current_version/* = nullptr*/) { + if (buildtag == nullptr) + { #ifdef BUILD_TAG - static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); + static const char buildtag_default[] = BOOST_PP_STRINGIZE(BUILD_TAG); #else - static const char buildtag[] = "source"; - // Override the subdir string when built from source - subdir = "source"; + static const char buildtag_default[] = "source"; + // Override the subdir string when built from source + subdir = "source"; #endif + buildtag = buildtag_default; + } std::string version, hash; MDEBUG("Checking for a new " << software << " version for " << buildtag); if (!tools::check_updates(software, buildtag, version, hash)) return std::make_tuple(false, "", "", "", ""); - if (tools::vercmp(version.c_str(), MONERO_VERSION) > 0) + if (tools::vercmp(version.c_str(), current_version != nullptr ? current_version : MONERO_VERSION) > 0) { std::string user_url = tools::get_update_url(software, subdir, buildtag, version, true); std::string auto_url = tools::get_update_url(software, subdir, buildtag, version, false); diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 537fc5ba6..0595b8327 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -46,7 +46,8 @@ public: const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight, - uint64_t kdf_rounds = 1) override; + uint64_t kdf_rounds = 1, + const std::string &seed_offset = {}) override; virtual Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index 6e2cb933f..1bd462ef5 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -48,7 +48,7 @@ namespace mms { -message_store::message_store() +message_store::message_store(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client) : m_transporter(std::move(http_client)) { m_active = false; m_auto_send = false; diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index 637bd29a1..d40daf186 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -43,6 +43,7 @@ #include "common/i18n.h" #include "common/command_line.h" #include "wipeable_string.h" +#include "net/abstract_http_client.h" #include "message_transporter.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -202,7 +203,8 @@ namespace mms class message_store { public: - message_store(); + message_store(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client); + // Initialize and start to use the MMS, set the first signer, this wallet itself // Filename, if not null and not empty, is used to create the ".mms" file // reset it if already used, with deletion of all signers and messages diff --git a/src/wallet/message_transporter.cpp b/src/wallet/message_transporter.cpp index cf9b45b37..4dd4b8f01 100644 --- a/src/wallet/message_transporter.cpp +++ b/src/wallet/message_transporter.cpp @@ -80,7 +80,7 @@ namespace bitmessage_rpc } -message_transporter::message_transporter() +message_transporter::message_transporter(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client) : m_http_client(std::move(http_client)) { m_run = true; } @@ -96,7 +96,7 @@ void message_transporter::set_options(const std::string &bitmessage_address, con } m_bitmessage_login = bitmessage_login; - m_http_client.set_server(address_parts.host, std::to_string(address_parts.port), boost::none); + m_http_client->set_server(address_parts.host, std::to_string(address_parts.port), boost::none); } bool message_transporter::receive_messages(const std::vector<std::string> &destination_transport_addresses, @@ -256,7 +256,7 @@ bool message_transporter::post_request(const std::string &request, std::string & additional_params.push_back(std::make_pair("Content-Type", "application/xml; charset=utf-8")); const epee::net_utils::http::http_response_info* response = NULL; std::chrono::milliseconds timeout = std::chrono::seconds(15); - bool r = m_http_client.invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params)); + bool r = m_http_client->invoke("/", "POST", request, timeout, std::addressof(response), std::move(additional_params)); if (r) { answer = response->m_body; @@ -266,7 +266,7 @@ bool message_transporter::post_request(const std::string &request, std::string & LOG_ERROR("POST request to Bitmessage failed: " << request.substr(0, 300)); THROW_WALLET_EXCEPTION(tools::error::no_connection_to_bitmessage, m_bitmessage_url); } - m_http_client.disconnect(); // see comment above + m_http_client->disconnect(); // see comment above std::string string_value = get_str_between_tags(answer, "<string>", "</string>"); if ((string_value.find("API Error") == 0) || (string_value.find("RPC ") == 0)) { diff --git a/src/wallet/message_transporter.h b/src/wallet/message_transporter.h index 28c099d87..84a2e9bae 100644 --- a/src/wallet/message_transporter.h +++ b/src/wallet/message_transporter.h @@ -34,9 +34,9 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "net/http_server_impl_base.h" #include "net/http_client.h" +#include "net/abstract_http_client.h" #include "common/util.h" #include "wipeable_string.h" -#include "serialization/keyvalue_serialization.h" #include <vector> namespace mms @@ -83,7 +83,7 @@ typedef epee::misc_utils::struct_init<transport_message_t> transport_message; class message_transporter { public: - message_transporter(); + message_transporter(std::unique_ptr<epee::net_utils::http::abstract_http_client> http_client); void set_options(const std::string &bitmessage_address, const epee::wipeable_string &bitmessage_login); bool send_message(const transport_message &message); bool receive_messages(const std::vector<std::string> &destination_transport_addresses, @@ -94,7 +94,7 @@ public: bool delete_transport_address(const std::string &transport_address); private: - epee::net_utils::http::http_simple_client m_http_client; + const std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client; std::string m_bitmessage_url; epee::wipeable_string m_bitmessage_login; std::atomic<bool> m_run; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index f3698b599..873c2ee51 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -51,7 +51,7 @@ 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, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex) +NodeRPCProxy::NodeRPCProxy(epee::net_utils::http::abstract_http_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) diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 65ca40640..b053659e9 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -31,7 +31,7 @@ #include <string> #include <boost/thread/mutex.hpp> #include "include_base_utils.h" -#include "net/http_client.h" +#include "net/abstract_http_client.h" #include "rpc/core_rpc_server_commands_defs.h" #include "wallet_rpc_helpers.h" @@ -41,7 +41,7 @@ namespace tools class NodeRPCProxy { public: - NodeRPCProxy(epee::net_utils::http::http_simple_client &http_client, rpc_payment_state_t &rpc_payment_state, boost::recursive_mutex &mutex); + NodeRPCProxy(epee::net_utils::http::abstract_http_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(); @@ -72,7 +72,7 @@ private: private: boost::optional<std::string> get_info(); - epee::net_utils::http::http_simple_client &m_http_client; + epee::net_utils::http::abstract_http_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; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index b7efdd75c..dfeb987ca 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -35,10 +35,13 @@ #include "misc_language.h" #include "wallet_errors.h" #include "ringdb.h" +#include "cryptonote_config.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.ringdb" +#define V1TAG ((uint64_t)798237759845202) + static const char zerokey[8] = {0}; static const MDB_val zerokeyval = { sizeof(zerokey), (void *)zerokey }; @@ -63,15 +66,16 @@ static int compare_uint64(const MDB_val *a, const MDB_val *b) return va < vb ? -1 : va > vb; } -static std::string compress_ring(const std::vector<uint64_t> &ring) +static std::string compress_ring(const std::vector<uint64_t> &ring, uint64_t tag) { std::string s; + s += tools::get_varint_data(tag); for (uint64_t out: ring) s += tools::get_varint_data(out); return s; } -static std::vector<uint64_t> decompress_ring(const std::string &s) +static std::vector<uint64_t> decompress_ring(const std::string &s, uint64_t tag) { std::vector<uint64_t> ring; int read = 0; @@ -81,6 +85,13 @@ static std::vector<uint64_t> decompress_ring(const std::string &s) std::string tmp(i, s.cend()); read = tools::read_varint(tmp.begin(), tmp.end(), out); THROW_WALLET_EXCEPTION_IF(read <= 0 || read > 256, tools::error::wallet_internal_error, "Internal error decompressing ring"); + if (tag) + { + if (tag != out) + return {}; + tag = 0; + continue; + } ring.push_back(out); } return ring; @@ -93,25 +104,25 @@ std::string get_rings_filename(boost::filesystem::path filename) return filename.string(); } -static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key) +static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - static const char salt[] = "ringdsb"; - - uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(salt)]; + uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(config::HASH_KEY_RINGDB) + sizeof(field)]; memcpy(buffer, &key_image, sizeof(key_image)); memcpy(buffer + sizeof(key_image), &key, sizeof(key)); - memcpy(buffer + sizeof(key_image) + sizeof(key), salt, sizeof(salt)); + memcpy(buffer + sizeof(key_image) + sizeof(key), config::HASH_KEY_RINGDB, sizeof(config::HASH_KEY_RINGDB)); + memcpy(buffer + sizeof(key_image) + sizeof(key) + sizeof(config::HASH_KEY_RINGDB), &field, sizeof(field)); crypto::hash hash; - crypto::cn_fast_hash(buffer, sizeof(buffer), hash.data); + // if field is 0, backward compat mode: hash without the field + crypto::cn_fast_hash(buffer, sizeof(buffer) - !field, hash.data); static_assert(sizeof(hash) >= CHACHA_IV_SIZE, "Incompatible hash and chacha IV sizes"); crypto::chacha_iv iv; memcpy(&iv, &hash, CHACHA_IV_SIZE); return iv; } -static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - const crypto::chacha_iv iv = make_iv(key_image, key); + const crypto::chacha_iv iv = make_iv(key_image, key, field); std::string ciphertext; ciphertext.resize(plaintext.size() + sizeof(iv)); crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); @@ -119,14 +130,14 @@ static std::string encrypt(const std::string &plaintext, const crypto::key_image return ciphertext; } -static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key); + return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key, field); } -static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key) +static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key, uint8_t field) { - const crypto::chacha_iv iv = make_iv(key_image, key); + const crypto::chacha_iv iv = make_iv(key_image, key, field); std::string plaintext; THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(iv), tools::error::wallet_internal_error, "Bad ciphertext text"); plaintext.resize(ciphertext.size() - sizeof(iv)); @@ -137,11 +148,11 @@ static std::string decrypt(const std::string &ciphertext, const crypto::key_imag static void store_relative_ring(MDB_txn *txn, MDB_dbi &dbi, const crypto::key_image &key_image, const std::vector<uint64_t> &relative_ring, const crypto::chacha_key &chacha_key) { MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); - std::string compressed_ring = compress_ring(relative_ring); - std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); + std::string compressed_ring = compress_ring(relative_ring, V1TAG); + std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key, 1); data.mv_size = data_ciphertext.size(); data.mv_data = (void*)data_ciphertext.c_str(); int dbr = mdb_put(txn, dbi, &key, &data, 0); @@ -297,7 +308,7 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const std::vecto for (const crypto::key_image &key_image: key_images) { MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); @@ -349,7 +360,7 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im tx_active = true; MDB_val key, data; - std::string key_ciphertext = encrypt(key_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key, 0); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); dbr = mdb_get(txn, dbi_rings, &key, &data); @@ -358,8 +369,15 @@ bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_im return false; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); - std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key); - outs = decompress_ring(data_plaintext); + bool try_v0 = false; + std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 1); + try { outs = decompress_ring(data_plaintext, V1TAG); if (outs.empty()) try_v0 = true; } + catch(...) { try_v0 = true; } + if (try_v0) + { + data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key, 0); + outs = decompress_ring(data_plaintext, 0); + } MDEBUG("Found ring for key image " << key_image << ":"); MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); outs = cryptonote::relative_output_offsets_to_absolute(outs); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6a622d953..4220f18be 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -44,6 +44,7 @@ using namespace epee; #include "cryptonote_config.h" +#include "cryptonote_core/tx_sanity_check.h" #include "wallet_rpc_helpers.h" #include "wallet2.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -101,10 +102,6 @@ using namespace cryptonote; // used to target a given block weight (additional outputs may be added on top to build fee) #define TX_WEIGHT_TARGET(bytes) (bytes*2/3) -// arbitrary, used to generate different hashes from the same input -#define CHACHA8_KEY_TAIL 0x8c -#define CACHE_KEY_TAIL 0x8d - #define UNSIGNED_TX_PREFIX "Monero unsigned tx set\004" #define SIGNED_TX_PREFIX "Monero signed tx set\004" #define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" @@ -149,6 +146,9 @@ static const std::string MULTISIG_EXTRA_INFO_MAGIC = "MultisigxV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; +boost::mutex tools::wallet2::default_daemon_address_lock; +std::string tools::wallet2::default_daemon_address = ""; + namespace { std::string get_default_ringdb_path() @@ -236,8 +236,6 @@ namespace add_reason(reason, "overspend"); if (res.fee_too_low) add_reason(reason, "fee too low"); - if (res.not_rct) - add_reason(reason, "tx is not ringct"); if (res.sanity_check_failed) add_reason(reason, "tx sanity check failed"); if (res.not_relayed) @@ -357,7 +355,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl else if (!daemon_ssl_ca_file.empty() || !daemon_ssl_allowed_fingerprints.empty()) { std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; - std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex_locale::to_vector); for (const auto &fpr: ssl_allowed_fingerprints) { THROW_WALLET_EXCEPTION_IF(fpr.size() != SSL_FINGERPRINT_SIZE, tools::error::wallet_internal_error, @@ -412,6 +410,15 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl daemon_port = get_config(nettype).RPC_DEFAULT_PORT; } + // if no daemon settings are given and we have a previous one, reuse that one + if (command_line::is_arg_defaulted(vm, opts.daemon_host) && command_line::is_arg_defaulted(vm, opts.daemon_port) && command_line::is_arg_defaulted(vm, opts.daemon_address)) + { + // not a bug: taking a const ref to a temporary in this way is actually ok in a recent C++ standard + const std::string &def = tools::wallet2::get_default_daemon_address(); + if (!def.empty()) + daemon_address = def; + } + if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); @@ -428,6 +435,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl verification_required && !ssl_options.has_strong_verification(real_daemon), tools::error::wallet_internal_error, tools::wallet2::tr("Enabling --") + std::string{use_proxy ? opts.proxy.name : opts.daemon_ssl.name} + tools::wallet2::tr(" requires --") + + opts.daemon_ssl_allow_any_cert.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_ca_certificates.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_allowed_fingerprints.name + tools::wallet2::tr(" or use of a .onion/.i2p domain") ); } @@ -591,6 +599,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f } viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); crypto::public_key pkey; + if (viewkey == crypto::null_skey) + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("view secret key may not be all zeroes")); if (!crypto::secret_key_to_public_key(viewkey, pkey)) { THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } @@ -607,6 +617,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f } spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); crypto::public_key pkey; + if (spendkey == crypto::null_skey) + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("spend secret key may not be all zeroes")); if (!crypto::secret_key_to_public_key(spendkey, pkey)) { THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } @@ -879,20 +891,6 @@ uint8_t get_bulletproof_fork() return 8; } -uint64_t estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) -{ - if (use_per_byte_fee) - { - const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); - return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); - } - else - { - const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); - return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); - } -} - uint64_t calculate_fee(bool use_per_byte_fee, const cryptonote::transaction &tx, size_t blob_size, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) { if (use_per_byte_fee) @@ -1124,7 +1122,8 @@ void wallet_device_callback::on_progress(const hw::device_progress& event) wallet->on_device_progress(event); } -wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): +wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory): + m_http_client(std::move(http_client_factory->create())), m_multisig_rescan_info(NULL), m_multisig_rescan_k(NULL), m_upper_transaction_weight_limit(0), @@ -1169,7 +1168,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_watch_only(false), m_multisig(false), m_multisig_threshold(0), - m_node_rpc_proxy(m_http_client, m_rpc_payment_state, 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), @@ -1180,12 +1179,13 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), m_original_keys_available(false), - m_message_store(), + m_message_store(http_client_factory->create()), m_key_device_type(hw::device::device_type::SOFTWARE), m_ring_history_saved(false), m_ringdb(), m_last_block_reward(0), m_encrypt_keys_after_refresh(boost::none), + m_decrypt_keys_lockers(0), m_unattended(unattended), m_devices_registered(false), m_device_last_key_image_sync(0), @@ -1299,8 +1299,8 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(m_http_client.is_connected()) - m_http_client.disconnect(); + 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); @@ -1312,8 +1312,15 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u 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)); + const std::string address = get_daemon_address(); + MINFO("setting daemon to " << address); + bool ret = m_http_client->set_server(address, get_daemon_login(), std::move(ssl_options)); + if (ret) + { + CRITICAL_REGION_LOCAL(default_daemon_address_lock); + default_daemon_address = address; + } + return ret; } //---------------------------------------------------------------------------------------------------- bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) @@ -1322,7 +1329,12 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils:: m_is_initialized = true; m_upper_transaction_weight_limit = upper_transaction_weight_limit; if (proxy != boost::asio::ip::tcp::endpoint{}) - m_http_client.set_connector(net::socks::connector{std::move(proxy)}); + { + epee::net_utils::http::abstract_http_client* abstract_http_client = m_http_client.get(); + epee::net_utils::http::http_simple_client* http_simple_client = dynamic_cast<epee::net_utils::http::http_simple_client*>(abstract_http_client); + CHECK_AND_ASSERT_MES(http_simple_client != nullptr, false, "http_simple_client must be used to set proxy"); + http_simple_client->set_connector(net::socks::connector{std::move(proxy)}); + } return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- @@ -1547,6 +1559,12 @@ void wallet2::expand_subaddresses(const cryptonote::subaddress_index& index) } } //---------------------------------------------------------------------------------------------------- +void wallet2::create_one_off_subaddress(const cryptonote::subaddress_index& index) +{ + const crypto::public_key pkey = get_subaddress_spend_public_key(index); + m_subaddresses[pkey] = index; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& index) const { if (index.major >= m_subaddress_labels.size() || index.minor >= m_subaddress_labels[index.major].size()) @@ -2088,7 +2106,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; - expand_subaddresses(tx_scan_info[o].received->index); + if (tx_scan_info[o].received->index.major < m_subaddress_labels.size() && tx_scan_info[o].received->index.minor < m_subaddress_labels[tx_scan_info[o].received->index.major].size()) + expand_subaddresses(tx_scan_info[o].received->index); if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; @@ -2166,7 +2185,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_amount = amount; td.m_pk_index = pk_index - 1; td.m_subaddr_index = tx_scan_info[o].received->index; - expand_subaddresses(tx_scan_info[o].received->index); + if (tx_scan_info[o].received->index.major < m_subaddress_labels.size() && tx_scan_info[o].received->index.minor < m_subaddress_labels[tx_scan_info[o].received->index.major].size()) + expand_subaddresses(tx_scan_info[o].received->index); if (tx.vout[o].amount == 0) { td.m_mask = tx_scan_info[o].mask; @@ -2579,7 +2599,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, 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); + 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 (" + @@ -2608,7 +2628,7 @@ void wallet2::pull_hashes(uint64_t start_height, uint64_t &blocks_start_height, 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); + 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); } @@ -2873,7 +2893,7 @@ void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashe } //---------------------------------------------------------------------------------------------------- -void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed) +void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed) { MTRACE("update_pool_state start"); @@ -2893,7 +2913,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b 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); + 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); } @@ -2933,7 +2953,6 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; // the inputs aren't spent anymore, since the tx failed - remove_rings(pit->second.m_tx); for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) { if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) @@ -3039,7 +3058,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b 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); + 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); } @@ -3063,7 +3082,7 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_txs.push_back(std::make_pair(tx, tx_entry.double_spend_seen)); + process_txs.push_back(std::make_tuple(tx, tx_hash, tx_entry.double_spend_seen)); } else { @@ -3094,14 +3113,14 @@ void wallet2::update_pool_state(std::vector<std::pair<cryptonote::transaction, b MTRACE("update_pool_state end"); } //---------------------------------------------------------------------------------------------------- -void wallet2::process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs) +void wallet2::process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs) { const time_t now = time(NULL); for (const auto &e: txs) { - const cryptonote::transaction &tx = e.first; - const bool double_spend_seen = e.second; - const crypto::hash tx_hash = get_transaction_hash(tx); + const cryptonote::transaction &tx = std::get<0>(e); + const crypto::hash &tx_hash = std::get<1>(e); + const bool double_spend_seen = std::get<2>(e); process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, 0, now, false, true, double_spend_seen, {}); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) @@ -3140,6 +3159,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, MERROR("Blocks start before blockchain offset: " << blocks_start_height << " " << m_blockchain.offset()); return; } + current_index = blocks_start_height; if (hashes.size() + current_index < stop_height) { drop_from_short_history(short_chain_history, 3); std::vector<crypto::hash>::iterator right = hashes.end(); @@ -3149,7 +3169,6 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, short_chain_history.push_front(*right); } } - current_index = blocks_start_height; for(auto& bl_id: hashes) { if(current_index >= m_blockchain.size()) @@ -3322,7 +3341,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo // since that might cause a password prompt, which would introduce a data // leak allowing a passive adversary with traffic analysis capability to // infer when we get an incoming output - std::vector<std::pair<cryptonote::transaction, bool>> process_pool_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_pool_txs; update_pool_state(process_pool_txs, true); bool first = true, last = false; @@ -3525,7 +3544,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t> 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); + 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); } @@ -3684,6 +3703,30 @@ void wallet2::clear_soft(bool keep_key_images) */ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only) { + boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(password, watch_only); + CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); + + std::string tmp_file_name = keys_file_name + ".new"; + std::string buf; + bool r = ::serialization::dump_binary(keys_file_data.get(), buf); + r = r && save_to_file(tmp_file_name, buf); + CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); + + unlock_keys_file(); + std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); + lock_keys_file(); + + if (e) { + boost::filesystem::remove(tmp_file_name); + LOG_ERROR("failed to update wallet keys file " << keys_file_name); + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) +{ std::string account_data; std::string multisig_signers; std::string multisig_derivations; @@ -3704,8 +3747,8 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable account.encrypt_keys(key); bool r = epee::serialization::store_t_to_binary(account, account_data); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); - wallet2::keys_file_data keys_file_data = {}; + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet keys"); + boost::optional<wallet2::keys_file_data> keys_file_data = (wallet2::keys_file_data) {}; // Create a JSON object with "key_data" and "seed_language" as keys. rapidjson::Document json; @@ -3736,12 +3779,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable if (m_multisig) { bool r = ::serialization::dump_binary(m_multisig_signers, multisig_signers); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig signers"); + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig signers"); value.SetString(multisig_signers.c_str(), multisig_signers.length()); json.AddMember("multisig_signers", value, json.GetAllocator()); r = ::serialization::dump_binary(m_multisig_derivations, multisig_derivations); - CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet multisig derivations"); + CHECK_AND_ASSERT_MES(r, boost::none, "failed to serialize wallet multisig derivations"); value.SetString(multisig_derivations.c_str(), multisig_derivations.length()); json.AddMember("multisig_derivations", value, json.GetAllocator()); @@ -3884,27 +3927,10 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable // Encrypt the entire JSON object. std::string cipher; cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand<crypto::chacha_iv>(); - crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); - keys_file_data.account_data = cipher; - - std::string tmp_file_name = keys_file_name + ".new"; - std::string buf; - r = ::serialization::dump_binary(keys_file_data, buf); - r = r && save_to_file(tmp_file_name, buf); - CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); - - unlock_keys_file(); - std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); - lock_keys_file(); - - if (e) { - boost::filesystem::remove(tmp_file_name); - LOG_ERROR("failed to update wallet keys file " << keys_file_name); - return false; - } - - return true; + keys_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.get().iv, &cipher[0]); + keys_file_data.get().account_data = cipher; + return keys_file_data; } //---------------------------------------------------------------------------------------------------- void wallet2::setup_keys(const epee::wipeable_string &password) @@ -3922,7 +3948,7 @@ void wallet2::setup_keys(const epee::wipeable_string &password) static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = CACHE_KEY_TAIL; + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); get_ringdb_key(); } @@ -3944,16 +3970,51 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ */ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_string& password) { - rapidjson::Document json; - wallet2::keys_file_data keys_file_data; - std::string buf; - bool encrypted_secret_keys = false; - bool r = load_from_file(keys_file_name, buf); + std::string keys_file_buf; + bool r = load_from_file(keys_file_name, keys_file_buf); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, keys_file_name); + // Load keys from buffer + boost::optional<crypto::chacha_key> keys_to_encrypt; + try { + r = wallet2::load_keys_buf(keys_file_buf, password, keys_to_encrypt); + } catch (const std::exception& e) { + std::size_t found = string(e.what()).find("failed to deserialize keys buffer"); + THROW_WALLET_EXCEPTION_IF(found != std::string::npos, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + throw e; + } + + // Rewrite with encrypted keys if unencrypted, ignore errors + if (r && keys_to_encrypt != boost::none) + { + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + encrypt_keys(keys_to_encrypt.get()); + bool saved_ret = store_keys(keys_file_name, password, m_watch_only); + if (!saved_ret) + { + // just moan a bit, but not fatal + MERROR("Error saving keys file with encrypted keys, not fatal"); + } + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + decrypt_keys(keys_to_encrypt.get()); + m_keys_file_locker.reset(); + } + return r; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password) { + boost::optional<crypto::chacha_key> keys_to_encrypt; + return wallet2::load_keys_buf(keys_buf, password, keys_to_encrypt); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt) { + // Decrypt the contents - r = ::serialization::parse_binary(buf, keys_file_data); - THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); + rapidjson::Document json; + wallet2::keys_file_data keys_file_data; + bool encrypted_secret_keys = false; + bool r = ::serialization::parse_binary(keys_buf, keys_file_data); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize keys buffer"); crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); std::string account_data; @@ -4087,9 +4148,18 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_always_confirm_transfers = field_always_confirm_transfers; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true); m_print_ring_members = field_print_ring_members; - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true); - GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true); - m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0)); + if (json.HasMember("store_tx_info")) + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, true, true); + m_store_tx_info = field_store_tx_info; + } + else if (json.HasMember("store_tx_keys")) // backward compatibility + { + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, true, true); + m_store_tx_info = field_store_tx_keys; + } + else + m_store_tx_info = true; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false, 0); m_default_mixin = field_default_mixin; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_priority, unsigned int, Uint, false, 0); @@ -4228,8 +4298,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } else { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password"); - return false; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "invalid password"); + return false; } r = epee::serialization::load_t_from_binary(m_account, account_data); @@ -4263,24 +4333,13 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ } else { - // rewrite with encrypted keys, ignore errors - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - encrypt_keys(key); - bool saved_ret = store_keys(keys_file_name, password, m_watch_only); - if (!saved_ret) - { - // just moan a bit, but not fatal - MERROR("Error saving keys file with encrypted keys, not fatal"); - } - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - decrypt_keys(key); - m_keys_file_locker.reset(); + keys_to_encrypt = key; } } const cryptonote::account_keys& keys = m_account.get_keys(); hw::device &hwdev = m_account.get_device(); r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if(!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) + if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); @@ -4371,12 +4430,18 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip void wallet2::encrypt_keys(const crypto::chacha_key &key) { + boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); + if (--m_decrypt_keys_lockers) // another lock left ? + return; m_account.encrypt_keys(key); m_account.decrypt_viewkey(key); } void wallet2::decrypt_keys(const crypto::chacha_key &key) { + boost::lock_guard<boost::mutex> lock(m_decrypt_keys_lock); + if (m_decrypt_keys_lockers++) // already unlocked ? + return; m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -4893,7 +4958,8 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, // re-encrypt keys keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + if (!m_wallet_file.empty()) + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); setup_new_blockchain(); @@ -5033,7 +5099,9 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor ++m_multisig_rounds_passed; - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + if (!m_wallet_file.empty()) + create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + return extra_multisig_info; } @@ -5407,13 +5475,13 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(!m_http_client.is_connected(ssl)) + if(!m_http_client->is_connected(ssl)) { m_rpc_version = 0; m_node_rpc_proxy.invalidate(); - if (!m_http_client.connect(std::chrono::milliseconds(timeout))) + if (!m_http_client->connect(std::chrono::milliseconds(timeout))) return false; - if(!m_http_client.is_connected(ssl)) + if(!m_http_client->is_connected(ssl)) return false; } } @@ -5441,12 +5509,12 @@ void wallet2::set_offline(bool offline) { m_offline = offline; m_node_rpc_proxy.set_offline(offline); - m_http_client.set_auto_connect(!offline); + m_http_client->set_auto_connect(!offline); if (offline) { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - if(m_http_client.is_connected()) - m_http_client.disconnect(); + if(m_http_client->is_connected()) + m_http_client->disconnect(); } } //---------------------------------------------------------------------------------------------------- @@ -5461,48 +5529,63 @@ void wallet2::generate_chacha_key_from_password(const epee::wipeable_string &pas crypto::generate_chacha_key(pass.data(), pass.size(), key, m_kdf_rounds); } //---------------------------------------------------------------------------------------------------- -void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password) +void wallet2::load(const std::string& wallet_, const epee::wipeable_string& password, const std::string& keys_buf, const std::string& cache_buf) { clear(); prepare_file_names(wallet_); + // determine if loading from file system or string buffer + bool use_fs = !wallet_.empty(); + THROW_WALLET_EXCEPTION_IF((use_fs && !keys_buf.empty()) || (!use_fs && keys_buf.empty()), error::file_read_error, "must load keys either from file system or from buffer");\ + boost::system::error_code e; - bool exists = boost::filesystem::exists(m_keys_file, e); - THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); - lock_keys_file(); - THROW_WALLET_EXCEPTION_IF(!is_keys_file_locked(), error::wallet_internal_error, "internal error: \"" + m_keys_file + "\" is opened by another wallet program"); + if (use_fs) + { + bool exists = boost::filesystem::exists(m_keys_file, e); + THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); + lock_keys_file(); + THROW_WALLET_EXCEPTION_IF(!is_keys_file_locked(), error::wallet_internal_error, "internal error: \"" + m_keys_file + "\" is opened by another wallet program"); - // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). - unlock_keys_file(); - if (!load_keys(m_keys_file, password)) + // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). + unlock_keys_file(); + if (!load_keys(m_keys_file, password)) + { + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); + } + LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); + lock_keys_file(); + } + else if (!load_keys_buf(keys_buf, password)) { - THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer"); } - LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); - lock_keys_file(); wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); //keys loaded ok! //try to load wallet file. but even if we failed, it is not big problem - if(!boost::filesystem::exists(m_wallet_file, e) || e) + if (use_fs && (!boost::filesystem::exists(m_wallet_file, e) || e)) { LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; } - else + else if (use_fs || !cache_buf.empty()) { wallet2::cache_file_data cache_file_data; - std::string buf; - bool r = load_from_file(m_wallet_file, buf, std::numeric_limits<size_t>::max()); - THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + std::string cache_file_buf; + bool r = true; + if (use_fs) + { + load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + } // try to read it as an encrypted cache try { LOG_PRINT_L1("Trying to decrypt cache data"); - r = ::serialization::parse_binary(buf, cache_file_data); + r = ::serialization::parse_binary(use_fs ? cache_file_buf : cache_buf, cache_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); @@ -5539,7 +5622,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); std::stringstream iss; iss.str(""); iss << cache_data; @@ -5554,17 +5637,17 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); try { std::stringstream iss; - iss << buf; + iss << cache_file_buf; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); std::stringstream iss; iss.str(""); - iss << buf; + iss << cache_file_buf; boost::archive::binary_iarchive ar(iss); ar >> *this; } @@ -5608,7 +5691,8 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass try { - m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); + if (use_fs) + m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file); } catch (const std::exception &e) { @@ -5636,7 +5720,7 @@ void wallet2::trim_hashchain() 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); + 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); } @@ -5711,18 +5795,10 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } } - // preparing wallet data - std::stringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - ar << *this; - wallet2::cache_file_data cache_file_data = {}; - cache_file_data.cache_data = oss.str(); - std::string cipher; - cipher.resize(cache_file_data.cache_data.size()); - cache_file_data.iv = crypto::rand<crypto::chacha_iv>(); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cipher[0]); - cache_file_data.cache_data = cipher; + // get wallet cache data + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; const std::string old_file = m_wallet_file; @@ -5773,7 +5849,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas // The price to pay is temporary higher memory consumption for string stream + binary archive std::ostringstream oss; binary_archive<true> oar(oss); - bool success = ::serialization::serialize(oar, cache_file_data); + bool success = ::serialization::serialize(oar, cache_file_data.get()); if (success) { success = save_to_file(new_file, oss.str()); } @@ -5782,7 +5858,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas std::ofstream ostr; ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); binary_archive<true> oar(ostr); - bool success = ::serialization::serialize(oar, cache_file_data); + bool success = ::serialization::serialize(oar, cache_file_data.get()); ostr.close(); THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif @@ -5798,7 +5874,30 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas // store should only exist if the MMS is really active m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file); } - +} +//---------------------------------------------------------------------------------------------------- +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +{ + trim_hashchain(); + try + { + std::stringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + ar << *this; + + boost::optional<wallet2::cache_file_data> cache_file_data = (wallet2::cache_file_data) {}; + cache_file_data.get().cache_data = oss.str(); + std::string cipher; + cipher.resize(cache_file_data.get().cache_data.size()); + cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]); + cache_file_data.get().cache_data = cipher; + return cache_file_data; + } + catch(...) + { + return boost::none; + } } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::balance(uint32_t index_major, bool strict) const @@ -6002,7 +6101,7 @@ void wallet2::rescan_spent() 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); + 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 = " + @@ -6331,7 +6430,7 @@ void wallet2::commit_tx(pending_tx& ptx) oreq.tx = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); { 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"); + 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); @@ -6350,7 +6449,7 @@ void wallet2::commit_tx(pending_tx& ptx) 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); + 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); } @@ -7127,6 +7226,20 @@ 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::estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const +{ + if (use_per_byte_fee) + { + const size_t estimated_tx_weight = estimate_tx_weight(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + return calculate_fee_from_weight(base_fee, estimated_tx_weight, fee_multiplier, fee_quantization_mask); + } + else + { + const size_t estimated_tx_size = estimate_tx_size(use_rct, n_inputs, mixin, n_outputs, extra_size, bulletproof); + return calculate_fee(base_fee, estimated_tx_size, fee_multiplier); + } +} + uint64_t wallet2::get_fee_multiplier(uint32_t priority, int fee_algorithm) { static const struct @@ -7308,7 +7421,7 @@ uint32_t wallet2::adjust_priority(uint32_t priority) 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); + 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); } @@ -7534,7 +7647,7 @@ bool wallet2::find_and_save_rings(bool force) 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("/gettransactions", req, res, m_http_client, rpc_timeout); + 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 = " + @@ -7682,7 +7795,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ { 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"); + 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); @@ -7768,8 +7881,50 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } } +std::pair<std::set<uint64_t>, size_t> outs_unique(const std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs) +{ + std::set<uint64_t> unique; + size_t total = 0; + + for (const auto &it : outs) + { + for (const auto &out : it) + { + const uint64_t global_index = std::get<0>(out); + unique.insert(global_index); + } + total += it.size(); + } + + return std::make_pair(std::move(unique), total); +} + void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { + std::vector<uint64_t> rct_offsets; + for (size_t attempts = 3; attempts > 0; --attempts) + { + get_outs(outs, selected_transfers, fake_outputs_count, rct_offsets); + + const auto unique = outs_unique(outs); + if (tx_sanity_check(unique.first, unique.second, rct_offsets.empty() ? 0 : rct_offsets.back())) + { + return; + } + + std::vector<crypto::key_image> key_images; + key_images.reserve(selected_transfers.size()); + std::for_each(selected_transfers.begin(), selected_transfers.end(), [this, &key_images](size_t index) { + key_images.push_back(m_transfers[index].m_key_image); + }); + unset_ring(key_images); + } + + THROW_WALLET_EXCEPTION(error::wallet_internal_error, tr("Transaction sanity check failed")); +} + +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets) +{ LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -7790,7 +7945,6 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // if we have at least one rct out, get the distribution, or fall back to the previous system uint64_t rct_start_height; - std::vector<uint64_t> rct_offsets; bool has_rct = false; uint64_t max_rct_index = 0; for (size_t idx: selected_transfers) @@ -7799,7 +7953,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> has_rct = true; max_rct_index = std::max(max_rct_index, m_transfers[idx].m_global_output_index); } - const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets); + const bool has_rct_distribution = has_rct && (!rct_offsets.empty() || get_rct_distribution(rct_start_height, rct_offsets)); if (has_rct_distribution) { // check we're clear enough of rct start, to avoid corner cases below @@ -7828,7 +7982,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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); + 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()); } @@ -7854,7 +8008,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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); + 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); @@ -8208,7 +8362,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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); + 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 = " + @@ -10417,7 +10571,7 @@ uint8_t wallet2::get_current_hard_fork() m_daemon_rpc_mutex.lock(); req_t.version = 0; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout); + 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(); THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, "hard_fork_info"); THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "hard_fork_info"); @@ -10512,7 +10666,7 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co 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); + 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); @@ -10554,7 +10708,7 @@ uint64_t wallet2::get_num_rct_outputs() 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); + 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"); @@ -10685,7 +10839,7 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s 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); + 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); @@ -10738,7 +10892,7 @@ void wallet2::set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_ 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); + 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 = " + @@ -10791,7 +10945,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string 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); + 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 = " + @@ -10855,7 +11009,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string 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); + 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 = " + @@ -10913,7 +11067,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes 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); + 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 = " + @@ -10988,7 +11142,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes 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); + 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 = " + @@ -11090,7 +11244,7 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de 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); + 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); @@ -11145,7 +11299,7 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac 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); + 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); @@ -11306,7 +11460,7 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account 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); + 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); @@ -11603,7 +11757,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr 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); + 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); @@ -11620,7 +11774,7 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr 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); + 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); @@ -12194,7 +12348,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag 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); + 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 = " + @@ -12283,7 +12437,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag 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); + 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())); @@ -12601,7 +12755,8 @@ process: const crypto::public_key& out_key = boost::get<cryptonote::txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); - expand_subaddresses(td.m_subaddr_index); + if (td.m_subaddr_index.major < m_subaddress_labels.size() && td.m_subaddr_index.minor < m_subaddress_labels[td.m_subaddr_index.major].size()) + expand_subaddresses(td.m_subaddr_index); td.m_key_image_known = true; td.m_key_image_request = true; td.m_key_image_partial = false; @@ -13223,7 +13378,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui 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); + 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); } @@ -13301,7 +13456,7 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: 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); + 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()); } @@ -13640,12 +13795,12 @@ void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const c //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_bytes_sent() const { - return m_http_client.get_bytes_sent(); + return m_http_client->get_bytes_sent(); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_bytes_received() const { - return m_http_client.get_bytes_received(); + return m_http_client->get_bytes_received(); } //---------------------------------------------------------------------------------------------------- std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only) @@ -13658,7 +13813,7 @@ std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool 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); + 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"); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 810c002fe..1c3c00152 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -269,7 +269,7 @@ private: static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); - wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false); + wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_simple_client_factory>(new epee::net_utils::http::http_simple_client_factory())); ~wallet2(); struct multisig_info @@ -708,7 +708,7 @@ private: */ void rewrite(const std::string& wallet_name, const epee::wipeable_string& password); void write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password, std::string &new_keys_filename); - void load(const std::string& wallet, const epee::wipeable_string& password); + void load(const std::string& wallet, const epee::wipeable_string& password, const std::string& keys_buf = "", const std::string& cache_buf = ""); void store(); /*! * \brief store_to Stores wallet to another file(s), deleting old ones @@ -716,6 +716,19 @@ private: * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); + /*! + * \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file. + * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?) + * \param watch_only true to include only view key, false to include both spend and view keys + * \return Encrypted wallet keys data which can be stored to a wallet file + */ + boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only); + /*! + * \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file. + * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?) + * \return Encrypted wallet cache data which can be stored to a wallet file + */ + boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password); std::string path() const; @@ -793,6 +806,7 @@ private: size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; } void add_subaddress(uint32_t index_major, const std::string& label); // throws when index is out of bound void expand_subaddresses(const cryptonote::subaddress_index& index); + void create_one_off_subaddress(const cryptonote::subaddress_index& index); std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_lookahead(size_t major, size_t minor); @@ -1224,8 +1238,8 @@ private: bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; - void update_pool_state(std::vector<std::pair<cryptonote::transaction, bool>> &process_txs, bool refreshed = false); - void process_pool_state(const std::vector<std::pair<cryptonote::transaction, bool>> &txs); + void update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed = false); + void process_pool_state(const std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &txs); void remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes); std::string encrypt(const char *plaintext, size_t len, const crypto::secret_key &skey, bool authenticated = true) const; @@ -1246,6 +1260,7 @@ private: 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 estimate_fee(bool use_per_byte_fee, bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size, bool bulletproof, uint64_t base_fee, uint64_t fee_multiplier, uint64_t fee_quantization_mask) const; uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_base_fee(); uint64_t get_fee_quantization_mask(); @@ -1317,25 +1332,25 @@ private: crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; template<class t_request, class t_response> - inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_json(uri, req, res, m_http_client, timeout, http_method); + return epee::net_utils::invoke_http_json(uri, req, res, *m_http_client, timeout, http_method); } template<class t_request, class t_response> - inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_bin(uri, req, res, m_http_client, timeout, http_method); + return epee::net_utils::invoke_http_bin(uri, req, res, *m_http_client, timeout, http_method); } template<class t_request, class t_response> - inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0") + inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "POST", const std::string& req_id = "0") { if (m_offline) return false; boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); - return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id); + return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, *m_http_client, timeout, http_method, req_id); } bool set_ring_database(const std::string &filename); @@ -1389,6 +1404,8 @@ private: 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; } + static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } + private: /*! * \brief Stores wallet information to wallet file. @@ -1399,11 +1416,18 @@ private: */ bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false); /*! - * \brief Load wallet information from wallet file. + * \brief Load wallet keys information from wallet file. * \param keys_file_name Name of wallet file * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password); + /*! + * \brief Load wallet keys information from a string buffer. + * \param keys_buf Keys buffer to load + * \param password Password of keys buffer + */ + bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); + bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt); 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); @@ -1440,6 +1464,7 @@ private: bool is_spent(const transfer_details &td, bool strict = true) const; bool is_spent(size_t idx, bool strict = true) const; void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count, std::vector<uint64_t> &rct_offsets); bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; bool should_pick_a_second_output(bool use_rct, size_t n_transfers, const std::vector<size_t> &unused_transfers_indices, const std::vector<size_t> &unused_dust_indices) const; std::vector<size_t> get_only_rct(const std::vector<size_t> &unused_dust_indices, const std::vector<size_t> &unused_transfers_indices) const; @@ -1497,7 +1522,7 @@ private: std::string m_wallet_file; std::string m_keys_file; std::string m_mms_file; - epee::net_utils::http::http_simple_client m_http_client; + const std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client; hashchain m_blockchain; std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; @@ -1618,6 +1643,8 @@ private: crypto::chacha_key m_cache_key; boost::optional<epee::wipeable_string> m_encrypt_keys_after_refresh; + boost::mutex m_decrypt_keys_lock; + unsigned int m_decrypt_keys_lockers; bool m_unattended; bool m_devices_registered; @@ -1626,6 +1653,9 @@ private: std::unique_ptr<wallet_device_callback> m_device_callback; ExportFormat m_export_format; + + static boost::mutex default_daemon_address_lock; + static std::string default_daemon_address; }; } BOOST_CLASS_VERSION(tools::wallet2, 29) diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index 41696d13b..4f5364269 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -85,7 +85,7 @@ bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credit 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); + 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"); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 2a051553b..db2e2344b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1180,7 +1180,6 @@ namespace tools } } - std::vector<tools::wallet2::pending_tx> ptx; try { // gather info to ask the user @@ -1417,11 +1416,22 @@ namespace tools return false; } + std::set<uint32_t> subaddr_indices; + if (req.subaddr_indices_all) + { + for (uint32_t i = 0; i < m_wallet->get_num_subaddresses(req.account_index); ++i) + subaddr_indices.insert(i); + } + else + { + subaddr_indices= req.subaddr_indices; + } + try { uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, subaddr_indices); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_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); @@ -2461,7 +2471,7 @@ namespace tools if (req.pool) { - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); @@ -2544,7 +2554,7 @@ namespace tools } } - std::vector<std::pair<cryptonote::transaction, bool>> process_txs; + std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs; m_wallet->update_pool_state(process_txs); if (!process_txs.empty()) m_wallet->process_pool_state(process_txs); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 614e7af08..a212b79e6 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -761,6 +761,7 @@ namespace wallet_rpc std::string address; uint32_t account_index; std::set<uint32_t> subaddr_indices; + bool subaddr_indices_all; uint32_t priority; uint64_t ring_size; uint64_t outputs; @@ -776,6 +777,7 @@ namespace wallet_rpc KV_SERIALIZE(address) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE_OPT(subaddr_indices_all, false) KV_SERIALIZE(priority) KV_SERIALIZE_OPT(ring_size, (uint64_t)0) KV_SERIALIZE_OPT(outputs, (uint64_t)1) |