diff options
Diffstat (limited to 'src')
55 files changed, 1910 insertions, 428 deletions
diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 136d4fa80..a6774a25c 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -31,6 +31,7 @@ #include "blockchain_db.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "profile_tools.h" +#include "ringct/rctOps.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "blockchain.db" diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 6bb96d1db..0073ddf54 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -37,6 +37,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "crypto/crypto.h" #include "profile_tools.h" +#include "ringct/rctOps.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "blockchain.db.lmdb" diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index ab38cbbae..e7ff11c5c 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -26,12 +26,9 @@ // 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 "common/command_line.h" -#include "common/i18n.h" #include "common/dns_utils.h" +#include "common/i18n.h" #include "cryptonote_basic/cryptonote_basic_impl.h" -#include <cstring> -#include <sstream> // check local first (in the event of static or in-source compilation of libunbound) #include "unbound.h" @@ -307,12 +304,8 @@ DNSResolver& DNSResolver::instance() { boost::lock_guard<boost::mutex> lock(instance_lock); - static DNSResolver* staticInstance = NULL; - if (staticInstance == NULL) - { - staticInstance = new DNSResolver(); - } - return *staticInstance; + static DNSResolver staticInstance; + return staticInstance; } DNSResolver DNSResolver::create() @@ -405,7 +398,7 @@ std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec return addresses; } -std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, bool cli_confirm) +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm) { // attempt to get address from dns query auto addresses = addresses_from_url(url, dnssec_valid); @@ -414,44 +407,7 @@ std::string get_account_address_as_str_from_url(const std::string& url, bool& dn LOG_ERROR("wrong address: " << url); return {}; } - // for now, move on only if one address found - if (addresses.size() > 1) - { - LOG_ERROR("not yet supported: Multiple Monero addresses found for given URL: " << url); - return {}; - } - if (!cli_confirm) - return addresses[0]; - // prompt user for confirmation. - // inform user of DNSSEC validation status as well. - std::string dnssec_str; - if (dnssec_valid) - { - dnssec_str = tr("DNSSEC validation passed"); - } - else - { - dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); - } - std::stringstream prompt; - prompt << tr("For URL: ") << url - << ", " << dnssec_str << std::endl - << tr(" Monero Address = ") << addresses[0] - << std::endl - << tr("Is this OK? (Y/n) ") - ; - // prompt the user for confirmation given the dns query and dnssec status - std::string confirm_dns_ok = command_line::input_line(prompt.str()); - if (std::cin.eof()) - { - return {}; - } - if (!command_line::is_yes(confirm_dns_ok)) - { - std::cout << tr("you have cancelled the transfer request") << std::endl; - return {}; - } - return addresses[0]; + return dns_confirm(url, addresses, dnssec_valid); } namespace diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 53c0c1c7b..61e1358ca 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -29,7 +29,6 @@ #include <vector> #include <string> -#include "cryptonote_basic/cryptonote_basic.h" namespace tools { @@ -101,7 +100,7 @@ public: * * @return A vector of strings containing a TXT record; or an empty vector */ - // TODO: modify this to accomodate DNSSEC + // TODO: modify this to accommodate DNSSEC std::vector<std::string> get_txt_record(const std::string& url, bool& dnssec_available, bool& dnssec_valid); /** @@ -142,7 +141,7 @@ private: * * @return A vector of strings containing the requested record; or an empty vector */ - // TODO: modify this to accomodate DNSSEC + // TODO: modify this to accommodate DNSSEC std::vector<std::string> get_record(const std::string& url, int record_type, std::string (*reader)(const char *,size_t), bool& dnssec_available, bool& dnssec_valid); /** @@ -163,7 +162,7 @@ namespace dns_utils std::string address_from_txt_record(const std::string& s); std::vector<std::string> addresses_from_url(const std::string& url, bool& dnssec_valid); -std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, bool cli_confirm = true); +std::string get_account_address_as_str_from_url(const std::string& url, bool& dnssec_valid, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> confirm_dns); bool load_txt_records_from_dns(std::vector<std::string> &records, const std::vector<std::string> &dns_urls); diff --git a/src/common/scoped_message_writer.h b/src/common/scoped_message_writer.h index 7ee4f1379..e31f8f0b2 100644 --- a/src/common/scoped_message_writer.h +++ b/src/common/scoped_message_writer.h @@ -91,7 +91,7 @@ public: { m_flush = false; - MCLOG(m_log_level, "msgwriter", m_oss.str()); + MCLOG_FILE(m_log_level, "msgwriter", m_oss.str()); if (epee::console_color_default == m_color) { diff --git a/src/common/stack_trace.cpp b/src/common/stack_trace.cpp index ef64c20c5..6fdf4dd47 100644 --- a/src/common/stack_trace.cpp +++ b/src/common/stack_trace.cpp @@ -30,8 +30,7 @@ #define USE_UNWIND #endif -#include "common/stack_trace.h" -#include "misc_log_ex.h" +#include <stdexcept> #ifdef USE_UNWIND #define UNW_LOCAL_ONLY #include <libunwind.h> @@ -40,6 +39,8 @@ #ifndef STATICLIB #include <dlfcn.h> #endif +#include "common/stack_trace.h" +#include "misc_log_ex.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "stacktrace" diff --git a/src/common/stack_trace.h b/src/common/stack_trace.h index 25eec9fb3..0f6bdc08b 100644 --- a/src/common/stack_trace.h +++ b/src/common/stack_trace.h @@ -29,7 +29,6 @@ #ifndef MONERO_EXCEPTION_H #define MONERO_EXCEPTION_H -#include <stdexcept> #include <string> namespace tools diff --git a/src/common/util.h b/src/common/util.h index 4291d7e18..2452bc9d5 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -40,7 +40,6 @@ #include <string> #include "crypto/hash.h" -#include "p2p/p2p_protocol_defs.h" /*! \brief Various Tools * @@ -108,14 +107,6 @@ namespace tools bool sanitize_locale(); - 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()); - } - /*! \brief Defines a signal handler for win32 and *nix */ class signal_handler diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 8d739900e..fefd91e1a 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -53,6 +53,7 @@ namespace cryptonote std::unordered_set<crypto::hash> m_requested_objects; uint64_t m_remote_blockchain_height; uint64_t m_last_response_height; + boost::posix_time::ptime m_last_request_time; epee::copyable_atomic m_callback_request_count; //in debug purpose: problem with double callback rise //size_t m_score; TODO: add score calculations }; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index edd67793f..a59f96956 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -308,13 +308,13 @@ namespace cryptonote { , crypto::hash8& payment_id , bool testnet , const std::string& str_or_url - , bool cli_confirm + , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm ) { if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url)) return true; bool dnssec_valid; - std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, cli_confirm); + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm); return !address_str.empty() && get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str); } @@ -323,12 +323,12 @@ namespace cryptonote { cryptonote::account_public_address& address , bool testnet , const std::string& str_or_url - , bool cli_confirm + , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm ) { bool has_payment_id; crypto::hash8 payment_id; - return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, cli_confirm); + return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, dns_confirm); } //-------------------------------------------------------------------------------- bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) { diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 14c03ac4c..9838fcb47 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -67,6 +67,15 @@ namespace cryptonote { }; #pragma pack (pop) + namespace + { + std::string return_first_address(const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid) + { + if (addresses.empty()) + return {}; + return addresses[0]; + } + } /************************************************************************/ /* Cryptonote helper functions */ @@ -109,14 +118,14 @@ namespace cryptonote { , crypto::hash8& payment_id , bool testnet , const std::string& str_or_url - , bool cli_confirm = true + , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address ); bool get_account_address_from_str_or_url( cryptonote::account_public_address& address , bool testnet , const std::string& str_or_url - , bool cli_confirm = true + , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address ); bool is_coinbase(const transaction& tx); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 5c10907fd..d8ccf8eec 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -35,7 +35,6 @@ #include "include_base_utils.h" #include "crypto/crypto.h" #include "crypto/hash.h" -#include "ringct/rctOps.h" namespace cryptonote { diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index cd0943618..9248e2e1d 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -846,40 +846,100 @@ namespace cryptonote #elif defined(__linux__) - // i've only tested on UBUNTU, these paths might be different on other systems - // need to figure out a way to make this more flexible - std::string power_supply_path = ""; - const std::string POWER_SUPPLY_STATUS_PATHS[] = - { - "/sys/class/power_supply/ACAD/online", - "/sys/class/power_supply/AC/online", - "/sys/class/power_supply/AC0/online", - "/sys/class/power_supply/ADP0/online" - }; + // Use the power_supply class http://lxr.linux.no/#linux+v4.10.1/Documentation/power/power_supply_class.txt + std::string power_supply_class_path = "/sys/class/power_supply"; - for(const std::string& path : POWER_SUPPLY_STATUS_PATHS) + boost::tribool on_battery = boost::logic::tribool(boost::logic::indeterminate); + if (boost::filesystem::is_directory(power_supply_class_path)) { - if( epee::file_io_utils::is_file_exist(path) ) + const boost::filesystem::directory_iterator end_itr; + for (boost::filesystem::directory_iterator iter(power_supply_class_path); iter != end_itr; ++iter) { - power_supply_path = path; - break; + const boost::filesystem::path& power_supply_path = iter->path(); + if (boost::filesystem::is_directory(power_supply_path)) + { + std::ifstream power_supply_present_stream((power_supply_path / "present").string()); + if (power_supply_present_stream.fail()) + { + LOG_PRINT_L0("Unable to read from " << power_supply_path << " to check if power supply present"); + continue; + } + + if (power_supply_present_stream.get() != '1') + { + LOG_PRINT_L4("Power supply not present at " << power_supply_path); + continue; + } + + boost::filesystem::path power_supply_type_path = power_supply_path / "type"; + if (boost::filesystem::is_regular_file(power_supply_type_path)) + { + std::ifstream power_supply_type_stream(power_supply_type_path.string()); + if (power_supply_type_stream.fail()) + { + LOG_PRINT_L0("Unable to read from " << power_supply_type_path << " to check power supply type"); + continue; + } + + std::string power_supply_type; + std::getline(power_supply_type_stream, power_supply_type); + + // If there is an AC adapter that's present and online we can break early + if (boost::starts_with(power_supply_type, "Mains")) + { + boost::filesystem::path power_supply_online_path = power_supply_path / "online"; + if (boost::filesystem::is_regular_file(power_supply_online_path)) + { + std::ifstream power_supply_online_stream(power_supply_online_path.string()); + if (power_supply_online_stream.fail()) + { + LOG_PRINT_L0("Unable to read from " << power_supply_online_path << " to check ac power supply status"); + continue; + } + + if (power_supply_online_stream.get() == '1') + { + return boost::logic::tribool(false); + } + } + } + else if (boost::starts_with(power_supply_type, "Battery") && boost::logic::indeterminate(on_battery)) + { + boost::filesystem::path power_supply_status_path = power_supply_path / "status"; + if (boost::filesystem::is_regular_file(power_supply_status_path)) + { + std::ifstream power_supply_status_stream(power_supply_status_path.string()); + if (power_supply_status_stream.fail()) + { + LOG_PRINT_L0("Unable to read from " << power_supply_status_path << " to check battery power supply status"); + continue; + } + + // Possible status are Charging, Full, Discharging, Not Charging, and Unknown + // We are only need to handle negative states right now + std::string power_supply_status; + std::getline(power_supply_status_stream, power_supply_status); + if (boost::starts_with(power_supply_status, "Charging") || boost::starts_with(power_supply_status, "Full")) + { + on_battery = boost::logic::tribool(false); + } + + if (boost::starts_with(power_supply_status, "Discharging")) + { + on_battery = boost::logic::tribool(true); + } + } + } + } + } } } - if( power_supply_path.empty() ) + if (boost::logic::indeterminate(on_battery)) { - LOG_ERROR("Couldn't find battery/power status file, can't determine if plugged in!"); - return boost::logic::tribool(boost::logic::indeterminate);; + LOG_ERROR("couldn't query power status from " << power_supply_class_path); } - - std::ifstream power_stream(power_supply_path); - if( power_stream.fail() ) - { - LOG_ERROR("failed to open '" << power_supply_path << "'"); - return boost::logic::tribool(boost::logic::indeterminate);; - } - - return boost::logic::tribool( (power_stream.get() != '1') ); + return on_battery; #endif diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 745608b9f..792bee8d1 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -118,6 +118,8 @@ static const struct { { 3, 800500, 0, 1472415034 }, { 4, 801219, 0, 1472415035 }, { 5, 802660, 0, 1472415036 + 86400*180 }, // add 5 months on testnet to shut the update warning up since there's a large gap to v6 + + { 6, 971400, 0, 1501709789 }, }; static const uint64_t testnet_hard_fork_version_1_till = 624633; @@ -279,7 +281,8 @@ uint64_t Blockchain::get_current_blockchain_height() const bool Blockchain::init(BlockchainDB* db, const bool testnet, const cryptonote::test_options *test_options) { LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); + CRITICAL_REGION_LOCAL(m_tx_pool); + CRITICAL_REGION_LOCAL1(m_blockchain_lock); bool fakechain = test_options != NULL; @@ -3097,6 +3100,8 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& CRITICAL_REGION_LOCAL(m_blockchain_lock); TIME_MEASURE_START(t1); + static bool seen_future_version = false; + m_db->block_txn_start(true); if(bl.prev_id != get_tail_id()) { @@ -3106,6 +3111,18 @@ leave: return false; } + // warn users if they're running an old version + if (!seen_future_version && bl.major_version > m_hardfork->get_ideal_version()) + { + seen_future_version = true; + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "**********************************************************************"); + MCLOG_RED(level, "global", "A block was seen on the network with a version higher than the last"); + MCLOG_RED(level, "global", "known one. This may be an old version of the daemon, and a software"); + MCLOG_RED(level, "global", "update may be required to sync further. Try running: update check"); + MCLOG_RED(level, "global", "**********************************************************************"); + } + // this is a cheap test if (!m_hardfork->check(bl)) { @@ -3541,19 +3558,17 @@ void Blockchain::set_enforce_dns_checkpoints(bool enforce_checkpoints) } //------------------------------------------------------------------ -void Blockchain::block_longhash_worker(const uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const +void Blockchain::block_longhash_worker(uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const { TIME_MEASURE_START(t); slow_hash_allocate_state(); - //FIXME: height should be changing here, as get_block_longhash expects - // the height of the block passed to it for (const auto & block : blocks) { if (m_cancel) - return; + break; crypto::hash id = get_block_hash(block); - crypto::hash pow = get_block_longhash(block, height); + crypto::hash pow = get_block_longhash(block, height++); map.emplace(id, pow); } @@ -3745,9 +3760,11 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::list<block_complete_e if (!blocks_exist) { m_blocks_longhash_table.clear(); + uint64_t thread_height = height; for (uint64_t i = 0; i < threads; i++) { - thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, height + (i * batches), std::cref(blocks[i]), std::ref(maps[i])))); + thread_list.push_back(new boost::thread(attrs, boost::bind(&Blockchain::block_longhash_worker, this, thread_height, std::cref(blocks[i]), std::ref(maps[i])))); + thread_height += blocks[i].size(); } for (size_t j = 0; j < thread_list.size(); j++) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 4f2e4f0d3..564b53af3 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -846,7 +846,7 @@ namespace cryptonote * @param blocks the blocks to be hashed * @param map return-by-reference the hashes for each block */ - void block_longhash_worker(const uint64_t height, const std::vector<block> &blocks, + void block_longhash_worker(uint64_t height, const std::vector<block> &blocks, std::unordered_map<crypto::hash, crypto::hash> &map) const; /** diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 4cfa52441..75d5f7a13 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -37,6 +37,7 @@ using namespace epee; #include "common/util.h" #include "common/updates.h" #include "common/download.h" +#include "common/task_region.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" @@ -76,6 +77,7 @@ namespace cryptonote m_checkpoints_path(""), m_last_dns_checkpoints_update(0), m_last_json_checkpoints_update(0), + m_threadpool(tools::thread_group::optimal()), m_update_download(0) { m_checkpoints_updating.clear(); @@ -414,6 +416,8 @@ namespace cryptonote if (block_sync_size == 0) block_sync_size = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; + MGINFO("Loading checkpoints"); + // load json & DNS checkpoints, and verify them // with respect to what blocks we already have CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); @@ -483,11 +487,9 @@ namespace cryptonote return false; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay) { tvc = boost::value_initialized<tx_verification_context>(); - //want to process all transactions sequentially - CRITICAL_REGION_LOCAL(m_incoming_tx_lock); if(tx_blob.size() > get_max_tx_size()) { @@ -497,9 +499,8 @@ namespace cryptonote return false; } - crypto::hash tx_hash = null_hash; - crypto::hash tx_prefixt_hash = null_hash; - transaction tx; + tx_hash = null_hash; + tx_prefixt_hash = null_hash; if(!parse_tx_from_blob(tx, tx_hash, tx_prefixt_hash, tx_blob)) { @@ -509,15 +510,18 @@ namespace cryptonote } //std::cout << "!"<< tx.vin.size() << std::endl; + bad_semantics_txes_lock.lock(); for (int idx = 0; idx < 2; ++idx) { if (bad_semantics_txes[idx].find(tx_hash) != bad_semantics_txes[idx].end()) { + bad_semantics_txes_lock.unlock(); LOG_PRINT_L1("Transaction already seen with bad semantics, rejected"); tvc.m_verifivation_failed = true; return false; } } + bad_semantics_txes_lock.unlock(); uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); const size_t max_tx_version = version == 1 ? 1 : 2; @@ -528,18 +532,11 @@ namespace cryptonote return false; } - if(m_mempool.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); - return true; - } - - if(m_blockchain_storage.have_tx(tx_hash)) - { - LOG_PRINT_L2("tx " << tx_hash << " already have transaction in blockchain"); - return true; - } - + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + { if(!check_tx_syntax(tx)) { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); @@ -568,23 +565,84 @@ namespace cryptonote { LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); tvc.m_verifivation_failed = true; + bad_semantics_txes_lock.lock(); bad_semantics_txes[0].insert(tx_hash); if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE) { std::swap(bad_semantics_txes[0], bad_semantics_txes[1]); bad_semantics_txes[0].clear(); } + bad_semantics_txes_lock.unlock(); return false; } - bool r = add_new_tx(tx, tx_hash, tx_prefixt_hash, tx_blob.size(), tvc, keeped_by_block, relayed, do_not_relay); - if(tvc.m_verifivation_failed) - {MERROR_VER("Transaction verification failed: " << tx_hash);} - else if(tvc.m_verifivation_impossible) - {MERROR_VER("Transaction verification impossible: " << tx_hash);} + return true; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + { + struct result { bool res; cryptonote::transaction tx; crypto::hash hash; crypto::hash prefix_hash; bool in_txpool; bool in_blockchain; }; + std::vector<result> results(tx_blobs.size()); + + tvc.resize(tx_blobs.size()); + tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + region.run([&, i, it] { + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + }); + } + }); + tools::task_region(m_threadpool, [&] (tools::task_region_handle& region) { + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + continue; + if(m_mempool.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); + } + else if(m_blockchain_storage.have_tx(results[i].hash)) + { + LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain"); + } + else + { + region.run([&, i, it] { + results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, results[i].prefix_hash, keeped_by_block, relayed, do_not_relay); + }); + } + } + }); + + bool ok = true; + std::list<blobdata>::const_iterator it = tx_blobs.begin(); + for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { + if (!results[i].res) + { + ok = false; + continue; + } + + ok &= add_new_tx(results[i].tx, results[i].hash, results[i].prefix_hash, it->size(), tvc[i], keeped_by_block, relayed, do_not_relay); + if(tvc[i].m_verifivation_failed) + {MERROR_VER("Transaction verification failed: " << results[i].hash);} + else if(tvc[i].m_verifivation_impossible) + {MERROR_VER("Transaction verification impossible: " << results[i].hash);} - if(tvc.m_added_to_pool) - MDEBUG("tx added: " << tx_hash); + if(tvc[i].m_added_to_pool) + MDEBUG("tx added: " << results[i].hash); + } + return ok; + } + //----------------------------------------------------------------------------------------------- + bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + { + std::list<cryptonote::blobdata> tx_blobs; + tx_blobs.push_back(tx_blob); + std::vector<tx_verification_context> tvcv(1); + bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay); + tvc = tvcv[0]; return r; } //----------------------------------------------------------------------------------------------- @@ -660,6 +718,12 @@ namespace cryptonote return false; } + if (!check_tx_inputs_ring_members_diff(tx)) + { + MERROR_VER("tx uses duplicate ring members"); + return false; + } + if (!check_tx_inputs_keyimages_domain(tx)) { MERROR_VER("tx uses key image not in the valid domain"); @@ -752,6 +816,22 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::check_tx_inputs_ring_members_diff(const transaction& tx) const + { + const uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); + if (version >= 6) + { + for(const auto& in: tx.vin) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + for (size_t n = 1; n < tokey_in.key_offsets.size(); ++n) + if (tokey_in.key_offsets[n] == 0) + return false; + } + } + return true; + } + //----------------------------------------------------------------------------------------------- bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const { std::unordered_set<crypto::key_image> ki; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index e5fbf7f91..4dbd51040 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -40,6 +40,7 @@ #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" +#include "common/thread_group.h" #include "tx_pool.h" #include "blockchain.h" #include "cryptonote_basic/miner.h" @@ -115,6 +116,22 @@ namespace cryptonote bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); /** + * @brief handles a list of incoming transactions + * + * Parses incoming transactions and, if nothing is obviously wrong, + * passes them along to the transaction pool + * + * @param tx_blobs the txs to handle + * @param tvc metadata about the transactions' validity + * @param keeped_by_block if the transactions have been in a block + * @param relayed whether or not the transactions were relayed to us + * @param do_not_relay whether to prevent the transactions from being relayed + * + * @return true if the transactions made it to the transaction pool, otherwise false + */ + bool handle_incoming_txs(const std::list<blobdata>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + + /** * @brief handles an incoming block * * periodic update to checkpoints is triggered here @@ -753,6 +770,9 @@ namespace cryptonote */ bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; + bool handle_incoming_tx_pre(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx_post(const blobdata& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, crypto::hash &tx_prefixt_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + /** * @copydoc miner::on_block_chain_update * @@ -781,6 +801,15 @@ namespace cryptonote bool check_tx_inputs_keyimages_diff(const transaction& tx) const; /** + * @brief verify that each ring uses distinct members + * + * @param tx the transaction to check + * + * @return false if any ring uses duplicate members, true otherwise + */ + bool check_tx_inputs_ring_members_diff(const transaction& tx) const; + + /** * @brief verify that each input key image in a transaction is in * the valid domain * @@ -859,6 +888,9 @@ namespace cryptonote time_t start_time; std::unordered_set<crypto::hash> bad_semantics_txes[2]; + boost::mutex bad_semantics_txes_lock; + + tools::thread_group m_threadpool; enum { UPDATES_DISABLED, diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 26d5fb767..abb4b31ec 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -265,7 +265,7 @@ namespace cryptonote // "Shuffle" outs std::vector<tx_destination_entry> shuffled_dsts(destinations); - std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; } ); + std::random_shuffle(shuffled_dsts.begin(), shuffled_dsts.end(), [](int i) { return crypto::rand<int>() % i; }); uint64_t summary_outs_money = 0; //fill outputs diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 933070e1e..7aa7c280d 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -32,6 +32,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include <boost/serialization/vector.hpp> #include <boost/serialization/utility.hpp> +#include "ringct/rctOps.h" namespace cryptonote { diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 1858ccdd8..47a41d070 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -493,7 +493,6 @@ private: */ std::unordered_set<crypto::hash> m_timed_out_transactions; - std::string m_config_folder; //!< the folder to save state to Blockchain& m_blockchain; //!< reference to the Blockchain object }; } diff --git a/src/cryptonote_protocol/block_queue.cpp b/src/cryptonote_protocol/block_queue.cpp new file mode 100644 index 000000000..583c3abf4 --- /dev/null +++ b/src/cryptonote_protocol/block_queue.cpp @@ -0,0 +1,434 @@ +// Copyright (c) 2017, 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include <vector> +#include <unordered_map> +#include <boost/uuid/nil_generator.hpp> +#include "cryptonote_protocol_defs.h" +#include "block_queue.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn.block_queue" + +namespace std { + static_assert(sizeof(size_t) <= sizeof(boost::uuids::uuid), "boost::uuids::uuid too small"); + template<> struct hash<boost::uuids::uuid> { + std::size_t operator()(const boost::uuids::uuid &_v) const { + return reinterpret_cast<const std::size_t &>(_v); + } + }; +} + +namespace cryptonote +{ + +void block_queue::add_blocks(uint64_t height, std::list<cryptonote::block_complete_entry> bcel, const boost::uuids::uuid &connection_id, float rate, size_t size) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + remove_span(height); + blocks.insert(span(height, std::move(bcel), connection_id, rate, size)); +} + +void block_queue::add_blocks(uint64_t height, uint64_t nblocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + blocks.insert(span(height, nblocks, connection_id, time)); +} + +void block_queue::flush_spans(const boost::uuids::uuid &connection_id, bool all) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + block_map::iterator i = blocks.begin(); + while (i != blocks.end()) + { + block_map::iterator j = i++; + if (j->connection_id == connection_id && (all || j->blocks.size() == 0)) + { + blocks.erase(j); + } + } +} + +void block_queue::flush_stale_spans(const std::set<boost::uuids::uuid> &live_connections) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + block_map::iterator i = blocks.begin(); + if (i != blocks.end() && is_blockchain_placeholder(*i)) + ++i; + while (i != blocks.end()) + { + block_map::iterator j = i++; + if (live_connections.find(j->connection_id) == live_connections.end() && j->blocks.size() == 0) + { + blocks.erase(j); + } + } +} + +void block_queue::remove_span(uint64_t start_block_height) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + for (block_map::iterator i = blocks.begin(); i != blocks.end(); ++i) + { + if (i->start_block_height == start_block_height) + { + blocks.erase(i); + return; + } + } +} + +void block_queue::remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + for (block_map::iterator i = blocks.begin(); i != blocks.end(); ) + { + block_map::iterator j = i++; + if (j->connection_id == connection_id && j->start_block_height <= start_block_height) + { + blocks.erase(j); + } + } +} + +void block_queue::mark_last_block(uint64_t last_block_height) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + if (!blocks.empty() && is_blockchain_placeholder(*blocks.begin())) + blocks.erase(*blocks.begin()); + for (block_map::iterator i = blocks.begin(); i != blocks.end(); ) + { + block_map::iterator j = i++; + if (j->start_block_height + j->nblocks - 1 <= last_block_height) + { + blocks.erase(j); + } + } + + // mark the current state of the db (for a fresh db, it's just the genesis block) + add_blocks(0, last_block_height + 1, boost::uuids::nil_uuid()); + MDEBUG("last blocked marked at " << last_block_height); +} + +uint64_t block_queue::get_max_block_height() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + uint64_t height = 0; + for (const auto &span: blocks) + { + const uint64_t h = span.start_block_height + span.nblocks - 1; + if (h > height) + height = h; + } + return height; +} + +void block_queue::print() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + MDEBUG("Block queue has " << blocks.size() << " spans"); + for (const auto &span: blocks) + MDEBUG(" " << span.start_block_height << " - " << (span.start_block_height+span.nblocks-1) << " (" << span.nblocks << ") - " << (is_blockchain_placeholder(span) ? "blockchain" : span.blocks.empty() ? "scheduled" : "filled ") << " " << span.connection_id << " (" << ((unsigned)(span.rate*10/1024.f))/10.f << " kB/s)"); +} + +std::string block_queue::get_overview() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + if (blocks.empty()) + return "[]"; + block_map::const_iterator i = blocks.begin(); + std::string s = std::string("[") + std::to_string(i->start_block_height + i->nblocks - 1) + ":"; + while (++i != blocks.end()) + s += i->blocks.empty() ? "." : "o"; + s += "]"; + return s; +} + +std::pair<uint64_t, uint64_t> block_queue::reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + + if (last_block_height < first_block_height || max_blocks == 0) + { + MDEBUG("reserve_span: early out: first_block_height " << first_block_height << ", last_block_height " << last_block_height << ", max_blocks " << max_blocks); + return std::make_pair(0, 0); + } + + uint64_t max_block_height = get_max_block_height(); + if (last_block_height > max_block_height) + max_block_height = last_block_height; + if (max_block_height == 0) + { + MDEBUG("reserve_span: max_block_height is 0"); + return std::make_pair(first_block_height, std::min(last_block_height - first_block_height + 1, max_blocks)); + } + + uint64_t base = 0, last_placeholder_block = 0; + bool has_placeholder = false; + block_map::const_iterator i = blocks.begin(); + if (i != blocks.end() && is_blockchain_placeholder(*i)) + { + base = i->start_block_height + i->nblocks; + last_placeholder_block = base - 1; + has_placeholder = true; + ++i; + for (block_map::const_iterator j = i; j != blocks.end(); ++j) + { + if (j->start_block_height < base) + base = j->start_block_height; + } + } + if (base > first_block_height) + base = first_block_height; + + CHECK_AND_ASSERT_MES (base <= max_block_height + 1, std::make_pair(0, 0), "Blockchain placeholder larger than max block height"); + std::vector<uint8_t> bitmap(max_block_height + 1 - base, 0); + MDEBUG("base " << base << ", last_placeholder_block " << (has_placeholder ? std::to_string(last_placeholder_block) : "none") << ", first_block_height " << first_block_height); + if (has_placeholder && last_placeholder_block >= base) + memset(bitmap.data(), 1, last_placeholder_block + 1 - base); + while (i != blocks.end()) + { + CHECK_AND_ASSERT_MES (i->start_block_height >= base, std::make_pair(0, 0), "Span starts before blochckain placeholder"); + memset(bitmap.data() + i->start_block_height - base, 1, i->nblocks); + ++i; + } + + const uint8_t *ptr = (const uint8_t*)memchr(bitmap.data() + first_block_height - base, 0, bitmap.size() - (first_block_height - base)); + if (!ptr) + { + MDEBUG("reserve_span: 0 not found in bitmap: " << first_block_height << " " << bitmap.size()); + print(); + return std::make_pair(0, 0); + } + uint64_t start_block_height = ptr - bitmap.data() + base; + if (start_block_height > last_block_height) + { + MDEBUG("reserve_span: start_block_height > last_block_height: " << start_block_height << " < " << last_block_height); + return std::make_pair(0, 0); + } + if (start_block_height + max_blocks - 1 < first_block_height) + { + MDEBUG("reserve_span: start_block_height + max_blocks - 1 < first_block_height: " << start_block_height << " + " << max_blocks << " - 1 < " << first_block_height); + return std::make_pair(0, 0); + } + + uint64_t nblocks = 1; + while (start_block_height + nblocks <= last_block_height && nblocks < max_blocks && bitmap[start_block_height + nblocks - base] == 0) + ++nblocks; + + MDEBUG("Reserving span " << start_block_height << " - " << (start_block_height + nblocks - 1) << " for " << connection_id); + add_blocks(start_block_height, nblocks, connection_id, time); + return std::make_pair(start_block_height, nblocks); +} + +bool block_queue::is_blockchain_placeholder(const span &span) const +{ + static const boost::uuids::uuid uuid0 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + return span.connection_id == uuid0; +} + +std::pair<uint64_t, uint64_t> block_queue::get_start_gap_span() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + if (blocks.empty()) + return std::make_pair(0, 0); + block_map::const_iterator i = blocks.begin(); + if (!is_blockchain_placeholder(*i)) + return std::make_pair(0, 0); + uint64_t current_height = i->start_block_height + i->nblocks - 1; + ++i; + if (i == blocks.end()) + return std::make_pair(0, 0); + uint64_t first_span_height = i->start_block_height; + if (first_span_height <= current_height + 1) + return std::make_pair(0, 0); + MDEBUG("Found gap at start of spans: last blockchain block height " << current_height << ", first span's block height " << first_span_height); + print(); + return std::make_pair(current_height + 1, first_span_height - current_height - 1); +} + +std::pair<uint64_t, uint64_t> block_queue::get_next_span_if_scheduled(std::list<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + if (blocks.empty()) + return std::make_pair(0, 0); + block_map::const_iterator i = blocks.begin(); + if (is_blockchain_placeholder(*i)) + ++i; + if (i == blocks.end()) + return std::make_pair(0, 0); + if (!i->blocks.empty()) + return std::make_pair(0, 0); + hashes = i->hashes; + connection_id = i->connection_id; + time = i->time; + return std::make_pair(i->start_block_height, i->nblocks); +} + +void block_queue::set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::list<crypto::hash> hashes) +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + for (block_map::iterator i = blocks.begin(); i != blocks.end(); ++i) + { + if (i->start_block_height == start_height && i->connection_id == connection_id) + { + span s = *i; + blocks.erase(i); + s.hashes = std::move(hashes); + blocks.insert(s); + return; + } + } +} + +bool block_queue::get_next_span(uint64_t &height, std::list<cryptonote::block_complete_entry> &bcel, boost::uuids::uuid &connection_id, bool filled) const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + if (blocks.empty()) + return false; + block_map::const_iterator i = blocks.begin(); + if (is_blockchain_placeholder(*i)) + ++i; + for (; i != blocks.end(); ++i) + { + if (!filled || !i->blocks.empty()) + { + height = i->start_block_height; + bcel = i->blocks; + connection_id = i->connection_id; + return true; + } + } + return false; +} + +size_t block_queue::get_data_size() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + size_t size = 0; + for (const auto &span: blocks) + size += span.size; + return size; +} + +size_t block_queue::get_num_filled_spans_prefix() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + + if (blocks.empty()) + return 0; + block_map::const_iterator i = blocks.begin(); + if (is_blockchain_placeholder(*i)) + ++i; + size_t size = 0; + while (i != blocks.end() && !i->blocks.empty()) + { + ++i; + ++size; + } + return size; +} + +size_t block_queue::get_num_filled_spans() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + size_t size = 0; + for (const auto &span: blocks) + if (!span.blocks.empty()) + ++size; + return size; +} + +crypto::hash block_queue::get_last_known_hash() const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + crypto::hash hash = cryptonote::null_hash; + uint64_t highest_height = 0; + for (const auto &span: blocks) + { + uint64_t h = span.start_block_height + span.nblocks - 1; + if (h > highest_height && span.hashes.size() == span.nblocks) + { + hash = span.hashes.back(); + highest_height = h; + } + } + return hash; +} + +float block_queue::get_speed(const boost::uuids::uuid &connection_id) const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + std::unordered_map<boost::uuids::uuid, float> speeds; + for (const auto &span: blocks) + { + if (span.blocks.empty()) + continue; + // note that the average below does not average over the whole set, but over the + // previous pseudo average and the latest rate: this gives much more importance + // to the latest measurements, which is fine here + std::unordered_map<boost::uuids::uuid, float>::iterator i = speeds.find(span.connection_id); + if (i == speeds.end()) + speeds.insert(std::make_pair(span.connection_id, span.rate)); + else + i->second = (i->second + span.rate) / 2; + } + float conn_rate = -1, best_rate = 0; + for (auto i: speeds) + { + if (i.first == connection_id) + conn_rate = i.second; + if (i.second > best_rate) + best_rate = i.second; + } + + if (conn_rate <= 0) + return 1.0f; // not found, assume good speed + if (best_rate == 0) + return 1.0f; // everything dead ? Can't happen, but let's trap anyway + + const float speed = conn_rate / best_rate; + MTRACE(" Relative speed for " << connection_id << ": " << speed << " (" << conn_rate << "/" << best_rate); + return speed; +} + +bool block_queue::foreach(std::function<bool(const span&)> f, bool include_blockchain_placeholder) const +{ + boost::unique_lock<boost::recursive_mutex> lock(mutex); + block_map::const_iterator i = blocks.begin(); + if (!include_blockchain_placeholder && i != blocks.end() && is_blockchain_placeholder(*i)) + ++i; + while (i != blocks.end()) + if (!f(*i++)) + return false; + return true; +} + +} diff --git a/src/cryptonote_protocol/block_queue.h b/src/cryptonote_protocol/block_queue.h new file mode 100644 index 000000000..c75ebc0b9 --- /dev/null +++ b/src/cryptonote_protocol/block_queue.h @@ -0,0 +1,97 @@ +// Copyright (c) 2017, 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. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include <string> +#include <list> +#include <set> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/uuid/uuid.hpp> + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn.block_queue" + +namespace cryptonote +{ + struct block_complete_entry; + + class block_queue + { + public: + struct span + { + uint64_t start_block_height; + std::list<crypto::hash> hashes; + std::list<cryptonote::block_complete_entry> blocks; + boost::uuids::uuid connection_id; + uint64_t nblocks; + float rate; + size_t size; + boost::posix_time::ptime time; + + span(uint64_t start_block_height, std::list<cryptonote::block_complete_entry> blocks, const boost::uuids::uuid &connection_id, float rate, size_t size): + start_block_height(start_block_height), blocks(std::move(blocks)), connection_id(connection_id), nblocks(this->blocks.size()), rate(rate), size(size), time() {} + span(uint64_t start_block_height, uint64_t nblocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time): + start_block_height(start_block_height), connection_id(connection_id), nblocks(nblocks), rate(0.0f), size(0), time(time) {} + + bool operator<(const span &s) const { return start_block_height < s.start_block_height; } + }; + typedef std::set<span> block_map; + + public: + void add_blocks(uint64_t height, std::list<cryptonote::block_complete_entry> bcel, const boost::uuids::uuid &connection_id, float rate, size_t size); + void add_blocks(uint64_t height, uint64_t nblocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time = boost::date_time::min_date_time); + void flush_spans(const boost::uuids::uuid &connection_id, bool all = false); + void flush_stale_spans(const std::set<boost::uuids::uuid> &live_connections); + void remove_span(uint64_t start_block_height); + void remove_spans(const boost::uuids::uuid &connection_id, uint64_t start_block_height); + void mark_last_block(uint64_t last_block_height); + uint64_t get_max_block_height() const; + void print() const; + std::string get_overview() const; + std::pair<uint64_t, uint64_t> reserve_span(uint64_t first_block_height, uint64_t last_block_height, uint64_t max_blocks, const boost::uuids::uuid &connection_id, boost::posix_time::ptime time = boost::posix_time::microsec_clock::universal_time()); + bool is_blockchain_placeholder(const span &span) const; + std::pair<uint64_t, uint64_t> get_start_gap_span() const; + std::pair<uint64_t, uint64_t> get_next_span_if_scheduled(std::list<crypto::hash> &hashes, boost::uuids::uuid &connection_id, boost::posix_time::ptime &time) const; + void set_span_hashes(uint64_t start_height, const boost::uuids::uuid &connection_id, std::list<crypto::hash> hashes); + bool get_next_span(uint64_t &height, std::list<cryptonote::block_complete_entry> &bcel, boost::uuids::uuid &connection_id, bool filled = true) const; + size_t get_data_size() const; + size_t get_num_filled_spans_prefix() const; + size_t get_num_filled_spans() const; + crypto::hash get_last_known_hash() const; + float get_speed(const boost::uuids::uuid &connection_id) const; + bool foreach(std::function<bool(const span&)> f, bool include_blockchain_placeholder = false) const; + + private: + block_map blocks; + mutable boost::recursive_mutex mutex; + }; +} diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index 6f6c1a803..042ae49f6 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -74,6 +74,8 @@ namespace cryptonote uint32_t support_flags; + boost::uuids::uuid connection_id; + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(incoming) KV_SERIALIZE(localhost) @@ -94,6 +96,7 @@ namespace cryptonote KV_SERIALIZE(avg_upload) KV_SERIALIZE(current_upload) KV_SERIALIZE(support_flags) + KV_SERIALIZE_VAL_POD_AS_BLOB(connection_id) END_KV_SERIALIZE_MAP() }; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp index e31276031..3bda50c22 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp +++ b/src/cryptonote_protocol/cryptonote_protocol_handler-base.cpp @@ -49,8 +49,8 @@ #include "syncobj.h" -#include "../../contrib/epee/include/net/net_utils_base.h" -#include "../../contrib/epee/include/misc_log_ex.h" +#include "net/net_utils_base.h" +#include "misc_log_ex.h" #include <boost/lambda/bind.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> @@ -69,10 +69,10 @@ #include <boost/asio/basic_socket.hpp> #include <boost/asio/ip/unicast.hpp> -#include "../../src/cryptonote_protocol/cryptonote_protocol_handler.h" -#include "../../src/p2p/network_throttle.hpp" +#include "cryptonote_protocol_handler.h" +#include "p2p/network_throttle.hpp" -#include "../../../src/cryptonote_core/cryptonote_core.h" // e.g. for the send_stop_signal() +#include "cryptonote_core/cryptonote_core.h" // e.g. for the send_stop_signal() #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.cn" @@ -120,10 +120,6 @@ cryptonote_protocol_handler_base::~cryptonote_protocol_handler_base() { } void cryptonote_protocol_handler_base::handler_request_blocks_history(std::list<crypto::hash>& ids) { - using namespace epee::net_utils; - MDEBUG("### ~~~RRRR~~~~ ### sending request (type 2), limit = " << ids.size()); - MDEBUG("RATE LIMIT NOT IMPLEMENTED HERE YET (download at unlimited speed?)"); - // TODO } void cryptonote_protocol_handler_base::handler_response_blocks_now(size_t packet_size) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 9d8bc43c2..dcee03f66 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -42,6 +42,7 @@ #include "warnings.h" #include "cryptonote_protocol_defs.h" #include "cryptonote_protocol_handler_common.h" +#include "block_queue.h" #include "cryptonote_basic/connection_context.h" #include "cryptonote_basic/cryptonote_stat_info.h" #include "cryptonote_basic/verification_context.h" @@ -107,6 +108,7 @@ namespace cryptonote bool is_synchronized(){return m_synchronized;} void log_connections(); std::list<connection_info> get_connections(); + const block_queue &get_block_queue() const { return m_block_queue; } void stop(); private: //----------------- commands handlers ---------------------------------------------- @@ -124,18 +126,19 @@ namespace cryptonote virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context); //---------------------------------------------------------------------------------- //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context); - bool request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks); + bool request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span = false); size_t get_synchronizing_connections_count(); bool on_connection_synchronized(); + bool should_download_next_span(cryptonote_connection_context& context) const; t_core& m_core; nodetool::p2p_endpoint_stub<connection_context> m_p2p_stub; nodetool::i_p2p_endpoint<connection_context>* m_p2p; std::atomic<uint32_t> m_syncronized_connections_count; std::atomic<bool> m_synchronized; - bool m_one_request = true; std::atomic<bool> m_stopping; - epee::critical_section m_sync_lock; + boost::mutex m_sync_lock; + block_queue m_block_queue; boost::mutex m_buffer_mutex; double get_avg_block_size(); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index c5bc834ad..4652bf23e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -41,13 +41,17 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "profile_tools.h" -#include "../../src/p2p/network_throttle-detail.hpp" +#include "p2p/network_throttle-detail.hpp" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.cn" #define MLOG_P2P_MESSAGE(x) MCINFO("net.p2p.msg", context << x) +#define BLOCK_QUEUE_NBLOCKS_THRESHOLD 10 // chunks of N blocks +#define BLOCK_QUEUE_SIZE_THRESHOLD (100*1024*1024) // MB +#define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (5 * 1000000) // microseconds + namespace cryptonote { @@ -208,7 +212,7 @@ namespace cryptonote cnx.recv_idle_time = timestamp - cntxt.m_last_recv; cnx.send_count = cntxt.m_send_cnt; - cnx.send_idle_time = timestamp; + cnx.send_idle_time = timestamp - cntxt.m_last_send; cnx.state = get_protocol_state_string(cntxt.m_state); @@ -233,6 +237,8 @@ namespace cryptonote cnx.current_download = cntxt.m_current_speed_down / 1024; cnx.current_upload = cntxt.m_current_speed_up / 1024; + cnx.connection_id = cntxt.m_connection_id; + connections.push_back(cnx); return true; @@ -270,7 +276,7 @@ namespace cryptonote m_core.set_target_blockchain_height(static_cast<int64_t>(hshd.current_height)); int64_t diff = static_cast<int64_t>(hshd.current_height) - static_cast<int64_t>(m_core.get_current_blockchain_height()); int64_t max_block_height = max(static_cast<int64_t>(hshd.current_height),static_cast<int64_t>(m_core.get_current_blockchain_height())); - int64_t last_block_v1 = 1009826; + int64_t last_block_v1 = m_core.get_testnet() ? 624633 : 1009826; int64_t diff_v2 = max_block_height > last_block_v1 ? min(abs(diff), max_block_height - last_block_v1) : 0; MCLOG(is_inital ? el::Level::Info : el::Level::Debug, "global", context << "Sync data returned a new top block candidate: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height << " [Your node is " << std::abs(diff) << " blocks (" << ((abs(diff) - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " @@ -310,6 +316,11 @@ namespace cryptonote MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; + if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks + { + LOG_DEBUG_CC(context, "Received new block while syncing, ignored"); + return 1; + } m_core.pause_mine(); std::list<block_complete_entry> blocks; blocks.push_back(arg.b); @@ -324,6 +335,7 @@ namespace cryptonote m_p2p->drop_connection(context); m_core.cleanup_handle_incoming_blocks(); m_core.resume_mine(); + m_block_queue.flush_spans(context.m_connection_id); return 1; } } @@ -336,6 +348,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } if(bvc.m_added_to_main_chain) @@ -361,6 +374,11 @@ namespace cryptonote MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", hop " << arg.hop << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; + if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks + { + LOG_DEBUG_CC(context, "Received new block while syncing, ignored"); + return 1; + } m_core.pause_mine(); @@ -384,6 +402,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -417,6 +436,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -431,6 +451,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -455,6 +476,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -472,6 +494,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -494,6 +517,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -514,6 +538,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); m_core.resume_mine(); return 1; } @@ -591,6 +616,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L0("Block verification failed, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } if( bvc.m_added_to_main_chain ) @@ -624,6 +650,7 @@ namespace cryptonote m_core.resume_mine(); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } @@ -644,6 +671,7 @@ namespace cryptonote { LOG_ERROR_CCONTEXT("failed to find block: " << arg.block_hash << ", dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } @@ -671,6 +699,7 @@ namespace cryptonote ); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } } @@ -682,6 +711,7 @@ namespace cryptonote LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, " << "failed to get requested transactions"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } if (!missed.empty() || txs.size() != txids.size()) @@ -689,6 +719,7 @@ namespace cryptonote LOG_ERROR_CCONTEXT("Failed to handle request NOTIFY_REQUEST_FLUFFY_MISSING_TX, " << missed.size() << " requested transactions not found" << ", dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } @@ -732,6 +763,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } if(tvc.m_should_be_relayed) @@ -758,6 +790,8 @@ namespace cryptonote { LOG_ERROR_CCONTEXT("failed to handle request NOTIFY_REQUEST_GET_OBJECTS, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); + return 1; } LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_GET_OBJECTS: blocks.size()=" << rsp.blocks.size() << ", txs.size()=" << rsp.txs.size() << ", rsp.m_current_blockchain_height=" << rsp.current_blockchain_height << ", missed_ids.size()=" << rsp.missed_ids.size()); @@ -770,21 +804,15 @@ namespace cryptonote template<class t_core> - double t_cryptonote_protocol_handler<t_core>::get_avg_block_size() { - // return m_core.get_blockchain_storage().get_avg_block_size(count); // this does not count too well the actuall network-size of data we need to download - + double t_cryptonote_protocol_handler<t_core>::get_avg_block_size() + { CRITICAL_REGION_LOCAL(m_buffer_mutex); - double avg = 0; - if (m_avg_buffer.size() == 0) { - _warn("m_avg_buffer.size() == 0"); + if (m_avg_buffer.empty()) { + MWARNING("m_avg_buffer.size() == 0"); return 500; } - - const bool dbg_poke_lock = 0; // debug: try to trigger an error by poking around with locks. TODO: configure option - long int dbg_repeat=0; - do { - for (auto element : m_avg_buffer) avg += element; - } while(dbg_poke_lock && (dbg_repeat++)<100000); // in debug/poke mode, repeat this calculation to trigger hidden locking error if there is one + double avg = 0; + for (const auto &element : m_avg_buffer) avg += element; return avg / m_avg_buffer.size(); } @@ -794,31 +822,30 @@ namespace cryptonote { MLOG_P2P_MESSAGE("Received NOTIFY_RESPONSE_GET_OBJECTS (" << arg.blocks.size() << " blocks, " << arg.txs.size() << " txes)"); - // calculate size of request - mainly for logging/debug + bool force_next_span = false; + + // calculate size of request size_t size = 0; - for (auto element : arg.txs) size += element.size(); + for (const auto &element : arg.txs) size += element.size(); - for (auto element : arg.blocks) { - size += element.block.size(); - for (auto tx : element.txs) - size += tx.size(); + size_t blocks_size = 0; + for (const auto &element : arg.blocks) { + blocks_size += element.block.size(); + for (const auto &tx : element.txs) + blocks_size += tx.size(); } + size += blocks_size; - for (auto element : arg.missed_ids) + for (const auto &element : arg.missed_ids) size += sizeof(element.data); size += sizeof(arg.current_blockchain_height); { CRITICAL_REGION_LOCAL(m_buffer_mutex); m_avg_buffer.push_back(size); - - const bool dbg_poke_lock = 0; // debug: try to trigger an error by poking around with locks. TODO: configure option - long int dbg_repeat=0; - do { - m_avg_buffer.push_back(666); // a test value - m_avg_buffer.erase_end(1); - } while(dbg_poke_lock && (dbg_repeat++)<100000); // in debug/poke mode, repeat this calculation to trigger hidden locking error if there is one } + MDEBUG(context << " downloaded " << size << " bytes worth of blocks"); + /*using namespace boost::chrono; auto point = steady_clock::now(); auto time_from_epoh = point.time_since_epoch(); @@ -831,14 +858,17 @@ namespace cryptonote LOG_ERROR_CCONTEXT("sent wrong NOTIFY_HAVE_OBJECTS: arg.m_current_blockchain_height=" << arg.current_blockchain_height << " < m_last_response_height=" << context.m_last_response_height << ", dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } context.m_remote_blockchain_height = arg.current_blockchain_height; - size_t count = 0; std::vector<crypto::hash> block_hashes; block_hashes.reserve(arg.blocks.size()); + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + uint64_t start_height = std::numeric_limits<uint64_t>::max(); + cryptonote::block b; for(const block_complete_entry& block_entry: arg.blocks) { if (m_stopping) @@ -846,35 +876,33 @@ namespace cryptonote return 1; } - ++count; - block b; if(!parse_and_validate_block_from_blob(block_entry.block, b)) { LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: " << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } - //to avoid concurrency in core between connections, suspend connections which delivered block later then first one - const crypto::hash block_hash = get_block_hash(b); - if(count == 2) + if (b.miner_tx.vin.size() != 1 || b.miner_tx.vin.front().type() != typeid(txin_gen)) { - if(m_core.have_block(block_hash)) - { - context.m_state = cryptonote_connection_context::state_idle; - context.m_needed_objects.clear(); - context.m_requested_objects.clear(); - LOG_PRINT_CCONTEXT_L1("Connection set to idle state."); - return 1; - } + LOG_ERROR_CCONTEXT("sent wrong block: block: miner tx does not have exactly one txin_gen input" + << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection"); + m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); + return 1; } + if (start_height == std::numeric_limits<uint64_t>::max()) + start_height = boost::get<txin_gen>(b.miner_tx.vin[0]).height; + const crypto::hash block_hash = get_block_hash(b); auto req_it = context.m_requested_objects.find(block_hash); if(req_it == context.m_requested_objects.end()) { LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block)) << " wasn't requested, dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } if(b.tx_hashes.size() != block_entry.txs.size()) @@ -882,6 +910,7 @@ namespace cryptonote LOG_ERROR_CCONTEXT("sent wrong NOTIFY_RESPONSE_GET_OBJECTS: block with id=" << epee::string_tools::pod_to_hex(get_blob_hash(block_entry.block)) << ", tx_hashes.size()=" << b.tx_hashes.size() << " mismatch with block_complete_entry.m_txs.size()=" << block_entry.txs.size() << ", dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } @@ -894,114 +923,189 @@ namespace cryptonote MERROR("returned not all requested objects (context.m_requested_objects.size()=" << context.m_requested_objects.size() << "), dropping connection"); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } + // get the last parsed block, which should be the highest one + if(m_core.have_block(cryptonote::get_block_hash(b))) + { + const uint64_t subchain_height = start_height + arg.blocks.size(); + LOG_DEBUG_CC(context, "These are old blocks, ignoring: blocks " << start_height << " - " << (subchain_height-1) << ", blockchain height " << m_core.get_current_blockchain_height()); + goto skip; + } { MLOG_YELLOW(el::Level::Debug, "Got NEW BLOCKS inside of " << __FUNCTION__ << ": size: " << arg.blocks.size()); + // add that new span to the block queue + const boost::posix_time::time_duration dt = now - context.m_last_request_time; + const float rate = size * 1e6 / (dt.total_microseconds() + 1); + MDEBUG(context << " adding span: " << arg.blocks.size() << " at height " << start_height << ", " << dt.total_microseconds()/1e6 << " seconds, " << (rate/1e3) << " kB/s, size now " << (m_block_queue.get_data_size() + blocks_size) / 1048576.f << " MB"); + m_block_queue.add_blocks(start_height, arg.blocks, context.m_connection_id, rate, blocks_size); + if (m_core.get_test_drop_download() && m_core.get_test_drop_download_height()) { // DISCARD BLOCKS for testing - // we lock all the rest to avoid having multiple connections redo a lot - // of the same work, and one of them doing it for nothing: subsequent - // connections will wait until the current one's added its blocks, then - // will add any extra it has, if any - CRITICAL_REGION_LOCAL(m_sync_lock); + // We try to lock the sync lock. If we can, it means no other thread is + // currently adding blocks, so we do that for as long as we can from the + // block queue. Then, we go back to download. + const boost::unique_lock<boost::mutex> sync{m_sync_lock, boost::try_to_lock}; + if (!sync.owns_lock()) + { + MINFO("Failed to lock m_sync_lock, going back to download"); + goto skip; + } + MDEBUG(context << " lock m_sync_lock, adding blocks to chain..."); m_core.pause_mine(); epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler( boost::bind(&t_core::resume_mine, &m_core)); - const uint64_t previous_height = m_core.get_current_blockchain_height(); - - // dismiss what another connection might already have done (likely everything) - uint64_t top_height; - crypto::hash top_hash; - if (m_core.get_blockchain_top(top_height, top_hash)) { - uint64_t dismiss = 1; - for (const auto &h: block_hashes) { - if (top_hash == h) { - LOG_DEBUG_CC(context, "Found current top block in synced blocks, dismissing " - << dismiss << "/" << arg.blocks.size() << " blocks"); - while (dismiss--) - arg.blocks.pop_front(); - break; - } - ++dismiss; + while (1) + { + const uint64_t previous_height = m_core.get_current_blockchain_height(); + uint64_t start_height; + std::list<cryptonote::block_complete_entry> blocks; + boost::uuids::uuid span_connection_id; + m_block_queue.mark_last_block(previous_height - 1); + if (!m_block_queue.get_next_span(start_height, blocks, span_connection_id)) + { + MDEBUG(context << " no next span found, going back to download"); + break; + } + MDEBUG(context << " next span in the queue has blocks " << start_height << "-" << (start_height + blocks.size() - 1) + << ", we need " << previous_height); + if (previous_height < start_height || previous_height >= start_height + blocks.size()) + { + MDEBUG(context << " this span is not what we need, going back to download"); + break; } - } - if (arg.blocks.empty()) - goto skip; + const boost::posix_time::ptime start = boost::posix_time::microsec_clock::universal_time(); - m_core.prepare_handle_incoming_blocks(arg.blocks); + m_core.prepare_handle_incoming_blocks(blocks); - for(const block_complete_entry& block_entry: arg.blocks) - { - if (m_stopping) + uint64_t block_process_time_full = 0, transactions_process_time_full = 0; + size_t num_txs = 0; + for(const block_complete_entry& block_entry: blocks) { - m_core.cleanup_handle_incoming_blocks(); - return 1; - } + if (m_stopping) + { + m_core.cleanup_handle_incoming_blocks(); + return 1; + } - // process transactions - TIME_MEASURE_START(transactions_process_time); - for(auto& tx_blob: block_entry.txs) - { - tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(tx_blob, tvc, true, true, false); - if(tvc.m_verifivation_failed) + // process transactions + TIME_MEASURE_START(transactions_process_time); + num_txs += block_entry.txs.size(); + std::vector<tx_verification_context> tvc; + m_core.handle_incoming_txs(block_entry.txs, tvc, true, true, false); + std::list<blobdata>::const_iterator it = block_entry.txs.begin(); + for (size_t i = 0; i < tvc.size(); ++i, ++it) { - LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, tx_id = " - << epee::string_tools::pod_to_hex(get_blob_hash(tx_blob)) << ", dropping connection"); - m_p2p->drop_connection(context); + if(tvc[i].m_verifivation_failed) + { + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ + LOG_ERROR_CCONTEXT("transaction verification failed on NOTIFY_RESPONSE_GET_OBJECTS, tx_id = " + << epee::string_tools::pod_to_hex(get_blob_hash(*it)) << ", dropping connection"); + m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id, true); + return true; + })) + LOG_ERROR_CCONTEXT("span connection id not found"); + + m_core.cleanup_handle_incoming_blocks(); + // in case the peer had dropped beforehand, remove the span anyway so other threads can wake up and get it + m_block_queue.remove_spans(span_connection_id, start_height); + return 1; + } + } + TIME_MEASURE_FINISH(transactions_process_time); + transactions_process_time_full += transactions_process_time; + + // process block + + TIME_MEASURE_START(block_process_time); + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + + m_core.handle_incoming_block(block_entry.block, bvc, false); // <--- process block + + if(bvc.m_verifivation_failed) + { + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ + LOG_PRINT_CCONTEXT_L1("Block verification failed, dropping connection"); + m_p2p->drop_connection(context); + m_p2p->add_host_fail(context.m_remote_address); + m_block_queue.flush_spans(context.m_connection_id, true); + return true; + })) + LOG_ERROR_CCONTEXT("span connection id not found"); + m_core.cleanup_handle_incoming_blocks(); + // in case the peer had dropped beforehand, remove the span anyway so other threads can wake up and get it + m_block_queue.remove_spans(span_connection_id, start_height); return 1; } - } - TIME_MEASURE_FINISH(transactions_process_time); + if(bvc.m_marked_as_orphaned) + { + if (!m_p2p->for_connection(span_connection_id, [&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t f)->bool{ + LOG_PRINT_CCONTEXT_L1("Block received at sync phase was marked as orphaned, dropping connection"); + m_p2p->drop_connection(context); + m_p2p->add_host_fail(context.m_remote_address); + m_block_queue.flush_spans(context.m_connection_id, true); + return true; + })) + LOG_ERROR_CCONTEXT("span connection id not found"); - // process block + m_core.cleanup_handle_incoming_blocks(); + // in case the peer had dropped beforehand, remove the span anyway so other threads can wake up and get it + m_block_queue.remove_spans(span_connection_id, start_height); + return 1; + } - TIME_MEASURE_START(block_process_time); - block_verification_context bvc = boost::value_initialized<block_verification_context>(); + TIME_MEASURE_FINISH(block_process_time); + block_process_time_full += block_process_time; - m_core.handle_incoming_block(block_entry.block, bvc, false); // <--- process block + } // each download block - if(bvc.m_verifivation_failed) - { - LOG_PRINT_CCONTEXT_L1("Block verification failed, dropping connection"); - m_p2p->drop_connection(context); - m_p2p->add_host_fail(context.m_remote_address); - m_core.cleanup_handle_incoming_blocks(); - return 1; - } - if(bvc.m_marked_as_orphaned) - { - LOG_PRINT_CCONTEXT_L1("Block received at sync phase was marked as orphaned, dropping connection"); - m_p2p->drop_connection(context); - m_p2p->add_host_fail(context.m_remote_address); - m_core.cleanup_handle_incoming_blocks(); - return 1; - } + MCINFO("sync-info", "Block process time (" << blocks.size() << " blocks, " << num_txs << " txs): " << block_process_time_full + transactions_process_time_full << " (" << transactions_process_time_full << "/" << block_process_time_full << ") ms"); - TIME_MEASURE_FINISH(block_process_time); - LOG_PRINT_CCONTEXT_L2("Block process time: " << block_process_time + transactions_process_time << "(" << transactions_process_time << "/" << block_process_time << ")ms"); + m_core.cleanup_handle_incoming_blocks(); - } // each download block - m_core.cleanup_handle_incoming_blocks(); + m_block_queue.mark_last_block(m_core.get_current_blockchain_height() - 1); - if (m_core.get_current_blockchain_height() > previous_height) - { - MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height()); + if (m_core.get_current_blockchain_height() > previous_height) + { + const boost::posix_time::time_duration dt = boost::posix_time::microsec_clock::universal_time() - start; + std::string timing_message = ""; + if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info")) + timing_message = std::string(" (") + std::to_string(dt.total_microseconds()/1e6) + " sec, " + + std::to_string((m_core.get_current_blockchain_height() - previous_height) * 1e6 / dt.total_microseconds()) + + " blocks/sec), " + std::to_string(m_block_queue.get_data_size() / 1048576.f) + " MB queued"; + if (ELPP->vRegistry()->allowed(el::Level::Debug, "sync-info")) + timing_message += std::string(": ") + m_block_queue.get_overview(); + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + << timing_message); + } } } // if not DISCARD BLOCK - + if (should_download_next_span(context)) + { + context.m_needed_objects.clear(); + context.m_last_response_height = 0; + force_next_span = true; + } } + skip: - request_missing_objects(context, true); + if (!request_missing_objects(context, true, force_next_span)) + { + LOG_ERROR_CCONTEXT("Failed to request missing objects, dropping connection"); + m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); + return 1; + } return 1; } //------------------------------------------------------------------------------------------------------------------------ @@ -1020,6 +1124,7 @@ skip: { LOG_ERROR_CCONTEXT("Failed to handle NOTIFY_REQUEST_CHAIN."); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); return 1; } LOG_PRINT_CCONTEXT_L2("-->>NOTIFY_RESPONSE_CHAIN_ENTRY: m_start_height=" << r.start_height << ", m_total_height=" << r.total_height << ", m_block_ids.size()=" << r.m_block_ids.size()); @@ -1028,54 +1133,260 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks) + bool t_cryptonote_protocol_handler<t_core>::should_download_next_span(cryptonote_connection_context& context) const { - //if (!m_one_request == false) - //return true; - m_one_request = false; - // save request size to log (dr monero) - /*using namespace boost::chrono; - auto point = steady_clock::now(); - auto time_from_epoh = point.time_since_epoch(); - auto sec = duration_cast< seconds >( time_from_epoh ).count();*/ + std::list<crypto::hash> hashes; + boost::uuids::uuid span_connection_id; + boost::posix_time::ptime request_time; + std::pair<uint64_t, uint64_t> span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id, request_time); - if(context.m_needed_objects.size()) + // if the next span is not scheduled (or there is none) + if (span.second == 0) + { + // we might be in a weird case where there is a filled next span, + // but it starts higher than the current height + uint64_t height; + std::list<cryptonote::block_complete_entry> bcel; + if (!m_block_queue.get_next_span(height, bcel, span_connection_id, true)) + return false; + if (height > m_core.get_current_blockchain_height()) + { + MDEBUG(context << " we should download it as the next block isn't scheduled"); + return true; + } + return false; + } + // if it was scheduled by this particular peer + if (span_connection_id == context.m_connection_id) + return false; + + float span_speed = m_block_queue.get_speed(span_connection_id); + float speed = m_block_queue.get_speed(context.m_connection_id); + MDEBUG(context << " next span is scheduled for " << span_connection_id << ", speed " << span_speed << ", ours " << speed); + + // we try for that span too if: + // - we're substantially faster, or: + // - we're the fastest and the other one isn't (avoids a peer being waaaay slow but yet unmeasured) + // - the other one asked at least 5 seconds ago + if (span_speed < .25 && speed > .75f) + { + MDEBUG(context << " we should download it as we're substantially faster"); + return true; + } + if (speed == 1.0f && span_speed != 1.0f) + { + MDEBUG(context << " we should download it as we're the fastest peer"); + return true; + } + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + if ((now - request_time).total_microseconds() > REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD) + { + MDEBUG(context << " we should download it as this span was requested long ago"); + return true; + } + return false; + } + //------------------------------------------------------------------------------------------------------------------------ + template<class t_core> + bool t_cryptonote_protocol_handler<t_core>::request_missing_objects(cryptonote_connection_context& context, bool check_having_blocks, bool force_next_span) + { + m_block_queue.mark_last_block(m_core.get_current_blockchain_height() - 1); + + // flush stale spans + std::set<boost::uuids::uuid> live_connections; + m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool{ + live_connections.insert(context.m_connection_id); + return true; + }); + m_block_queue.flush_stale_spans(live_connections); + + // if we don't need to get next span, and the block queue is full enough, wait a bit + bool start_from_current_chain = false; + if (!force_next_span) + { + bool first = true; + while (1) + { + size_t nblocks = m_block_queue.get_num_filled_spans(); + size_t size = m_block_queue.get_data_size(); + if (nblocks < BLOCK_QUEUE_NBLOCKS_THRESHOLD || size < BLOCK_QUEUE_SIZE_THRESHOLD) + { + if (!first) + { + LOG_DEBUG_CC(context, "Block queue is " << nblocks << " and " << size << ", resuming"); + } + break; + } + + if (should_download_next_span(context)) + { + MDEBUG(context << " we should try for that next span too, we think we could get it faster, resuming"); + force_next_span = true; + break; + } + + if (first) + { + LOG_DEBUG_CC(context, "Block queue is " << nblocks << " and " << size << ", pausing"); + first = false; + } + for (size_t n = 0; n < 50; ++n) + { + if (m_stopping) + return 1; + boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); + } + } + } + + MDEBUG(context << " request_missing_objects: check " << check_having_blocks << ", force_next_span " << force_next_span << ", m_needed_objects " << context.m_needed_objects.size() << " lrh " << context.m_last_response_height << ", chain " << m_core.get_current_blockchain_height()); + if(context.m_needed_objects.size() || force_next_span) { //we know objects that we need, request this objects NOTIFY_REQUEST_GET_OBJECTS::request req; + bool is_next = false; size_t count = 0; - auto it = context.m_needed_objects.begin(); - const size_t count_limit = m_core.get_block_sync_size(); - MDEBUG("Setting count_limit: " << count_limit); - while(it != context.m_needed_objects.end() && count < count_limit) + std::pair<uint64_t, uint64_t> span = std::make_pair(0, 0); + if (force_next_span) { - if( !(check_having_blocks && m_core.have_block(*it))) + MDEBUG(context << " force_next_span is true, trying next span"); + span = m_block_queue.get_start_gap_span(); + if (span.second > 0) { - req.blocks.push_back(*it); - ++count; - context.m_requested_objects.insert(*it); + const uint64_t first_block_height_known = context.m_last_response_height - context.m_needed_objects.size() + 1; + const uint64_t last_block_height_known = context.m_last_response_height; + const uint64_t first_block_height_needed = span.first; + const uint64_t last_block_height_needed = span.first + std::min(span.second, (uint64_t)count_limit) - 1; + MDEBUG(context << " gap found, span: " << span.first << " - " << span.first + span.second - 1 << " (" << last_block_height_needed << ")"); + MDEBUG(context << " current known hashes from from " << first_block_height_known << " to " << last_block_height_known); + if (first_block_height_needed < first_block_height_known || last_block_height_needed > last_block_height_known) + { + MDEBUG(context << " we are missing some of the necessary hashes for this gap, requesting chain again"); + context.m_needed_objects.clear(); + context.m_last_response_height = 0; + start_from_current_chain = true; + goto skip; + } + MDEBUG(context << " we have the hashes for this gap"); + } + if (span.second == 0) + { + std::list<crypto::hash> hashes; + boost::uuids::uuid span_connection_id; + boost::posix_time::ptime time; + span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id, time); + if (span.second > 0) + { + is_next = true; + for (const auto &hash: hashes) + { + req.blocks.push_back(hash); + context.m_requested_objects.insert(hash); + } + } } - context.m_needed_objects.erase(it++); } - LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << ", txs.size()=" << req.txs.size() - << "requested blocks count=" << count << " / " << count_limit); - //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); + if (span.second == 0) + { + MDEBUG(context << " span size is 0"); + if (context.m_last_response_height + 1 < context.m_needed_objects.size()) + { + MERROR(context << " ERROR: inconsistent context: lrh " << context.m_last_response_height << ", nos " << context.m_needed_objects.size()); + context.m_needed_objects.clear(); + context.m_last_response_height = 0; + goto skip; + } + const uint64_t first_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1; + span = m_block_queue.reserve_span(first_block_height, context.m_last_response_height, count_limit, context.m_connection_id); + MDEBUG(context << " span from " << first_block_height << ": " << span.first << "/" << span.second); + } + if (span.second == 0 && !force_next_span) + { + MDEBUG(context << " still no span reserved, we may be in the corner case of next span scheduled and everything else scheduled/filled"); + std::list<crypto::hash> hashes; + boost::uuids::uuid span_connection_id; + boost::posix_time::ptime time; + span = m_block_queue.get_next_span_if_scheduled(hashes, span_connection_id, time); + if (span.second > 0) + { + is_next = true; + for (const auto &hash: hashes) + { + req.blocks.push_back(hash); + context.m_requested_objects.insert(hash); + } + } + } + MDEBUG(context << " span: " << span.first << "/" << span.second << " (" << span.first << " - " << (span.first + span.second - 1) << ")"); + if (span.second > 0) + { + if (!is_next) + { + const uint64_t first_context_block_height = context.m_last_response_height - context.m_needed_objects.size() + 1; + uint64_t skip = span.first - first_context_block_height; + if (skip > context.m_needed_objects.size()) + { + MERROR("ERROR: skip " << skip << ", m_needed_objects " << context.m_needed_objects.size() << ", first_context_block_height" << first_context_block_height); + return false; + } + while (skip--) + context.m_needed_objects.pop_front(); + if (context.m_needed_objects.size() < span.second) + { + MERROR("ERROR: span " << span.first << "/" << span.second << ", m_needed_objects " << context.m_needed_objects.size()); + return false; + } + + std::list<crypto::hash> hashes; + auto it = context.m_needed_objects.begin(); + for (size_t n = 0; n < span.second; ++n) + { + req.blocks.push_back(*it); + ++count; + context.m_requested_objects.insert(*it); + hashes.push_back(*it); + auto j = it++; + context.m_needed_objects.erase(j); + } + + m_block_queue.set_span_hashes(span.first, context.m_connection_id, hashes); + } + + context.m_last_request_time = boost::posix_time::microsec_clock::universal_time(); + LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_GET_OBJECTS: blocks.size()=" << req.blocks.size() << ", txs.size()=" << req.txs.size() + << "requested blocks count=" << count << " / " << count_limit << " from " << span.first); + //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); + + post_notify<NOTIFY_REQUEST_GET_OBJECTS>(req, context); + return true; + } + } - post_notify<NOTIFY_REQUEST_GET_OBJECTS>(req, context); - }else if(context.m_last_response_height < context.m_remote_blockchain_height-1) +skip: + context.m_needed_objects.clear(); + if(context.m_last_response_height < context.m_remote_blockchain_height-1) {//we have to fetch more objects ids, request blockchain entry NOTIFY_REQUEST_CHAIN::request r = boost::value_initialized<NOTIFY_REQUEST_CHAIN::request>(); m_core.get_short_chain_history(r.block_ids); + + if (!start_from_current_chain) + { + // we'll want to start off from where we are on the download side, which may not be added yet + crypto::hash last_known_hash = m_block_queue.get_last_known_hash(); + if (last_known_hash != cryptonote::null_hash && r.block_ids.front() != last_known_hash) + r.block_ids.push_front(last_known_hash); + } + handler_request_blocks_history( r.block_ids ); // change the limit(?), sleep(?) //std::string blob; // for calculate size of request //epee::serialization::store_t_to_binary(r, blob); //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); - LOG_PRINT_CCONTEXT_L1("r = " << 200); + //LOG_PRINT_CCONTEXT_L1("r = " << 200); - LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() ); + LOG_PRINT_CCONTEXT_L1("-->>NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << r.block_ids.size() << ", start_from_current_chain " << start_from_current_chain); post_notify<NOTIFY_REQUEST_CHAIN>(r, context); }else { @@ -1089,8 +1400,18 @@ skip: << "\r\non connection [" << epee::net_utils::print_connection_context_short(context)<< "]"); context.m_state = cryptonote_connection_context::state_normal; - MGINFO_GREEN("SYNCHRONIZED OK"); - on_connection_synchronized(); + if (context.m_remote_blockchain_height >= m_core.get_target_blockchain_height()) + { + if (m_core.get_current_blockchain_height() >= m_core.get_target_blockchain_height()) + { + MGINFO_GREEN("SYNCHRONIZED OK"); + on_connection_synchronized(); + } + } + else + { + MINFO(context << " we've reached this peer's blockchain height"); + } } return true; } @@ -1134,15 +1455,7 @@ skip: LOG_ERROR_CCONTEXT("sent empty m_block_ids, dropping connection"); m_p2p->drop_connection(context); m_p2p->add_host_fail(context.m_remote_address); - return 1; - } - - if(!m_core.have_block(arg.m_block_ids.front())) - { - LOG_ERROR_CCONTEXT("sent m_block_ids starting from unknown id: " - << epee::string_tools::pod_to_hex(arg.m_block_ids.front()) << " , dropping connection"); - m_p2p->drop_connection(context); - m_p2p->add_host_fail(context.m_remote_address); + m_block_queue.flush_spans(context.m_connection_id); return 1; } @@ -1154,15 +1467,22 @@ skip: << ", m_start_height=" << arg.start_height << ", m_block_ids.size()=" << arg.m_block_ids.size()); m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); + return 1; } for(auto& bl_id: arg.m_block_ids) { - if(!m_core.have_block(bl_id)) - context.m_needed_objects.push_back(bl_id); + context.m_needed_objects.push_back(bl_id); } - request_missing_objects(context, false); + if (!request_missing_objects(context, false)) + { + LOG_ERROR_CCONTEXT("Failed to request missing objects, dropping connection"); + m_p2p->drop_connection(context); + m_block_queue.flush_spans(context.m_connection_id); + return 1; + } return 1; } //------------------------------------------------------------------------------------------------------------------------ diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index a7caeeffc..d949a57b1 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -256,7 +256,8 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, args.front())) { bool dnssec_valid; - std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid); + std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(args.front(), dnssec_valid, + [](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid){return addresses[0];}); if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, false, address_str)) { if(!cryptonote::get_account_integrated_address_from_str(adr, has_payment_id, payment_id, true, address_str)) @@ -578,4 +579,11 @@ bool t_command_parser_executor::relay_tx(const std::vector<std::string>& args) return m_executor.relay_tx(txid); } +bool t_command_parser_executor::sync_info(const std::vector<std::string>& args) +{ + if (args.size() != 0) return false; + + return m_executor.sync_info(); +} + } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index a453553f1..f301ef14a 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -134,6 +134,8 @@ public: bool update(const std::vector<std::string>& args); bool relay_tx(const std::vector<std::string>& args); + + bool sync_info(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index f47891fdd..12f7c5fa4 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -253,6 +253,11 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::relay_tx, &m_parser, p::_1) , "Relay a given transaction by its txid" ); + m_command_lookup.set_handler( + "sync_info" + , std::bind(&t_command_parser_executor::sync_info, &m_parser, p::_1) + , "Print information about blockchain sync state" + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 241cb3883..683eaf4ff 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -41,7 +41,7 @@ #include "daemon/rpc.h" #include "daemon/command_server.h" #include "version.h" -#include "../../contrib/epee/include/syncobj.h" +#include "syncobj.h" using namespace epee; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 5d8d95b03..34d87d282 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -111,6 +111,13 @@ namespace { return base; return base + " -- " + status; } + + std::string pad(std::string s, size_t n) + { + if (s.size() < n) + s.append(n - s.size(), ' '); + return s; + } } t_rpc_command_executor::t_rpc_command_executor( @@ -1583,6 +1590,7 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) double avgreward = 0; std::vector<uint64_t> sizes; sizes.reserve(nblocks); + uint64_t earliest = std::numeric_limits<uint64_t>::max(), latest = 0; std::vector<unsigned> major_versions(256, 0), minor_versions(256, 0); for (const auto &bhr: bhres.headers) { @@ -1594,12 +1602,14 @@ bool t_rpc_command_executor::print_blockchain_dynamic_stats(uint64_t nblocks) static_assert(sizeof(bhr.minor_version) == 1, "major_version expected to be uint8_t"); major_versions[(unsigned)bhr.major_version]++; minor_versions[(unsigned)bhr.minor_version]++; + earliest = std::min(earliest, bhr.timestamp); + latest = std::max(latest, bhr.timestamp); } avgdiff /= nblocks; avgnumtxes /= nblocks; avgreward /= nblocks; uint64_t median_block_size = epee::misc_utils::median(sizes); - tools::msg_writer() << "Last " << nblocks << ": avg. diff " << (uint64_t)avgdiff << ", avg num txes " << avgnumtxes + tools::msg_writer() << "Last " << nblocks << ": avg. diff " << (uint64_t)avgdiff << ", " << (latest - earliest) / nblocks << " avg sec/block, avg num txes " << avgnumtxes << ", avg. reward " << cryptonote::print_money(avgreward) << ", median block size " << median_block_size; unsigned int max_major = 256, max_minor = 256; @@ -1694,4 +1704,65 @@ bool t_rpc_command_executor::relay_tx(const std::string &txid) return true; } +bool t_rpc_command_executor::sync_info() +{ + cryptonote::COMMAND_RPC_SYNC_INFO::request req; + cryptonote::COMMAND_RPC_SYNC_INFO::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "sync_info", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_sync_info(req, res, error_resp) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + uint64_t target = res.target_height < res.height ? res.height : res.target_height; + tools::success_msg_writer() << "Height: " << res.height << ", target: " << target << " (" << (100.0 * res.height / target) << "%)"; + uint64_t current_download = 0; + for (const auto &p: res.peers) + current_download += p.info.current_download; + tools::success_msg_writer() << "Downloading at " << current_download << " kB/s"; + + tools::success_msg_writer() << std::to_string(res.peers.size()) << " peers"; + for (const auto &p: res.peers) + { + std::string address = pad(p.info.address, 24); + uint64_t nblocks = 0, size = 0; + for (const auto &s: res.spans) + if (s.rate > 0.0f && s.connection_id == p.info.connection_id) + nblocks += s.nblocks, size += s.size; + tools::success_msg_writer() << address << " " << p.info.peer_id << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; + } + + uint64_t total_size = 0; + for (const auto &s: res.spans) + total_size += s.size; + tools::success_msg_writer() << std::to_string(res.spans.size()) << " spans, " << total_size/1e6 << " MB"; + for (const auto &s: res.spans) + { + std::string address = pad(s.remote_address, 24); + if (s.size == 0) + { + tools::success_msg_writer() << address << " " << s.nblocks << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ") -"; + } + else + { + tools::success_msg_writer() << address << " " << s.nblocks << " (" << s.start_block_height << " - " << (s.start_block_height + s.nblocks - 1) << ", " << (uint64_t)(s.size/1e3) << " kB) " << (unsigned)(s.rate/1e3) << " kB/s (" << s.speed/100.0f << ")"; + } + } + + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 3f551bd14..fc0b39654 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -42,10 +42,7 @@ #include "common/common_fwd.h" #include "common/rpc_client.h" -#include "misc_log_ex.h" -#include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_protocol/cryptonote_protocol_handler.h" -#include "p2p/net_node.h" +#include "cryptonote_basic/cryptonote_basic.h" #include "rpc/core_rpc_server.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -155,6 +152,8 @@ public: bool update(const std::string &command); bool relay_tx(const std::string &txid); + + bool sync_info(); }; } // namespace daemonize diff --git a/src/daemonizer/posix_daemonizer.inl b/src/daemonizer/posix_daemonizer.inl index f8be15dda..506c7766f 100644 --- a/src/daemonizer/posix_daemonizer.inl +++ b/src/daemonizer/posix_daemonizer.inl @@ -43,6 +43,10 @@ namespace daemonizer "detach" , "Run as daemon" }; + const command_line::arg_descriptor<std::string> arg_pidfile = { + "pidfile" + , "File path to write the daemon's PID to (optional, requires --detach)" + }; const command_line::arg_descriptor<bool> arg_non_interactive = { "non-interactive" , "Run non-interactive" @@ -55,6 +59,7 @@ namespace daemonizer ) { command_line::add_arg(normal_options, arg_detach); + command_line::add_arg(normal_options, arg_pidfile); command_line::add_arg(normal_options, arg_non_interactive); } @@ -80,7 +85,12 @@ namespace daemonizer if (command_line::has_arg(vm, arg_detach)) { tools::success_msg_writer() << "Forking to background..."; - posix::fork(); + std::string pidfile; + if (command_line::has_arg(vm, arg_pidfile)) + { + pidfile = command_line::get_arg(vm, arg_pidfile); + } + posix::fork(pidfile); auto daemon = executor.create_daemon(vm); return daemon.run(); } diff --git a/src/daemonizer/posix_fork.cpp b/src/daemonizer/posix_fork.cpp index d9b4a6d0e..4dff04f3f 100644 --- a/src/daemonizer/posix_fork.cpp +++ b/src/daemonizer/posix_fork.cpp @@ -21,15 +21,43 @@ namespace posix { namespace { - void quit(std::string const & message) + void quit(const std::string & message) { LOG_ERROR(message); throw std::runtime_error(message); } } -void fork() +void fork(const std::string & pidfile) { + // If a PID file is specified, we open the file here, because + // we can't report errors after the fork operation. + // When we fork, we close thise file in each of the parent + // processes. + // Only in the final child process do we write the PID to the + // file (and close it). + std::ofstream pidofs; + if (! pidfile.empty ()) + { + int oldpid; + std::ifstream pidrifs; + pidrifs.open(pidfile, std::fstream::in); + if (! pidrifs.fail()) + { + // Read the PID and send signal 0 to see if the process exists. + if (pidrifs >> oldpid && oldpid > 1 && kill(oldpid, 0) == 0) + { + quit("PID file " + pidfile + " already exists and the PID therein is valid"); + } + pidrifs.close(); + } + + pidofs.open(pidfile, std::fstream::out | std::fstream::trunc); + if (pidofs.fail()) + { + quit("Failed to open specified PID file for writing"); + } + } // Fork the process and have the parent exit. If the process was started // from a shell, this returns control to the user. Forking a new process is // also a prerequisite for the subsequent call to setsid(). @@ -38,7 +66,7 @@ void fork() if (pid > 0) { // We're in the parent process and need to exit. - // + pidofs.close(); // When the exit() function is used, the program terminates without // invoking local variables' destructors. Only global variables are // destroyed. @@ -59,6 +87,7 @@ void fork() { if (pid > 0) { + pidofs.close(); exit(0); } else @@ -67,6 +96,13 @@ void fork() } } + if (! pidofs.fail()) + { + int pid = ::getpid(); + pidofs << pid << std::endl; + pidofs.close(); + } + // Close the standard streams. This decouples the daemon from the terminal // that started it. close(0); diff --git a/src/daemonizer/posix_fork.h b/src/daemonizer/posix_fork.h index 459417d25..77ef4cb19 100644 --- a/src/daemonizer/posix_fork.h +++ b/src/daemonizer/posix_fork.h @@ -1,21 +1,21 @@ // Copyright (c) 2014-2017, 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 @@ -27,12 +27,13 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once +#include <string> #ifndef WIN32 namespace posix { -void fork(); +void fork(const std::string & pidfile); } diff --git a/src/p2p/connection_basic.cpp b/src/p2p/connection_basic.cpp index df34379e2..d4fbc79e1 100644 --- a/src/p2p/connection_basic.cpp +++ b/src/p2p/connection_basic.cpp @@ -53,8 +53,8 @@ #include "syncobj.h" -#include "../../contrib/epee/include/net/net_utils_base.h" -#include "../../contrib/epee/include/misc_log_ex.h" +#include "net/net_utils_base.h" +#include "misc_log_ex.h" #include <boost/lambda/bind.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> @@ -74,11 +74,11 @@ #include <boost/asio/basic_socket.hpp> #include <boost/asio/ip/unicast.hpp> -#include "../../contrib/epee/include/net/abstract_tcp_server2.h" +#include "net/abstract_tcp_server2.h" // TODO: -#include "../../src/p2p/network_throttle-detail.hpp" -#include "../../src/cryptonote_core/cryptonote_core.h" +#include "network_throttle-detail.hpp" +#include "cryptonote_core/cryptonote_core.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.p2p" diff --git a/src/p2p/connection_basic.hpp b/src/p2p/connection_basic.hpp index bea2df1cd..16de469a7 100644 --- a/src/p2p/connection_basic.hpp +++ b/src/p2p/connection_basic.hpp @@ -59,8 +59,8 @@ #include <memory> -#include "../../contrib/epee/include/net/net_utils_base.h" -#include "../../contrib/epee/include/syncobj.h" +#include "net/net_utils_base.h" +#include "syncobj.h" namespace epee { diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 8798a52e0..fb7cebde2 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -61,8 +61,11 @@ namespace nodetool template<class base_type> struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { + p2p_connection_context_t(): support_flags(0), m_in_timedsync(false) {} + peerid_type peer_id; uint32_t support_flags; + bool m_in_timedsync; }; template<class t_payload_net_handler> @@ -186,6 +189,7 @@ namespace nodetool virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); + virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); virtual bool add_host_fail(const epee::net_utils::network_address &address); //----------------- i_connection_filter -------------------------------------------------------- virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index b23090c7d..a977f17b7 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -200,6 +200,14 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::for_connection(const boost::uuids::uuid &connection_id, std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f) + { + return m_net_server.get_config_object().for_connection(connection_id, [&](p2p_connection_context& cntx){ + return f(cntx, cntx.peer_id, cntx.support_flags); + }); + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address) { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); @@ -444,11 +452,11 @@ namespace nodetool std::vector<std::vector<std::string>> dns_results; dns_results.resize(m_seed_nodes_list.size()); - std::list<boost::thread*> dns_threads; + std::list<boost::thread> dns_threads; uint64_t result_index = 0; for (const std::string& addr_str : m_seed_nodes_list) { - boost::thread* th = new boost::thread([=, &dns_results, &addr_str] + boost::thread th = boost::thread([=, &dns_results, &addr_str] { MDEBUG("dns_threads[" << result_index << "] created for: " << addr_str); // TODO: care about dnssec avail/valid @@ -474,19 +482,19 @@ namespace nodetool dns_results[result_index] = addr_list; }); - dns_threads.push_back(th); + dns_threads.push_back(std::move(th)); ++result_index; } MDEBUG("dns_threads created, now waiting for completion or timeout of " << CRYPTONOTE_DNS_TIMEOUT_MS << "ms"); boost::chrono::system_clock::time_point deadline = boost::chrono::system_clock::now() + boost::chrono::milliseconds(CRYPTONOTE_DNS_TIMEOUT_MS); uint64_t i = 0; - for (boost::thread* th : dns_threads) + for (boost::thread& th : dns_threads) { - if (! th->try_join_until(deadline)) + if (! th.try_join_until(deadline)) { MWARNING("dns_threads[" << i << "] timed out, sending interrupt"); - th->interrupt(); + th.interrupt(); } ++i; } @@ -714,6 +722,14 @@ namespace nodetool template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::send_stop_signal() { + std::list<boost::uuids::uuid> connection_ids; + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { + connection_ids.push_back(cntxt.m_connection_id); + return true; + }); + for (const auto &connection_id: connection_ids) + m_net_server.get_config_object().close(connection_id); + m_payload_handler.stop(); m_net_server.send_stop_signal(); MDEBUG("[node] Stop signal sent"); @@ -1382,7 +1398,7 @@ namespace nodetool } 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 = tools::get_proof_of_trust_hash(tr); + crypto::hash h = get_proof_of_trust_hash(tr); if(!crypto::check_signature(h, pk, tr.sign)) { LOG_ERROR("check_trust failed: sign check failed"); diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 42de2655d..26bad7c72 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -51,6 +51,7 @@ namespace nodetool virtual void request_callback(const epee::net_utils::connection_context_base& context)=0; virtual uint64_t get_connections_count()=0; virtual void for_each_connection(std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0; + virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&, peerid_type, uint32_t)> f)=0; virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; virtual bool unblock_host(const epee::net_utils::network_address &address)=0; virtual std::map<std::string, time_t> get_blocked_hosts()=0; @@ -88,6 +89,10 @@ namespace nodetool { } + virtual bool for_connection(const boost::uuids::uuid&, std::function<bool(t_connection_context&,peerid_type,uint32_t)> f) + { + return false; + } virtual uint64_t get_connections_count() { diff --git a/src/p2p/network_throttle-detail.cpp b/src/p2p/network_throttle-detail.cpp index 0747b6f36..1df48ee26 100644 --- a/src/p2p/network_throttle-detail.cpp +++ b/src/p2p/network_throttle-detail.cpp @@ -51,8 +51,8 @@ #include "syncobj.h" -#include "../../contrib/epee/include/net/net_utils_base.h" -#include "../../contrib/epee/include/misc_log_ex.h" +#include "net/net_utils_base.h" +#include "misc_log_ex.h" #include <boost/lambda/bind.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> @@ -71,10 +71,10 @@ #include <boost/asio/basic_socket.hpp> #include <boost/asio/ip/unicast.hpp> -#include "../../contrib/epee/include/net/abstract_tcp_server2.h" +#include "net/abstract_tcp_server2.h" // TODO: -#include "../../src/p2p/network_throttle-detail.hpp" +#include "network_throttle-detail.hpp" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.throttle" diff --git a/src/p2p/network_throttle-detail.hpp b/src/p2p/network_throttle-detail.hpp index c514db450..27caa85d3 100644 --- a/src/p2p/network_throttle-detail.hpp +++ b/src/p2p/network_throttle-detail.hpp @@ -36,7 +36,7 @@ #ifndef INCLUDED_src_p2p_throttle_detail_hpp #define INCLUDED_src_p2p_throttle_detail_hpp -#include "../../src/p2p/network_throttle.hpp" +#include "network_throttle.hpp" namespace epee { diff --git a/src/p2p/network_throttle.cpp b/src/p2p/network_throttle.cpp index 6d68f3286..74b20376d 100644 --- a/src/p2p/network_throttle.cpp +++ b/src/p2p/network_throttle.cpp @@ -54,7 +54,7 @@ Throttling work by: // 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 "../../src/p2p/network_throttle-detail.hpp" +#include "network_throttle-detail.hpp" namespace epee { @@ -77,28 +77,22 @@ int network_throttle_manager::xxx; // ================================================================================================ // methods: i_network_throttle & network_throttle_manager::get_global_throttle_in() { - boost::call_once(m_once_get_global_throttle_in, [] { m_obj_get_global_throttle_in.reset(new network_throttle("in/all","<<< global-IN",10)); } ); - return * m_obj_get_global_throttle_in; + static network_throttle obj_get_global_throttle_in("in/all","<<< global-IN",10); + return obj_get_global_throttle_in; } -boost::once_flag network_throttle_manager::m_once_get_global_throttle_in; -std::unique_ptr<i_network_throttle> network_throttle_manager::m_obj_get_global_throttle_in; i_network_throttle & network_throttle_manager::get_global_throttle_inreq() { - boost::call_once(m_once_get_global_throttle_inreq, [] { m_obj_get_global_throttle_inreq.reset(new network_throttle("inreq/all", "<== global-IN-REQ",10)); } ); - return * m_obj_get_global_throttle_inreq; + static network_throttle obj_get_global_throttle_inreq("inreq/all", "<== global-IN-REQ",10); + return obj_get_global_throttle_inreq; } -boost::once_flag network_throttle_manager::m_once_get_global_throttle_inreq; -std::unique_ptr<i_network_throttle> network_throttle_manager::m_obj_get_global_throttle_inreq; i_network_throttle & network_throttle_manager::get_global_throttle_out() { - boost::call_once(m_once_get_global_throttle_out, [] { m_obj_get_global_throttle_out.reset(new network_throttle("out/all", ">>> global-OUT",10)); } ); - return * m_obj_get_global_throttle_out; + static network_throttle obj_get_global_throttle_out("out/all", ">>> global-OUT",10); + return obj_get_global_throttle_out; } -boost::once_flag network_throttle_manager::m_once_get_global_throttle_out; -std::unique_ptr<i_network_throttle> network_throttle_manager::m_obj_get_global_throttle_out; diff --git a/src/p2p/network_throttle.hpp b/src/p2p/network_throttle.hpp index a747cdd71..9853df5e1 100644 --- a/src/p2p/network_throttle.hpp +++ b/src/p2p/network_throttle.hpp @@ -54,8 +54,8 @@ #include "syncobj.h" -#include "../../contrib/epee/include/net/net_utils_base.h" -#include "../../contrib/epee/include/misc_log_ex.h" +#include "net/net_utils_base.h" +#include "misc_log_ex.h" #include <boost/lambda/bind.hpp> #include <boost/lambda/lambda.hpp> #include <boost/uuid/random_generator.hpp> @@ -111,13 +111,6 @@ class network_throttle_manager { //protected: public: // XXX - // [[note1]] - static boost::once_flag m_once_get_global_throttle_in; - static boost::once_flag m_once_get_global_throttle_inreq; // [[note2]] - static boost::once_flag m_once_get_global_throttle_out; - static std::unique_ptr<i_network_throttle> m_obj_get_global_throttle_in; - static std::unique_ptr<i_network_throttle> m_obj_get_global_throttle_inreq; - static std::unique_ptr<i_network_throttle> m_obj_get_global_throttle_out; static boost::mutex m_lock_get_global_throttle_in; static boost::mutex m_lock_get_global_throttle_inreq; diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index a471211a6..f38615def 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -35,7 +35,9 @@ #include "net/net_utils_base.h" #include "misc_language.h" #include "cryptonote_config.h" +#ifdef ALLOW_DEBUG_COMMANDS #include "crypto/crypto.h" +#endif namespace nodetool { @@ -440,6 +442,14 @@ namespace nodetool #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/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 97fe18696..877256d16 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -227,6 +227,28 @@ namespace cryptonote res.status = CORE_RPC_STATUS_OK; return true; } + bool core_rpc_server::on_get_alt_blocks_hashes(const COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request& req, COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response& res) + { + CHECK_CORE_BUSY(); + std::list<block> blks; + + if(!m_core.get_alternative_blocks(blks)) + { + res.status = "Failed"; + return false; + } + + res.blks_hashes.reserve(blks.size()); + + for (auto const& blk: blks) + { + res.blks_hashes.push_back(epee::string_tools::pod_to_hex(get_block_hash(blk))); + } + + MDEBUG("on_get_alt_blocks_hashes: " << blks.size() << " blocks " ); + res.status = CORE_RPC_STATUS_OK; + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res) { @@ -1668,6 +1690,41 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp) + { + if(!check_core_busy()) + { + error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; + error_resp.message = "Core is busy."; + return false; + } + + crypto::hash top_hash; + if (!m_core.get_blockchain_top(res.height, top_hash)) + { + res.status = "Failed"; + return false; + } + ++res.height; // turn top block height into blockchain height + res.target_height = m_core.get_target_blockchain_height(); + + for (const auto &c: m_p2p.get_payload_object().get_connections()) + res.peers.push_back({c}); + const cryptonote::block_queue &block_queue = m_p2p.get_payload_object().get_block_queue(); + block_queue.foreach([&](const cryptonote::block_queue::span &span) { + uint32_t speed = (uint32_t)(100.0f * block_queue.get_speed(span.connection_id) + 0.5f); + std::string address = ""; + for (const auto &c: m_p2p.get_payload_object().get_connections()) + if (c.connection_id == span.connection_id) + address = c.address; + res.spans.push_back({span.start_block_height, span.nblocks, span.connection_id, (uint32_t)(span.rate + 0.5f), speed, span.size, address}); + return true; + }); + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_bind_port = { "rpc-bind-port" diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 44ac6f07a..1d1d9da66 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -81,6 +81,7 @@ namespace cryptonote MAP_URI_AUTO_BIN2("/get_outs.bin", on_get_outs_bin, COMMAND_RPC_GET_OUTPUTS_BIN) MAP_URI_AUTO_BIN2("/getrandom_rctouts.bin", on_get_random_rct_outs, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS) MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) + MAP_URI_AUTO_JON2("/get_alt_blocks_hashes", on_get_alt_blocks_hashes, COMMAND_RPC_GET_ALT_BLOCKS_HASHES) MAP_URI_AUTO_JON2("/is_key_image_spent", on_is_key_image_spent, COMMAND_RPC_IS_KEY_IMAGE_SPENT) MAP_URI_AUTO_JON2("/sendrawtransaction", on_send_raw_tx, COMMAND_RPC_SEND_RAW_TX) MAP_URI_AUTO_JON2_IF("/start_mining", on_start_mining, COMMAND_RPC_START_MINING, !m_restricted) @@ -123,11 +124,13 @@ namespace cryptonote MAP_JON_RPC_WE("get_fee_estimate", on_get_per_kb_fee_estimate, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE) MAP_JON_RPC_WE_IF("get_alternate_chains",on_get_alternate_chains, COMMAND_RPC_GET_ALTERNATE_CHAINS, !m_restricted) MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted) + MAP_JON_RPC_WE_IF("sync_info", on_sync_info, COMMAND_RPC_SYNC_INFO, !m_restricted) END_JSON_RPC_MAP() END_URI_MAP2() bool on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res); bool on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res); + bool on_get_alt_blocks_hashes(const COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request& req, COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response& res); bool on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res); bool on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res); bool on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res); @@ -178,6 +181,7 @@ namespace cryptonote bool on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp); bool on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp); bool on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp); + bool on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp); //----------------------- private: diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 7a1f5a963..c413f9af8 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 12 +#define CORE_RPC_VERSION_MINOR 13 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -146,6 +146,25 @@ namespace cryptonote }; }; + struct COMMAND_RPC_GET_ALT_BLOCKS_HASHES + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<std::string> blks_hashes; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(blks_hashes) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; struct COMMAND_RPC_GET_HASHES_FAST { @@ -1587,4 +1606,60 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_SYNC_INFO + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct peer + { + connection_info info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(info) + END_KV_SERIALIZE_MAP() + }; + + struct span + { + uint64_t start_block_height; + uint64_t nblocks; + boost::uuids::uuid connection_id; + uint32_t rate; + uint32_t speed; + uint64_t size; + std::string remote_address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(start_block_height) + KV_SERIALIZE(nblocks) + KV_SERIALIZE_VAL_POD_AS_BLOB(connection_id) + KV_SERIALIZE(rate) + KV_SERIALIZE(speed) + KV_SERIALIZE(size) + KV_SERIALIZE(remote_address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + uint64_t height; + uint64_t target_height; + std::list<peer> peers; + std::list<span> spans; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(height) + KV_SERIALIZE(target_height) + KV_SERIALIZE(peers) + KV_SERIALIZE(spans) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6b87d253c..14bc8c004 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -282,6 +282,42 @@ namespace { return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff); } + + std::string oa_prompter(const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid) + { + if (addresses.empty()) + return {}; + // prompt user for confirmation. + // inform user of DNSSEC validation status as well. + std::string dnssec_str; + if (dnssec_valid) + { + dnssec_str = tr("DNSSEC validation passed"); + } + else + { + dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); + } + std::stringstream prompt; + prompt << tr("For URL: ") << url + << ", " << dnssec_str << std::endl + << tr(" Monero Address = ") << addresses[0] + << std::endl + << tr("Is this OK? (Y/n) ") + ; + // prompt the user for confirmation given the dns query and dnssec status + std::string confirm_dns_ok = command_line::input_line(prompt.str()); + if (std::cin.eof()) + { + return {}; + } + if (!command_line::is_yes(confirm_dns_ok)) + { + std::cout << tr("you have cancelled the transfer request") << std::endl; + return {}; + } + return addresses[0]; + } } @@ -2355,7 +2391,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if (!cryptonote::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i])) + if (!cryptonote::get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -2515,7 +2551,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { auto & ptx = ptx_vector.back(); m_wallet->commit_tx(ptx); - success_msg_writer(true) << tr("Money successfully sent, transaction ") << get_transaction_hash(ptx.tx); + success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL + << tr("You can check its status by using the `show_transfers` command."); // if no exception, remove element from vector ptx_vector.pop_back(); @@ -2853,7 +2890,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a bool has_payment_id; crypto::hash8 new_payment_id; cryptonote::account_public_address address; - if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0])) + if (!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[0], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3118,12 +3155,39 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, // gather info to ask the user uint64_t amount = 0, amount_to_dests = 0, change = 0; size_t min_mixin = ~0; - std::unordered_map<std::string, uint64_t> dests; + std::unordered_map<std::string, std::pair<std::string, uint64_t>> dests; const std::string wallet_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); int first_known_non_zero_change_index = -1; + std::string payment_id_string = ""; for (size_t n = 0; n < get_num_txes(); ++n) { const tools::wallet2::tx_construction_data &cd = get_tx(n); + + std::vector<tx_extra_field> tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = cryptonote::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + payment_id_string = std::string("unencrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id); + } + } + } + for (size_t s = 0; s < cd.sources.size(); ++s) { amount += cd.sources[s].amount; @@ -3134,23 +3198,30 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) { const tx_destination_entry &entry = cd.splitted_dsts[d]; - std::string address = get_account_address_as_str(m_wallet->testnet(), entry.addr); - std::unordered_map<std::string,uint64_t>::iterator i = dests.find(address); + std::string address, standard_address = get_account_address_as_str(m_wallet->testnet(), entry.addr); + if (has_encrypted_payment_id) + { + address = get_account_integrated_address_as_str(m_wallet->testnet(), entry.addr, payment_id8); + address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); + } + else + address = standard_address; + std::unordered_map<std::string,std::pair<std::string,uint64_t>>::iterator i = dests.find(standard_address); if (i == dests.end()) - dests.insert(std::make_pair(address, entry.amount)); + dests.insert(std::make_pair(standard_address, std::make_pair(address, entry.amount))); else - i->second += entry.amount; + i->second.second += entry.amount; amount_to_dests += entry.amount; } if (cd.change_dts.amount > 0) { - std::unordered_map<std::string, uint64_t>::iterator it = dests.find(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); + std::unordered_map<std::string, std::pair<std::string, uint64_t>>::iterator it = dests.find(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); if (it == dests.end()) { fail_msg_writer() << tr("Claimed change does not go to a paid address"); return false; } - if (it->second < cd.change_dts.amount) + if (it->second.second < cd.change_dts.amount) { fail_msg_writer() << tr("Claimed change is larger than payment to the change address"); return false; @@ -3166,15 +3237,19 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, } } change += cd.change_dts.amount; - it->second -= cd.change_dts.amount; - if (it->second == 0) + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) dests.erase(get_account_address_as_str(m_wallet->testnet(), cd.change_dts.addr)); } } + + if (payment_id_string.empty()) + payment_id_string = "no payment ID"; + std::string dest_string; - for (std::unordered_map<std::string, uint64_t>::const_iterator i = dests.begin(); i != dests.end(); ) + for (std::unordered_map<std::string, std::pair<std::string, uint64_t>>::const_iterator i = dests.begin(); i != dests.end(); ) { - dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second) % i->first).str(); + dest_string += (boost::format(tr("sending %s to %s")) % print_money(i->second.second) % i->second.first).str(); ++i; if (i != dests.end()) dest_string += ", "; @@ -3192,7 +3267,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, change_string += tr("no change"); uint64_t fee = amount - amount_to_dests; - std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu. %sIs this okay? (Y/Yes/N/No): ")) % (unsigned long)get_num_txes() % print_money(amount) % print_money(fee) % dest_string % change_string % (unsigned long)min_mixin % extra_message).str(); + std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min mixin %lu, %s. %sIs this okay? (Y/Yes/N/No): ")) % (unsigned long)get_num_txes() % print_money(amount) % print_money(fee) % dest_string % change_string % (unsigned long)min_mixin % payment_id_string % extra_message).str(); return command_line::is_yes(command_line::input_line(prompt_str)); } //---------------------------------------------------------------------------------------------------- @@ -3413,7 +3488,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1])) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3515,7 +3590,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2])) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), local_args[2], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -3667,7 +3742,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1])) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -4185,7 +4260,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id8; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1])) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), args[1], oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; @@ -4376,7 +4451,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args) cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string)) + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), address_string, oa_prompter)) { fail_msg_writer() << tr("failed to parse address"); return true; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 6a0e1727c..9915a9ca1 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -99,7 +99,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); @@ -114,7 +114,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) + virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); @@ -129,8 +129,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount, - const cryptonote::transaction& spend_tx) + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, + uint64_t amount, const cryptonote::transaction& spend_tx) { // TODO; std::string tx_hash = epee::string_tools::pod_to_hex(txid); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6b1026a55..034b0a02a 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1388,7 +1388,7 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei } } //---------------------------------------------------------------------------------------------------- -void wallet2::update_pool_state() +void wallet2::update_pool_state(bool refreshed) { MDEBUG("update_pool_state start"); @@ -1424,13 +1424,14 @@ void wallet2::update_pool_state() // a tx is removed from the pool due to being found in a new block, but // just before the block is visible by refresh. So we keep a boolean, so // that the first time we don't see the tx, we set that boolean, and only - // delete it the second time it is checked + // delete it the second time it is checked (but only when refreshed, so + // we're sure we've seen the blockchain state first) if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending) { LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as not in pool"); pit->second.m_state = wallet2::unconfirmed_transfer_details::pending_not_in_pool; } - else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool) + else if (pit->second.m_state == wallet2::unconfirmed_transfer_details::pending_not_in_pool && refreshed) { LOG_PRINT_L1("Pending txid " << txid << " not in pool, marking as failed"); pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; @@ -1459,24 +1460,30 @@ void wallet2::update_pool_state() MDEBUG("update_pool_state done first loop"); // remove pool txes to us that aren't in the pool anymore - std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); - while (uit != m_unconfirmed_payments.end()) + // but only if we just refreshed, so that the tx can go in + // the in transfers list instead (or nowhere if it just + // disappeared without being mined) + if (refreshed) { - const crypto::hash &txid = uit->second.m_tx_hash; - bool found = false; - for (const auto &it2: res.tx_hashes) + std::unordered_map<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); + while (uit != m_unconfirmed_payments.end()) { - if (it2 == txid) + const crypto::hash &txid = uit->second.m_tx_hash; + bool found = false; + for (const auto &it2: res.tx_hashes) { - found = true; - break; + if (it2 == txid) + { + found = true; + break; + } + } + auto pit = uit++; + if (!found) + { + MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool"); + m_unconfirmed_payments.erase(pit); } - } - auto pit = uit++; - if (!found) - { - MDEBUG("Removing " << txid << " from unconfirmed payments, not found in pool"); - m_unconfirmed_payments.erase(pit); } } MDEBUG("update_pool_state done second loop"); @@ -1490,7 +1497,16 @@ void wallet2::update_pool_state() LOG_PRINT_L2("Already seen " << txid << ", skipped"); continue; } - if (m_unconfirmed_payments.find(txid) == m_unconfirmed_payments.end()) + bool txid_found_in_up = false; + for (const auto &up: m_unconfirmed_payments) + { + if (up.second.m_tx_hash == txid) + { + txid_found_in_up = true; + break; + } + } + if (!txid_found_in_up) { LOG_PRINT_L1("Found new pool tx: " << txid); bool found = false; @@ -1685,6 +1701,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re uint64_t blocks_start_height; std::list<cryptonote::block_complete_entry> blocks; std::vector<COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> o_indices; + bool refreshed = false; // pull the first set of blocks get_short_chain_history(short_chain_history); @@ -1726,6 +1743,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re if(blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); + refreshed = true; break; } @@ -1764,7 +1782,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re { // If stop() is called we don't need to check pending transactions if(m_run.load(std::memory_order_relaxed)) - update_pool_state(); + update_pool_state(refreshed); } catch (...) { @@ -4359,7 +4377,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // try to pick outputs not from the same block. We will get two outputs, one for // the destination, and one for change. LOG_PRINT_L2("checking preferred"); - std::vector<size_t> prefered_inputs; + std::vector<size_t> preferred_inputs; uint64_t rct_outs_needed = 2 * (fake_outs_count + 1); rct_outs_needed += 100; // some fudge factor since we don't know how many are locked if (use_rct && get_num_rct_outputs() >= rct_outs_needed) @@ -4367,12 +4385,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which // will get us a known fee. uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); - prefered_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee); - if (!prefered_inputs.empty()) + preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee); + if (!preferred_inputs.empty()) { string s; - for (auto i: prefered_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; - LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); + for (auto i: preferred_inputs) s += boost::lexical_cast<std::string>(i) + "(" + print_money(m_transfers[i].amount()) + ") "; + LOG_PRINT_L1("Found preferred rct inputs for rct tx: " << s); } } LOG_PRINT_L2("done checking preferred"); @@ -4427,8 +4445,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } pop_if_present(unused_transfers_indices, idx); pop_if_present(unused_dust_indices, idx); - } else if (!prefered_inputs.empty()) { - idx = pop_back(prefered_inputs); + } else if (!preferred_inputs.empty()) { + idx = pop_back(preferred_inputs); pop_if_present(unused_transfers_indices, idx); pop_if_present(unused_dust_indices, idx); } else @@ -4521,7 +4539,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee; LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " << - print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accomodate " << + print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accommodate " << print_money(needed_fee) << " fee"); dsts[0].amount += i->amount - new_paid_amount; i->amount = new_paid_amount; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e7692badb..b853c5f3c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -585,7 +585,7 @@ namespace tools uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, uint64_t &spent, uint64_t &unspent); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); - void update_pool_state(); + void update_pool_state(bool refreshed = false); std::string encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated = true) const; std::string encrypt_with_view_secret_key(const std::string &plaintext, bool authenticated = true) const; diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index 1508f3791..34c5a2a5d 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -149,8 +149,10 @@ namespace wallet_args if (command_line::get_arg(vm, command_line::arg_help)) { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - tools::msg_writer() << wallet_args::tr("Usage:") << ' ' << usage; + tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" + "daemon to work correctly.") << ENDL; + tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; tools::msg_writer() << desc_all; return boost::none; } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index e7b9b5a71..58b020740 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -352,10 +352,25 @@ namespace tools cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if(!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), it->address, false)) + er.message = ""; + if(!get_account_address_from_str_or_url(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), it->address, + [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + er.message = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + er.message = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; - er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; + if (er.message.empty()) + er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; return false; } de.amount = it->amount; @@ -454,7 +469,8 @@ namespace tools return false; } - m_wallet->commit_tx(ptx_vector); + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); // populate response with tx hash res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); @@ -463,6 +479,13 @@ namespace tools res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); } res.fee = ptx_vector.back().fee; + + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx_vector.back().tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } return true; } catch (const tools::error::daemon_busy& e) @@ -519,9 +542,12 @@ namespace tools ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - LOG_PRINT_L2("on_transfer_split calling commit_tx"); - m_wallet->commit_tx(ptx_vector); - LOG_PRINT_L2("on_transfer_split called commit_tx"); + if (!req.do_not_relay) + { + LOG_PRINT_L2("on_transfer_split calling commit_tx"); + m_wallet->commit_tx(ptx_vector); + LOG_PRINT_L2("on_transfer_split called commit_tx"); + } // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -538,6 +564,13 @@ namespace tools res.amount_list.push_back(ptx_amount); res.fee_list.push_back(ptx.fee); + + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } } return true; @@ -577,7 +610,8 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - m_wallet->commit_tx(ptx_vector); + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -588,6 +622,12 @@ namespace tools res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } res.fee_list.push_back(ptx.fee); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } } return true; @@ -640,7 +680,8 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, req.mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); - m_wallet->commit_tx(ptx_vector); + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); // populate response with tx hashes for (auto & ptx : ptx_vector) @@ -650,7 +691,12 @@ namespace tools { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); } - res.fee_list.push_back(ptx.fee); + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); + } } return true; @@ -1018,10 +1064,23 @@ namespace tools cryptonote::account_public_address address; bool has_payment_id; crypto::hash8 payment_id; - if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), req.address, false)) + er.message = ""; + if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), req.address, + [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + er.message = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + er.message = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; - er.message = ""; return false; } @@ -1412,10 +1471,25 @@ namespace tools bool has_payment_id; crypto::hash8 payment_id8; crypto::hash payment_id = cryptonote::null_hash; - if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), req.address, false)) + er.message = ""; + if(!get_account_address_from_str_or_url(address, has_payment_id, payment_id8, m_wallet->testnet(), req.address, + [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { + if (!dnssec_valid) + { + er.message = std::string("Invalid DNSSEC for ") + url; + return {}; + } + if (addresses.empty()) + { + er.message = std::string("No Monero address found at ") + url; + return {}; + } + return addresses[0]; + })) { er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; - er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; + if (er.message.empty()) + er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; return false; } if (has_payment_id) diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 12ac281e4..5832bb032 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -119,6 +119,8 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool get_tx_key; + bool do_not_relay; + bool get_tx_hex; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -127,6 +129,8 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_key) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) END_KV_SERIALIZE_MAP() }; @@ -136,12 +140,14 @@ namespace wallet_rpc std::string tx_key; std::list<std::string> amount_keys; uint64_t fee; + std::string tx_blob; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount_keys) KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) END_KV_SERIALIZE_MAP() }; }; @@ -156,6 +162,8 @@ namespace wallet_rpc uint64_t unlock_time; std::string payment_id; bool get_tx_keys; + bool do_not_relay; + bool get_tx_hex; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -164,6 +172,8 @@ namespace wallet_rpc KV_SERIALIZE(unlock_time) KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_keys) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) END_KV_SERIALIZE_MAP() }; @@ -182,12 +192,14 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; + std::list<std::string> tx_blob_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(tx_blob_list) END_KV_SERIALIZE_MAP() }; }; @@ -197,9 +209,13 @@ namespace wallet_rpc struct request { bool get_tx_keys; + bool do_not_relay; + bool get_tx_hex; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) END_KV_SERIALIZE_MAP() }; @@ -217,11 +233,13 @@ namespace wallet_rpc std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; + std::list<std::string> tx_blob_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(tx_blob_list) END_KV_SERIALIZE_MAP() }; }; @@ -237,6 +255,8 @@ namespace wallet_rpc std::string payment_id; bool get_tx_keys; uint64_t below_amount; + bool do_not_relay; + bool get_tx_hex; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -246,6 +266,8 @@ namespace wallet_rpc KV_SERIALIZE(payment_id) KV_SERIALIZE(get_tx_keys) KV_SERIALIZE(below_amount) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) END_KV_SERIALIZE_MAP() }; @@ -263,11 +285,13 @@ namespace wallet_rpc std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; + std::list<std::string> tx_blob_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(tx_blob_list) END_KV_SERIALIZE_MAP() }; }; |