diff options
Diffstat (limited to 'src')
26 files changed, 1220 insertions, 124 deletions
diff --git a/src/blockchain_utilities/CMakeLists.txt b/src/blockchain_utilities/CMakeLists.txt index f88180686..5538e7d26 100644 --- a/src/blockchain_utilities/CMakeLists.txt +++ b/src/blockchain_utilities/CMakeLists.txt @@ -77,6 +77,16 @@ bitmonero_private_headers(blockchain_dump ${blockchain_dump_private_headers}) +set(cn_deserialize_sources + cn_deserialize.cpp + ) + +set(cn_deserialize_private_headers) + +bitmonero_private_headers(cn_deserialize + ${cn_deserialize_private_headers}) + + if (BLOCKCHAIN_DB STREQUAL DB_LMDB) bitmonero_add_executable(blockchain_converter ${blockchain_converter_sources} @@ -147,3 +157,19 @@ set_property(TARGET blockchain_dump PROPERTY OUTPUT_NAME "blockchain_dump") +bitmonero_add_executable(cn_deserialize + ${cn_deserialize_sources} + ${cn_deserialize_private_headers}) + +target_link_libraries(cn_deserialize + LINK_PRIVATE + cryptonote_core + p2p + ${CMAKE_THREAD_LIBS_INIT}) + +add_dependencies(cn_deserialize + version) +set_property(TARGET cn_deserialize + PROPERTY + OUTPUT_NAME "cn_deserialize") + diff --git a/src/blockchain_utilities/cn_deserialize.cpp b/src/blockchain_utilities/cn_deserialize.cpp new file mode 100644 index 000000000..e831e790b --- /dev/null +++ b/src/blockchain_utilities/cn_deserialize.cpp @@ -0,0 +1,170 @@ +// Copyright (c) 2014-2015, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "cryptonote_core/cryptonote_basic.h" +#include "cryptonote_core/blockchain.h" +#include "blockchain_utilities.h" +#include "common/command_line.h" +#include "version.h" + +unsigned int epee::g_test_dbg_lock_sleep = 0; + +namespace po = boost::program_options; +using namespace epee; // log_space + +using namespace cryptonote; + +int main(int argc, char* argv[]) +{ + uint32_t log_level = 0; + std::string input; + + tools::sanitize_locale(); + + boost::filesystem::path output_file_path; + + po::options_description desc_cmd_only("Command line options"); + po::options_description desc_cmd_sett("Command line options and settings options"); + const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; + const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", log_level}; + const command_line::arg_descriptor<std::string> arg_input = {"input", "Specify input has a hexadecimal string", ""}; + + command_line::add_arg(desc_cmd_sett, arg_output_file); + command_line::add_arg(desc_cmd_sett, arg_log_level); + command_line::add_arg(desc_cmd_sett, arg_input); + + command_line::add_arg(desc_cmd_only, command_line::arg_help); + + po::options_description desc_options("Allowed options"); + desc_options.add(desc_cmd_only).add(desc_cmd_sett); + + po::variables_map vm; + bool r = command_line::handle_error_helper(desc_options, [&]() + { + po::store(po::parse_command_line(argc, argv, desc_options), vm); + po::notify(vm); + return true; + }); + if (! r) + return 1; + + if (command_line::get_arg(vm, command_line::arg_help)) + { + std::cout << CRYPTONOTE_NAME << " v" << MONERO_VERSION_FULL << ENDL << ENDL; + std::cout << desc_options << std::endl; + return 1; + } + + log_level = command_line::get_arg(vm, arg_log_level); + input = command_line::get_arg(vm, arg_input); + if (input.empty()) + { + std::cerr << "--input is mandatory" << std::endl; + return 1; + } + + log_space::get_set_log_detalisation_level(true, log_level); + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + + std::string m_config_folder; + + std::ostream *output; + std::ofstream *raw_data_file = NULL; + if (command_line::has_arg(vm, arg_output_file)) + { + output_file_path = boost::filesystem::path(command_line::get_arg(vm, arg_output_file)); + + const boost::filesystem::path dir_path = output_file_path.parent_path(); + if (!dir_path.empty()) + { + if (boost::filesystem::exists(dir_path)) + { + if (!boost::filesystem::is_directory(dir_path)) + { + std::cerr << "output directory path is a file: " << dir_path << std::endl; + return 1; + } + } + else + { + if (!boost::filesystem::create_directory(dir_path)) + { + std::cerr << "Failed to create directory " << dir_path << std::endl; + return 1; + } + } + } + + raw_data_file = new std::ofstream(); + raw_data_file->open(output_file_path.string(), std::ios_base::out | std::ios::trunc); + if (raw_data_file->fail()) + return 1; + output = raw_data_file; + } + else + { + output_file_path = ""; + output = &std::cout; + } + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(input, blob)) + { + std::cerr << "Invalid hex input" << std::endl; + std::cerr << "Invalid hex input: " << input << std::endl; + return 1; + } + + cryptonote::block block; + cryptonote::transaction tx; + if (cryptonote::parse_and_validate_block_from_blob(blob, block)) + { + std::cout << "Parsed block:" << std::endl; + std::cout << cryptonote::obj_to_json_str(block) << std::endl; + } + else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx)) + { + std::cout << "Parsed transaction:" << std::endl; + std::cout << cryptonote::obj_to_json_str(tx) << std::endl; + } + else + { + std::cerr << "Not a recognized CN type" << std::endl; + return 1; + } + + + + if (output->fail()) + return 1; + output->flush(); + if (raw_data_file) + delete raw_data_file; + + return 0; +} diff --git a/src/common/util.cpp b/src/common/util.cpp index b41862718..e5d22acb0 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -48,7 +48,7 @@ using namespace epee; namespace tools { - std::function<void(void)> signal_handler::m_handler; + std::function<void(int)> signal_handler::m_handler; #ifdef WIN32 std::string get_windows_version_display_string() diff --git a/src/common/util.h b/src/common/util.h index 236a0b6f0..937f7e44d 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -130,7 +130,7 @@ namespace tools { if (CTRL_C_EVENT == type || CTRL_BREAK_EVENT == type) { - handle_signal(); + handle_signal(type); } else { @@ -141,21 +141,21 @@ namespace tools } #else /*! \brief handler for NIX */ - static void posix_handler(int /*type*/) + static void posix_handler(int type) { - handle_signal(); + handle_signal(type); } #endif /*! \brief calles m_handler */ - static void handle_signal() + static void handle_signal(int type) { static std::mutex m_mutex; std::unique_lock<std::mutex> lock(m_mutex); - m_handler(); + m_handler(type); } /*! \brief where the installed handler is stored */ - static std::function<void(void)> m_handler; + static std::function<void(int)> m_handler; }; } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 6053387ac..33005660a 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -105,6 +105,11 @@ #define P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 5000 //5 seconds #define P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70 +#define P2P_FAILED_ADDR_FORGET_SECONDS (60*60) //1 hour +#define P2P_IP_BLOCKTIME (60*60*24) //24 hour +#define P2P_IP_FAILS_BEFORE_BLOCK 10 +#define P2P_IDLE_CONNECTION_KILL_INTERVAL (5*60) //5 minutes + #define ALLOW_DEBUG_COMMANDS #define CRYPTONOTE_NAME "bitmonero" diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8bb63ad09..af7713972 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -257,13 +257,13 @@ bool Blockchain::init(BlockchainDB* db, const bool testnet) if (testnet) { m_hardfork = new HardFork(*db, 1, testnet_hard_fork_version_1_till); for (size_t n = 0; n < sizeof(testnet_hard_forks) / sizeof(testnet_hard_forks[0]); ++n) - m_hardfork->add(testnet_hard_forks[n].version, testnet_hard_forks[n].height, testnet_hard_forks[n].threshold, testnet_hard_forks[n].time); + m_hardfork->add_fork(testnet_hard_forks[n].version, testnet_hard_forks[n].height, testnet_hard_forks[n].threshold, testnet_hard_forks[n].time); } else { m_hardfork = new HardFork(*db, 1, mainnet_hard_fork_version_1_till); for (size_t n = 0; n < sizeof(mainnet_hard_forks) / sizeof(mainnet_hard_forks[0]); ++n) - m_hardfork->add(mainnet_hard_forks[n].version, mainnet_hard_forks[n].height, mainnet_hard_forks[n].threshold, mainnet_hard_forks[n].time); + m_hardfork->add_fork(mainnet_hard_forks[n].version, mainnet_hard_forks[n].height, mainnet_hard_forks[n].threshold, mainnet_hard_forks[n].time); } m_hardfork->init(); @@ -2338,7 +2338,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& // this is a cheap test if (!m_hardfork->check(bl)) { - LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version: " << bl.major_version << std::endl << "current: " << m_hardfork->get_current_version()); + LOG_PRINT_L1("Block with id: " << id << std::endl << "has old version: " << (unsigned)bl.major_version << std::endl << "current: " << (unsigned)m_hardfork->get_current_version()); return false; } diff --git a/src/cryptonote_core/checkpoints_create.cpp b/src/cryptonote_core/checkpoints_create.cpp index d9bfa9807..de7d65009 100644 --- a/src/cryptonote_core/checkpoints_create.cpp +++ b/src/cryptonote_core/checkpoints_create.cpp @@ -101,6 +101,7 @@ bool create_checkpoints(cryptonote::checkpoints& checkpoints) ADD_CHECKPOINT(232150, "955de8e6b6508af2c24f7334f97beeea651d78e9ade3ab18fec3763be3201aa8"); ADD_CHECKPOINT(249380, "654fb0a81ce3e5caf7e3264a70f447d4bd07586c08fa50f6638cc54da0a52b2d"); ADD_CHECKPOINT(460000, "75037a7aed3e765db96c75bcf908f59d690a5f3390baebb9edeafd336a1c4831"); + ADD_CHECKPOINT(825000, "56503f9ad766774b575be3aff73245e9d159be88132c93d1754764f28da2ff60"); return true; } diff --git a/src/cryptonote_core/hardfork.cpp b/src/cryptonote_core/hardfork.cpp index edc2f33a9..2a2e25635 100644 --- a/src/cryptonote_core/hardfork.cpp +++ b/src/cryptonote_core/hardfork.cpp @@ -46,6 +46,11 @@ static uint8_t get_block_vote(const cryptonote::block &b) return b.minor_version; } +static uint8_t get_block_version(const cryptonote::block &b) +{ + return b.major_version; +} + HardFork::HardFork(cryptonote::BlockchainDB &db, uint8_t original_version, uint64_t original_version_till_height, time_t forked_time, time_t update_time, uint64_t window_size, uint8_t default_threshold_percent): db(db), original_version(original_version), @@ -61,7 +66,7 @@ HardFork::HardFork(cryptonote::BlockchainDB &db, uint8_t original_version, uint6 throw "default_threshold_percent needs to be between 0 and 100"; } -bool HardFork::add(uint8_t version, uint64_t height, uint8_t threshold, time_t time) +bool HardFork::add_fork(uint8_t version, uint64_t height, uint8_t threshold, time_t time) { CRITICAL_REGION_LOCAL(lock); @@ -82,42 +87,43 @@ bool HardFork::add(uint8_t version, uint64_t height, uint8_t threshold, time_t t return true; } -bool HardFork::add(uint8_t version, uint64_t height, time_t time) +bool HardFork::add_fork(uint8_t version, uint64_t height, time_t time) { - return add(version, height, default_threshold_percent, time); + return add_fork(version, height, default_threshold_percent, time); } -uint8_t HardFork::get_effective_version(uint8_t version) const +uint8_t HardFork::get_effective_version(uint8_t voting_version) const { if (!heights.empty()) { uint8_t max_version = heights.back().version; - if (version > max_version) - version = max_version; + if (voting_version > max_version) + voting_version = max_version; } - return version; + return voting_version; } -bool HardFork::do_check(uint8_t version) const +bool HardFork::do_check(uint8_t block_version, uint8_t voting_version) const { - return version >= heights[current_fork_index].version; + return block_version >= heights[current_fork_index].version + && voting_version >= heights[current_fork_index].version; } bool HardFork::check(const cryptonote::block &block) const { CRITICAL_REGION_LOCAL(lock); - return do_check(get_block_vote(block)); + return do_check(::get_block_version(block), ::get_block_vote(block)); } -bool HardFork::add(uint8_t block_version, uint64_t height) +bool HardFork::add(uint8_t block_version, uint8_t voting_version, uint64_t height) { CRITICAL_REGION_LOCAL(lock); - if (!do_check(block_version)) + if (!do_check(block_version, voting_version)) return false; db.set_hard_fork_version(height, heights[current_fork_index].version); - const uint8_t version = get_effective_version(block_version); + voting_version = get_effective_version(voting_version); while (versions.size() >= window_size) { const uint8_t old_version = versions.front(); @@ -126,8 +132,8 @@ bool HardFork::add(uint8_t block_version, uint64_t height) versions.pop_front(); } - last_versions[version]++; - versions.push_back(version); + last_versions[voting_version]++; + versions.push_back(voting_version); uint8_t voted = get_voted_fork_index(height + 1); if (voted > current_fork_index) { @@ -143,7 +149,7 @@ bool HardFork::add(uint8_t block_version, uint64_t height) bool HardFork::add(const cryptonote::block &block, uint64_t height) { - return add(get_block_vote(block), height); + return add(::get_block_version(block), ::get_block_vote(block), height); } void HardFork::init() @@ -185,7 +191,7 @@ uint8_t HardFork::get_block_version(uint64_t height) const return original_version; const cryptonote::block &block = db.get_block_from_height(height); - return get_block_vote(block); + return ::get_block_version(block); } bool HardFork::reorganize_from_block_height(uint64_t height) @@ -225,7 +231,7 @@ bool HardFork::reorganize_from_block_height(uint64_t height) const uint64_t bc_height = db.height(); for (uint64_t h = height + 1; h < bc_height; ++h) { - add(get_block_version(h), h); + add(db.get_block_from_height(h), h); } db.batch_stop(); @@ -258,7 +264,7 @@ bool HardFork::rescan_from_block_height(uint64_t height) versions.push_back(v); } - uint8_t lastv = db.get_hard_fork_version(height); + uint8_t lastv = db.get_hard_fork_version(db.height() - 1); current_fork_index = 0; while (current_fork_index + 1 < heights.size() && heights[current_fork_index].version != lastv) ++current_fork_index; diff --git a/src/cryptonote_core/hardfork.h b/src/cryptonote_core/hardfork.h index 6800749da..6d2a3c55b 100644 --- a/src/cryptonote_core/hardfork.h +++ b/src/cryptonote_core/hardfork.h @@ -71,7 +71,7 @@ namespace cryptonote * @param threshold The threshold of votes needed for this fork (0-100) * @param time Approximate time of the hardfork (seconds since epoch) */ - bool add(uint8_t version, uint64_t height, uint8_t threshold, time_t time); + bool add_fork(uint8_t version, uint64_t height, uint8_t threshold, time_t time); /** * @brief add a new hardfork height @@ -79,10 +79,11 @@ namespace cryptonote * returns true if no error, false otherwise * * @param version the major block version for the fork + * @param voting_version the minor block version for the fork, used for voting * @param height The height the hardfork takes effect * @param time Approximate time of the hardfork (seconds since epoch) */ - bool add(uint8_t version, uint64_t height, time_t time); + bool add_fork(uint8_t version, uint64_t height, time_t time); /** * @brief initialize the object @@ -203,10 +204,10 @@ namespace cryptonote private: uint8_t get_block_version(uint64_t height) const; - bool do_check(uint8_t version) const; + bool do_check(uint8_t block_version, uint8_t voting_version) const; int get_voted_fork_index(uint64_t height) const; - uint8_t get_effective_version(uint8_t version) const; - bool add(uint8_t block_version, uint64_t height); + uint8_t get_effective_version(uint8_t voting_version) const; + bool add(uint8_t block_version, uint8_t voting_version, uint64_t height); bool rescan_from_block_height(uint64_t height); bool rescan_from_chain_height(uint64_t height); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 83c7233b1..a6761101f 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -571,6 +571,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L1("Block verification failed, dropping connection"); m_p2p->drop_connection(context); + m_p2p->add_ip_fail(context.m_remote_ip); m_core.cleanup_handle_incoming_blocks(); return 1; } @@ -578,6 +579,7 @@ namespace cryptonote { LOG_PRINT_CCONTEXT_L1("Block received at sync phase was marked as orphaned, dropping connection"); m_p2p->drop_connection(context); + m_p2p->add_ip_fail(context.m_remote_ip); m_core.cleanup_handle_incoming_blocks(); return 1; } @@ -728,6 +730,7 @@ namespace cryptonote { LOG_ERROR_CCONTEXT("sent empty m_block_ids, dropping connection"); m_p2p->drop_connection(context); + m_p2p->add_ip_fail(context.m_remote_ip); return 1; } @@ -736,6 +739,7 @@ namespace cryptonote 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_ip_fail(context.m_remote_ip); return 1; } diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index a07bb25de..487d86071 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -392,5 +392,34 @@ bool t_command_parser_executor::hard_fork_info(const std::vector<std::string>& a return m_executor.hard_fork_info(version); } +bool t_command_parser_executor::show_bans(const std::vector<std::string>& args) +{ + if (!args.empty()) return false; + return m_executor.print_bans(); +} + +bool t_command_parser_executor::ban(const std::vector<std::string>& args) +{ + if (args.size() != 1 && args.size() != 2) return false; + std::string ip = args[0]; + time_t seconds = P2P_IP_BLOCKTIME; + if (args.size() > 1) + { + seconds = std::stoi(args[0]); + if (seconds == 0) + { + return false; + } + } + return m_executor.ban(ip, seconds); +} + +bool t_command_parser_executor::unban(const std::vector<std::string>& args) +{ + if (args.size() != 1) return false; + std::string ip = args[0]; + return m_executor.unban(ip); +} + } // namespace daemonize diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h index f00fbd77e..0c042cd5d 100644 --- a/src/daemon/command_parser_executor.h +++ b/src/daemon/command_parser_executor.h @@ -106,6 +106,12 @@ public: bool stop_save_graph(const std::vector<std::string>& args); bool hard_fork_info(const std::vector<std::string>& args); + + bool show_bans(const std::vector<std::string>& args); + + bool ban(const std::vector<std::string>& args); + + bool unban(const std::vector<std::string>& args); }; } // namespace daemonize diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 8714b2569..0999ed30c 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -194,6 +194,21 @@ t_command_server::t_command_server( , std::bind(&t_command_parser_executor::hard_fork_info, &m_parser, p::_1) , "Print hard fork voting information" ); + m_command_lookup.set_handler( + "bans" + , std::bind(&t_command_parser_executor::show_bans, &m_parser, p::_1) + , "Show the currently banned IPs" + ); + m_command_lookup.set_handler( + "ban" + , std::bind(&t_command_parser_executor::ban, &m_parser, p::_1) + , "Ban a given IP for a time" + ); + m_command_lookup.set_handler( + "unban" + , std::bind(&t_command_parser_executor::unban, &m_parser, p::_1) + , "Unban a given IP" + ); } bool t_command_server::process_command_str(const std::string& cmd) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index a28b4290d..74dbc0012 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -242,7 +242,7 @@ bool t_rpc_command_executor::show_difficulty() { tools::success_msg_writer() << "BH: " << res.height << ", DIFF: " << res.difficulty - << ", HR: " << (int) res.difficulty / 60L << " H/s"; + << ", HR: " << (int) res.difficulty / res.target << " H/s"; return true; } @@ -283,11 +283,11 @@ bool t_rpc_command_executor::show_status() { tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s, net hash %s, v%u, %s, %u+%u connections") % (unsigned long long)ires.height - % (unsigned long long)(ires.target_height ? ires.target_height : ires.height) + % (unsigned long long)(ires.target_height >= ires.height ? ires.target_height : ires.height) % (100.0f * ires.height / (ires.target_height ? ires.target_height < ires.height ? ires.height : ires.target_height : ires.height)) % (ires.testnet ? "testnet" : "mainnet") % [&ires]()->std::string { - float hr = ires.difficulty / 60.0f; + float hr = ires.difficulty / ires.target; if (hr>1e9) return (boost::format("%.2f GH/s") % (hr/1e9)).str(); if (hr>1e6) return (boost::format("%.2f MH/s") % (hr/1e6)).str(); if (hr>1e3) return (boost::format("%.2f kH/s") % (hr/1e3)).str(); @@ -1036,4 +1036,110 @@ bool t_rpc_command_executor::hard_fork_info(uint8_t version) return true; } +bool t_rpc_command_executor::print_bans() +{ + cryptonote::COMMAND_RPC_GETBANS::request req; + cryptonote::COMMAND_RPC_GETBANS::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, "get_bans", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_bans(req, res, error_resp)) + { + tools::fail_msg_writer() << fail_message.c_str(); + return true; + } + } + + time_t now = time(nullptr); + for (auto i = res.bans.begin(); i != res.bans.end(); ++i) + { + time_t seconds = i->seconds - now; + tools::msg_writer() << epee::string_tools::get_ip_string_from_int32(i->ip) << " banned for " << seconds << " seconds"; + } + + return true; +} + + +bool t_rpc_command_executor::ban(const std::string &ip, time_t seconds) +{ + cryptonote::COMMAND_RPC_SETBANS::request req; + cryptonote::COMMAND_RPC_SETBANS::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + cryptonote::COMMAND_RPC_SETBANS::ban ban; + if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) + { + tools::fail_msg_writer() << "Invalid IP"; + return true; + } + ban.ban = true; + ban.seconds = seconds; + req.bans.push_back(ban); + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "set_bans", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_set_bans(req, res, error_resp)) + { + tools::fail_msg_writer() << fail_message.c_str(); + return true; + } + } + + return true; +} + +bool t_rpc_command_executor::unban(const std::string &ip) +{ + cryptonote::COMMAND_RPC_SETBANS::request req; + cryptonote::COMMAND_RPC_SETBANS::response res; + std::string fail_message = "Unsuccessful"; + epee::json_rpc::error error_resp; + + cryptonote::COMMAND_RPC_SETBANS::ban ban; + if (!epee::string_tools::get_ip_int32_from_string(ban.ip, ip)) + { + tools::fail_msg_writer() << "Invalid IP"; + return true; + } + ban.ban = false; + ban.seconds = 0; + req.bans.push_back(ban); + + if (m_is_rpc) + { + if (!m_rpc_client->json_rpc_request(req, res, "set_bans", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_set_bans(req, res, error_resp)) + { + tools::fail_msg_writer() << fail_message.c_str(); + return true; + } + } + + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 778b73acb..95c5624fa 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -124,6 +124,12 @@ public: bool stop_save_graph(); bool hard_fork_info(uint8_t version); + + bool print_bans(); + + bool ban(const std::string &ip, time_t seconds); + + bool unban(const std::string &ip); }; } // namespace daemonize diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 3eb125208..39cbe01fa 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -42,6 +42,7 @@ #include <boost/program_options/variables_map.hpp> #include <boost/serialization/version.hpp> #include <boost/uuid/uuid.hpp> +#include <boost/serialization/map.hpp> #include "cryptonote_config.h" #include "warnings.h" @@ -66,7 +67,8 @@ namespace nodetool template<class t_payload_net_handler> class node_server: public epee::levin::levin_commands_handler<p2p_connection_context_t<typename t_payload_net_handler::connection_context> >, - public i_p2p_endpoint<typename t_payload_net_handler::connection_context> + public i_p2p_endpoint<typename t_payload_net_handler::connection_context>, + public epee::net_utils::i_connection_filter { struct by_conn_id{}; struct by_peer_id{}; @@ -115,6 +117,9 @@ namespace nodetool size_t get_outgoing_connections_count(); peerlist_manager& get_peerlist_manager(){return m_peerlist;} void delete_connections(size_t count); + virtual bool block_ip(uint32_t adress, time_t seconds = P2P_IP_BLOCKTIME); + virtual bool unblock_ip(uint32_t address); + virtual std::map<uint32_t, time_t> get_blocked_ips() const { return m_blocked_ips; } private: const std::vector<std::string> m_seed_nodes_list = { "seeds.moneroseeds.se" @@ -169,6 +174,9 @@ 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)> f); + virtual bool add_ip_fail(uint32_t address); + //----------------- i_connection_filter -------------------------------------------------------- + virtual bool is_remote_ip_allowed(uint32_t adress); //----------------------------------------------------------------------------------------------- bool parse_peer_from_string(nodetool::net_address& pe, const std::string& node_addr); bool handle_command_line( @@ -196,6 +204,8 @@ namespace nodetool template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb); bool make_expected_connections_count(bool white_list, size_t expected_connections); + void cache_connect_fail_info(const net_address& addr); + bool is_addr_recently_failed(const net_address& addr); bool is_priority_node(const net_address& na); template <class Container> @@ -282,6 +292,15 @@ namespace nodetool //keep connections to initiate some interactions net_server m_net_server; boost::uuids::uuid m_network_id; + + std::map<net_address, time_t> m_conn_fails_cache; + epee::critical_section m_conn_fails_cache_lock; + + epee::critical_section m_blocked_ips_lock; + std::map<uint32_t, time_t> m_blocked_ips; + + epee::critical_section m_ip_fails_score_lock; + std::map<uint32_t, uint64_t> m_ip_fails_score; }; } diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index d92b4bb2a..067f6378d 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -28,6 +28,8 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +// IP blocking adapted from Boolberry + #pragma once #include <algorithm> @@ -161,6 +163,22 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::is_remote_ip_allowed(uint32_t addr) + { + CRITICAL_REGION_LOCAL(m_blocked_ips_lock); + auto it = m_blocked_ips.find(addr); + if(it == m_blocked_ips.end()) + return true; + if(time(nullptr) >= it->second) + { + m_blocked_ips.erase(it); + LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << "is unblocked.", LOG_LEVEL_0); + return true; + } + return false; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::make_default_config() { m_config.m_peer_id = crypto::rand<uint64_t>(); @@ -168,6 +186,43 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::block_ip(uint32_t addr, time_t seconds) + { + CRITICAL_REGION_LOCAL(m_blocked_ips_lock); + m_blocked_ips[addr] = time(nullptr) + seconds; + LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << " blocked.", LOG_LEVEL_0); + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::unblock_ip(uint32_t addr) + { + CRITICAL_REGION_LOCAL(m_blocked_ips_lock); + auto i = m_blocked_ips.find(addr); + if (i == m_blocked_ips.end()) + return false; + m_blocked_ips.erase(i); + LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked.", LOG_LEVEL_0); + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::add_ip_fail(uint32_t address) + { + CRITICAL_REGION_LOCAL(m_ip_fails_score_lock); + uint64_t fails = ++m_ip_fails_score[address]; + LOG_PRINT_CYAN("IP " << epee::string_tools::get_ip_string_from_int32(address) << " fail score=" << fails, LOG_LEVEL_1); + if(fails > P2P_IP_FAILS_BEFORE_BLOCK) + { + auto it = m_ip_fails_score.find(address); + CHECK_AND_ASSERT_MES(it != m_ip_fails_score.end(), false, "internal error"); + it->second = P2P_IP_FAILS_BEFORE_BLOCK/2; + block_ip(address); + } + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::parse_peer_from_string(nodetool::net_address& pe, const std::string& node_addr) { return epee::string_tools::parse_peer_from_string(pe.ip, pe.port, node_addr); @@ -428,6 +483,7 @@ namespace nodetool m_net_server.set_threads_prefix("P2P"); m_net_server.get_config_object().m_pcommands_handler = this; m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; + m_net_server.set_connection_filter(this); //try to bind LOG_PRINT_L0("Binding on " << m_bind_ip << ":" << m_port); @@ -624,6 +680,7 @@ namespace nodetool if(!handle_remote_peerlist(rsp.local_peerlist, rsp.node_data.local_time, context)) { LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); + add_ip_fail(context.m_remote_ip); return; } hsh_result = true; @@ -685,6 +742,7 @@ namespace nodetool { LOG_ERROR_CCONTEXT("COMMAND_TIMED_SYNC: failed to handle_remote_peerlist(...), closing connection."); m_net_server.get_config_object().close(context.m_connection_id ); + add_ip_fail(context.m_remote_ip); } if(!context.m_is_income) m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_ip, context.m_remote_port); @@ -831,6 +889,20 @@ namespace nodetool //----------------------------------------------------------------------------------- template<class t_payload_net_handler> + bool node_server<t_payload_net_handler>::is_addr_recently_failed(const net_address& addr) + { + CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); + auto it = m_conn_fails_cache.find(addr); + if(it == m_conn_fails_cache.end()) + return false; + + if(time(NULL) - it->second > P2P_FAILED_ADDR_FORGET_SECONDS) + return false; + else + return true; + } + //----------------------------------------------------------------------------------- + template<class t_payload_net_handler> bool node_server<t_payload_net_handler>::make_new_connection_from_peerlist(bool use_white_list) { size_t local_peers_count = use_white_list ? m_peerlist.get_white_peers_count():m_peerlist.get_gray_peers_count(); @@ -866,7 +938,13 @@ namespace nodetool continue; } - LOG_PRINT_L1("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) + if(!is_remote_ip_allowed(pe.adr.ip)) + continue; + + if(is_addr_recently_failed(pe.adr)) + continue; + + LOG_PRINT_L2("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port) << "[white=" << use_white_list << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); @@ -1270,6 +1348,7 @@ namespace nodetool LOG_PRINT_CCONTEXT_L1("WRONG NETWORK AGENT CONNECTED! id=" << epee::string_tools::get_str_from_guid_a(arg.node_data.network_id)); drop_connection(context); + add_ip_fail(context.m_remote_ip); return 1; } @@ -1277,6 +1356,7 @@ namespace nodetool { LOG_ERROR_CCONTEXT("COMMAND_HANDSHAKE came not from incoming connection"); drop_connection(context); + add_ip_fail(context.m_remote_ip); return 1; } diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index a7b8bf6f3..2505006ad 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -50,6 +50,10 @@ 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)> f)=0; + virtual bool block_ip(uint32_t adress, time_t seconds = 0)=0; + virtual bool unblock_ip(uint32_t adress)=0; + virtual std::map<uint32_t, time_t> get_blocked_ips()const=0; + virtual bool add_ip_fail(uint32_t adress)=0; }; template<class t_connection_context> @@ -84,5 +88,21 @@ namespace nodetool { return false; } + virtual bool block_ip(uint32_t adress, time_t seconds) + { + return true; + } + virtual bool unblock_ip(uint32_t adress) + { + return true; + } + virtual std::map<uint32_t, time_t> get_blocked_ips() const + { + return std::map<uint32_t, time_t>(); + } + virtual bool add_ip_fail(uint32_t adress) + { + return true; + } }; } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index f738c68f6..3d8b08ce6 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -53,6 +53,7 @@ #include "net_peerlist_boost_serialization.h" +#define CURRENT_PEERLIST_STORAGE_ARCHIVE_VER 4 namespace nodetool { @@ -394,4 +395,4 @@ namespace nodetool //-------------------------------------------------------------------------------------------------- } -BOOST_CLASS_VERSION(nodetool::peerlist_manager, 4) +BOOST_CLASS_VERSION(nodetool::peerlist_manager, CURRENT_PEERLIST_STORAGE_ARCHIVE_VER) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f5e700033..4ba3acc37 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -51,6 +51,7 @@ namespace cryptonote command_line::add_arg(desc, arg_rpc_bind_ip); command_line::add_arg(desc, arg_rpc_bind_port); command_line::add_arg(desc, arg_testnet_rpc_bind_port); + command_line::add_arg(desc, arg_restricted_rpc); } //------------------------------------------------------------------------------------------------------------------------------ core_rpc_server::core_rpc_server( @@ -69,6 +70,7 @@ namespace cryptonote m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); m_port = command_line::get_arg(vm, p2p_bind_arg); + m_restricted = command_line::get_arg(vm, arg_restricted_rpc); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -119,6 +121,7 @@ namespace cryptonote res.height = m_core.get_current_blockchain_height(); res.target_height = m_core.get_target_blockchain_height(); res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block(); + res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET; res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase res.tx_pool_size = m_core.get_pool_transactions_count(); res.alt_blocks_count = m_core.get_blockchain_storage().get_alternative_blocks_count(); @@ -899,6 +902,49 @@ namespace cryptonote #endif } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::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; + } + + std::map<uint32_t, time_t> blocked_ips = m_p2p.get_blocked_ips(); + for (std::map<uint32_t, time_t>::const_iterator i = blocked_ips.begin(); i != blocked_ips.end(); ++i) + { + COMMAND_RPC_GETBANS::ban b; + b.ip = i->first; + b.seconds = i->second; + res.bans.push_back(b); + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::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; + } + + for (auto i = req.bans.begin(); i != req.bans.end(); ++i) + { + if (i->ban) + m_p2p.block_ip(i->ip, i->seconds); + else + m_p2p.unblock_ip(i->ip); + } + + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_fast_exit(const COMMAND_RPC_FAST_EXIT::request& req, COMMAND_RPC_FAST_EXIT::response& res) { cryptonote::core::set_fast_exit(); @@ -957,4 +1003,10 @@ namespace cryptonote , std::to_string(config::testnet::RPC_DEFAULT_PORT) }; + const command_line::arg_descriptor<bool> core_rpc_server::arg_restricted_rpc = { + "restricted-rpc" + , "Restrict RPC to view only commands" + , false + }; + } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 1fbd0981f..8fe17ff5d 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -55,6 +55,7 @@ namespace cryptonote static const command_line::arg_descriptor<std::string> arg_rpc_bind_ip; static const command_line::arg_descriptor<std::string> arg_rpc_bind_port; static const command_line::arg_descriptor<std::string> arg_testnet_rpc_bind_port; + static const command_line::arg_descriptor<bool> arg_restricted_rpc; typedef epee::net_utils::connection_context_base connection_context; @@ -79,20 +80,20 @@ namespace cryptonote MAP_URI_AUTO_JON2("/gettransactions", on_get_transactions, COMMAND_RPC_GET_TRANSACTIONS) 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("/start_mining", on_start_mining, COMMAND_RPC_START_MINING) - MAP_URI_AUTO_JON2("/stop_mining", on_stop_mining, COMMAND_RPC_STOP_MINING) - MAP_URI_AUTO_JON2("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS) - MAP_URI_AUTO_JON2("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC) - MAP_URI_AUTO_JON2("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST) - MAP_URI_AUTO_JON2("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE) - MAP_URI_AUTO_JON2("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL) + MAP_URI_AUTO_JON2_IF("/start_mining", on_start_mining, COMMAND_RPC_START_MINING, !m_restricted) + MAP_URI_AUTO_JON2_IF("/stop_mining", on_stop_mining, COMMAND_RPC_STOP_MINING, !m_restricted) + MAP_URI_AUTO_JON2_IF("/mining_status", on_mining_status, COMMAND_RPC_MINING_STATUS, !m_restricted) + MAP_URI_AUTO_JON2_IF("/save_bc", on_save_bc, COMMAND_RPC_SAVE_BC, !m_restricted) + MAP_URI_AUTO_JON2_IF("/get_peer_list", on_get_peer_list, COMMAND_RPC_GET_PEER_LIST, !m_restricted) + MAP_URI_AUTO_JON2_IF("/set_log_hash_rate", on_set_log_hash_rate, COMMAND_RPC_SET_LOG_HASH_RATE, !m_restricted) + MAP_URI_AUTO_JON2_IF("/set_log_level", on_set_log_level, COMMAND_RPC_SET_LOG_LEVEL, !m_restricted) MAP_URI_AUTO_JON2("/get_transaction_pool", on_get_transaction_pool, COMMAND_RPC_GET_TRANSACTION_POOL) - MAP_URI_AUTO_JON2("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON) + MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted) MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO) - MAP_URI_AUTO_JON2("/fast_exit", on_fast_exit, COMMAND_RPC_FAST_EXIT) - MAP_URI_AUTO_JON2("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS) - MAP_URI_AUTO_JON2("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH) - MAP_URI_AUTO_JON2("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH) + MAP_URI_AUTO_JON2_IF("/fast_exit", on_fast_exit, COMMAND_RPC_FAST_EXIT, !m_restricted) + MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted) + MAP_URI_AUTO_JON2_IF("/start_save_graph", on_start_save_graph, COMMAND_RPC_START_SAVE_GRAPH, !m_restricted) + MAP_URI_AUTO_JON2_IF("/stop_save_graph", on_stop_save_graph, COMMAND_RPC_STOP_SAVE_GRAPH, !m_restricted) BEGIN_JSON_RPC_MAP("/json_rpc") MAP_JON_RPC("getblockcount", on_getblockcount, COMMAND_RPC_GETBLOCKCOUNT) MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) @@ -105,6 +106,8 @@ namespace cryptonote MAP_JON_RPC_WE("get_connections", on_get_connections, COMMAND_RPC_GET_CONNECTIONS) MAP_JON_RPC_WE("get_info", on_get_info_json, COMMAND_RPC_GET_INFO) MAP_JON_RPC_WE("hard_fork_info", on_hard_fork_info, COMMAND_RPC_HARD_FORK_INFO) + MAP_JON_RPC_WE("setbans", on_set_bans, COMMAND_RPC_SETBANS) + MAP_JON_RPC_WE("getbans", on_get_bans, COMMAND_RPC_GETBANS) END_JSON_RPC_MAP() END_URI_MAP2() @@ -142,6 +145,8 @@ namespace cryptonote bool on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp); bool on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp); bool on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp); + bool on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp); + bool on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp); //----------------------- private: @@ -161,5 +166,6 @@ private: std::string m_port; std::string m_bind_ip; bool m_testnet; + bool m_restricted; }; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index aa88ffcb4..b70164614 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -273,6 +273,7 @@ namespace cryptonote uint64_t height; uint64_t target_height; uint64_t difficulty; + uint64_t target; uint64_t tx_count; uint64_t tx_pool_size; uint64_t alt_blocks_count; @@ -888,5 +889,70 @@ namespace cryptonote END_KV_SERIALIZE_MAP() }; }; + + struct COMMAND_RPC_GETBANS + { + struct ban + { + uint32_t ip; + uint32_t seconds; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(ip) + KV_SERIALIZE(seconds) + END_KV_SERIALIZE_MAP() + }; + + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + std::vector<ban> bans; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + KV_SERIALIZE(bans) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SETBANS + { + struct ban + { + uint32_t ip; + bool ban; + uint32_t seconds; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(ip) + KV_SERIALIZE(ban) + KV_SERIALIZE(seconds) + END_KV_SERIALIZE_MAP() + }; + + struct request + { + std::vector<ban> bans; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(bans) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + }; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d3669965f..537fc72ef 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -93,6 +93,7 @@ namespace const command_line::arg_descriptor<bool> arg_testnet = {"testnet", sw::tr("Used to deploy test nets. The daemon must be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", sw::tr("Restricts RPC to view only commands"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; + const command_line::arg_descriptor<std::string> arg_refresh_type = {"refresh-type", sw::tr("Control the wallet refresh speedup/assumptions balance: full (slowest, no assumptions), optimize-coinbase (fast, assumes the whole coinbase is paid to a single address), no-coinbase (fastest, assumes we receive no coinbase transaction)"), "optimize-coinbase"}; const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; @@ -420,6 +421,42 @@ bool simple_wallet::set_default_mixin(const std::vector<std::string> &args/* = s } } +bool simple_wallet::set_auto_refresh(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + bool success = false; + tools::password_container pwd_container; + success = pwd_container.read_password(); + if (!success) + { + fail_msg_writer() << tr("failed to read wallet password"); + return true; + } + + /* verify password before using so user doesn't accidentally set a new password for rewritten wallet */ + success = m_wallet->verify_password(pwd_container.password()); + if (!success) + { + fail_msg_writer() << tr("invalid password"); + return true; + } + + bool auto_refresh = is_it_true(args[1]); + m_wallet->auto_refresh(auto_refresh); + if (auto_refresh && !m_auto_refresh_run.load(std::memory_order_relaxed)) + { + m_auto_refresh_run.store(true, std::memory_order_relaxed); + m_auto_refresh_thread = std::thread([&]{wallet_refresh_thread();}); + } + else if (!auto_refresh && m_auto_refresh_run.load(std::memory_order_relaxed)) + { + m_auto_refresh_run.store(false, std::memory_order_relaxed); + m_auto_refresh_thread.join(); + } + + m_wallet->rewrite(m_wallet_file, pwd_container.password()); + return true; +} + bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { success_msg_writer() << get_commands_str(); @@ -429,6 +466,9 @@ bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<st simple_wallet::simple_wallet() : m_daemon_port(0) , m_refresh_progress_reporter(*this) + , m_auto_refresh_run(false) + , m_auto_refresh_refreshing(false) + , m_in_manual_refresh(false) { m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), tr("start_mining [<number_of_threads>] - Start mining in daemon")); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), tr("Stop mining in daemon")); @@ -449,7 +489,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Get viewkey")); m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Get spendkey")); m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Get deterministic seed")); - m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("available options: seed language - Set wallet seed langage; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store per outgoing tx info (destination address, payment id, tx secret key) for future reference; default_mixin <n> - set default mixin (default default is 4")); + m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("available options: seed language - Set wallet seed langage; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store per outgoing tx info (destination address, payment id, tx secret key) for future reference; default_mixin <n> - set default mixin (default default is 4; auto-refresh <1|0> - whether to automatically refresh new blocks from the daemon")); m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given tx")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to a given address in a partcular tx")); @@ -461,7 +501,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) { if (args.empty()) { - fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, default-mixin"); + fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, default-mixin, auto-refresh"); return true; } else @@ -526,6 +566,21 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) return true; } } + else if (args[0] == "auto-refresh") + { + if (args.size() <= 1) + { + fail_msg_writer() << tr("set auto-refresh: needs an argument (0 or 1)"); + return true; + } + else + { + std::vector<std::string> local_args = args; + local_args.erase(local_args.begin(), local_args.begin()+2); + set_auto_refresh(local_args); + return true; + } + } } fail_msg_writer() << tr("set: unrecognized argument(s)"); return true; @@ -631,7 +686,8 @@ void simple_wallet::print_seed(std::string seed) //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { - handle_command_line(vm); + if (!handle_command_line(vm)) + return false; if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port) { @@ -743,19 +799,19 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet_file += std::string(":") + parts[n]; bool r = new_wallet(m_wallet_file, pwd_container.password(), address, viewkey, testnet); - CHECK_AND_ASSERT_MES(r, false, "account creation failed"); + CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } else { bool r = new_wallet(m_wallet_file, pwd_container.password(), m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, testnet, old_language); - CHECK_AND_ASSERT_MES(r, false, "account creation failed"); + CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } } else { bool r = open_wallet(m_wallet_file, pwd_container.password(), testnet); - CHECK_AND_ASSERT_MES(r, false, "could not open account"); + CHECK_AND_ASSERT_MES(r, false, tr("could not open account")); } return true; @@ -769,7 +825,32 @@ bool simple_wallet::deinit() return close_wallet(); } //---------------------------------------------------------------------------------------------------- -void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) +static bool parse_refresh_type(const std::string &s, tools::wallet2::RefreshType &refresh_type) +{ + static const struct + { + const char *name; + tools::wallet2::RefreshType refresh_type; + } names[] = + { + { "full", tools::wallet2::RefreshFull }, + { "optimize-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, + { "optimized-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, + { "no-coinbase", tools::wallet2::RefreshNoCoinbase }, + }; + for (size_t n = 0; n < sizeof(names) / sizeof(names[0]); ++n) + { + if (s == names[n].name) + { + refresh_type = names[n].refresh_type; + return true; + } + } + fail_msg_writer() << tr("Failed to parse refresh type"); + return false; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) { m_wallet_file = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); @@ -781,6 +862,12 @@ void simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon); + std::string refresh_type = command_line::get_arg(vm, arg_refresh_type); + + if (!parse_refresh_type(refresh_type, m_refresh_type)) + return false; + + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::try_connect_to_daemon() @@ -863,6 +950,7 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); m_wallet->set_seed_language(mnemonic_language); + m_wallet->set_refresh_type(m_refresh_type); crypto::secret_key recovery_val; try @@ -911,6 +999,7 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); + m_wallet->set_refresh_type(m_refresh_type); try { @@ -941,6 +1030,7 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa m_wallet_file = wallet_file; m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); + m_wallet->set_refresh_type(m_refresh_type); try { @@ -982,7 +1072,6 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa m_wallet->init(m_daemon_address); - refresh(std::vector<std::string>()); success_msg_writer() << "**********************************************************************\n" << tr("Use \"help\" command to see the list of available commands.\n") << @@ -992,6 +1081,17 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa //---------------------------------------------------------------------------------------------------- bool simple_wallet::close_wallet() { + if (m_auto_refresh_run.load(std::memory_order_relaxed)) + { + m_auto_refresh_run.store(false, std::memory_order_relaxed); + m_wallet->stop(); + { + std::unique_lock<std::mutex> lock(m_auto_refresh_mutex); + m_auto_refresh_cond.notify_one(); + } + m_auto_refresh_thread.join(); + } + bool r = m_wallet->deinit(); if (!r) { @@ -1139,34 +1239,44 @@ bool simple_wallet::save_bc(const std::vector<std::string>& args) //---------------------------------------------------------------------------------------------------- void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block) { - m_refresh_progress_reporter.update(height, false); + if (!m_auto_refresh_refreshing) + m_refresh_progress_reporter.update(height, false); } //---------------------------------------------------------------------------------------------------- void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) { - message_writer(epee::log_space::console_color_green, false) << + message_writer(epee::log_space::console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(tx) << ", " << tr("received ") << print_money(tx.vout[out_index].amount); - m_refresh_progress_reporter.update(height, true); + if (m_auto_refresh_refreshing) + m_cmd_binder.print_prompt(); + else + m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) { - message_writer(epee::log_space::console_color_magenta, false) << + message_writer(epee::log_space::console_color_magenta, false) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(spend_tx) << ", " << tr("spent ") << print_money(in_tx.vout[out_index].amount); - m_refresh_progress_reporter.update(height, true); + if (m_auto_refresh_refreshing) + m_cmd_binder.print_prompt(); + else + m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- void simple_wallet::on_skip_transaction(uint64_t height, const cryptonote::transaction& tx) { - message_writer(epee::log_space::console_color_red, true) << + message_writer(epee::log_space::console_color_red, true) << "\r" << tr("Height ") << height << ", " << tr("transaction ") << get_transaction_hash(tx) << ", " << tr("unsupported transaction format"); - m_refresh_progress_reporter.update(height, true); + if (m_auto_refresh_refreshing) + m_cmd_binder.print_prompt(); + else + m_refresh_progress_reporter.update(height, true); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector<std::string>& args) @@ -1174,6 +1284,13 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; + bool auto_refresh_run = m_auto_refresh_run.load(std::memory_order_relaxed); + m_auto_refresh_run.store(false, std::memory_order_relaxed); + // stop any background refresh, and take over + m_wallet->stop(); + std::unique_lock<std::mutex> lock(m_auto_refresh_mutex); + m_auto_refresh_cond.notify_one(); + message_writer() << tr("Starting refresh..."); uint64_t fetched_blocks = 0; @@ -1193,7 +1310,9 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) std::ostringstream ss; try { + m_in_manual_refresh.store(true, std::memory_order_relaxed); m_wallet->refresh(start_height, fetched_blocks); + m_in_manual_refresh.store(false, std::memory_order_relaxed); ok = true; // Clear line "Height xxx of xxx" std::cout << "\r \r"; @@ -1239,6 +1358,8 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) fail_msg_writer() << tr("refresh failed: ") << ss.str() << ". " << tr("Blocks received: ") << fetched_blocks; } + m_in_manual_refresh.store(false, std::memory_order_relaxed); + m_auto_refresh_run.store(auto_refresh_run, std::memory_order_relaxed); return true; } //---------------------------------------------------------------------------------------------------- @@ -1284,7 +1405,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << boost::format("%21s%8s%16u%68s") % print_money(td.amount()) % - (td.m_spent ? "T" : "F") % + (td.m_spent ? tr("T") : tr("F")) % td.m_global_output_index % get_transaction_hash (td.m_tx); } @@ -1983,11 +2104,11 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) if (received > 0) { - success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " received " << print_money(received) << " in txid " << txid; + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; } else { - fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " received nothing in txid " << txid; + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), address) << " " << tr("received nothing in txid") << " " << txid; } return true; @@ -2032,7 +2153,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) min_height = boost::lexical_cast<uint64_t>(local_args[0]); } catch (boost::bad_lexical_cast &) { - fail_msg_writer() << "Bad min_height parameter: " << local_args[0]; + fail_msg_writer() << tr("Bad min_height parameter:") << " " << local_args[0]; return true; } local_args.erase(local_args.begin()); @@ -2044,7 +2165,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) max_height = boost::lexical_cast<uint64_t>(local_args[0]); } catch (boost::bad_lexical_cast &) { - fail_msg_writer() << "Bad max_height parameter: " << local_args[0]; + fail_msg_writer() << tr("Bad max_height parameter:") << " " << local_args[0]; return true; } local_args.erase(local_args.begin()); @@ -2086,7 +2207,7 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) // print in and out sorted by height for (std::map<uint64_t, std::pair<bool, std::string>>::const_iterator i = output.begin(); i != output.end(); ++i) { - message_writer(i->second.first ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << + message_writer(i->second.first ? epee::log_space::console_color_green : epee::log_space::console_color_magenta, false) << boost::format("%8.8llu %6.6s %s") % ((unsigned long long)i->first) % (i->second.first ? tr("in") : tr("out")) % i->second.second; } @@ -2110,9 +2231,39 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +void simple_wallet::wallet_refresh_thread() +{ + while (true) + { + std::unique_lock<std::mutex> lock(m_auto_refresh_mutex); + if (!m_auto_refresh_run.load(std::memory_order_relaxed)) + break; + m_auto_refresh_refreshing = true; + try + { + uint64_t fetched_blocks; + m_wallet->refresh(0, fetched_blocks); + } + catch(...) {} + m_auto_refresh_refreshing = false; + if (!m_auto_refresh_run.load(std::memory_order_relaxed)) + break; + m_auto_refresh_cond.wait_for(lock, chrono::seconds(90)); + } +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::run() { std::string addr_start = m_wallet->get_account().get_public_address_str(m_wallet->testnet()).substr(0, 6); + m_auto_refresh_run = m_wallet->auto_refresh(); + if (m_auto_refresh_run) + { + m_auto_refresh_thread = std::thread([&]{wallet_refresh_thread();}); + } + else + { + refresh(std::vector<std::string>()); + } return m_cmd_binder.run_handling(std::string("[") + tr("wallet") + " " + addr_start + "]: ", ""); } //---------------------------------------------------------------------------------------------------- @@ -2175,6 +2326,18 @@ bool simple_wallet::process_command(const std::vector<std::string> &args) return m_cmd_binder.process_command_vec(args); } //---------------------------------------------------------------------------------------------------- +void simple_wallet::interrupt() +{ + if (m_in_manual_refresh.load(std::memory_order_relaxed)) + { + m_wallet->stop(); + } + else + { + stop(); + } +} +//---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { #ifdef WIN32 @@ -2229,6 +2392,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_testnet); command_line::add_arg(desc_params, arg_restricted); command_line::add_arg(desc_params, arg_trusted_daemon); + command_line::add_arg(desc_params, arg_refresh_type); tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; @@ -2348,7 +2512,7 @@ int main(int argc, char* argv[]) bool r = wrpc.init(vm); CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet rpc server")); - tools::signal_handler::install([&wrpc, &wal] { + tools::signal_handler::install([&wrpc, &wal](int) { wrpc.send_stop_signal(); wal.store(); }); @@ -2381,8 +2545,16 @@ int main(int argc, char* argv[]) } else { - tools::signal_handler::install([&w] { - w.stop(); + tools::signal_handler::install([&w](int type) { + if (type == SIGINT) + { + // if we're pressing ^C when refreshing, just stop refreshing + w.interrupt(); + } + else + { + w.stop(); + } }); w.run(); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 94ad724be..dd1e59d6c 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -68,15 +68,18 @@ namespace cryptonote bool deinit(); bool run(); void stop(); + void interrupt(); //wallet *create_wallet(); bool process_command(const std::vector<std::string> &args); std::string get_commands_str(); private: - void handle_command_line(const boost::program_options::variables_map& vm); + bool handle_command_line(const boost::program_options::variables_map& vm); bool run_console_handler(); + void wallet_refresh_thread(); + bool new_wallet(const std::string &wallet_file, const std::string& password, const crypto::secret_key& recovery_key, bool recover, bool two_random, bool testnet, const std::string &old_language); bool new_wallet(const std::string &wallet_file, const std::string& password, const cryptonote::account_public_address& address, @@ -101,6 +104,7 @@ namespace cryptonote bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>()); bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>()); bool set_default_mixin(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); @@ -131,6 +135,7 @@ namespace cryptonote uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(); bool ask_wallet_create_if_needed(); + /*! * \brief Prints the seed with a nice message * \param seed seed to print @@ -222,10 +227,19 @@ namespace cryptonote std::string m_daemon_host; int m_daemon_port; + tools::wallet2::RefreshType m_refresh_type; + epee::console_handlers_binder m_cmd_binder; std::unique_ptr<tools::wallet2> m_wallet; epee::net_utils::http::http_simple_client m_http_client; refresh_progress_reporter_t m_refresh_progress_reporter; + + std::atomic<bool> m_auto_refresh_run; + bool m_auto_refresh_refreshing; + std::thread m_auto_refresh_thread; + std::mutex m_auto_refresh_mutex; + std::condition_variable m_auto_refresh_cond; + std::atomic<bool> m_in_manual_refresh; }; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f26809af8..b189f975c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -68,6 +68,13 @@ using namespace cryptonote; // arbitrary, used to generate different hashes from the same input #define CHACHA8_KEY_TAIL 0x8c +#define KILL_IOSERVICE() \ + do { \ + work.reset(); \ + threadpool.join_all(); \ + ioservice.stop(); \ + } while(0) + namespace { void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -148,9 +155,29 @@ bool wallet2::is_deprecated() const return is_old_file_format; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height) +void wallet2::check_acc_out(const account_keys &acc, const tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const { - process_unconfirmed(tx, height); + if (o.target.type() != typeid(txout_to_key)) + { + error = true; + LOG_ERROR("wrong type id in transaction out"); + return; + } + if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i)) + { + money_transfered = o.amount; + } + else + { + money_transfered = 0; + } + error = false; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height, bool miner_tx) +{ + if (!miner_tx) + process_unconfirmed(tx, height); std::vector<size_t> outs; uint64_t tx_money_got_in_outs = 0; crypto::public_key tx_pub_key = null_pkey; @@ -175,7 +202,102 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ } tx_pub_key = pub_key_field.pub_key; - bool r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); + bool r = true; + int threads = std::thread::hardware_concurrency(); + if (miner_tx && m_refresh_type == RefreshNoCoinbase) + { + // assume coinbase isn't for us + } + else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) + { + uint64_t money_transfered = 0; + bool error = false; + check_acc_out(m_account.get_keys(), tx.vout[0], tx_pub_key, 0, money_transfered, error); + if (error) + { + r = false; + } + else + { + // this assumes that the miner tx pays a single address + if (money_transfered > 0) + { + outs.push_back(0); + tx_money_got_in_outs = money_transfered; + + // process the other outs from that tx + boost::asio::io_service ioservice; + boost::thread_group threadpool; + std::auto_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); + for (int i = 0; i < threads; i++) + { + threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); + } + + const account_keys &keys = m_account.get_keys(); + std::vector<uint64_t> money_transfered(tx.vout.size()); + std::deque<bool> error(tx.vout.size()); + // the first one was already checked + for (size_t i = 1; i < tx.vout.size(); ++i) + { + ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + std::ref(money_transfered[i]), std::ref(error[i]))); + } + KILL_IOSERVICE(); + for (size_t i = 1; i < tx.vout.size(); ++i) + { + if (error[i]) + { + r = false; + break; + } + if (money_transfered[i]) + { + outs.push_back(i); + tx_money_got_in_outs += money_transfered[i]; + } + } + } + } + } + else if (tx.vout.size() > 1 && threads > 1) + { + boost::asio::io_service ioservice; + boost::thread_group threadpool; + std::auto_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); + for (int i = 0; i < threads; i++) + { + threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); + } + + const account_keys &keys = m_account.get_keys(); + std::vector<uint64_t> money_transfered(tx.vout.size()); + std::deque<bool> error(tx.vout.size()); + for (size_t i = 0; i < tx.vout.size(); ++i) + { + ioservice.dispatch(boost::bind(&wallet2::check_acc_out, this, std::cref(keys), std::cref(tx.vout[i]), std::cref(tx_pub_key), i, + std::ref(money_transfered[i]), std::ref(error[i]))); + } + KILL_IOSERVICE(); + tx_money_got_in_outs = 0; + for (size_t i = 0; i < tx.vout.size(); ++i) + { + if (error[i]) + { + r = false; + break; + } + if (money_transfered[i]) + { + outs.push_back(i); + tx_money_got_in_outs += money_transfered[i]; + } + } + } + else + { + r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); + } THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if(!outs.empty() && tx_money_got_in_outs) @@ -185,7 +307,9 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); req.txid = get_transaction_hash(tx); + m_daemon_rpc_mutex.lock(); bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); @@ -236,50 +360,53 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ } } - tx_extra_nonce extra_nonce; - crypto::hash payment_id = null_hash; - if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + if (tx_money_spent_in_ins > 0) { - crypto::hash8 payment_id8 = null_hash8; - if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + process_outgoing(tx, height, tx_money_spent_in_ins, tx_money_got_in_outs); + } + + uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; + if (0 < received) + { + tx_extra_nonce extra_nonce; + crypto::hash payment_id = null_hash; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) { - // We got a payment ID to go with this tx - LOG_PRINT_L2("Found encrypted payment ID: " << payment_id8); - if (tx_pub_key != null_pkey) + crypto::hash8 payment_id8 = null_hash8; + if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { - if (!decrypt_payment_id(payment_id8, tx_pub_key, m_account.get_keys().m_view_secret_key)) + // We got a payment ID to go with this tx + LOG_PRINT_L2("Found encrypted payment ID: " << payment_id8); + if (tx_pub_key != null_pkey) { - LOG_PRINT_L0("Failed to decrypt payment ID: " << payment_id8); + if (!decrypt_payment_id(payment_id8, tx_pub_key, m_account.get_keys().m_view_secret_key)) + { + LOG_PRINT_L0("Failed to decrypt payment ID: " << payment_id8); + } + else + { + LOG_PRINT_L2("Decrypted payment ID: " << payment_id8); + // put the 64 bit decrypted payment id in the first 8 bytes + memcpy(payment_id.data, payment_id8.data, 8); + // rest is already 0, but guard against code changes above + memset(payment_id.data + 8, 0, 24); + } } else { - LOG_PRINT_L2("Decrypted payment ID: " << payment_id8); - // put the 64 bit decrypted payment id in the first 8 bytes - memcpy(payment_id.data, payment_id8.data, 8); - // rest is already 0, but guard against code changes above - memset(payment_id.data + 8, 0, 24); + LOG_PRINT_L1("No public key found in tx, unable to decrypt payment id"); } } - else + else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { - LOG_PRINT_L1("No public key found in tx, unable to decrypt payment id"); + LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id); } } else if (get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { LOG_PRINT_L2("Found unencrypted payment ID: " << payment_id); } - } - - uint64_t received = (tx_money_spent_in_ins < tx_money_got_in_outs) ? tx_money_got_in_outs - tx_money_spent_in_ins : 0; - if (tx_money_spent_in_ins > 0) - { - process_outgoing(tx, height, tx_money_spent_in_ins, tx_money_got_in_outs); - } - - if (0 < received) - { payment_details payment; payment.m_tx_hash = cryptonote::get_transaction_hash(tx); payment.m_amount = received; @@ -320,7 +447,7 @@ void wallet2::process_outgoing(const cryptonote::transaction &tx, uint64_t heigh ctd.m_block_height = height; } //---------------------------------------------------------------------------------------------------- -void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height) +void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height) { //handle transactions from new block @@ -328,7 +455,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonot if(b.timestamp + 60*60*24 > m_account.get_createtime()) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height); + process_new_transaction(b.miner_tx, height, true); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -337,7 +464,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonot cryptonote::transaction tx; bool r = parse_and_validate_tx_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(tx, height); + process_new_transaction(tx, height, false); } TIME_MEASURE_FINISH(txs_handle_time); LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); @@ -379,23 +506,106 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids) const ids.push_back(m_blockchain[0]); } //---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(uint64_t start_height, uint64_t& blocks_added) +void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const +{ + error = !cryptonote::parse_and_validate_block_from_blob(blob, bl); + if (!error) + bl_id = get_block_hash(bl); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks) { - blocks_added = 0; cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); - get_short_chain_history(req.block_ids); + req.block_ids = short_chain_history; + req.start_height = start_height; + m_daemon_rpc_mutex.lock(); bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getblocks.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status); - size_t current_index = res.start_height; - BOOST_FOREACH(auto& bl_entry, res.blocks) + blocks_start_height = res.start_height; + blocks = res.blocks; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added) +{ + size_t current_index = start_height; + blocks_added = 0; + + int threads = std::thread::hardware_concurrency(); + if (threads > 1) + { + std::vector<crypto::hash> round_block_hashes(threads); + std::vector<cryptonote::block> round_blocks(threads); + std::deque<bool> error(threads); + size_t blocks_size = blocks.size(); + std::list<block_complete_entry>::const_iterator blocki = blocks.begin(); + for (size_t b = 0; b < blocks_size; b += threads) + { + size_t round_size = std::min((size_t)threads, blocks_size - b); + + boost::asio::io_service ioservice; + boost::thread_group threadpool; + std::unique_ptr < boost::asio::io_service::work > work(new boost::asio::io_service::work(ioservice)); + for (size_t i = 0; i < round_size; i++) + { + threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioservice)); + } + + std::list<block_complete_entry>::const_iterator tmpblocki = blocki; + for (size_t i = 0; i < round_size; ++i) + { + ioservice.dispatch(boost::bind(&wallet2::parse_block_round, this, std::cref(tmpblocki->block), + std::ref(round_blocks[i]), std::ref(round_block_hashes[i]), std::ref(error[i]))); + ++tmpblocki; + } + KILL_IOSERVICE(); + tmpblocki = blocki; + for (size_t i = 0; i < round_size; ++i) + { + THROW_WALLET_EXCEPTION_IF(error[i], error::block_parse_error, tmpblocki->block); + ++tmpblocki; + } + for (size_t i = 0; i < round_size; ++i) + { + const crypto::hash &bl_id = round_block_hashes[i]; + cryptonote::block &bl = round_blocks[i]; + + if(current_index >= m_blockchain.size()) + { + process_new_blockchain_entry(bl, *blocki, bl_id, current_index); + ++blocks_added; + } + else if(bl_id != m_blockchain[current_index]) + { + //split detected here !!! + THROW_WALLET_EXCEPTION_IF(current_index == start_height, error::wallet_internal_error, + "wrong daemon response: split starts from the first block in response " + string_tools::pod_to_hex(bl_id) + + " (height " + std::to_string(start_height) + "), local block id at this height: " + + string_tools::pod_to_hex(m_blockchain[current_index])); + + detach_blockchain(current_index); + process_new_blockchain_entry(bl, *blocki, bl_id, current_index); + } + else + { + LOG_PRINT_L2("Block is already in blockchain: " << string_tools::pod_to_hex(bl_id)); + } + ++current_index; + ++blocki; + } + } + } + else + { + BOOST_FOREACH(auto& bl_entry, blocks) { cryptonote::block bl; - r = cryptonote::parse_and_validate_block_from_blob(bl_entry.block, bl); + bool r = cryptonote::parse_and_validate_block_from_blob(bl_entry.block, bl); THROW_WALLET_EXCEPTION_IF(!r, error::block_parse_error, bl_entry.block); crypto::hash bl_id = get_block_hash(bl); @@ -407,9 +617,9 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t& blocks_added) else if(bl_id != m_blockchain[current_index]) { //split detected here !!! - THROW_WALLET_EXCEPTION_IF(current_index == res.start_height, error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(current_index == start_height, error::wallet_internal_error, "wrong daemon response: split starts from the first block in response " + string_tools::pod_to_hex(bl_id) + - " (height " + std::to_string(res.start_height) + "), local block id at this height: " + + " (height " + std::to_string(start_height) + "), local block id at this height: " + string_tools::pod_to_hex(m_blockchain[current_index])); detach_blockchain(current_index); @@ -422,6 +632,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t& blocks_added) ++current_index; } + } } //---------------------------------------------------------------------------------------------------- void wallet2::refresh() @@ -436,6 +647,23 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched) refresh(start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- +void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks) +{ + // prepend the last 3 blocks, should be enough to guard against a block or two's reorg + cryptonote::block bl; + std::list<cryptonote::block_complete_entry>::const_reverse_iterator i = prev_blocks.rbegin(); + for (size_t n = 0; n < std::min((size_t)3, prev_blocks.size()); ++n) + { + bool ok = cryptonote::parse_and_validate_block_from_blob(i->block, bl); + THROW_WALLET_EXCEPTION_IF(!ok, error::block_parse_error, i->block); + short_chain_history.push_front(cryptonote::get_block_hash(bl)); + ++i; + } + + // pull the new blocks + pull_blocks(start_height, blocks_start_height, short_chain_history, blocks); +} +//---------------------------------------------------------------------------------------------------- void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money) { received_money = false; @@ -443,15 +671,34 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re uint64_t added_blocks = 0; size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash; + std::list<crypto::hash> short_chain_history; + std::thread pull_thread; + uint64_t blocks_start_height; + std::list<cryptonote::block_complete_entry> blocks; + + // pull the first set of blocks + get_short_chain_history(short_chain_history); + pull_blocks(start_height, blocks_start_height, short_chain_history, blocks); + m_run.store(true, std::memory_order_relaxed); while(m_run.load(std::memory_order_relaxed)) { try { - pull_blocks(start_height, added_blocks); + // pull the next set of blocks while we're processing the current one + uint64_t next_blocks_start_height; + std::list<cryptonote::block_complete_entry> next_blocks; + pull_thread = std::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks);}); + + process_blocks(blocks_start_height, blocks, added_blocks); blocks_fetched += added_blocks; + pull_thread.join(); if(!added_blocks) break; + + // switch to the new blocks from the daemon + blocks_start_height = next_blocks_start_height; + blocks = next_blocks; } catch (const std::exception&) { @@ -517,6 +764,14 @@ void wallet2::detach_blockchain(uint64_t height) ++it; } + for (auto it = m_confirmed_txs.begin(); it != m_confirmed_txs.end(); ) + { + if(height <= it->second.m_block_height) + it = m_confirmed_txs.erase(it); + else + ++it; + } + LOG_PRINT_L0("Detached blockchain on height " << height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); } //---------------------------------------------------------------------------------------------------- @@ -576,6 +831,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p value2.SetUint(m_default_mixin); json.AddMember("default_mixin", value2, json.GetAllocator()); + value2.SetInt(m_auto_refresh ? 1 :0); + json.AddMember("auto_refresh", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -660,6 +918,7 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa m_store_tx_info = (json.HasMember("store_tx_keys") && (json["store_tx_keys"].GetInt() != 0)) || (json.HasMember("store_tx_info") && (json["store_tx_info"].GetInt() != 0)); m_default_mixin = json.HasMember("default_mixin") ? json["default_mixin"].GetUint() : 0; + m_auto_refresh = !json.HasMember("auto_refresh") || (json["auto_refresh"].GetInt() != 0); } const cryptonote::account_keys& keys = m_account.get_keys(); @@ -882,6 +1141,8 @@ bool wallet2::prepare_file_names(const std::string& file_path) //---------------------------------------------------------------------------------------------------- bool wallet2::check_connection() { + std::lock_guard<std::mutex> lock(m_daemon_rpc_mutex); + if(m_http_client.is_connected()) return true; @@ -1094,7 +1355,9 @@ void wallet2::rescan_spent() COMMAND_RPC_IS_KEY_IMAGE_SPENT::request req = AUTO_VAL_INIT(req); COMMAND_RPC_IS_KEY_IMAGE_SPENT::response daemon_resp = AUTO_VAL_INIT(daemon_resp); req.key_images = key_images; + m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/is_key_image_spent", req, daemon_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "is_key_image_spent"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "is_key_image_spent"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::is_key_image_spent_error, daemon_resp.status); @@ -1401,7 +1664,9 @@ void wallet2::commit_tx(pending_tx& ptx) COMMAND_RPC_SEND_RAW_TX::request req; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)); COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction"); THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status); @@ -1589,7 +1854,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent req.amounts.push_back(it->amount()); } + m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", req, daemon_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 8df6c757d..d6aea182d 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -79,9 +79,18 @@ namespace tools class wallet2 { - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0) {} public: - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0) {} + enum RefreshType { + RefreshFull, + RefreshOptimizeCoinbase, + RefreshNoCoinbase, + }; + + private: + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + + public: + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} struct transfer_details { uint64_t m_block_height; @@ -234,6 +243,9 @@ namespace tools void refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& received_money); bool refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok); + void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; } + RefreshType get_refresh_type(RefreshType refresh_type) const { return m_refresh_type; } + bool testnet() const { return m_testnet; } bool restricted() const { return m_restricted; } bool watch_only() const { return m_watch_only; } @@ -318,6 +330,8 @@ namespace tools void store_tx_info(bool store) { m_store_tx_info = store; } uint32_t default_mixin() const { return m_default_mixin; } void default_mixin(uint32_t m) { m_default_mixin = m; } + bool auto_refresh() const { return m_auto_refresh; } + void auto_refresh(bool r) { m_auto_refresh = r; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; @@ -336,14 +350,16 @@ namespace tools * \param password Password of wallet file */ void load_keys(const std::string& keys_file_name, const std::string& password); - void process_new_transaction(const cryptonote::transaction& tx, uint64_t height); - void process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height); + void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, bool miner_tx); + void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; bool is_transfer_unlocked(const transfer_details& td) const; bool clear(); - void pull_blocks(uint64_t start_height, uint64_t& blocks_added); + void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::list<cryptonote::block_complete_entry> &blocks); + void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks); + void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, uint64_t& blocks_added); uint64_t select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, std::list<transfer_container::iterator>& selected_transfers); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const cryptonote::transaction& tx, uint64_t height); @@ -353,6 +369,8 @@ namespace tools void check_genesis(const crypto::hash& genesis_hash) const; //throws bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; + void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; + void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; cryptonote::account_base m_account; std::string m_daemon_address; @@ -373,6 +391,8 @@ namespace tools std::atomic<bool> m_run; + std::mutex m_daemon_rpc_mutex; + i_wallet2_callback* m_callback; bool m_testnet; bool m_restricted; @@ -382,6 +402,8 @@ namespace tools bool m_always_confirm_transfers; bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */ uint32_t m_default_mixin; + RefreshType m_refresh_type; + bool m_auto_refresh; }; } BOOST_CLASS_VERSION(tools::wallet2, 10) @@ -548,7 +570,9 @@ namespace tools req.amounts.push_back(it->amount()); } + m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", req, daemon_resp, m_http_client, 200000); + m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); |