diff options
48 files changed, 1509 insertions, 315 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a9d6be986..a8060f90c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -655,6 +655,19 @@ endif() list(APPEND EXTRA_LIBRARIES ${CMAKE_DL_LIBS}) +option(USE_READLINE "Build with GNU readline support." ON) +if(USE_READLINE) + find_package(Readline) + if(READLINE_FOUND AND GNU_READLINE_FOUND) + add_definitions(-DHAVE_READLINE) + include_directories(${Readline_INCLUDE_DIR}) + list(APPEND EXTRA_LIBRARIES ${Readline_LIBRARY}) + message(STATUS "Found readline library at: ${Readline_ROOT_DIR}") + else() + message(STATUS "Could not find GNU readline library so building without readline support") + endif() +endif() + if(ANDROID) set(ATOMIC libatomic.a) endif() diff --git a/README.i18n b/README.i18n.md index 755c5cf38..755c5cf38 100644 --- a/README.i18n +++ b/README.i18n.md @@ -364,7 +364,7 @@ monero-wallet-cli, and possibly monerod, if you get crashes refreshing. ## Internationalization -See [README.i18n](README.i18n). +See [README.i18n.md](README.i18n.md). ## Using Tor diff --git a/cmake/FindReadline.cmake b/cmake/FindReadline.cmake new file mode 100644 index 000000000..9ccef7ad8 --- /dev/null +++ b/cmake/FindReadline.cmake @@ -0,0 +1,66 @@ +# - Try to find readline include dirs and libraries +# +# Usage of this module as follows: +# +# find_package(Readline) +# +# Variables used by this module, they can change the default behaviour and need +# to be set before calling find_package: +# +# Readline_ROOT_DIR Set this variable to the root installation of +# readline if the module has problems finding the +# proper installation path. +# +# Variables defined by this module: +# +# READLINE_FOUND System has readline, include and lib dirs found +# GNU_READLINE_FOUND Version of readline found is GNU readline, not libedit! +# Readline_INCLUDE_DIR The readline include directories. +# Readline_LIBRARY The readline library. + +find_path(Readline_ROOT_DIR + NAMES include/readline/readline.h + PATHS /opt/local/ /usr/local/ /usr/ + NO_DEFAULT_PATH +) + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + PATHS ${Readline_ROOT_DIR}/include + NO_DEFAULT_PATH +) + +find_library(Readline_LIBRARY + NAMES readline + PATHS ${Readline_ROOT_DIR}/lib + NO_DEFAULT_PATH +) + +if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + set(READLINE_FOUND TRUE) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + FIND_LIBRARY(Readline_LIBRARY NAMES readline PATHS Readline_ROOT_DIR) + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) + MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + +mark_as_advanced( + Readline_ROOT_DIR + Readline_INCLUDE_DIR + Readline_LIBRARY +) + +set(CMAKE_REQUIRED_INCLUDES ${Readline_INCLUDE_DIR}) +set(CMAKE_REQUIRED_LIBRARIES ${Readline_LIBRARY}) +INCLUDE(CheckCXXSourceCompiles) +CHECK_CXX_SOURCE_COMPILES( +" +#include <stdio.h> +#include <readline/readline.h> +int +main() +{ + char * s = rl_copy_text(0, 0); +} +" GNU_READLINE_FOUND) diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 54e3e966d..bb20faa65 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -38,6 +38,10 @@ #endif #include <boost/thread.hpp> +#ifdef HAVE_READLINE + #include "readline_buffer.h" +#endif + namespace epee { class async_stdin_reader @@ -49,6 +53,10 @@ namespace epee , m_read_status(state_init) { m_reader_thread = boost::thread(std::bind(&async_stdin_reader::reader_thread_func, this)); +#ifdef HAVE_READLINE + m_readline_buffer.start(); + m_readline_thread = boost::thread(std::bind(&async_stdin_reader::readline_thread_func, this)); +#endif } ~async_stdin_reader() @@ -56,6 +64,13 @@ namespace epee stop(); } +#ifdef HAVE_READLINE + rdln::readline_buffer& get_readline_buffer() + { + return m_readline_buffer; + } +#endif + // Not thread safe. Only one thread can call this method at once. bool get_line(std::string& line) { @@ -98,6 +113,10 @@ namespace epee m_request_cv.notify_one(); m_reader_thread.join(); +#ifdef HAVE_READLINE + m_readline_buffer.stop(); + m_readline_thread.join(); +#endif } } @@ -174,6 +193,16 @@ namespace epee return true; } +#ifdef HAVE_READLINE + void readline_thread_func() + { + while (m_run.load(std::memory_order_relaxed)) + { + m_readline_buffer.process(); + } + } +#endif + void reader_thread_func() { while (true) @@ -187,7 +216,11 @@ namespace epee { if (m_run.load(std::memory_order_relaxed)) { +#ifdef HAVE_READLINE + m_readline_buffer.get_line(line); +#else std::getline(std::cin, line); +#endif read_ok = !std::cin.eof() && !std::cin.fail(); } } @@ -229,6 +262,10 @@ namespace epee private: boost::thread m_reader_thread; std::atomic<bool> m_run; +#ifdef HAVE_READLINE + boost::thread m_readline_thread; + rdln::readline_buffer m_readline_buffer; +#endif std::string m_line; bool m_has_read_request; @@ -277,12 +314,16 @@ namespace epee { if (!m_prompt.empty()) { +#ifdef HAVE_READLINE + m_stdin_reader.get_readline_buffer().set_prompt(m_prompt); +#else epee::set_console_color(epee::console_color_yellow, true); std::cout << m_prompt; if (' ' != m_prompt.back()) std::cout << ' '; epee::reset_console_color(); std::cout.flush(); +#endif } } diff --git a/contrib/epee/include/net/abstract_tcp_server.h b/contrib/epee/include/net/abstract_tcp_server.h index 1efd5091c..000305cfa 100644 --- a/contrib/epee/include/net/abstract_tcp_server.h +++ b/contrib/epee/include/net/abstract_tcp_server.h @@ -296,7 +296,7 @@ namespace net_utils } //---------------------------------------------------------------------------------------- template<class THandler> - bool abstract_tcp_server<THandler>::invoke_connection(SOCKET hnew_sock, long ip_from, int post_from) + bool abstract_tcp_server<THandler>::invoke_connection(SOCKET hnew_sock, const network_address &remote_address) { m_connections_lock.lock(); m_connections.push_back(thread_context()); @@ -304,8 +304,7 @@ namespace net_utils m_connections.back().m_socket = hnew_sock; m_connections.back().powner = this; m_connections.back().m_self_it = --m_connections.end(); - m_connections.back().m_context.m_remote_ip = ip_from; - m_connections.back().m_context.m_remote_port = post_from; + m_connections.back().m_context.m_remote_address = remote_address; m_connections.back().m_htread = threads_helper::create_thread(ConnectionHandlerProc, &m_connections.back()); return true; diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index 506949dbd..ca58d5467 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -69,7 +69,7 @@ namespace net_utils struct i_connection_filter { - virtual bool is_remote_ip_allowed(uint32_t adress)=0; + virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address)=0; protected: virtual ~i_connection_filter(){} }; diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 75a9c5be9..0fbd9ed28 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -133,6 +133,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::system::error_code ec; auto remote_ep = socket_.remote_endpoint(ec); CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get remote endpoint: " << ec.message() << ':' << ec.value()); + CHECK_AND_NO_ASSERT_MES(remote_ep.address().is_v4(), false, "IPv6 not supported here"); auto local_ep = socket_.local_endpoint(ec); CHECK_AND_NO_ASSERT_MES(!ec, false, "Failed to get local endpoint: " << ec.message() << ':' << ec.value()); @@ -145,14 +146,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) // that stuff turns out to be included, even though it's from src... Taking advantage random_uuid = crypto::rand<boost::uuids::uuid>(); - context.set_details(random_uuid, ip_, remote_ep.port(), is_income); + context.set_details(random_uuid, new epee::net_utils::ipv4_network_address(ip_, remote_ep.port()), is_income); _dbg3("[sock " << socket_.native_handle() << "] new connection from " << print_connection_context_short(context) << " to " << local_ep.address().to_string() << ':' << local_ep.port() << ", total sockets objects " << m_ref_sock_count); - if(m_pfilter && !m_pfilter->is_remote_ip_allowed(context.m_remote_ip)) + if(m_pfilter && !m_pfilter->is_remote_host_allowed(context.m_remote_address)) { - _dbg2("[sock " << socket_.native_handle() << "] ip denied " << string_tools::get_ip_string_from_int32(context.m_remote_ip) << ", shutdowning connection"); + _dbg2("[sock " << socket_.native_handle() << "] host denied " << context.m_remote_address.host_str() << ", shutdowning connection"); close(); return false; } diff --git a/contrib/epee/include/net/abstract_tcp_server_cp.inl b/contrib/epee/include/net/abstract_tcp_server_cp.inl index ba201e3bf..e0ef6470e 100644 --- a/contrib/epee/include/net/abstract_tcp_server_cp.inl +++ b/contrib/epee/include/net/abstract_tcp_server_cp.inl @@ -471,7 +471,7 @@ bool cp_server_impl<TProtocol>::run_server(int threads_count = 0) } //------------------------------------------------------------- template<class TProtocol> -bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, long ip_from, int port_from) +bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, const network_address &address_from) { PROFILE_FUNC("[add_new_connection]"); @@ -487,8 +487,7 @@ bool cp_server_impl<TProtocol>::add_new_connection(SOCKET new_sock, long ip_from m_connections_lock.unlock(); conn.init_buffers(); conn.m_sock = new_sock; - conn.context.m_remote_ip = ip_from; - conn.context.m_remote_port = port_from; + conn.context.m_remote_address = address_from; conn.m_completion_port = m_completion_port; { PROFILE_FUNC("[add_new_connection] CreateIoCompletionPort"); diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 8e39b4104..429e3e1af 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -39,7 +39,7 @@ epee::net_utils::http::http_response_info& response, \ context_type& m_conn_context) \ {\ - LOG_PRINT_L2("HTTP [" << epee::string_tools::get_ip_string_from_int32(m_conn_context.m_remote_ip ) << "] " << query_info.m_http_method_str << " " << query_info.m_URI); \ + LOG_PRINT_L2("HTTP [" << m_conn_context.m_remote_address.host_str() << "] " << query_info.m_http_method_str << " " << query_info.m_URI); \ response.m_response_code = 200; \ response.m_response_comment = "Ok"; \ if(!handle_http_request_map(query_info, response, m_conn_context)) \ diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h index 2cbdb4587..4b48070d6 100644 --- a/contrib/epee/include/net/levin_client_async.h +++ b/contrib/epee/include/net/levin_client_async.h @@ -492,8 +492,7 @@ namespace levin { net_utils::connection_context_base conn_context; - conn_context.m_remote_ip = m_ip; - conn_context.m_remote_port = m_port; + conn_context.m_remote_address = m_address; if(head.m_have_to_return_data) { std::string return_buff; diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index fd7e28849..1884412dc 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -29,8 +29,11 @@ #ifndef _NET_UTILS_BASE_H_ #define _NET_UTILS_BASE_H_ +#include <typeinfo> #include <boost/asio/io_service.hpp> #include <boost/uuid/uuid.hpp> +#include "serialization/keyvalue_serialization.h" +#include "net/local_ip.h" #include "string_tools.h" #include "misc_log_ex.h" @@ -46,14 +49,111 @@ namespace epee { namespace net_utils { + struct network_address_base + { + public: + bool operator==(const network_address_base &other) const { return m_full_id == other.m_full_id; } + bool operator!=(const network_address_base &other) const { return !operator==(other); } + bool operator<(const network_address_base &other) const { return m_full_id < other.m_full_id; } + bool is_same_host(const network_address_base &other) const { return m_host_id == other.m_host_id; } + virtual std::string str() const = 0; + virtual std::string host_str() const = 0; + virtual bool is_loopback() const = 0; + virtual bool is_local() const = 0; + virtual uint8_t get_type_id() const = 0; + protected: + // A very simple non cryptographic hash function by Fowler, Noll, Vo + uint64_t fnv1a(const uint8_t *data, size_t len) const { + uint64_t h = 0xcbf29ce484222325; + while (len--) + h = (h ^ *data++) * 0x100000001b3; + return h; + } + uint64_t m_host_id; + uint64_t m_full_id; + }; + struct ipv4_network_address: public network_address_base + { + void init_ids() + { + m_host_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip)); + m_full_id = fnv1a((const uint8_t*)&m_ip, sizeof(m_ip) + sizeof(m_port)); + } + public: + ipv4_network_address(uint32_t ip, uint16_t port): network_address_base(), m_ip(ip), m_port(port) { init_ids(); } + uint32_t ip() const { return m_ip; } + uint16_t port() const { return m_port; } + virtual std::string str() const { return epee::string_tools::get_ip_string_from_int32(m_ip) + ":" + std::to_string(m_port); } + virtual std::string host_str() const { return epee::string_tools::get_ip_string_from_int32(m_ip); } + virtual bool is_loopback() const { return epee::net_utils::is_ip_loopback(m_ip); } + virtual bool is_local() const { return epee::net_utils::is_ip_local(m_ip); } + virtual uint8_t get_type_id() const { return ID; } + public: // serialization + static const uint8_t ID = 1; +#pragma pack(push) +#pragma pack(1) + uint32_t m_ip; + uint16_t m_port; +#pragma pack(pop) + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_ip) + KV_SERIALIZE(m_port) + if (!is_store) + const_cast<ipv4_network_address&>(this_ref).init_ids(); + END_KV_SERIALIZE_MAP() + }; + class network_address: public boost::shared_ptr<network_address_base> + { + public: + network_address() {} + network_address(ipv4_network_address *address): boost::shared_ptr<network_address_base>(address) {} + bool operator==(const network_address &other) const { return (*this)->operator==(*other); } + bool operator!=(const network_address &other) const { return (*this)->operator!=(*other); } + bool operator<(const network_address &other) const { return (*this)->operator<(*other); } + bool is_same_host(const network_address &other) const { return (*this)->is_same_host(*other); } + std::string str() const { return (*this) ? (*this)->str() : "<none>"; } + std::string host_str() const { return (*this) ? (*this)->host_str() : "<none>"; } + bool is_loopback() const { return (*this)->is_loopback(); } + bool is_local() const { return (*this)->is_local(); } + const std::type_info &type() const { return typeid(**this); } + uint8_t get_type_id() const { return (*this)->get_type_id(); } + template<typename Type> Type &as() { if (type() != typeid(Type)) throw std::runtime_error("Bad type"); return *(Type*)get(); } + template<typename Type> const Type &as() const { if (type() != typeid(Type)) throw std::runtime_error("Bad type"); return *(const Type*)get(); } + + BEGIN_KV_SERIALIZE_MAP() + uint8_t type = is_store ? this_ref.get_type_id() : 0; + epee::serialization::selector<is_store>::serialize(type, stg, hparent_section, "type"); + switch (type) + { + case ipv4_network_address::ID: + if (!is_store) + const_cast<network_address&>(this_ref).reset(new ipv4_network_address(0, 0)); + KV_SERIALIZE(template as<ipv4_network_address>()); + break; + default: MERROR("Unsupported network address type: " << type); return false; + } + END_KV_SERIALIZE_MAP() + }; + inline bool create_network_address(network_address &address, const std::string &string, uint16_t default_port = 0) + { + uint32_t ip; + uint16_t port; + if (epee::string_tools::parse_peer_from_string(ip, port, string)) + { + if (default_port && !port) + port = default_port; + address.reset(new ipv4_network_address(ip, port)); + return true; + } + return false; + } /************************************************************************/ /* */ /************************************************************************/ struct connection_context_base { const boost::uuids::uuid m_connection_id; - const uint32_t m_remote_ip; - const uint32_t m_remote_port; + const network_address m_remote_address; const bool m_is_income; const time_t m_started; bool m_in_timedsync; @@ -65,12 +165,11 @@ namespace net_utils double m_current_speed_up; connection_context_base(boost::uuids::uuid connection_id, - long remote_ip, int remote_port, bool is_income, + const network_address &remote_address, bool is_income, time_t last_recv = 0, time_t last_send = 0, uint64_t recv_cnt = 0, uint64_t send_cnt = 0): m_connection_id(connection_id), - m_remote_ip(remote_ip), - m_remote_port(remote_port), + m_remote_address(remote_address), m_is_income(is_income), m_started(time(NULL)), m_in_timedsync(false), @@ -83,8 +182,7 @@ namespace net_utils {} connection_context_base(): m_connection_id(), - m_remote_ip(0), - m_remote_port(0), + m_remote_address(new ipv4_network_address(0,0)), m_is_income(false), m_started(time(NULL)), m_in_timedsync(false), @@ -98,17 +196,17 @@ namespace net_utils connection_context_base& operator=(const connection_context_base& a) { - set_details(a.m_connection_id, a.m_remote_ip, a.m_remote_port, a.m_is_income); + set_details(a.m_connection_id, a.m_remote_address, a.m_is_income); return *this; } private: template<class t_protocol_handler> friend class connection; - void set_details(boost::uuids::uuid connection_id, long remote_ip, int remote_port, bool is_income) + void set_details(boost::uuids::uuid connection_id, const network_address &remote_address, bool is_income) { this->~connection_context_base(); - new(this) connection_context_base(connection_id, remote_ip, remote_port, is_income); + new(this) connection_context_base(connection_id, remote_address, is_income); } }; @@ -138,7 +236,7 @@ namespace net_utils std::string print_connection_context(const connection_context_base& ctx) { std::stringstream ss; - ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address->str() << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } @@ -146,7 +244,7 @@ namespace net_utils std::string print_connection_context_short(const connection_context_base& ctx) { std::stringstream ss; - ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << (ctx.m_is_income ? " INC":" OUT"); + ss << ctx.m_remote_address->str() << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } diff --git a/contrib/epee/include/readline_buffer.h b/contrib/epee/include/readline_buffer.h new file mode 100644 index 000000000..7d929bc4c --- /dev/null +++ b/contrib/epee/include/readline_buffer.h @@ -0,0 +1,40 @@ +#pragma once + +#include <streambuf> +#include <sstream> +#include <iostream> + +namespace rdln +{ + class readline_buffer : public std::stringbuf + { + public: + readline_buffer(); + void start(); + void stop(); + int process(); + bool is_running() + { + return m_cout_buf != NULL; + } + void get_line(std::string& line); + void set_prompt(const std::string& prompt); + + protected: + virtual int sync(); + + private: + std::streambuf* m_cout_buf; + }; + + class suspend_readline + { + public: + suspend_readline(); + ~suspend_readline(); + private: + readline_buffer* m_buffer; + bool m_restart; + }; +} + diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 16e60e99a..258caa49e 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -193,7 +193,7 @@ POP_WARNINGS //---------------------------------------------------------------------------- bool get_ip_int32_from_string(uint32_t& ip, const std::string& ip_str); //---------------------------------------------------------------------------- - inline bool parse_peer_from_string(uint32_t& ip, uint32_t& port, const std::string& addres) + inline bool parse_peer_from_string(uint32_t& ip, uint16_t& port, const std::string& addres) { //parse ip and address std::string::size_type p = addres.find(':'); diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 437851833..c61a6e684 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -26,7 +26,12 @@ # 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. -add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) +if (USE_READLINE AND GNU_READLINE_FOUND) + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp readline_buffer.cpp) +else() + add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp string_tools.cpp) +endif() + # Build and install libepee if we're building for GUI if (BUILD_GUI_DEPS) if(IOS) diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp new file mode 100644 index 000000000..68b739db9 --- /dev/null +++ b/contrib/epee/src/readline_buffer.cpp @@ -0,0 +1,196 @@ +#include "readline_buffer.h" +#include <readline/readline.h> +#include <readline/history.h> +#include <sys/select.h> +#include <unistd.h> +#include <mutex> +#include <condition_variable> + +static int process_input(); +static void install_line_handler(); +static void remove_line_handler(); + +static std::string last_line; +static std::string last_prompt; +std::mutex line_mutex, sync_mutex; +std::condition_variable have_line; + +namespace +{ + rdln::readline_buffer* current = NULL; +} + +rdln::suspend_readline::suspend_readline() +{ + m_buffer = current; + if(!m_buffer) + return; + m_restart = m_buffer->is_running(); + if(m_restart) + m_buffer->stop(); +} + +rdln::suspend_readline::~suspend_readline() +{ + if(!m_buffer) + return; + if(m_restart) + m_buffer->start(); +} + +rdln::readline_buffer::readline_buffer() +: std::stringbuf() +{ + current = this; +} + +void rdln::readline_buffer::start() +{ + if(m_cout_buf != NULL) + return; + m_cout_buf = std::cout.rdbuf(); + std::cout.rdbuf(this); + install_line_handler(); +} + +void rdln::readline_buffer::stop() +{ + if(m_cout_buf == NULL) + return; + std::cout.rdbuf(m_cout_buf); + m_cout_buf = NULL; + remove_line_handler(); +} + +void rdln::readline_buffer::get_line(std::string& line) +{ + std::unique_lock<std::mutex> lock(line_mutex); + have_line.wait(lock); + line = last_line; +} + +void rdln::readline_buffer::set_prompt(const std::string& prompt) +{ + last_prompt = prompt; + if(m_cout_buf == NULL) + return; + rl_set_prompt(last_prompt.c_str()); + rl_redisplay(); +} + +int rdln::readline_buffer::process() +{ + if(m_cout_buf == NULL) + return 0; + return process_input(); +} + +int rdln::readline_buffer::sync() +{ + std::lock_guard<std::mutex> lock(sync_mutex); + char* saved_line; + int saved_point; + + saved_point = rl_point; + saved_line = rl_copy_text(0, rl_end); + + rl_set_prompt(""); + rl_replace_line("", 0); + rl_redisplay(); + + do + { + char x = this->sgetc(); + m_cout_buf->sputc(x); + } + while ( this->snextc() != EOF ); + + rl_set_prompt(last_prompt.c_str()); + rl_replace_line(saved_line, 0); + rl_point = saved_point; + rl_redisplay(); + free(saved_line); + + return 0; +} + +static fd_set fds; + +static int process_input() +{ + int count; + struct timeval t; + + t.tv_sec = 0; + t.tv_usec = 0; + + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + count = select(FD_SETSIZE, &fds, NULL, NULL, &t); + if (count < 1) + { + return count; + } + rl_callback_read_char(); + return count; +} + +static void handle_line(char* line) +{ + if (line != NULL) + { + std::lock_guard<std::mutex> lock(sync_mutex); + rl_set_prompt(last_prompt.c_str()); + rl_already_prompted = 1; + return; + } + rl_set_prompt(""); + rl_replace_line("", 0); + rl_redisplay(); + rl_set_prompt(last_prompt.c_str()); +} + +static int handle_enter(int x, int y) +{ + std::lock_guard<std::mutex> lock(sync_mutex); + char* line = NULL; + + line = rl_copy_text(0, rl_end); + rl_set_prompt(""); + rl_replace_line("", 1); + rl_redisplay(); + + if (strcmp(line, "") != 0) + { + last_line = line; + add_history(line); + have_line.notify_one(); + } + free(line); + + rl_set_prompt(last_prompt.c_str()); + rl_redisplay(); + + rl_done = 1; + return 0; +} + +static int startup_hook() +{ + rl_bind_key(RETURN, handle_enter); + rl_bind_key(NEWLINE, handle_enter); + return 0; +} + +static void install_line_handler() +{ + rl_startup_hook = startup_hook; + rl_callback_handler_install("", handle_line); +} + +static void remove_line_handler() +{ + rl_unbind_key(RETURN); + rl_callback_handler_remove(); +} + diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index b0a3d84e1..27e63801d 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1317,6 +1317,16 @@ public: * @brief get a txpool transaction's blob * * @param txid the transaction id of the transation to lookup + * @param bd the blob to return + * + * @return true if the txid was in the txpool, false otherwise + */ + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const = 0; + + /** + * @brief get a txpool transaction's blob + * + * @param txid the transaction id of the transation to lookup * * @return the blob for that transaction */ diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index d7947c8d0..6bb96d1db 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1576,7 +1576,7 @@ txpool_tx_meta_t BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid) co return meta; } -cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid) const +bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1587,12 +1587,21 @@ cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid MDB_val k = {sizeof(txid), (void *)&txid}; MDB_val v; auto result = mdb_cursor_get(m_cur_txpool_blob, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) + return false; if (result != 0) - throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Error finding txpool tx blob: ", result).c_str())); - blobdata bd; bd.assign(reinterpret_cast<const char*>(v.mv_data), v.mv_size); TXN_POSTFIX_RDONLY(); + return true; +} + +cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid) const +{ + cryptonote::blobdata bd; + if (!get_txpool_tx_blob(txid, bd)) + throw1(DB_ERROR("Tx not found in txpool: ")); return bd; } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 540fababb..14e5d34e2 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -245,6 +245,7 @@ public: virtual bool txpool_has_tx(const crypto::hash &txid) const; virtual void remove_txpool_tx(const crypto::hash& txid); virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false) const; diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index f71b3e576..8c2796bbe 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -37,6 +37,10 @@ #include "cryptonote_config.h" #include "string_tools.h" +#ifdef HAVE_READLINE + #include "readline_buffer.h" +#endif + namespace command_line { namespace @@ -49,6 +53,9 @@ namespace command_line std::string input_line(const std::string& prompt) { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif std::cout << prompt; std::string buf; diff --git a/src/common/password.cpp b/src/common/password.cpp index bdc9c69c0..5c04023f4 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -42,6 +42,10 @@ #include <unistd.h> #endif +#ifdef HAVE_READLINE + #include "readline_buffer.h" +#endif + namespace { #if defined(_WIN32) @@ -238,6 +242,9 @@ namespace tools boost::optional<password_container> password_container::prompt(const bool verify, const char *message) { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif password_container pass1{}; password_container pass2{}; if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index 98da466cc..1c7adff3b 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -81,12 +81,16 @@ namespace crypto { } /* generate a random 32-byte (256-bit) integer and copy it to res */ - static inline void random_scalar(ec_scalar &res) { + static inline void random_scalar_not_thread_safe(ec_scalar &res) { unsigned char tmp[64]; generate_random_bytes_not_thread_safe(64, tmp); sc_reduce(tmp); memcpy(&res, tmp, 32); } + static inline void random_scalar(ec_scalar &res) { + boost::lock_guard<boost::mutex> lock(random_lock); + random_scalar_not_thread_safe(res); + } static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { cn_fast_hash(data, length, reinterpret_cast<hash &>(res)); @@ -99,7 +103,6 @@ namespace crypto { * */ secret_key crypto_ops::generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key, bool recover) { - boost::lock_guard<boost::mutex> lock(random_lock); ge_p3 point; secret_key rng; @@ -197,8 +200,14 @@ namespace crypto { ec_point comm; }; + struct s_comm_2 { + hash msg; + ec_point D; + ec_point X; + ec_point Y; + }; + void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) { - boost::lock_guard<boost::mutex> lock(random_lock); ge_p3 tmp3; ec_scalar k; s_comm buf; @@ -242,6 +251,124 @@ namespace crypto { return sc_isnonzero(&c) == 0; } + void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { + // sanity check + ge_p3 R_p3; + ge_p3 A_p3; + ge_p3 D_p3; + if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid"); + if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid"); + if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid"); +#if !defined(NDEBUG) + { + assert(sc_check(&r) == 0); + // check R == r*G + ge_p3 dbg_R_p3; + ge_scalarmult_base(&dbg_R_p3, &r); + public_key dbg_R; + ge_p3_tobytes(&dbg_R, &dbg_R_p3); + assert(R == dbg_R); + // check D == r*A + ge_p2 dbg_D_p2; + ge_scalarmult(&dbg_D_p2, &r, &A_p3); + public_key dbg_D; + ge_tobytes(&dbg_D, &dbg_D_p2); + assert(D == dbg_D); + } +#endif + + // pick random k + ec_scalar k; + random_scalar(k); + + // compute X = k*G + ge_p3 X_p3; + ge_scalarmult_base(&X_p3, &k); + + // compute Y = k*A + ge_p2 Y_p2; + ge_scalarmult(&Y_p2, &k, &A_p3); + + // sig.c = Hs(Msg || D || X || Y) + s_comm_2 buf; + buf.msg = prefix_hash; + buf.D = D; + ge_p3_tobytes(&buf.X, &X_p3); + ge_tobytes(&buf.Y, &Y_p2); + hash_to_scalar(&buf, sizeof(s_comm_2), sig.c); + + // sig.r = k - sig.c*r + sc_mulsub(&sig.r, &sig.c, &r, &k); + } + + bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { + // sanity check + ge_p3 R_p3; + ge_p3 A_p3; + ge_p3 D_p3; + if (ge_frombytes_vartime(&R_p3, &R) != 0) return false; + if (ge_frombytes_vartime(&A_p3, &A) != 0) return false; + if (ge_frombytes_vartime(&D_p3, &D) != 0) return false; + if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false; + + // compute sig.c*R + ge_p2 cR_p2; + ge_scalarmult(&cR_p2, &sig.c, &R_p3); + + // compute sig.r*G + ge_p3 rG_p3; + ge_scalarmult_base(&rG_p3, &sig.r); + + // compute sig.c*D + ge_p2 cD_p2; + ge_scalarmult(&cD_p2, &sig.c, &D_p3); + + // compute sig.r*A + ge_p2 rA_p2; + ge_scalarmult(&rA_p2, &sig.r, &A_p3); + + // compute X = sig.c*R + sig.r*G + public_key cR; + ge_tobytes(&cR, &cR_p2); + ge_p3 cR_p3; + if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false; + ge_cached rG_cached; + ge_p3_to_cached(&rG_cached, &rG_p3); + ge_p1p1 X_p1p1; + ge_add(&X_p1p1, &cR_p3, &rG_cached); + ge_p2 X_p2; + ge_p1p1_to_p2(&X_p2, &X_p1p1); + + // compute Y = sig.c*D + sig.r*A + public_key cD; + public_key rA; + ge_tobytes(&cD, &cD_p2); + ge_tobytes(&rA, &rA_p2); + ge_p3 cD_p3; + ge_p3 rA_p3; + if (ge_frombytes_vartime(&cD_p3, &cD) != 0) return false; + if (ge_frombytes_vartime(&rA_p3, &rA) != 0) return false; + ge_cached rA_cached; + ge_p3_to_cached(&rA_cached, &rA_p3); + ge_p1p1 Y_p1p1; + ge_add(&Y_p1p1, &cD_p3, &rA_cached); + ge_p2 Y_p2; + ge_p1p1_to_p2(&Y_p2, &Y_p1p1); + + // compute c2 = Hs(Msg || D || X || Y) + s_comm_2 buf; + buf.msg = prefix_hash; + buf.D = D; + ge_tobytes(&buf.X, &X_p2); + ge_tobytes(&buf.Y, &Y_p2); + ec_scalar c2; + hash_to_scalar(&buf, sizeof(s_comm_2), c2); + + // test if c2 == sig.c + sc_sub(&c2, &c2, &sig.c); + return sc_isnonzero(&c2) == 0; + } + static void hash_to_ec(const public_key &key, ge_p3 &res) { hash h; ge_p2 point; @@ -280,7 +407,6 @@ POP_WARNINGS const public_key *const *pubs, size_t pubs_count, const secret_key &sec, size_t sec_index, signature *sig) { - boost::lock_guard<boost::mutex> lock(random_lock); size_t i; ge_p3 image_unp; ge_dsmp image_pre; diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 3b8c7996b..e99b6651f 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -123,6 +123,10 @@ namespace crypto { friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &); static bool check_signature(const hash &, const public_key &, const signature &); friend bool check_signature(const hash &, const public_key &, const signature &); + static void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); + friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &); + static bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); + friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &); static void generate_key_image(const public_key &, const secret_key &, key_image &); friend void generate_key_image(const public_key &, const secret_key &, key_image &); static void generate_ring_signature(const hash &, const key_image &, @@ -200,6 +204,16 @@ namespace crypto { return crypto_ops::check_signature(prefix_hash, pub, sig); } + /* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key + * derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A + */ + inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) { + crypto_ops::generate_tx_proof(prefix_hash, R, A, D, r, sig); + } + inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) { + return crypto_ops::check_tx_proof(prefix_hash, R, A, D, sig); + } + /* To send money to a key: * * The sender generates an ephemeral key and includes it in transaction output. * * To spend the money, the receiver generates a key image from it. diff --git a/src/crypto/slow-hash.c b/src/crypto/slow-hash.c index 6afa28934..b92b6e6c3 100644 --- a/src/crypto/slow-hash.c +++ b/src/crypto/slow-hash.c @@ -979,34 +979,31 @@ STATIC void cn_mul128(const uint64_t *a, const uint64_t *b, uint64_t *r) r[1] = lo; } #else /* ARM32 */ -/* Can work as inline, but actually runs slower. Keep it separate */ #define mul(a, b, c) cn_mul128((const uint32_t *)a, (const uint32_t *)b, (uint32_t *)c) -void cn_mul128(const uint32_t *aa, const uint32_t *bb, uint32_t *r) +STATIC void cn_mul128(const uint32_t *aa, const uint32_t *bb, uint32_t *r) { - uint32_t t0, t1; + uint32_t t0, t1, t2=0, t3=0; __asm__ __volatile__( "umull %[t0], %[t1], %[a], %[b]\n\t" - "str %[t0], [%[r], #8]\n\t" + "str %[t0], %[ll]\n\t" // accumulating with 0 can never overflow/carry - "mov %[t0], #0\n\t" + "eor %[t0], %[t0]\n\t" "umlal %[t1], %[t0], %[a], %[B]\n\t" - "mov %[a], #0\n\t" - "umlal %[t1], %[a], %[A], %[b]\n\t" - "str %[t1], [%[r], #12]\n\t" + "umlal %[t1], %[t2], %[A], %[b]\n\t" + "str %[t1], %[lh]\n\t" - "mov %[b], #0\n\t" - "umlal %[t0], %[b], %[A], %[B]\n\t" + "umlal %[t0], %[t3], %[A], %[B]\n\t" // final add may have a carry - "adds %[t0], %[t0], %[a]\n\t" - "adc %[t1], %[b], #0\n\t" + "adds %[t0], %[t0], %[t2]\n\t" + "adc %[t1], %[t3], #0\n\t" - "str %[t0], [%[r]]\n\t" - "str %[t1], [%[r], #4]\n\t" - : [t0]"=&r"(t0), [t1]"=&r"(t1), "=m"(r[0]), "=m"(r[1]), "=m"(r[2]), "=m"(r[3]) - : [A]"r"(aa[1]), [a]"r"(aa[0]), [B]"r"(bb[1]), [b]"r"(bb[0]), [r]"r"(r) + "str %[t0], %[hl]\n\t" + "str %[t1], %[hh]\n\t" + : [t0]"=&r"(t0), [t1]"=&r"(t1), [t2]"+r"(t2), [t3]"+r"(t3), [hl]"=m"(r[0]), [hh]"=m"(r[1]), [ll]"=m"(r[2]), [lh]"=m"(r[3]) + : [A]"r"(aa[1]), [a]"r"(aa[0]), [B]"r"(bb[1]), [b]"r"(bb[0]) : "cc"); } #endif /* !aarch64 */ diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index ec7aa251f..1503b277e 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -26,6 +26,12 @@ # 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. +if(APPLE) + find_library(IOKIT_LIBRARY IOKit) + mark_as_advanced(IOKIT_LIBRARY) + list(APPEND EXTRA_LIBRARIES ${IOKIT_LIBRARY}) +endif() + set(cryptonote_basic_sources account.cpp checkpoints.cpp diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index eeb7b6094..6928a0ded 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -43,6 +43,16 @@ #include "storages/portable_storage_template_helper.h" #include "boost/logic/tribool.hpp" +#ifdef __APPLE__ + #include <sys/times.h> + #include <IOKit/IOKitLib.h> + #include <IOKit/ps/IOPSKeys.h> + #include <IOKit/ps/IOPowerSources.h> + #include <mach/mach_host.h> + #include <AvailabilityMacros.h> + #include <TargetConditionals.h> +#endif + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "miner" @@ -757,6 +767,23 @@ namespace cryptonote return true; + #elif defined(__APPLE__) + + mach_msg_type_number_t count; + kern_return_t status; + host_cpu_load_info_data_t stats; + count = HOST_CPU_LOAD_INFO_COUNT; + status = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&stats, &count); + if(status != KERN_SUCCESS) + { + return false; + } + + idle_time = stats.cpu_ticks[CPU_STATE_IDLE]; + total_time = idle_time + stats.cpu_ticks[CPU_STATE_USER] + stats.cpu_ticks[CPU_STATE_SYSTEM]; + + return true; + #endif return false; // unsupported systemm.. @@ -779,7 +806,7 @@ namespace cryptonote return true; } - #elif defined(__linux__) && defined(_SC_CLK_TCK) + #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) struct tms tms; if ( times(&tms) != (clock_t)-1 ) @@ -808,6 +835,15 @@ namespace cryptonote return boost::logic::tribool(power_status.ACLineStatus != 1); } + #elif defined(__APPLE__) + + #if TARGET_OS_MAC && (!defined(MAC_OS_X_VERSION_MIN_REQUIRED) || MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_7) + return boost::logic::tribool(IOPSGetTimeRemainingEstimate() != kIOPSTimeRemainingUnlimited); + #else + // iOS or OSX <10.7 + return boost::logic::tribool(boost::logic::indeterminate); + #endif + #elif defined(__linux__) // i've only tested on UBUNTU, these paths might be different on other systems diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 6f2977c5b..745608b9f 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4005,6 +4005,11 @@ txpool_tx_meta_t Blockchain::get_txpool_tx_meta(const crypto::hash& txid) const return m_db->get_txpool_tx_meta(txid); } +bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const +{ + return m_db->get_txpool_tx_blob(txid, bd); +} + cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) const { return m_db->get_txpool_tx_blob(txid); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 52172012c..56373adf9 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -862,6 +862,7 @@ namespace cryptonote void remove_txpool_tx(const crypto::hash &txid); uint64_t get_txpool_tx_count() const; txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; + bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const; diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 515918cfa..ffb5b478b 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -556,8 +556,9 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); const uint64_t now = time(NULL); + std::map<uint64_t, txpool_histo> agebytes; stats.txs_total = m_blockchain.get_txpool_tx_count(); - m_blockchain.for_all_txpool_txes([&stats, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + m_blockchain.for_all_txpool_txes([&stats, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ stats.bytes_total += meta.blob_size; if (!stats.bytes_min || meta.blob_size < stats.bytes_min) stats.bytes_min = meta.blob_size; @@ -572,8 +573,53 @@ namespace cryptonote stats.num_10m++; if (meta.last_failed_height) stats.num_failing++; + uint64_t age = now - meta.receive_time; + agebytes[age].txs++; + agebytes[age].bytes += meta.blob_size; return true; }); + if (stats.txs_total > 1) + { + /* looking for 98th percentile */ + size_t end = stats.txs_total * 0.02; + uint64_t delta, factor; + std::map<uint64_t, txpool_histo>::iterator it, i2; + if (end) + { + /* If enough txs, spread the first 98% of results across + * the first 9 bins, drop final 2% in last bin. + */ + it=agebytes.end(); + for (size_t n=0; n <= end; n++, it--); + stats.histo_98pc = it->first; + factor = 9; + delta = it->first; + stats.histo.resize(10); + } else + { + /* If not enough txs, don't reserve the last slot; + * spread evenly across all 10 bins. + */ + stats.histo_98pc = 0; + it = agebytes.end(); + factor = stats.txs_total > 9 ? 10 : stats.txs_total; + delta = now - stats.oldest; + stats.histo.resize(factor); + } + if (!delta) + delta = 1; + for (i2 = agebytes.begin(); i2 != it; i2++) + { + size_t i = (i2->first * factor - 1) / delta; + stats.histo[i].txs += i2->second.txs; + stats.histo[i].bytes += i2->second.bytes; + } + for (; i2 != agebytes.end(); i2++) + { + stats.histo[factor].txs += i2->second.txs; + stats.histo[factor].bytes += i2->second.bytes; + } + } } //------------------------------------------------------------------ //TODO: investigate whether boolean return is appropriate @@ -627,8 +673,7 @@ namespace cryptonote CRITICAL_REGION_LOCAL1(m_blockchain); try { - txblob = m_blockchain.get_txpool_tx_blob(id); - return true; + return m_blockchain.get_txpool_tx_blob(id, txblob); } catch (const std::exception &e) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h index fd5b980b8..6f6c1a803 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_defs.h +++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h @@ -49,6 +49,8 @@ namespace cryptonote bool localhost; bool local_ip; + std::string address; + std::string host; std::string ip; std::string port; @@ -76,6 +78,8 @@ namespace cryptonote KV_SERIALIZE(incoming) KV_SERIALIZE(localhost) KV_SERIALIZE(local_ip) + KV_SERIALIZE(address) + KV_SERIALIZE(host) KV_SERIALIZE(ip) KV_SERIALIZE(port) KV_SERIALIZE(peer_id) diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 8b41ce514..0b99aa7bd 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -134,17 +134,12 @@ namespace cryptonote << std::setw(13) << "Up(now)" << ENDL; - uint32_t ip; m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id, uint32_t support_flags) { - bool local_ip = false; - ip = ntohl(cntxt.m_remote_ip); - // TODO: local ip in calss A, B - if (ip > 3232235520 && ip < 3232301055) // 192.168.x.x - local_ip = true; + bool local_ip = cntxt.m_remote_address.is_local(); auto connection_time = time(NULL) - cntxt.m_started; ss << std::setw(30) << std::left << std::string(cntxt.m_is_income ? " [INC]":"[OUT]") + - epee::string_tools::get_ip_string_from_int32(cntxt.m_remote_ip) + ":" + std::to_string(cntxt.m_remote_port) + cntxt.m_remote_address.str() << std::setw(20) << std::hex << peer_id << std::setw(20) << std::hex << support_flags << std::setw(30) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" @@ -155,7 +150,7 @@ namespace cryptonote << std::setw(10) << std::fixed << (connection_time == 0 ? 0.0 : cntxt.m_send_cnt / connection_time / 1024) << std::setw(13) << std::fixed << cntxt.m_current_speed_up / 1024 << (local_ip ? "[LAN]" : "") - << std::left << (ip == LOCALHOST_INT ? "[LOCALHOST]" : "") // 127.0.0.1 + << std::left << (cntxt.m_remote_address.is_loopback() ? "[LOCALHOST]" : "") // 127.0.0.1 << ENDL; if (connection_time > 1) @@ -193,8 +188,15 @@ namespace cryptonote cnx.incoming = cntxt.m_is_income ? true : false; - cnx.ip = epee::string_tools::get_ip_string_from_int32(cntxt.m_remote_ip); - cnx.port = std::to_string(cntxt.m_remote_port); + cnx.address = cntxt.m_remote_address.str(); + cnx.host = cntxt.m_remote_address.host_str(); + cnx.ip = ""; + cnx.port = ""; + if (cntxt.m_remote_address.type() == typeid(epee::net_utils::ipv4_network_address)) + { + cnx.ip = cnx.host; + cnx.port = std::to_string(cntxt.m_remote_address.as<epee::net_utils::ipv4_network_address>().port()); + } std::stringstream peer_id_str; peer_id_str << std::hex << peer_id; @@ -212,25 +214,8 @@ namespace cryptonote cnx.live_time = timestamp - cntxt.m_started; - uint32_t ip; - ip = ntohl(cntxt.m_remote_ip); - if (ip == LOCALHOST_INT) - { - cnx.localhost = true; - } - else - { - cnx.localhost = false; - } - - if (ip > 3232235520 && ip < 3232301055) // 192.168.x.x - { - cnx.local_ip = true; - } - else - { - cnx.local_ip = false; - } + cnx.localhost = cntxt.m_remote_address.is_loopback(); + cnx.local_ip = cntxt.m_remote_address.is_local(); auto connection_time = time(NULL) - cntxt.m_started; if (connection_time == 0) @@ -662,9 +647,6 @@ namespace cryptonote return 1; } - for (auto txidx: arg.missing_tx_indices) - MDEBUG(" tx " << b.tx_hashes[txidx]); - std::vector<crypto::hash> txids; NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response; fluffy_response.b.block = t_serializable_object_to_blob(b); @@ -674,6 +656,7 @@ namespace cryptonote { if(tx_idx < b.tx_hashes.size()) { + MDEBUG(" tx " << b.tx_hashes[tx_idx]); txids.push_back(b.tx_hashes[tx_idx]); } else @@ -990,7 +973,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_p2p->add_host_fail(context.m_remote_address); m_core.cleanup_handle_incoming_blocks(); return 1; } @@ -998,7 +981,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_p2p->add_host_fail(context.m_remote_address); m_core.cleanup_handle_incoming_blocks(); return 1; } @@ -1150,7 +1133,7 @@ skip: { LOG_ERROR_CCONTEXT("sent empty m_block_ids, dropping connection"); m_p2p->drop_connection(context); - m_p2p->add_ip_fail(context.m_remote_ip); + m_p2p->add_host_fail(context.m_remote_address); return 1; } @@ -1159,7 +1142,7 @@ skip: 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); + m_p2p->add_host_fail(context.m_remote_address); return 1; } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index d5bde7f09..1c9cd714d 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -92,6 +92,19 @@ namespace { return s + " " + (t > now ? "in the future" : "ago"); } + std::string get_time_hms(time_t t) + { + unsigned int hours, minutes, seconds; + char buffer[24]; + hours = t / 3600; + t %= 3600; + minutes = t / 60; + t %= 60; + seconds = t; + snprintf(buffer, sizeof(buffer), "%02u:%02u:%02u", hours, minutes, seconds); + return std::string(buffer); + } + std::string make_error(const std::string &base, const std::string &status) { if (status == CORE_RPC_STATUS_OK) @@ -939,7 +952,35 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ")" << std::endl << "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte )" << std::endl - << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << ")" << std::endl; + << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << ")"; + + if (n_transactions > 1 && res.pool_stats.histo.size()) + { + std::vector<uint64_t> times; + uint64_t numer; + size_t i, n = res.pool_stats.histo.size(), denom; + times.resize(n); + if (res.pool_stats.histo_98pc) + { + numer = res.pool_stats.histo_98pc; + denom = n-1; + for (i=0; i<denom; i++) + times[i] = i * numer / denom; + times[i] = res.pool_stats.oldest; + } else + { + numer = now - res.pool_stats.oldest; + denom = n; + for (i=0; i<denom; i++) + times[i] = i * numer / denom; + } + tools::msg_writer() << " Age Txes Bytes"; + for (i=0; i<n; i++) + { + tools::msg_writer() << get_time_hms(times[i]) << setw(8) << res.pool_stats.histo[i].txs << setw(12) << res.pool_stats.histo[i].bytes; + } + } + tools::msg_writer(); return true; } diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 13cd3f5b0..8798a52e0 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -123,9 +123,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() { CRITICAL_REGION_LOCAL(m_blocked_ips_lock); return m_blocked_ips; } + virtual bool block_host(const epee::net_utils::network_address &adress, time_t seconds = P2P_IP_BLOCKTIME); + virtual bool unblock_host(const epee::net_utils::network_address &address); + virtual std::map<std::string, time_t> get_blocked_hosts() { CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_blocked_hosts; } private: const std::vector<std::string> m_seed_nodes_list = { "seeds.moneroseeds.se" @@ -186,11 +186,11 @@ namespace nodetool virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); - virtual bool add_ip_fail(uint32_t address); + virtual bool add_host_fail(const epee::net_utils::network_address &address); //----------------- i_connection_filter -------------------------------------------------------- - virtual bool is_remote_ip_allowed(uint32_t adress); + virtual bool is_remote_host_allowed(const epee::net_utils::network_address &address); //----------------------------------------------------------------------------------------------- - bool parse_peer_from_string(nodetool::net_address& pe, const std::string& node_addr); + bool parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port = 0); bool handle_command_line( const boost::program_options::variables_map& vm ); @@ -209,18 +209,18 @@ namespace nodetool bool make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist); bool make_new_connection_from_peerlist(bool use_white_list); - bool try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0); + bool try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, PeerType peer_type = white, uint64_t first_seen_stamp = 0); size_t get_random_index_with_fixed_probability(size_t max_index); bool is_peer_used(const peerlist_entry& peer); bool is_peer_used(const anchor_peerlist_entry& peer); - bool is_addr_connected(const net_address& peer); + bool is_addr_connected(const epee::net_utils::network_address& peer); template<class t_callback> bool try_ping(basic_node_data& node_data, p2p_connection_context& context, t_callback cb); bool try_get_support_flags(const p2p_connection_context& context, std::function<void(p2p_connection_context&, const uint32_t&)> f); bool make_expected_connections_count(PeerType peer_type, 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); + void cache_connect_fail_info(const epee::net_utils::network_address& addr); + bool is_addr_recently_failed(const epee::net_utils::network_address& addr); + bool is_priority_node(const epee::net_utils::network_address& na); std::set<std::string> get_seed_nodes(bool testnet) const; template <class Container> @@ -236,9 +236,9 @@ namespace nodetool bool set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit); bool set_rate_limit(const boost::program_options::variables_map& vm, int64_t limit); - bool has_too_many_connections(const uint32_t ip); + bool has_too_many_connections(const epee::net_utils::network_address &address); - bool check_connection_and_handshake_with_peer(const net_address& na, uint64_t last_seen_stamp); + bool check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp); bool gray_peerlist_housekeeping(); void kill() { ///< will be called e.g. from deinit() @@ -308,23 +308,23 @@ namespace nodetool #ifdef ALLOW_DEBUG_COMMANDS uint64_t m_last_stat_request_time; #endif - std::list<net_address> m_priority_peers; - std::vector<net_address> m_exclusive_peers; - std::vector<net_address> m_seed_nodes; + std::list<epee::net_utils::network_address> m_priority_peers; + std::vector<epee::net_utils::network_address> m_exclusive_peers; + std::vector<epee::net_utils::network_address> m_seed_nodes; std::list<nodetool::peerlist_entry> m_command_line_peers; uint64_t m_peer_livetime; //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; + std::map<epee::net_utils::network_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_blocked_hosts_lock; + std::map<std::string, time_t> m_blocked_hosts; - epee::critical_section m_ip_fails_score_lock; - std::map<uint32_t, uint64_t> m_ip_fails_score; + epee::critical_section m_host_fails_score_lock; + std::map<std::string, uint64_t> m_host_fails_score; bool m_testnet; }; diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 0d0d86e14..c250d5185 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -200,16 +200,16 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::is_remote_ip_allowed(uint32_t addr) + bool node_server<t_payload_net_handler>::is_remote_host_allowed(const epee::net_utils::network_address &address) { - CRITICAL_REGION_LOCAL(m_blocked_ips_lock); - auto it = m_blocked_ips.find(addr); - if(it == m_blocked_ips.end()) + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); + auto it = m_blocked_hosts.find(address.host_str()); + if(it == m_blocked_hosts.end()) return true; if(time(nullptr) >= it->second) { - m_blocked_ips.erase(it); - MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked."); + m_blocked_hosts.erase(it); + MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); return true; } return false; @@ -229,16 +229,16 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::block_ip(uint32_t addr, time_t seconds) + bool node_server<t_payload_net_handler>::block_host(const epee::net_utils::network_address &addr, time_t seconds) { - CRITICAL_REGION_LOCAL(m_blocked_ips_lock); - m_blocked_ips[addr] = time(nullptr) + seconds; + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); + m_blocked_hosts[addr.host_str()] = time(nullptr) + seconds; // drop any connection to that IP std::list<boost::uuids::uuid> conns; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - if (cntxt.m_remote_ip == addr) + if (cntxt.m_remote_address.is_same_host(addr)) { conns.push_back(cntxt.m_connection_id); } @@ -247,42 +247,42 @@ namespace nodetool for (const auto &c: conns) m_net_server.get_config_object().close(c); - MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " blocked."); + MCLOG_CYAN(el::Level::Info, "global", "Host " << addr.host_str() << " blocked."); return true; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::unblock_ip(uint32_t addr) + bool node_server<t_payload_net_handler>::unblock_host(const epee::net_utils::network_address &address) { - CRITICAL_REGION_LOCAL(m_blocked_ips_lock); - auto i = m_blocked_ips.find(addr); - if (i == m_blocked_ips.end()) + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); + auto i = m_blocked_hosts.find(address.host_str()); + if (i == m_blocked_hosts.end()) return false; - m_blocked_ips.erase(i); - MCLOG_CYAN(el::Level::Info, "global", "IP " << epee::string_tools::get_ip_string_from_int32(addr) << " unblocked."); + m_blocked_hosts.erase(i); + MCLOG_CYAN(el::Level::Info, "global", "Host " << address.host_str() << " unblocked."); return true; } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::add_ip_fail(uint32_t address) + bool node_server<t_payload_net_handler>::add_host_fail(const epee::net_utils::network_address &address) { - CRITICAL_REGION_LOCAL(m_ip_fails_score_lock); - uint64_t fails = ++m_ip_fails_score[address]; - MDEBUG("IP " << epee::string_tools::get_ip_string_from_int32(address) << " fail score=" << fails); + CRITICAL_REGION_LOCAL(m_host_fails_score_lock); + uint64_t fails = ++m_host_fails_score[address.host_str()]; + MDEBUG("Host " << address.host_str() << " fail score=" << fails); 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"); + auto it = m_host_fails_score.find(address.host_str()); + CHECK_AND_ASSERT_MES(it != m_host_fails_score.end(), false, "internal error"); it->second = P2P_IP_FAILS_BEFORE_BLOCK/2; - block_ip(address); + block_host(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) + bool node_server<t_payload_net_handler>::parse_peer_from_string(epee::net_utils::network_address& pe, const std::string& node_addr, uint16_t default_port) { - return epee::string_tools::parse_peer_from_string(pe.ip, pe.port, node_addr); + return epee::net_utils::create_network_address(pe, node_addr, default_port); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> @@ -306,10 +306,9 @@ namespace nodetool { nodetool::peerlist_entry pe = AUTO_VAL_INIT(pe); pe.id = crypto::rand<uint64_t>(); - bool r = parse_peer_from_string(pe.adr, pr_str); + const uint16_t default_port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; + bool r = parse_peer_from_string(pe.adr, pr_str, default_port); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); - if (pe.adr.port == 0) - pe.adr.port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; m_command_line_peers.push_back(pe); } } @@ -359,7 +358,7 @@ namespace nodetool } //----------------------------------------------------------------------------------- inline void append_net_address( - std::vector<net_address> & seed_nodes + std::vector<epee::net_utils::network_address> & seed_nodes , std::string const & addr ) { @@ -383,15 +382,14 @@ namespace nodetool ip::tcp::endpoint endpoint = *i; if (endpoint.address().is_v4()) { - nodetool::net_address na; - na.ip = boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()); - na.port = endpoint.port(); + epee::net_utils::network_address na(new epee::net_utils::ipv4_network_address(boost::asio::detail::socket_ops::host_to_network_long(endpoint.address().to_v4().to_ulong()), endpoint.port())); seed_nodes.push_back(na); - MINFO("Added seed node: " << endpoint.address().to_v4().to_string(ec) << ':' << na.port); + MINFO("Added seed node: " << na.str()); } else { - MDEBUG("IPv6 doesn't supported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); + MERROR("IPv6 unsupported, skip '" << host << "' -> " << endpoint.address().to_v6().to_string(ec)); + throw std::runtime_error("IPv6 unsupported"); } } } @@ -752,10 +750,10 @@ namespace nodetool return; } - if(!handle_remote_peerlist(rsp.local_peerlist, rsp.node_data.local_time, context)) + if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.node_data.local_time, context)) { LOG_ERROR_CC(context, "COMMAND_HANDSHAKE: failed to handle_remote_peerlist(...), closing connection."); - add_ip_fail(context.m_remote_ip); + add_host_fail(context.m_remote_address); return; } hsh_result = true; @@ -769,7 +767,7 @@ namespace nodetool } pi = context.peer_id = rsp.node_data.peer_id; - m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_ip, context.m_remote_port); + m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address); if(rsp.node_data.peer_id == m_config.m_peer_id) { @@ -821,14 +819,14 @@ namespace nodetool return; } - if(!handle_remote_peerlist(rsp.local_peerlist, rsp.local_time, context)) + if(!handle_remote_peerlist(rsp.local_peerlist_new, rsp.local_time, context)) { LOG_WARNING_CC(context, "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); + add_host_fail(context.m_remote_address); } if(!context.m_is_income) - m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_ip, context.m_remote_port); + m_peerlist.set_peer_just_seen(context.peer_id, context.m_remote_address); m_payload_handler.process_payload_sync_data(rsp.payload_data, context, false); }); @@ -863,7 +861,7 @@ namespace nodetool bool used = false; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr.ip == cntxt.m_remote_ip && peer.adr.port == cntxt.m_remote_port)) + if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) { used = true; return false;//stop enumerating @@ -885,7 +883,7 @@ namespace nodetool m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr.ip == cntxt.m_remote_ip && peer.adr.port == cntxt.m_remote_port)) + if(cntxt.peer_id == peer.id || (!cntxt.m_is_income && peer.adr == cntxt.m_remote_address)) { used = true; @@ -899,12 +897,12 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::is_addr_connected(const net_address& peer) + bool node_server<t_payload_net_handler>::is_addr_connected(const epee::net_utils::network_address& peer) { bool connected = false; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - if(!cntxt.m_is_income && peer.ip == cntxt.m_remote_ip && peer.port == cntxt.m_remote_port) + if(!cntxt.m_is_income && peer == cntxt.m_remote_address) { connected = true; return false;//stop enumerating @@ -925,7 +923,7 @@ namespace nodetool } while(0) template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp) + bool node_server<t_payload_net_handler>::try_to_connect_and_handshake_with_new_peer(const epee::net_utils::network_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, PeerType peer_type, uint64_t first_seen_stamp) { if (m_current_number_of_out_peers == m_config.m_net_config.connections_count) // out peers limit { @@ -937,23 +935,24 @@ namespace nodetool m_current_number_of_out_peers --; // atomic variable, update time = 1s return false; } - MDEBUG("Connecting to " << epee::string_tools::get_ip_string_from_int32(na.ip) << ":" - << epee::string_tools::num_to_string_fast(na.port) << "(peer_type=" << peer_type << ", last_seen: " + MDEBUG("Connecting to " << na.str() << "(peer_type=" << peer_type << ", last_seen: " << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") << ")..."); + CHECK_AND_ASSERT_MES(na.type() == typeid(epee::net_utils::ipv4_network_address), false, + "Only IPv4 addresses are supported here, got " << na.type().name()); + const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); + typename net_server::t_connection_context con = AUTO_VAL_INIT(con); - bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(na.ip), - epee::string_tools::num_to_string_fast(na.port), + bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), + epee::string_tools::num_to_string_fast(ipv4.port()), m_config.m_net_config.connection_timeout, con); if(!res) { bool is_priority = is_priority_node(na); - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " - << epee::string_tools::get_ip_string_from_int32(na.ip) - << ":" << epee::string_tools::num_to_string_fast(na.port) + LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str() /*<< ", try " << try_count*/); //m_peerlist.set_peer_unreachable(pe); return false; @@ -966,8 +965,7 @@ namespace nodetool { bool is_priority = is_priority_node(na); LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " - << epee::string_tools::get_ip_string_from_int32(na.ip) - << ":" << epee::string_tools::num_to_string_fast(na.port) + << na.str() /*<< ", try " << try_count*/); return false; } @@ -1000,25 +998,26 @@ namespace nodetool } template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::check_connection_and_handshake_with_peer(const net_address& na, uint64_t last_seen_stamp) + bool node_server<t_payload_net_handler>::check_connection_and_handshake_with_peer(const epee::net_utils::network_address& na, uint64_t last_seen_stamp) { - LOG_PRINT_L1("Connecting to " << epee::string_tools::get_ip_string_from_int32(na.ip) << ":" - << epee::string_tools::num_to_string_fast(na.port) << "(last_seen: " + LOG_PRINT_L1("Connecting to " << na.str() << "(last_seen: " << (last_seen_stamp ? epee::misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never") << ")..."); + CHECK_AND_ASSERT_MES(na.type() == typeid(epee::net_utils::ipv4_network_address), false, + "Only IPv4 addresses are supported here, got " << na.type().name()); + const epee::net_utils::ipv4_network_address &ipv4 = na.as<epee::net_utils::ipv4_network_address>(); + typename net_server::t_connection_context con = AUTO_VAL_INIT(con); - bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(na.ip), - epee::string_tools::num_to_string_fast(na.port), + bool res = m_net_server.connect(epee::string_tools::get_ip_string_from_int32(ipv4.ip()), + epee::string_tools::num_to_string_fast(ipv4.port()), m_config.m_net_config.connection_timeout, con); if (!res) { bool is_priority = is_priority_node(na); - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " - << epee::string_tools::get_ip_string_from_int32(na.ip) - << ":" << epee::string_tools::num_to_string_fast(na.port)); + LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Connect failed to " << na.str()); return false; } @@ -1029,9 +1028,7 @@ namespace nodetool if (!res) { bool is_priority = is_priority_node(na); - LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " - << epee::string_tools::get_ip_string_from_int32(na.ip) - << ":" << epee::string_tools::num_to_string_fast(na.port)); + LOG_PRINT_CC_PRIORITY_NODE(is_priority, con, "Failed to HANDSHAKE with peer " << na.str()); return false; } @@ -1047,7 +1044,7 @@ namespace nodetool //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::is_addr_recently_failed(const net_address& addr) + bool node_server<t_payload_net_handler>::is_addr_recently_failed(const epee::net_utils::network_address& addr) { CRITICAL_REGION_LOCAL(m_conn_fails_cache_lock); auto it = m_conn_fails_cache.find(addr); @@ -1064,14 +1061,14 @@ namespace nodetool bool node_server<t_payload_net_handler>::make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist) { for (const auto& pe: anchor_peerlist) { - _note("Considering connecting (out) to peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port)); + _note("Considering connecting (out) to peer: " << pe.id << " " << pe.adr.str()); if(is_peer_used(pe)) { _note("Peer is used"); continue; } - if(!is_remote_ip_allowed(pe.adr.ip)) { + if(!is_remote_host_allowed(pe.adr)) { continue; } @@ -1079,8 +1076,7 @@ namespace nodetool continue; } - MDEBUG("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) - << ":" << boost::lexical_cast<std::string>(pe.adr.port) + MDEBUG("Selected peer: " << pe.id << " " << pe.adr.str() << "[peer_type=" << anchor << "] first_seen: " << epee::misc_utils::get_time_interval_string(time(NULL) - pe.first_seen)); @@ -1131,21 +1127,20 @@ namespace nodetool ++try_count; - _note("Considering connecting (out) to peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port)); + _note("Considering connecting (out) to peer: " << pe.id << " " << pe.adr.str()); if(is_peer_used(pe)) { _note("Peer is used"); continue; } - if(!is_remote_ip_allowed(pe.adr.ip)) + if(!is_remote_host_allowed(pe.adr)) continue; if(is_addr_recently_failed(pe.adr)) continue; - MDEBUG("Selected peer: " << pe.id << " " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) - << ":" << boost::lexical_cast<std::string>(pe.adr.port) + MDEBUG("Selected peer: " << pe.id << " " << pe.adr.str() << "[peer_list=" << (use_white_list ? white : gray) << "] last_seen: " << (pe.last_seen ? epee::misc_utils::get_time_interval_string(time(NULL) - pe.last_seen) : "never")); @@ -1329,7 +1324,7 @@ namespace nodetool { if(be.last_seen > local_time) { - MWARNING("FOUND FUTURE peerlist for entry " << epee::string_tools::get_ip_string_from_int32(be.adr.ip) << ":" << be.adr.port << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); + MWARNING("FOUND FUTURE peerlist for entry " << be.adr.str() << " last_seen: " << be.last_seen << ", local_time(on remote node):" << local_time); return false; } be.last_seen += delta; @@ -1425,8 +1420,7 @@ namespace nodetool m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { connection_entry ce; - ce.adr.ip = cntxt.m_remote_ip; - ce.adr.port = cntxt.m_remote_port; + ce.adr = cntxt.m_remote_address; ce.id = cntxt.peer_id; ce.is_income = cntxt.m_is_income; rsp.connections_list.push_back(ce); @@ -1516,19 +1510,24 @@ namespace nodetool if(!node_data.my_port) return false; - uint32_t actual_ip = context.m_remote_ip; - if(!m_peerlist.is_ip_allowed(actual_ip)) + CHECK_AND_ASSERT_MES(context.m_remote_address.type() == typeid(epee::net_utils::ipv4_network_address), false, + "Only IPv4 addresses are supported here, got " << context.m_remote_address.type().name()); + + const epee::net_utils::network_address na = context.m_remote_address; + uint32_t actual_ip = na.as<const epee::net_utils::ipv4_network_address>().ip(); + if(!m_peerlist.is_host_allowed(context.m_remote_address)) return false; std::string ip = epee::string_tools::get_ip_string_from_int32(actual_ip); std::string port = epee::string_tools::num_to_string_fast(node_data.my_port); + epee::net_utils::network_address address(new epee::net_utils::ipv4_network_address(actual_ip, node_data.my_port)); peerid_type pr = node_data.peer_id; - bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ ip, port, pr, this]( + bool r = m_net_server.connect_async(ip, port, m_config.m_net_config.ping_connection_timeout, [cb, /*context,*/ address, pr, this]( const typename net_server::t_connection_context& ping_context, const boost::system::error_code& ec)->bool { if(ec) { - LOG_WARNING_CC(ping_context, "back ping connect failed to " << ip << ":" << port); + LOG_WARNING_CC(ping_context, "back ping connect failed to " << address.str()); return false; } COMMAND_PING::request req; @@ -1547,13 +1546,13 @@ namespace nodetool { if(code <= 0) { - LOG_ERROR_CC(ping_context, "Failed to invoke COMMAND_PING to " << ip << ":" << port << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); + LOG_ERROR_CC(ping_context, "Failed to invoke COMMAND_PING to " << address.str() << "(" << code << ", " << epee::levin::get_err_descr(code) << ")"); return; } if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) { - LOG_ERROR_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << ip << ":" << port << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); + LOG_ERROR_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); m_net_server.get_config_object().close(ping_context.m_connection_id); return; } @@ -1563,7 +1562,7 @@ namespace nodetool if(!inv_call_res) { - LOG_ERROR_CC(ping_context, "back ping invoke failed to " << ip << ":" << port); + LOG_ERROR_CC(ping_context, "back ping invoke failed to " << address.str()); m_net_server.get_config_object().close(ping_context.m_connection_id); return false; } @@ -1614,7 +1613,7 @@ namespace nodetool //fill response rsp.local_time = time(NULL); - m_peerlist.get_peerlist_head(rsp.local_peerlist); + m_peerlist.get_peerlist_head(rsp.local_peerlist_new); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_TIMED_SYNC"); return 1; @@ -1628,7 +1627,7 @@ namespace nodetool LOG_INFO_CC(context, "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); + add_host_fail(context.m_remote_address); return 1; } @@ -1636,7 +1635,7 @@ namespace nodetool { LOG_ERROR_CC(context, "COMMAND_HANDSHAKE came not from incoming connection"); drop_connection(context); - add_ip_fail(context.m_remote_ip); + add_host_fail(context.m_remote_address); return 1; } @@ -1654,9 +1653,9 @@ namespace nodetool return 1; } - if(has_too_many_connections(context.m_remote_ip)) + if(has_too_many_connections(context.m_remote_address)) { - LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << epee::string_tools::get_ip_string_from_int32(context.m_remote_ip) << " REFUSED, too many connections from the same address"); + LOG_PRINT_CCONTEXT_L1("CONNECTION FROM " << context.m_remote_address.host_str() << " REFUSED, too many connections from the same address"); drop_connection(context); return 1; } @@ -1671,16 +1670,18 @@ namespace nodetool //try ping to be sure that we can add this peer to peer_list try_ping(arg.node_data, context, [peer_id_l, port_l, context, this]() { + CHECK_AND_ASSERT_MES(context.m_remote_address.type() == typeid(epee::net_utils::ipv4_network_address), void(), + "Only IPv4 addresses are supported here, got " << context.m_remote_address.type().name()); //called only(!) if success pinged, update local peerlist peerlist_entry pe; - pe.adr.ip = context.m_remote_ip; - pe.adr.port = port_l; + const epee::net_utils::network_address na = context.m_remote_address; + pe.adr.reset(new epee::net_utils::ipv4_network_address(na.as<epee::net_utils::ipv4_network_address>().ip(), port_l)); time_t last_seen; time(&last_seen); pe.last_seen = static_cast<int64_t>(last_seen); pe.id = peer_id_l; this->m_peerlist.append_with_peer_white(pe); - LOG_DEBUG_CC(context, "PING SUCCESS " << epee::string_tools::get_ip_string_from_int32(context.m_remote_ip) << ":" << port_l); + LOG_DEBUG_CC(context, "PING SUCCESS " << context.m_remote_address.host_str() << ":" << port_l); }); } @@ -1690,7 +1691,7 @@ namespace nodetool }); //fill response - m_peerlist.get_peerlist_head(rsp.local_peerlist); + m_peerlist.get_peerlist_head(rsp.local_peerlist_new); get_local_node_data(rsp.node_data); m_payload_handler.get_payload_sync_data(rsp.payload_data); LOG_DEBUG_CC(context, "COMMAND_HANDSHAKE"); @@ -1730,7 +1731,7 @@ namespace nodetool std::stringstream ss; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - ss << epee::string_tools::get_ip_string_from_int32(cntxt.m_remote_ip) << ":" << cntxt.m_remote_port + ss << cntxt.m_remote_address.str() << " \t\tpeer_id " << cntxt.peer_id << " \t\tconn_id " << epee::string_tools::get_str_from_guid_a(cntxt.m_connection_id) << (cntxt.m_is_income ? " INC":" OUT") << std::endl; @@ -1750,9 +1751,8 @@ namespace nodetool void node_server<t_payload_net_handler>::on_connection_close(p2p_connection_context& context) { if (!m_net_server.is_stop_signal_sent() && !context.m_is_income) { - nodetool::net_address na = AUTO_VAL_INIT(na); - na.ip = context.m_remote_ip; - na.port = context.m_remote_port; + epee::net_utils::network_address na = AUTO_VAL_INIT(na); + na = context.m_remote_address; m_peerlist.remove_from_peer_anchor(na); } @@ -1761,7 +1761,7 @@ namespace nodetool } template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::is_priority_node(const net_address& na) + bool node_server<t_payload_net_handler>::is_priority_node(const epee::net_utils::network_address& na) { return (std::find(m_priority_peers.begin(), m_priority_peers.end(), na) != m_priority_peers.end()) || (std::find(m_exclusive_peers.begin(), m_exclusive_peers.end(), na) != m_exclusive_peers.end()); } @@ -1769,7 +1769,7 @@ namespace nodetool template<class t_payload_net_handler> template <class Container> bool node_server<t_payload_net_handler>::connect_to_peerlist(const Container& peers) { - for(const net_address& na: peers) + for(const epee::net_utils::network_address& na: peers) { if(m_net_server.is_stop_signal_sent()) return false; @@ -1790,11 +1790,10 @@ namespace nodetool for(const std::string& pr_str: perrs) { - nodetool::net_address na = AUTO_VAL_INIT(na); - bool r = parse_peer_from_string(na, pr_str); + epee::net_utils::network_address na = AUTO_VAL_INIT(na); + const uint16_t default_port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; + bool r = parse_peer_from_string(na, pr_str, default_port); CHECK_AND_ASSERT_MES(r, false, "Failed to parse address from string: " << pr_str); - if (na.port == 0) - na.port = m_testnet ? ::config::testnet::P2P_DEFAULT_PORT : ::config::P2P_DEFAULT_PORT; container.push_back(na); } @@ -1888,14 +1887,14 @@ namespace nodetool } template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::has_too_many_connections(const uint32_t ip) + bool node_server<t_payload_net_handler>::has_too_many_connections(const epee::net_utils::network_address &address) { const uint8_t max_connections = 1; uint8_t count = 0; m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { - if (cntxt.m_is_income && cntxt.m_remote_ip == ip) { + if (cntxt.m_is_income && cntxt.m_remote_address.is_same_host(address)) { count++; if (count > max_connections) { @@ -1923,14 +1922,14 @@ namespace nodetool if (!success) { m_peerlist.remove_from_peer_gray(pe); - LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << " Peer ID: " << std::hex << pe.id); + LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << std::hex << pe.id); return true; } m_peerlist.set_peer_just_seen(pe.id, pe.adr); - LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << " Peer ID: " << std::hex << pe.id); + LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << std::hex << pe.id); return true; } diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 6aba7aa24..42de2655d 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -51,10 +51,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, uint32_t)> 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()=0; - virtual bool add_ip_fail(uint32_t adress)=0; + virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds = 0)=0; + virtual bool unblock_host(const epee::net_utils::network_address &address)=0; + virtual std::map<std::string, time_t> get_blocked_hosts()=0; + virtual bool add_host_fail(const epee::net_utils::network_address &address)=0; }; template<class t_connection_context> @@ -93,19 +93,19 @@ namespace nodetool { return false; } - virtual bool block_ip(uint32_t adress, time_t seconds) + virtual bool block_host(const epee::net_utils::network_address &address, time_t seconds) { return true; } - virtual bool unblock_ip(uint32_t adress) + virtual bool unblock_host(const epee::net_utils::network_address &address) { return true; } - virtual std::map<uint32_t, time_t> get_blocked_ips() + virtual std::map<std::string, time_t> get_blocked_hosts() { - return std::map<uint32_t, time_t>(); + return std::map<std::string, time_t>(); } - virtual bool add_ip_fail(uint32_t adress) + virtual bool add_host_fail(const epee::net_utils::network_address &address) { return true; } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index de0f51cc3..a30a05422 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -54,7 +54,7 @@ #include "net_peerlist_boost_serialization.h" -#define CURRENT_PEERLIST_STORAGE_ARCHIVE_VER 5 +#define CURRENT_PEERLIST_STORAGE_ARCHIVE_VER 6 namespace nodetool { @@ -78,14 +78,13 @@ namespace nodetool bool append_with_peer_white(const peerlist_entry& pr); bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_anchor(const anchor_peerlist_entry& ple); - bool set_peer_just_seen(peerid_type peer, uint32_t ip, uint32_t port); - bool set_peer_just_seen(peerid_type peer, const net_address& addr); + bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr); bool set_peer_unreachable(const peerlist_entry& pr); - bool is_ip_allowed(uint32_t ip); + bool is_host_allowed(const epee::net_utils::network_address &address); bool get_random_gray_peer(peerlist_entry& pe); bool remove_from_peer_gray(const peerlist_entry& pe); bool get_and_empty_anchor_peerlist(std::vector<anchor_peerlist_entry>& apl); - bool remove_from_peer_anchor(const net_address& addr); + bool remove_from_peer_anchor(const epee::net_utils::network_address& addr); private: struct by_time{}; @@ -130,7 +129,7 @@ namespace nodetool peerlist_entry, boost::multi_index::indexed_by< // access by peerlist_entry::net_adress - boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<peerlist_entry,net_address,&peerlist_entry::adr> >, + boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<peerlist_entry,epee::net_utils::network_address,&peerlist_entry::adr> >, // sort by peerlist_entry::last_seen< boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_time>, boost::multi_index::member<peerlist_entry,int64_t,&peerlist_entry::last_seen> > > @@ -142,7 +141,7 @@ namespace nodetool // access by peerlist_entry::id< boost::multi_index::ordered_unique<boost::multi_index::tag<by_id>, boost::multi_index::member<peerlist_entry,uint64_t,&peerlist_entry::id> >, // access by peerlist_entry::net_adress - boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<peerlist_entry,net_address,&peerlist_entry::adr> >, + boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<peerlist_entry,epee::net_utils::network_address,&peerlist_entry::adr> >, // sort by peerlist_entry::last_seen< boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_time>, boost::multi_index::member<peerlist_entry,int64_t,&peerlist_entry::last_seen> > > @@ -152,16 +151,45 @@ namespace nodetool anchor_peerlist_entry, boost::multi_index::indexed_by< // access by anchor_peerlist_entry::net_adress - boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<anchor_peerlist_entry,net_address,&anchor_peerlist_entry::adr> >, + boost::multi_index::ordered_unique<boost::multi_index::tag<by_addr>, boost::multi_index::member<anchor_peerlist_entry,epee::net_utils::network_address,&anchor_peerlist_entry::adr> >, // sort by anchor_peerlist_entry::first_seen boost::multi_index::ordered_non_unique<boost::multi_index::tag<by_time>, boost::multi_index::member<anchor_peerlist_entry,int64_t,&anchor_peerlist_entry::first_seen> > > > anchor_peers_indexed; public: + template <class Archive, class List, class Element, class t_version_type> + void serialize_peers(Archive &a, List &list, Element ple, const t_version_type ver) + { + if (typename Archive::is_saving()) + { + uint64_t size = list.size(); + a & size; + for (auto p: list) + { + a & p; + } + } + else + { + uint64_t size; + a & size; + list.clear(); + while (size--) + { + a & ple; + list.insert(ple); + } + } + } + template <class Archive, class t_version_type> void serialize(Archive &a, const t_version_type ver) { + // at v6, we drop existing peerlists, because annoying change + if (ver < 6) + return; + if(ver < 3) return; CRITICAL_REGION_LOCAL(m_peerlist_lock); @@ -174,14 +202,25 @@ namespace nodetool return; } +#if 0 + // trouble loading more than one peer, can't find why a & m_peers_white; a & m_peers_gray; +#else + serialize_peers(a, m_peers_white, peerlist_entry(), ver); + serialize_peers(a, m_peers_gray, peerlist_entry(), ver); +#endif if(ver < 5) { return; } +#if 0 + // trouble loading more than one peer, can't find why a & m_peers_anchor; +#else + serialize_peers(a, m_peers_anchor, anchor_peerlist_entry(), ver); +#endif } private: @@ -284,13 +323,13 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::is_ip_allowed(uint32_t ip) + bool peerlist_manager::is_host_allowed(const epee::net_utils::network_address &address) { //never allow loopback ip - if(epee::net_utils::is_ip_loopback(ip)) + if(address.is_loopback()) return false; - if(!m_allow_local_ip && epee::net_utils::is_ip_local(ip)) + if(!m_allow_local_ip && address.is_local()) return false; return true; @@ -336,16 +375,7 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::set_peer_just_seen(peerid_type peer, uint32_t ip, uint32_t port) - { - net_address addr; - addr.ip = ip; - addr.port = port; - return set_peer_just_seen(peer, addr); - } - //-------------------------------------------------------------------------------------------------- - inline - bool peerlist_manager::set_peer_just_seen(peerid_type peer, const net_address& addr) + bool peerlist_manager::set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr) { TRY_ENTRY(); CRITICAL_REGION_LOCAL(m_peerlist_lock); @@ -362,7 +392,7 @@ namespace nodetool bool peerlist_manager::append_with_peer_white(const peerlist_entry& ple) { TRY_ENTRY(); - if(!is_ip_allowed(ple.adr.ip)) + if(!is_host_allowed(ple.adr)) return true; CRITICAL_REGION_LOCAL(m_peerlist_lock); @@ -392,7 +422,7 @@ namespace nodetool bool peerlist_manager::append_with_peer_gray(const peerlist_entry& ple) { TRY_ENTRY(); - if(!is_ip_allowed(ple.adr.ip)) + if(!is_host_allowed(ple.adr)) return true; CRITICAL_REGION_LOCAL(m_peerlist_lock); @@ -496,7 +526,7 @@ namespace nodetool } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::remove_from_peer_anchor(const net_address& addr) + bool peerlist_manager::remove_from_peer_anchor(const epee::net_utils::network_address& addr) { TRY_ENTRY(); diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 6891ac80c..0a21895cf 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -30,16 +30,43 @@ #pragma once +#include "net/net_utils_base.h" + namespace boost { namespace serialization { - //BOOST_CLASS_VERSION(odetool::net_adress, 1) + enum { sertype_ipv4_address }; + static inline uint8_t get_type(const epee::net_utils::network_address &na) + { + if (na.type() == typeid(epee::net_utils::ipv4_network_address)) + return sertype_ipv4_address; + throw std::runtime_error("Unsupported network address type"); + return 0; + } + template <class Archive, class ver_type> + inline void serialize(Archive &a, epee::net_utils::network_address& na, const ver_type ver) + { + uint8_t type; + if (typename Archive::is_saving()) + type = get_type(na); + a & type; + switch (type) + { + case sertype_ipv4_address: + if (!typename Archive::is_saving()) + na.reset(new epee::net_utils::ipv4_network_address(0, 0)); + a & na.as<epee::net_utils::ipv4_network_address>(); + break; + default: + throw std::runtime_error("Unsupported network address type"); + } + } template <class Archive, class ver_type> - inline void serialize(Archive &a, nodetool::net_address& na, const ver_type ver) + inline void serialize(Archive &a, epee::net_utils::ipv4_network_address& na, const ver_type ver) { - a & na.ip; - a & na.port; + a & na.m_ip; + a & na.m_port; } diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 5ec012714..d60990f8a 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -32,6 +32,7 @@ #include <boost/uuid/uuid.hpp> #include "serialization/keyvalue_serialization.h" +#include "net/net_utils_base.h" #include "misc_language.h" #include "cryptonote_config.h" #include "crypto/crypto.h" @@ -43,46 +44,64 @@ namespace nodetool #pragma pack (push, 1) - struct net_address + struct network_address_old { uint32_t ip; uint32_t port; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(ip) + KV_SERIALIZE(port) + END_KV_SERIALIZE_MAP() }; - struct peerlist_entry + template<typename AddressType> + struct peerlist_entry_base { - net_address adr; + AddressType adr; peerid_type id; int64_t last_seen; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE(last_seen) + END_KV_SERIALIZE_MAP() }; + typedef peerlist_entry_base<epee::net_utils::network_address> peerlist_entry; - struct anchor_peerlist_entry + template<typename AddressType> + struct anchor_peerlist_entry_base { - net_address adr; + AddressType adr; peerid_type id; int64_t first_seen; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE(first_seen) + END_KV_SERIALIZE_MAP() }; + typedef anchor_peerlist_entry_base<epee::net_utils::network_address> anchor_peerlist_entry; - struct connection_entry + template<typename AddressType> + struct connection_entry_base { - net_address adr; + AddressType adr; peerid_type id; bool is_income; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(adr) + KV_SERIALIZE(id) + KV_SERIALIZE(is_income) + END_KV_SERIALIZE_MAP() }; + typedef connection_entry_base<epee::net_utils::network_address> connection_entry; #pragma pack(pop) - inline - bool operator < (const net_address& a, const net_address& b) - { - return epee::misc_utils::is_less_as_pod(a, b); - } - - inline - bool operator == (const net_address& a, const net_address& b) - { - return memcmp(&a, &b, sizeof(a)) == 0; - } inline std::string print_peerlist_to_string(const std::list<peerlist_entry>& pl) { @@ -92,7 +111,7 @@ namespace nodetool ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; for(const peerlist_entry& pe: pl) { - ss << pe.id << "\t" << epee::string_tools::get_ip_string_from_int32(pe.adr.ip) << ":" << boost::lexical_cast<std::string>(pe.adr.port) << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; + ss << pe.id << "\t" << pe.adr->str() << " \tlast_seen: " << epee::misc_utils::get_time_interval_string(now_time - pe.last_seen) << std::endl; } return ss.str(); } @@ -157,12 +176,40 @@ namespace nodetool { basic_node_data node_data; t_playload_type payload_data; - std::list<peerlist_entry> local_peerlist; + std::list<peerlist_entry> local_peerlist_new; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(node_data) KV_SERIALIZE(payload_data) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(local_peerlist) + if (is_store) + { + // saving: save both, so old and new peers can understand it + KV_SERIALIZE(local_peerlist_new) + std::list<peerlist_entry_base<network_address_old>> local_peerlist; + for (const auto &p: this_ref.local_peerlist_new) + { + if (p.adr.type() == typeid(epee::net_utils::ipv4_network_address)) + { + const epee::net_utils::network_address &na = p.adr; + const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); + local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen})); + } + else + MDEBUG("Not including in legacy peer list: " << p.adr.str()); + } + epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); + } + else + { + // loading: load old list only if there is no new one + if (!epee::serialization::selector<is_store>::serialize(this_ref.local_peerlist_new, stg, hparent_section, "local_peerlist_new")) + { + std::list<peerlist_entry_base<network_address_old>> local_peerlist; + epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); + for (const auto &p: local_peerlist) + ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); + } + } END_KV_SERIALIZE_MAP() }; }; @@ -188,12 +235,40 @@ namespace nodetool { uint64_t local_time; t_playload_type payload_data; - std::list<peerlist_entry> local_peerlist; + std::list<peerlist_entry> local_peerlist_new; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(local_time) KV_SERIALIZE(payload_data) - KV_SERIALIZE_CONTAINER_POD_AS_BLOB(local_peerlist) + if (is_store) + { + // saving: save both, so old and new peers can understand it + KV_SERIALIZE(local_peerlist_new) + std::list<peerlist_entry_base<network_address_old>> local_peerlist; + for (const auto &p: this_ref.local_peerlist_new) + { + if (p.adr.type() == typeid(epee::net_utils::ipv4_network_address)) + { + const epee::net_utils::network_address &na = p.adr; + const epee::net_utils::ipv4_network_address &ipv4 = na.as<const epee::net_utils::ipv4_network_address>(); + local_peerlist.push_back(peerlist_entry_base<network_address_old>({{ipv4.ip(), ipv4.port()}, p.id, p.last_seen})); + } + else + MDEBUG("Not including in legacy peer list: " << p.adr.str()); + } + epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); + } + else + { + // loading: load old list only if there is no new one + if (!epee::serialization::selector<is_store>::serialize(this_ref.local_peerlist_new, stg, hparent_section, "local_peerlist_new")) + { + std::list<peerlist_entry_base<network_address_old>> local_peerlist; + epee::serialization::selector<is_store>::serialize_stl_container_pod_val_as_blob(local_peerlist, stg, hparent_section, "local_peerlist"); + for (const auto &p: local_peerlist) + ((response&)this_ref).local_peerlist_new.push_back(peerlist_entry({new epee::net_utils::ipv4_network_address(p.adr.ip, p.adr.port), p.id, p.last_seen})); + } + } END_KV_SERIALIZE_MAP() }; }; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 43891458c..7dc5a1fd9 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -736,12 +736,20 @@ namespace cryptonote for (auto & entry : white_list) { - res.white_list.emplace_back(entry.id, entry.adr.ip, entry.adr.port, entry.last_seen); + if (entry.adr.type() == typeid(epee::net_utils::ipv4_network_address)) + res.white_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen); + else + res.white_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen); } for (auto & entry : gray_list) { - res.gray_list.emplace_back(entry.id, entry.adr.ip, entry.adr.port, entry.last_seen); + if (entry.adr.type() == typeid(epee::net_utils::ipv4_network_address)) + res.gray_list.emplace_back(entry.id, entry.adr.as<epee::net_utils::ipv4_network_address>().ip(), + entry.adr.as<epee::net_utils::ipv4_network_address>().port(), entry.last_seen); + else + res.gray_list.emplace_back(entry.id, entry.adr.str(), entry.last_seen); } res.status = CORE_RPC_STATUS_OK; @@ -1308,12 +1316,16 @@ namespace cryptonote } auto now = time(nullptr); - 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) + std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); + for (std::map<std::string, time_t>::const_iterator i = blocked_hosts.begin(); i != blocked_hosts.end(); ++i) { if (i->second > now) { COMMAND_RPC_GETBANS::ban b; - b.ip = i->first; + b.host = i->first; + b.ip = 0; + uint32_t ip; + if (epee::string_tools::get_ip_int32_from_string(ip, i->first)) + b.ip = ip; b.seconds = i->second - now; res.bans.push_back(b); } @@ -1334,10 +1346,24 @@ namespace cryptonote for (auto i = req.bans.begin(); i != req.bans.end(); ++i) { + epee::net_utils::network_address na; + if (!i->host.empty()) + { + if (!epee::net_utils::create_network_address(na, i->host)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Unsupported host type"; + return false; + } + } + else + { + na.reset(new epee::net_utils::ipv4_network_address(i->ip, 0)); + } if (i->ban) - m_p2p.block_ip(i->ip, i->seconds); + m_p2p.block_host(na, i->seconds); else - m_p2p.unblock_ip(i->ip); + m_p2p.unblock_host(na); } res.status = CORE_RPC_STATUS_OK; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 8b5f189ca..7a1f5a963 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 11 +#define CORE_RPC_VERSION_MINOR 12 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -861,18 +861,23 @@ namespace cryptonote struct peer { uint64_t id; + std::string host; uint32_t ip; uint16_t port; uint64_t last_seen; peer() = default; + peer(uint64_t id, const std::string &host, uint64_t last_seen) + : id(id), host(host), ip(0), port(0), last_seen(last_seen) + {} peer(uint64_t id, uint32_t ip, uint16_t port, uint64_t last_seen) - : id(id), ip(ip), port(port), last_seen(last_seen) + : id(id), host(std::to_string(ip)), ip(ip), port(port), last_seen(last_seen) {} BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(id) + KV_SERIALIZE(host) KV_SERIALIZE(ip) KV_SERIALIZE(port) KV_SERIALIZE(last_seen) @@ -1047,6 +1052,17 @@ namespace cryptonote }; }; + struct txpool_histo + { + uint32_t txs; + uint64_t bytes; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txs) + KV_SERIALIZE(bytes) + END_KV_SERIALIZE_MAP() + }; + struct txpool_stats { uint64_t bytes_total; @@ -1058,6 +1074,8 @@ namespace cryptonote uint32_t num_failing; uint32_t num_10m; uint32_t num_not_relayed; + uint64_t histo_98pc; + std::vector<txpool_histo> histo; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(bytes_total) @@ -1069,6 +1087,8 @@ namespace cryptonote KV_SERIALIZE(num_failing) KV_SERIALIZE(num_10m) KV_SERIALIZE(num_not_relayed) + KV_SERIALIZE(histo_98pc) + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(histo) END_KV_SERIALIZE_MAP() }; @@ -1271,10 +1291,12 @@ namespace cryptonote { struct ban { + std::string host; uint32_t ip; uint32_t seconds; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(host) KV_SERIALIZE(ip) KV_SERIALIZE(seconds) END_KV_SERIALIZE_MAP() @@ -1302,11 +1324,13 @@ namespace cryptonote { struct ban { + std::string host; uint32_t ip; bool ban; uint32_t seconds; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(host) KV_SERIALIZE(ip) KV_SERIALIZE(ban) KV_SERIALIZE(seconds) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 87dbcf513..4d53f063e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -47,6 +47,7 @@ #include "common/command_line.h" #include "common/util.h" #include "common/dns_utils.h" +#include "common/base58.h" #include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "simplewallet.h" @@ -398,6 +399,19 @@ bool simple_wallet::change_password(const std::vector<std::string> &args) return true; } +bool simple_wallet::payment_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + crypto::hash payment_id; + if (args.size() > 0) + { + fail_msg_writer() << tr("usage: payment_id"); + return true; + } + payment_id = crypto::rand<crypto::hash>(); + success_msg_writer() << tr("Random payment ID: ") << payment_id; + return true; +} + bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -701,6 +715,8 @@ simple_wallet::simple_wallet() 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 <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); + m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it")); + m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [<min_amount> <max_amount>] - Show unspent outputs within an optional amount range)")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); @@ -715,6 +731,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("import_outputs", boost::bind(&simple_wallet::import_outputs, this, _1), tr("Import set of outputs owned by this wallet")); m_cmd_binder.set_handler("show_transfer", boost::bind(&simple_wallet::show_transfer, this, _1), tr("Show information about a transfer to/from this address")); m_cmd_binder.set_handler("password", boost::bind(&simple_wallet::change_password, this, _1), tr("Change wallet password")); + m_cmd_binder.set_handler("payment_id", boost::bind(&simple_wallet::payment_id, this, _1), tr("Generate a new random full size payment id - these will be unencrypted on the blockchain, see integrated_address for encrypted short payment ids")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -929,6 +946,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("can't specify both --restore-deterministic-wallet and --non-deterministic"); return false; } + if (!m_wallet_file.empty()) + { + fail_msg_writer() << tr("--restore-deterministic-wallet uses --generate-new-wallet, not --wallet-file"); + return false; + } if (m_electrum_seed.empty()) { @@ -1090,6 +1112,10 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } else { + if (m_generate_new.empty()) { + fail_msg_writer() << tr("specify a wallet path with --generate-new-wallet (not --wallet-file)"); + return false; + } m_wallet_file = m_generate_new; bool r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); @@ -1167,7 +1193,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) bool r = open_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("failed to open account")); } - assert(m_wallet); + if (!m_wallet) + { + fail_msg_writer() << tr("wallet is null"); + return false; + } // set --trusted-daemon if local try @@ -1547,7 +1577,11 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; - assert(m_wallet); + if (!m_wallet) + { + fail_msg_writer() << tr("wallet is null"); + return true; + } COMMAND_RPC_START_MINING::request req = AUTO_VAL_INIT(req); req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); @@ -1590,7 +1624,11 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; - assert(m_wallet); + if (!m_wallet) + { + fail_msg_writer() << tr("wallet is null"); + return true; + } COMMAND_RPC_STOP_MINING::request req; COMMAND_RPC_STOP_MINING::response res; bool r = net_utils::invoke_http_json("/stop_mining", req, res, m_http_client); @@ -1607,7 +1645,11 @@ bool simple_wallet::save_bc(const std::vector<std::string>& args) if (!try_connect_to_daemon()) return true; - assert(m_wallet); + if (!m_wallet) + { + fail_msg_writer() << tr("wallet is null"); + return true; + } COMMAND_RPC_SAVE_BC::request req; COMMAND_RPC_SAVE_BC::response res; bool r = net_utils::invoke_http_json("/save_bc", req, res, m_http_client); @@ -3209,6 +3251,85 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) } } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address> [<tx_key>]"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + fail_msg_writer() << tr("failed to parse txid"); + return true; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id; + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1])) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + + LOCK_IDLE_SCOPE(); + + crypto::secret_key tx_key, tx_key2; + bool r = m_wallet->get_tx_key(txid, tx_key); + cryptonote::blobdata tx_key_data; + if (args.size() == 3) + { + if(!epee::string_tools::parse_hexstr_to_binbuff(args[2], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) + { + fail_msg_writer() << tr("failed to parse tx_key"); + return true; + } + tx_key2 = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); + } + if (r) + { + if (args.size() == 3 && tx_key != rct::sk2rct(tx_key2)) + { + fail_msg_writer() << tr("Tx secret key was found for the given txid, but you've also provided another tx secret key which doesn't match the found one."); + return true; + } + } + else + { + if (tx_key_data.empty()) + { + fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file. Provide it as the optional third parameter if you have it elsewhere."); + return true; + } + tx_key = tx_key2; + } + + crypto::public_key R; + crypto::secret_key_to_public_key(tx_key, R); + crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::signature sig; + try + { + crypto::generate_tx_proof(txid, R, address.m_view_public_key, rA, tx_key, sig); + } + catch (const std::runtime_error &e) + { + fail_msg_writer() << e.what(); + return true; + } + + std::string sig_str = std::string("ProofV1") + + tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))) + + tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + + success_msg_writer() << tr("Signature: ") << sig_str; + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; @@ -3221,7 +3342,11 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) if (!try_connect_to_daemon()) return true; - assert(m_wallet); + if (!m_wallet) + { + fail_msg_writer() << tr("wallet is null"); + return true; + } cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) { @@ -3230,8 +3355,6 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - LOCK_IDLE_SCOPE(); - if (local_args[1].size() < 64 || local_args[1].size() % 64) { fail_msg_writer() << tr("failed to parse tx key"); @@ -3255,6 +3378,18 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } + crypto::key_derivation derivation; + if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) + { + fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); + return true; + } + + return check_tx_key_helper(txid, address, derivation); +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation) +{ COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); @@ -3288,13 +3423,6 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation)) - { - fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); - return true; - } - uint64_t received = 0; try { for (size_t n = 0; n < tx.vout.size(); ++n) @@ -3317,14 +3445,6 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) { rct::key Ctmp; //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - crypto::key_derivation derivation; - bool r = crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation); - if (!r) - { - LOG_ERROR("Failed to generate key derivation to decode rct output " << n); - amount = 0; - } - else { crypto::secret_key scalar1; crypto::derivation_to_scalar(derivation, n, scalar1); @@ -3381,6 +3501,129 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) +{ + if(args.size() != 3) { + fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature>"); + return true; + } + + if (!try_connect_to_daemon()) + return true; + + // parse txid + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + fail_msg_writer() << tr("failed to parse txid"); + return true; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + // parse address + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 payment_id; + if(!cryptonote::get_account_address_from_str_or_url(address, has_payment_id, payment_id, m_wallet->testnet(), args[1])) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + + // parse pubkey r*A & signature + std::string sig_str = args[2]; + const size_t header_len = strlen("ProofV1"); + if (sig_str.size() < header_len || sig_str.substr(0, header_len) != "ProofV1") + { + fail_msg_writer() << tr("Signature header check error"); + return true; + } + crypto::public_key rA; + crypto::signature sig; + const size_t rA_len = tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))).size(); + const size_t sig_len = tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))).size(); + std::string rA_decoded; + std::string sig_decoded; + if (!tools::base58::decode(sig_str.substr(header_len, rA_len), rA_decoded)) + { + fail_msg_writer() << tr("Signature decoding error"); + return true; + } + if (!tools::base58::decode(sig_str.substr(header_len + rA_len, sig_len), sig_decoded)) + { + fail_msg_writer() << tr("Signature decoding error"); + return true; + } + if (sizeof(crypto::public_key) != rA_decoded.size() || sizeof(crypto::signature) != sig_decoded.size()) + { + fail_msg_writer() << tr("Signature decoding error"); + return true; + } + memcpy(&rA, rA_decoded.data(), sizeof(crypto::public_key)); + memcpy(&sig, sig_decoded.data(), sizeof(crypto::signature)); + + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || + (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + { + fail_msg_writer() << tr("failed to get transaction from daemon"); + return true; + } + cryptonote::blobdata tx_data; + bool ok; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) + { + fail_msg_writer() << tr("failed to parse transaction from daemon"); + return true; + } + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + { + fail_msg_writer() << tr("failed to validate transaction from daemon"); + return true; + } + if (tx_hash != txid) + { + fail_msg_writer() << tr("failed to get the right transaction from daemon"); + return true; + } + crypto::public_key R = get_tx_pub_key_from_extra(tx); + if (R == null_pkey) + { + fail_msg_writer() << tr("Tx pubkey was not found"); + return true; + } + + // check signature + if (crypto::check_tx_proof(txid, R, address.m_view_public_key, rA, sig)) + { + success_msg_writer() << tr("Good signature"); + } + else + { + fail_msg_writer() << tr("Bad signature"); + return true; + } + + // obtain key derivation by multiplying scalar 1 to the pubkey r*A included in the signature + crypto::key_derivation derivation; + if (!crypto::generate_key_derivation(rA, rct::rct2sk(rct::I), derivation)) + { + fail_msg_writer() << tr("failed to generate key derivation"); + return true; + } + + return check_tx_key_helper(txid, address, derivation); +} +//---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) { char buffer[64]; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 1b58ff32a..e04352295 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -154,6 +154,9 @@ namespace cryptonote bool set_log(const std::vector<std::string> &args); bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); + bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, const crypto::key_derivation &derivation); + bool get_tx_proof(const std::vector<std::string> &args); + bool check_tx_proof(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); @@ -170,6 +173,7 @@ namespace cryptonote bool import_outputs(const std::vector<std::string> &args); bool show_transfer(const std::vector<std::string> &args); bool change_password(const std::vector<std::string>& args); + bool payment_id(const std::vector<std::string> &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 1b4300edf..6a0e1727c 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -144,7 +144,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid) + virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) { // TODO; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 96ba04c47..ee2b6055c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -511,6 +511,7 @@ bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils:: { if(m_http_client.is_connected()) m_http_client.disconnect(); + m_is_initialized = true; m_upper_transaction_size_limit = upper_transaction_size_limit; m_daemon_address = std::move(daemon_address); m_daemon_login = std::move(daemon_login); @@ -1845,6 +1846,7 @@ void wallet2::detach_blockchain(uint64_t height) //---------------------------------------------------------------------------------------------------- bool wallet2::deinit() { + m_is_initialized=false; return true; } //---------------------------------------------------------------------------------------------------- @@ -2361,6 +2363,8 @@ bool wallet2::prepare_file_names(const std::string& file_path) //---------------------------------------------------------------------------------------------------- bool wallet2::check_connection(uint32_t *version, uint32_t timeout) { + THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); + boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); if(!m_http_client.is_connected()) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index bde233b33..e7692badb 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -105,7 +105,7 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} public: static const char* tr(const char* str); @@ -129,7 +129,7 @@ namespace tools //! Just parses variables. static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm); - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} struct transfer_details { @@ -695,6 +695,7 @@ namespace tools uint32_t m_min_output_count; uint64_t m_min_output_value; bool m_merge_destinations; + bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; }; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 3e3578149..16807e045 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -49,6 +49,7 @@ namespace tools // wallet_runtime_error * // wallet_internal_error // unexpected_txin_type + // wallet_not_initialized // std::logic_error // wallet_logic_error * // file_exists @@ -177,6 +178,15 @@ namespace tools cryptonote::transaction m_tx; }; //---------------------------------------------------------------------------------------------------- + struct wallet_not_initialized : public wallet_internal_error + { + explicit wallet_not_initialized(std::string&& loc) + : wallet_internal_error(std::move(loc), "wallet is not initialized") + { + } + }; + + //---------------------------------------------------------------------------------------------------- const char* const file_error_messages[] = { "file already exists", "file not found", diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index add48eebe..7cad04047 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -472,6 +472,7 @@ inline bool do_replay_events(std::vector<test_event_entry>& events) MERROR("Failed to init core"); return false; } + c.get_blockchain_storage().get_db().set_batch_transactions(true); // start with a clean pool std::vector<crypto::hash> pool_txs; diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index 4bfe90733..febf8b2f7 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -118,6 +118,7 @@ public: virtual bool txpool_has_tx(const crypto::hash &txid) const { return false; } virtual void remove_txpool_tx(const crypto::hash& txid) {} virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const { return txpool_tx_meta_t(); } + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const { return false; } virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const { return ""; } virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const { return false; } |