diff options
94 files changed, 2671 insertions, 823 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 543e58eca..f40021764 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1028,3 +1028,5 @@ option(INSTALL_VENDORED_LIBUNBOUND "Install libunbound binary built from source CHECK_C_COMPILER_FLAG(-std=c11 HAVE_C11) + +find_package(PythonInterp) @@ -127,8 +127,9 @@ Dates are provided in the format YYYY-MM-DD. | 1546000 | 2018-04-06 | v7 | v0.12.0.0 | v0.12.3.0 | Cryptonight variant 1, ringsize >= 7, sorted inputs | 1685555 | 2018-10-18 | v8 | v0.13.0.0 | v0.13.0.4 | max transaction size at half the penalty free block size, bulletproofs enabled, cryptonight variant 2, fixed ringsize [11](https://youtu.be/KOO5S4vxi0o) | 1686275 | 2018-10-19 | v9 | v0.13.0.0 | v0.13.0.4 | bulletproofs required -| 1788000 | 2019-03-09 | v10 | v0.14.0.0 | v0.14.0.2 | Cryptonight-R PoW, new block weight algorithm, slightly more efficient RingCT format +| 1788000 | 2019-03-09 | v10 | v0.14.0.0 | v0.14.0.2 | New PoW based on Cryptonight-R, new block weight algorithm, slightly more efficient RingCT format | 1788720 | 2019-03-10 | v11 | v0.14.0.0 | v0.14.0.2 | forbid old RingCT transaction format +| XXXXXXX | 2019-10-XX | XX | XXXXXXXXX | XXXXXXXXX | X X's indicate that these details have not been determined as of commit date. @@ -175,9 +176,13 @@ library archives (`.a`). build the library binary manually. This can be done with the following command ```sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv libg* /usr/lib/ ``` [2] libnorm-dev is needed if your zmq library was built with libnorm, and not needed otherwise -Debian / Ubuntu one liner for all dependencies +Install all dependencies at once on Debian/Ubuntu: + ``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpgm-dev``` +Install all dependencies at once on macOS: +``` brew update && brew install cmake pkg-config openssl boost hidapi zmq libpgm unbound libsodium miniupnpc readline ldns expat doxygen graphviz protobuf ``` + FreeBSD one liner for required to build dependencies ```pkg install git gmake cmake pkgconf boost-libs cppzmq libsodium``` @@ -363,7 +368,7 @@ application. cd monero -* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.14.1.0'. If you dont care about the version and just want binaries from master, skip this step: +* If you would like a specific [version/tag](https://github.com/monero-project/monero/tags), do a git checkout for that version. eg. 'v0.14.1.0'. If you don't care about the version and just want binaries from master, skip this step: git checkout v0.14.1.0 @@ -671,7 +676,7 @@ allow inbound connections. Full example: ## Debugging -This section contains general instructions for debugging failed installs or problems encountered with Monero. First ensure you are running the latest version built from the Github repo. +This section contains general instructions for debugging failed installs or problems encountered with Monero. First, ensure you are running the latest version built from the Github repo. ### Obtaining stack traces and core dumps on Unix systems diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index ec08c0f4b..d0eabbba5 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -90,10 +90,10 @@ namespace net_utils public: typedef typename t_protocol_handler::connection_context t_connection_context; - struct shared_state : socket_stats + struct shared_state : connection_basic_shared_state { shared_state() - : socket_stats(), pfilter(nullptr), config() + : connection_basic_shared_state(), pfilter(nullptr), config() {} i_connection_filter* pfilter; @@ -104,14 +104,12 @@ namespace net_utils explicit connection( boost::asio::io_service& io_service, boost::shared_ptr<shared_state> state, t_connection_type connection_type, - epee::net_utils::ssl_support_t ssl_support, - ssl_context_t &ssl_context); + epee::net_utils::ssl_support_t ssl_support); explicit connection( boost::asio::ip::tcp::socket&& sock, boost::shared_ptr<shared_state> state, t_connection_type connection_type, - epee::net_utils::ssl_support_t ssl_support, - ssl_context_t &ssl_context); + epee::net_utils::ssl_support_t ssl_support); @@ -228,8 +226,8 @@ namespace net_utils std::map<std::string, t_connection_type> server_type_map; void create_server_type_map(); - bool init_server(uint32_t port, const std::string address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, bool allow_any_cert = false); - bool init_server(const std::string port, const std::string& address = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = std::make_pair(std::string(), std::string()), const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, bool allow_any_cert = false); + bool init_server(uint32_t port, const std::string address = "0.0.0.0", ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); + bool init_server(const std::string port, const std::string& address = "0.0.0.0", ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect); /// Run the server's io_service loop. bool run_server(size_t threads_count, bool wait = true, const boost::thread::attributes& attrs = boost::thread::attributes()); @@ -380,10 +378,6 @@ namespace net_utils boost::mutex connections_mutex; std::set<connection_ptr> connections_; - - ssl_context_t m_ssl_context; - std::list<std::string> m_allowed_certificates; - }; // class <>boosted_tcp_server diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 67c63cca5..14fbec5d9 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -80,10 +80,9 @@ PRAGMA_WARNING_DISABLE_VS(4355) connection<t_protocol_handler>::connection( boost::asio::io_service& io_service, boost::shared_ptr<shared_state> state, t_connection_type connection_type, - epee::net_utils::ssl_support_t ssl_support, - ssl_context_t &ssl_context + ssl_support_t ssl_support ) - : connection(boost::asio::ip::tcp::socket{io_service}, std::move(state), connection_type, ssl_support, ssl_context) + : connection(boost::asio::ip::tcp::socket{io_service}, std::move(state), connection_type, ssl_support) { } @@ -91,12 +90,12 @@ PRAGMA_WARNING_DISABLE_VS(4355) connection<t_protocol_handler>::connection( boost::asio::ip::tcp::socket&& sock, boost::shared_ptr<shared_state> state, t_connection_type connection_type, - epee::net_utils::ssl_support_t ssl_support, - ssl_context_t &ssl_context + ssl_support_t ssl_support ) : - connection_basic(std::move(sock), state, ssl_support, ssl_context), + connection_basic(std::move(sock), state, ssl_support), m_protocol_handler(this, check_and_get(state).config, context), + buffer_ssl_init_fill(0), m_connection_type( connection_type ), m_throttle_speed_in("speed_in", "throttle_speed_in"), m_throttle_speed_out("speed_out", "throttle_speed_out"), @@ -176,9 +175,9 @@ PRAGMA_WARNING_DISABLE_VS(4355) _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 " << get_stats().sock_count); + ", total sockets objects " << get_state().sock_count); - if(static_cast<shared_state&>(get_stats()).pfilter && !static_cast<shared_state&>(get_stats()).pfilter->is_remote_host_allowed(context.m_remote_address)) + if(static_cast<shared_state&>(get_state()).pfilter && !static_cast<shared_state&>(get_state()).pfilter->is_remote_host_allowed(context.m_remote_address)) { _dbg2("[sock " << socket().native_handle() << "] host denied " << context.m_remote_address.host_str() << ", shutdowning connection"); close(); @@ -901,8 +900,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_threads_count(0), m_thread_index(0), m_connection_type( connection_type ), - new_connection_(), - m_ssl_context({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}}) + new_connection_() { create_server_type_map(); m_thread_name_prefix = "NET"; @@ -918,8 +916,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) m_threads_count(0), m_thread_index(0), m_connection_type(connection_type), - new_connection_(), - m_ssl_context({boost::asio::ssl::context(boost::asio::ssl::context::sslv23), {}}) + new_connection_() { create_server_type_map(); m_thread_name_prefix = "NET"; @@ -941,14 +938,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) } //--------------------------------------------------------------------------------- template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert) + bool boosted_tcp_server<t_protocol_handler>::init_server(uint32_t port, const std::string address, ssl_options_t ssl_options) { TRY_ENTRY(); m_stop_signal_sent = false; m_port = port; m_address = address; - if (ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled) - m_ssl_context = create_ssl_context(private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); + if (ssl_options) + m_state->configure_ssl(std::move(ssl_options)); // Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR). boost::asio::ip::tcp::resolver resolver(io_service_); boost::asio::ip::tcp::resolver::query query(address, boost::lexical_cast<std::string>(port), boost::asio::ip::tcp::resolver::query::canonical_name); @@ -960,7 +957,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) boost::asio::ip::tcp::endpoint binded_endpoint = acceptor_.local_endpoint(); m_port = binded_endpoint.port(); MDEBUG("start accept"); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support, m_ssl_context)); + new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, m_state->ssl_options().support)); acceptor_.async_accept(new_connection_->socket(), boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, boost::asio::placeholders::error)); @@ -982,7 +979,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) PUSH_WARNINGS DISABLE_GCC_WARNING(maybe-uninitialized) template<class t_protocol_handler> - bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert) + bool boosted_tcp_server<t_protocol_handler>::init_server(const std::string port, const std::string& address, ssl_options_t ssl_options) { uint32_t p = 0; @@ -990,7 +987,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized) MERROR("Failed to convert port no = " << port); return false; } - return this->init_server(p, address, ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); + return this->init_server(p, address, std::move(ssl_options)); } POP_WARNINGS //--------------------------------------------------------------------------------- @@ -1165,7 +1162,7 @@ POP_WARNINGS new_connection_->setRpcStation(); // hopefully this is not needed actually } connection_ptr conn(std::move(new_connection_)); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, conn->get_ssl_support(), m_ssl_context)); + new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, conn->get_ssl_support())); acceptor_.async_accept(new_connection_->socket(), boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, boost::asio::placeholders::error)); @@ -1200,7 +1197,7 @@ POP_WARNINGS assert(m_state != nullptr); // always set in constructor _erro("Some problems at accept: " << e.message() << ", connections_count = " << m_state->sock_count); misc_utils::sleep_no_w(100); - new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, new_connection_->get_ssl_support(), m_ssl_context)); + new_connection_.reset(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, new_connection_->get_ssl_support())); acceptor_.async_accept(new_connection_->socket(), boost::bind(&boosted_tcp_server<t_protocol_handler>::handle_accept, this, boost::asio::placeholders::error)); @@ -1211,7 +1208,7 @@ POP_WARNINGS { if(std::addressof(get_io_service()) == std::addressof(GET_IO_SERVICE(sock))) { - connection_ptr conn(new connection<t_protocol_handler>(std::move(sock), m_state, m_connection_type, ssl_support, m_ssl_context)); + connection_ptr conn(new connection<t_protocol_handler>(std::move(sock), m_state, m_connection_type, ssl_support)); if(conn->start(false, 1 < m_threads_count, std::move(real_remote))) { conn->get_context(out); @@ -1298,7 +1295,7 @@ POP_WARNINGS _dbg3("Connected success to " << adr << ':' << port); - const epee::net_utils::ssl_support_t ssl_support = new_connection_l->get_ssl_support(); + const ssl_support_t ssl_support = new_connection_l->get_ssl_support(); if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { // Handshake @@ -1329,7 +1326,7 @@ POP_WARNINGS { TRY_ENTRY(); - connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support, m_ssl_context) ); + connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) ); connections_mutex.lock(); connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); @@ -1393,7 +1390,7 @@ POP_WARNINGS bool boosted_tcp_server<t_protocol_handler>::connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeout, const t_callback &cb, const std::string& bind_ip, epee::net_utils::ssl_support_t ssl_support) { TRY_ENTRY(); - connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support, m_ssl_context) ); + connection_ptr new_connection_l(new connection<t_protocol_handler>(io_service_, m_state, m_connection_type, ssl_support) ); connections_mutex.lock(); connections_.insert(new_connection_l); MDEBUG("connections_ size now " << connections_.size()); diff --git a/contrib/epee/include/net/connection_basic.hpp b/contrib/epee/include/net/connection_basic.hpp index feedc6895..d3f5f4f24 100644 --- a/contrib/epee/include/net/connection_basic.hpp +++ b/contrib/epee/include/net/connection_basic.hpp @@ -57,15 +57,30 @@ namespace epee { namespace net_utils { - struct socket_stats - { - socket_stats() - : sock_count(0), sock_number(0) - {} - - std::atomic<long> sock_count; - std::atomic<long> sock_number; - }; + + class connection_basic_shared_state + { + ssl_options_t ssl_options_; + public: + boost::asio::ssl::context ssl_context; + std::atomic<long> sock_count; + std::atomic<long> sock_number; + + connection_basic_shared_state() + : ssl_options_(ssl_support_t::e_ssl_support_disabled), + ssl_context(boost::asio::ssl::context::tlsv12), + sock_count(0), + sock_number(0) + {} + + void configure_ssl(ssl_options_t src) + { + ssl_options_ = std::move(src); + ssl_context = ssl_options_.create_context(); + } + + const ssl_options_t& ssl_options() const noexcept { return ssl_options_; } + }; /************************************************************************/ /* */ @@ -83,9 +98,10 @@ class connection_basic_pimpl; // PIMPL for this class std::string to_string(t_connection_type type); class connection_basic { // not-templated base class for rapid developmet of some code parts - // beware of removing const, net_utils::connection is sketchily doing a cast to prevent storing ptr twice - const boost::shared_ptr<socket_stats> m_stats; + // beware of removing const, net_utils::connection is sketchily doing a cast to prevent storing ptr twice + const boost::shared_ptr<connection_basic_shared_state> m_state; public: + std::unique_ptr< connection_basic_pimpl > mI; // my Implementation // moved here from orginal connecton<> - common member variables that do not depend on template in connection<> @@ -97,20 +113,19 @@ class connection_basic { // not-templated base class for rapid developmet of som /// Strand to ensure the connection's handlers are not called concurrently. boost::asio::io_service::strand strand_; /// Socket for the connection. - ssl_context_t &m_ssl_context; - ssl_support_t m_ssl_support; boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_; + ssl_support_t m_ssl_support; public: // first counter is the ++/-- count of current sockets, the other socket_number is only-increasing ++ number generator - connection_basic(boost::asio::ip::tcp::socket&& socket, boost::shared_ptr<socket_stats> stats, ssl_support_t ssl_support, ssl_context_t &ssl_context); - connection_basic(boost::asio::io_service &io_service, boost::shared_ptr<socket_stats> stats, ssl_support_t ssl_support, ssl_context_t &ssl_context); + connection_basic(boost::asio::ip::tcp::socket&& socket, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support); + connection_basic(boost::asio::io_service &io_service, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support); virtual ~connection_basic() noexcept(false); - //! \return `socket_stats` object passed in construction (ptr never changes). - socket_stats& get_stats() noexcept { return *m_stats; /* verified in constructor */ } - connection_basic(boost::asio::io_service& io_service, std::atomic<long> &ref_sock_count, std::atomic<long> &sock_number, ssl_support_t ssl, ssl_context_t &ssl_context); + //! \return `shared_state` object passed in construction (ptr never changes). + connection_basic_shared_state& get_state() noexcept { return *m_state; /* verified in constructor */ } + connection_basic(boost::asio::io_service& io_service, std::atomic<long> &ref_sock_count, std::atomic<long> &sock_number, ssl_support_t ssl); boost::asio::ip::tcp::socket& socket() { return socket_.next_layer(); } ssl_support_t get_ssl_support() const { return m_ssl_support; } @@ -118,7 +133,8 @@ class connection_basic { // not-templated base class for rapid developmet of som bool handshake(boost::asio::ssl::stream_base::handshake_type type) { - return ssl_handshake(socket_, type, m_ssl_context); + //m_state != nullptr verified in constructor + return m_state->ssl_options().handshake(socket_, type); } template<typename MutableBufferSequence, typename ReadHandler> diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index f0425278d..bb10c8efc 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -275,11 +275,6 @@ namespace net_utils chunked_state m_chunked_state; std::string m_chunked_cache; critical_section m_lock; - epee::net_utils::ssl_support_t m_ssl_support; - std::pair<std::string, std::string> m_ssl_private_key_and_certificate_path; - std::list<std::string> m_ssl_allowed_certificates; - std::vector<std::vector<uint8_t>> m_ssl_allowed_fingerprints; - bool m_ssl_allow_any_cert; public: explicit http_simple_client_template() @@ -297,34 +292,28 @@ namespace net_utils , m_chunked_state() , m_chunked_cache() , m_lock() - , m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect) {} const std::string &get_host() const { return m_host_buff; }; const std::string &get_port() const { return m_port; }; - bool set_server(const std::string& address, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_ssl_fingerprints = {}, bool allow_any_cert = false) + bool set_server(const std::string& address, boost::optional<login> user, ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect) { http::url_content parsed{}; const bool r = parse_url(address, parsed); CHECK_AND_ASSERT_MES(r, false, "failed to parse url: " << address); - set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), ssl_support, private_key_and_certificate_path, allowed_ssl_certificates, allowed_ssl_fingerprints, allow_any_cert); + set_server(std::move(parsed.host), std::to_string(parsed.port), std::move(user), std::move(ssl_options)); return true; } - void set_server(std::string host, std::string port, boost::optional<login> user, epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, const std::list<std::string> &allowed_ssl_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_ssl_fingerprints = {}, bool allow_any_cert = false) + void set_server(std::string host, std::string port, boost::optional<login> user, ssl_options_t ssl_options = ssl_support_t::e_ssl_support_autodetect) { CRITICAL_REGION_LOCAL(m_lock); disconnect(); m_host_buff = std::move(host); m_port = std::move(port); m_auth = user ? http_client_auth{std::move(*user)} : http_client_auth{}; - m_ssl_support = ssl_support; - m_ssl_private_key_and_certificate_path = private_key_and_certificate_path; - m_ssl_allowed_certificates = allowed_ssl_certificates; - m_ssl_allowed_fingerprints = allowed_ssl_fingerprints; - m_ssl_allow_any_cert = allow_any_cert; - m_net_client.set_ssl(m_ssl_support, m_ssl_private_key_and_certificate_path, m_ssl_allowed_certificates, m_ssl_allowed_fingerprints, m_ssl_allow_any_cert); + m_net_client.set_ssl(std::move(ssl_options)); } template<typename F> @@ -455,6 +444,16 @@ namespace net_utils return handle_reciev(timeout); } //--------------------------------------------------------------------------- + uint64_t get_bytes_sent() const + { + return m_net_client.get_bytes_sent(); + } + //--------------------------------------------------------------------------- + uint64_t get_bytes_received() const + { + return m_net_client.get_bytes_received(); + } + //--------------------------------------------------------------------------- private: //--------------------------------------------------------------------------- inline bool handle_reciev(std::chrono::milliseconds timeout) @@ -841,21 +840,21 @@ namespace net_utils const char *ptr = m_header_cache.c_str(); CHECK_AND_ASSERT_MES(!memcmp(ptr, "HTTP/", 5), false, "Invalid first response line: " + m_header_cache); ptr += 5; - CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + CHECK_AND_ASSERT_MES(epee::misc_utils::parse::isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); unsigned long ul; char *end; ul = strtoul(ptr, &end, 10); CHECK_AND_ASSERT_MES(ul <= INT_MAX && *end =='.', false, "Invalid first response line: " + m_header_cache); m_response_info.m_http_ver_hi = ul; ptr = end + 1; - CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); + CHECK_AND_ASSERT_MES(epee::misc_utils::parse::isdigit(*ptr), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); ul = strtoul(ptr, &end, 10); CHECK_AND_ASSERT_MES(ul <= INT_MAX && isblank(*end), false, "Invalid first response line: " + m_header_cache + ", ptr: " << ptr); m_response_info.m_http_ver_lo = ul; ptr = end + 1; while (isblank(*ptr)) ++ptr; - CHECK_AND_ASSERT_MES(isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); + CHECK_AND_ASSERT_MES(epee::misc_utils::parse::isdigit(*ptr), false, "Invalid first response line: " + m_header_cache); ul = strtoul(ptr, &end, 10); CHECK_AND_ASSERT_MES(ul >= 100 && ul <= 999 && isspace(*end), false, "Invalid first response line: " + m_header_cache); m_response_info.m_response_code = ul; diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h index 0922f21f2..fc2dcbf67 100644 --- a/contrib/epee/include/net/http_server_impl_base.h +++ b/contrib/epee/include/net/http_server_impl_base.h @@ -59,11 +59,7 @@ namespace epee bool init(std::function<void(size_t, uint8_t*)> rng, const std::string& bind_port = "0", const std::string& bind_ip = "0.0.0.0", std::vector<std::string> access_control_origins = std::vector<std::string>(), boost::optional<net_utils::http::login> user = boost::none, - epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, - const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, - std::list<std::string> allowed_certificates = {}, - std::vector<std::vector<uint8_t>> allowed_fingerprints = {}, - bool allow_any_cert = false) + net_utils::ssl_options_t ssl_options = net_utils::ssl_support_t::e_ssl_support_autodetect) { //set self as callback handler @@ -80,7 +76,7 @@ namespace epee m_net_server.get_config_object().m_user = std::move(user); MGINFO("Binding on " << bind_ip << ":" << bind_port); - bool res = m_net_server.init_server(bind_port, bind_ip, ssl_support, private_key_and_certificate_path, std::move(allowed_certificates), std::move(allowed_fingerprints), allow_any_cert); + bool res = m_net_server.init_server(bind_port, bind_ip, std::move(ssl_options)); if(!res) { LOG_ERROR("Failed to bind server"); diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h index aa3df7160..e8fb40a0a 100644 --- a/contrib/epee/include/net/net_helper.h +++ b/contrib/epee/include/net/net_helper.h @@ -101,14 +101,16 @@ namespace net_utils inline blocked_mode_client() : m_io_service(), - m_ctx({boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}}), + m_ctx(boost::asio::ssl::context::tlsv12), m_connector(direct_connect{}), - m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx.context)), - m_ssl_support(epee::net_utils::ssl_support_t::e_ssl_support_autodetect), + m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx)), + m_ssl_options(epee::net_utils::ssl_support_t::e_ssl_support_autodetect), m_initialized(true), m_connected(false), m_deadline(m_io_service), - m_shutdowned(0) + m_shutdowned(0), + m_bytes_sent(0), + m_bytes_received(0) { } @@ -136,13 +138,13 @@ namespace net_utils catch(...) { /* ignore */ } } - inline void set_ssl(epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, std::list<std::string> allowed_certificates = {}, std::vector<std::vector<uint8_t>> allowed_fingerprints = {}, bool allow_any_cert = false) + inline void set_ssl(ssl_options_t ssl_options) { - if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_disabled) - m_ctx = {boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), {}, {}}; + if (ssl_options) + m_ctx = ssl_options.create_context(); else - m_ctx = create_ssl_context(private_key_and_certificate_path, std::move(allowed_certificates), std::move(allowed_fingerprints), allow_any_cert); - m_ssl_support = ssl_support; + m_ctx = boost::asio::ssl::context(boost::asio::ssl::context::tlsv12); + m_ssl_options = std::move(ssl_options); } inline @@ -174,7 +176,7 @@ namespace net_utils // SSL Options if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { - if (!ssl_handshake(*m_ssl_socket, boost::asio::ssl::stream_base::client, m_ctx)) + if (!m_ssl_options.handshake(*m_ssl_socket, boost::asio::ssl::stream_base::client, addr)) { if (ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { @@ -191,7 +193,7 @@ namespace net_utils return CONNECT_FAILURE; } } - m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + m_ssl_options.support = ssl_support_t::e_ssl_support_enabled; } return CONNECT_SUCCESS; }else @@ -212,23 +214,21 @@ namespace net_utils // Set SSL options // disable sslv2 - m_ctx.context.set_options(boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2); - m_ctx.context.set_default_verify_paths(); - m_ssl_socket.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx.context)); + m_ssl_socket.reset(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx)); // Get a list of endpoints corresponding to the server name. - try_connect_result_t try_connect_result = try_connect(addr, port, timeout, m_ssl_support); + try_connect_result_t try_connect_result = try_connect(addr, port, timeout, m_ssl_options.support); if (try_connect_result == CONNECT_FAILURE) return false; - if (m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) + if (m_ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_autodetect) { - m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + m_ssl_options.support = epee::net_utils::ssl_support_t::e_ssl_support_enabled; if (try_connect_result == CONNECT_NO_SSL) { MERROR("SSL handshake failed on an autodetect connection, reconnecting without SSL"); - m_ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; - if (try_connect(addr, port, timeout, m_ssl_support) != CONNECT_SUCCESS) + m_ssl_options.support = epee::net_utils::ssl_support_t::e_ssl_support_disabled; + if (try_connect(addr, port, timeout, m_ssl_options.support) != CONNECT_SUCCESS) return false; } } @@ -260,7 +260,7 @@ namespace net_utils if(m_connected) { m_connected = false; - if(m_ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled) + if(m_ssl_options) shutdown_ssl(); m_ssl_socket->next_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both); } @@ -315,6 +315,7 @@ namespace net_utils }else { m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); + m_bytes_sent += buff.size(); } } @@ -371,6 +372,7 @@ namespace net_utils }else { m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); + m_bytes_sent += sz; } } @@ -394,7 +396,7 @@ namespace net_utils if (!m_connected || !m_ssl_socket->next_layer().is_open()) return false; if (ssl) - *ssl = m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled; + *ssl = m_ssl_options.support == ssl_support_t::e_ssl_support_enabled; return true; } @@ -460,6 +462,7 @@ namespace net_utils /*if(!bytes_transfered) return false;*/ + m_bytes_received += bytes_transfered; buff.assign(local_buff, bytes_transfered); return true; } @@ -528,6 +531,7 @@ namespace net_utils m_deadline.expires_at(std::chrono::steady_clock::time_point::max()); } + m_bytes_received += bytes_transfered; if(bytes_transfered != buff.size()) { LOG_ERROR("Transferred mismatch with transfer_at_least value: m_bytes_transferred=" << bytes_transfered << " at_least value=" << buff.size()); @@ -558,7 +562,7 @@ namespace net_utils { m_deadline.cancel(); boost::system::error_code ec; - if(m_ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_disabled) + if(m_ssl_options.support != ssl_support_t::e_ssl_support_disabled) shutdown_ssl(); m_ssl_socket->next_layer().cancel(ec); if(ec) @@ -583,7 +587,17 @@ namespace net_utils { return m_ssl_socket->next_layer(); } - + + uint64_t get_bytes_sent() const + { + return m_bytes_sent; + } + + uint64_t get_bytes_received() const + { + return m_bytes_received; + } + private: void check_deadline() @@ -635,7 +649,7 @@ namespace net_utils bool write(const void* data, size_t sz, boost::system::error_code& ec) { bool success; - if(m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled) + if(m_ssl_options.support == ssl_support_t::e_ssl_support_enabled) success = boost::asio::write(*m_ssl_socket, boost::asio::buffer(data, sz), ec); else success = boost::asio::write(m_ssl_socket->next_layer(), boost::asio::buffer(data, sz), ec); @@ -644,7 +658,7 @@ namespace net_utils void async_write(const void* data, size_t sz, boost::system::error_code& ec) { - if(m_ssl_support == epee::net_utils::ssl_support_t::e_ssl_support_enabled) + if(m_ssl_options.support == ssl_support_t::e_ssl_support_enabled) boost::asio::async_write(*m_ssl_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); else boost::asio::async_write(m_ssl_socket->next_layer(), boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); @@ -652,7 +666,7 @@ namespace net_utils void async_read(char* buff, size_t sz, boost::asio::detail::transfer_at_least_t transfer_at_least, handler_obj& hndlr) { - if(m_ssl_support != epee::net_utils::ssl_support_t::e_ssl_support_enabled) + if(m_ssl_options.support != ssl_support_t::e_ssl_support_enabled) boost::asio::async_read(m_ssl_socket->next_layer(), boost::asio::buffer(buff, sz), transfer_at_least, hndlr); else boost::asio::async_read(*m_ssl_socket, boost::asio::buffer(buff, sz), transfer_at_least, hndlr); @@ -661,14 +675,16 @@ namespace net_utils protected: boost::asio::io_service m_io_service; - epee::net_utils::ssl_context_t m_ctx; + boost::asio::ssl::context m_ctx; std::shared_ptr<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> m_ssl_socket; std::function<connect_func> m_connector; - epee::net_utils::ssl_support_t m_ssl_support; + ssl_options_t m_ssl_options; bool m_initialized; bool m_connected; boost::asio::steady_timer m_deadline; volatile uint32_t m_shutdowned; + std::atomic<uint64_t> m_bytes_sent; + std::atomic<uint64_t> m_bytes_received; }; diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h index f7b102164..957903ff8 100644 --- a/contrib/epee/include/net/net_ssl.h +++ b/contrib/epee/include/net/net_ssl.h @@ -31,10 +31,11 @@ #include <stdint.h> #include <string> -#include <list> +#include <vector> #include <boost/utility/string_ref.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ssl.hpp> +#include <boost/system/error_code.hpp> namespace epee { @@ -45,23 +46,93 @@ namespace net_utils e_ssl_support_enabled, e_ssl_support_autodetect, }; - - struct ssl_context_t - { - boost::asio::ssl::context context; - std::list<std::string> allowed_certificates; - std::vector<std::vector<uint8_t>> allowed_fingerprints; - bool allow_any_cert; - }; + + enum class ssl_verification_t : uint8_t + { + none = 0, //!< Do not verify peer. + system_ca, //!< Verify peer via system ca only (do not inspect user certificates) + user_certificates,//!< Verify peer via specific (non-chain) certificate(s) only. + user_ca //!< Verify peer via specific (possibly chain) certificate(s) only. + }; + + struct ssl_authentication_t + { + std::string private_key_path; //!< Private key used for authentication + std::string certificate_path; //!< Certificate used for authentication to peer. + + //! Load `private_key_path` and `certificate_path` into `ssl_context`. + void use_ssl_certificate(boost::asio::ssl::context &ssl_context) const; + }; + + /*! + \note `verification != disabled && support == disabled` is currently + "allowed" via public interface but obviously invalid configuation. + */ + class ssl_options_t + { + // force sorted behavior in private + std::vector<std::vector<std::uint8_t>> fingerprints_; + + public: + std::string ca_path; + ssl_authentication_t auth; + ssl_support_t support; + ssl_verification_t verification; + + //! Verification is set to system ca unless SSL is disabled. + ssl_options_t(ssl_support_t support) + : fingerprints_(), + ca_path(), + auth(), + support(support), + verification(support == ssl_support_t::e_ssl_support_disabled ? ssl_verification_t::none : ssl_verification_t::system_ca) + {} + + //! Provide user fingerprints and/or ca path. Enables SSL and user_certificate verification + ssl_options_t(std::vector<std::vector<std::uint8_t>> fingerprints, std::string ca_path); + + ssl_options_t(const ssl_options_t&) = default; + ssl_options_t(ssl_options_t&&) = default; + + ssl_options_t& operator=(const ssl_options_t&) = default; + ssl_options_t& operator=(ssl_options_t&&) = default; + + //! \return False iff ssl is disabled, otherwise true. + explicit operator bool() const noexcept { return support != ssl_support_t::e_ssl_support_disabled; } + + //! \retrurn True if `host` can be verified using `this` configuration WITHOUT system "root" CAs. + bool has_strong_verification(boost::string_ref host) const noexcept; + + //! Search against internal fingerprints. Always false if `behavior() != user_certificate_check`. + bool has_fingerprint(boost::asio::ssl::verify_context &ctx) const; + + boost::asio::ssl::context create_context() const; + + /*! \note If `this->support == autodetect && this->verification != none`, + then the handshake will not fail when peer verification fails. The + assumption is that a re-connect will be attempted, so a warning is + logged instead of failure. + + \note It is strongly encouraged that clients using `system_ca` + verification provide a non-empty `host` for rfc2818 verification. + + \param socket Used in SSL handshake and verification + \param type Client or server + \param host This parameter is only used when + `type == client && !host.empty()`. The value is sent to the server for + situations where multiple hostnames are being handled by a server. If + `verification == system_ca` the client also does a rfc2818 check to + ensure that the server certificate is to the provided hostname. + + \return True if the SSL handshake completes with peer verification + settings. */ + bool handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const std::string& host = {}) const; + }; // https://security.stackexchange.com/questions/34780/checking-client-hello-for-https-classification constexpr size_t get_ssl_magic_size() { return 9; } bool is_ssl(const unsigned char *data, size_t len); - ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, std::vector<std::vector<uint8_t>> allowed_fingerprints, bool allow_any_cert); - void use_ssl_certificate(ssl_context_t &ssl_context, const std::pair<std::string, std::string> &private_key_and_certificate_path); - bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const ssl_context_t &ssl_context); - bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context); - bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s); + bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s); } } diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h index 19720ea8b..e100452ca 100644 --- a/contrib/epee/include/span.h +++ b/contrib/epee/include/span.h @@ -31,6 +31,7 @@ #include <algorithm> #include <cstdint> #include <memory> +#include <string> #include <type_traits> namespace epee diff --git a/contrib/epee/include/storages/parserse_base_utils.h b/contrib/epee/include/storages/parserse_base_utils.h index 69b650cd4..b5c4138c5 100644 --- a/contrib/epee/include/storages/parserse_base_utils.h +++ b/contrib/epee/include/storages/parserse_base_utils.h @@ -42,13 +42,14 @@ namespace misc_utils // 4: alpha // 8: whitespace // 16: allowed in float but doesn't necessarily mean it's a float + // 32: \ and " (end of verbatim string) static const constexpr uint8_t lut[256]={ 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 0, 0, // 16 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 16, 18, 0, // 48 + 8, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 16, 18, 0, // 48 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, // 64 0, 4, 4, 4, 4, 22, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 80 - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 96 + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 32, 0, 0, 0, // 96 0, 4, 4, 4, 4, 22, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 112 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 128 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -130,7 +131,7 @@ namespace misc_utils std::string::const_iterator it = star_end_string; ++it; std::string::const_iterator fi = it; - while (fi != buf_end && *fi != '\\' && *fi != '\"') + while (fi != buf_end && ((lut[(uint8_t)*fi] & 32)) == 0) ++fi; val.assign(it, fi); val.reserve(std::distance(star_end_string, buf_end)); diff --git a/contrib/epee/include/storages/portable_storage_val_converters.h b/contrib/epee/include/storages/portable_storage_val_converters.h index 36bb28627..e54cda828 100644 --- a/contrib/epee/include/storages/portable_storage_val_converters.h +++ b/contrib/epee/include/storages/portable_storage_val_converters.h @@ -144,7 +144,7 @@ POP_WARNINGS { MTRACE("Converting std::string to uint64_t. Source: " << from); // String only contains digits - if(std::all_of(from.begin(), from.end(), ::isdigit)) + if(std::all_of(from.begin(), from.end(), epee::misc_utils::parse::isdigit)) to = boost::lexical_cast<uint64_t>(from); // MyMonero ISO 8061 timestamp (2017-05-06T16:27:06Z) else if (boost::regex_match (from, boost::regex("\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\dZ"))) diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 2e65876e6..da47b7d55 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -42,6 +42,8 @@ #include <type_traits> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> +#include "misc_log_ex.h" +#include "storages/parserse_base_utils.h" #include "hex.h" #include "memwipe.h" #include "mlocker.h" @@ -126,7 +128,7 @@ DISABLE_GCC_WARNING(maybe-uninitialized) { for (char c : str_id) { - if (!std::isdigit(c)) + if (!epee::misc_utils::parse::isdigit(c)) return false; } } diff --git a/contrib/epee/src/connection_basic.cpp b/contrib/epee/src/connection_basic.cpp index cafc08ead..19f2c7b02 100644 --- a/contrib/epee/src/connection_basic.cpp +++ b/contrib/epee/src/connection_basic.cpp @@ -65,6 +65,15 @@ namespace epee namespace net_utils { +namespace +{ + boost::asio::ssl::context& get_context(connection_basic_shared_state* state) + { + CHECK_AND_ASSERT_THROW_MES(state != nullptr, "state shared_ptr cannot be null"); + return state->ssl_context; + } +} + std::string to_string(t_connection_type type) { if (type == e_connection_type_NET) @@ -119,56 +128,54 @@ connection_basic_pimpl::connection_basic_pimpl(const std::string &name) : m_thro int connection_basic_pimpl::m_default_tos; // methods: -connection_basic::connection_basic(boost::asio::ip::tcp::socket&& sock, boost::shared_ptr<socket_stats> stats, ssl_support_t ssl_support, ssl_context_t &ssl_context) +connection_basic::connection_basic(boost::asio::ip::tcp::socket&& sock, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support) : - m_stats(std::move(stats)), + m_state(std::move(state)), mI( new connection_basic_pimpl("peer") ), strand_(GET_IO_SERVICE(sock)), - socket_(GET_IO_SERVICE(sock), ssl_context.context), + socket_(GET_IO_SERVICE(sock), get_context(m_state.get())), m_want_close_connection(false), m_was_shutdown(false), - m_ssl_support(ssl_support), - m_ssl_context(ssl_context) + m_ssl_support(ssl_support) { // add nullptr checks if removed - CHECK_AND_ASSERT_THROW_MES(bool(m_stats), "stats shared_ptr cannot be null"); + assert(m_state != nullptr); // release runtime check in get_context socket_.next_layer() = std::move(sock); - ++(m_stats->sock_count); // increase the global counter - mI->m_peer_number = m_stats->sock_number.fetch_add(1); // use, and increase the generated number + ++(m_state->sock_count); // increase the global counter + mI->m_peer_number = m_state->sock_number.fetch_add(1); // use, and increase the generated number std::string remote_addr_str = "?"; try { boost::system::error_code e; remote_addr_str = socket().remote_endpoint(e).address().to_string(); } catch(...){} ; - _note("Spawned connection #"<<mI->m_peer_number<<" to " << remote_addr_str << " currently we have sockets count:" << m_stats->sock_count); + _note("Spawned connection #"<<mI->m_peer_number<<" to " << remote_addr_str << " currently we have sockets count:" << m_state->sock_count); } -connection_basic::connection_basic(boost::asio::io_service &io_service, boost::shared_ptr<socket_stats> stats, ssl_support_t ssl_support, ssl_context_t &ssl_context) +connection_basic::connection_basic(boost::asio::io_service &io_service, boost::shared_ptr<connection_basic_shared_state> state, ssl_support_t ssl_support) : - m_stats(std::move(stats)), + m_state(std::move(state)), mI( new connection_basic_pimpl("peer") ), strand_(io_service), - socket_(io_service, ssl_context.context), - m_want_close_connection(false), + socket_(io_service, get_context(m_state.get())), + m_want_close_connection(false), m_was_shutdown(false), - m_ssl_support(ssl_support), - m_ssl_context(ssl_context) + m_ssl_support(ssl_support) { // add nullptr checks if removed - CHECK_AND_ASSERT_THROW_MES(bool(m_stats), "stats shared_ptr cannot be null"); + assert(m_state != nullptr); // release runtime check in get_context - ++(m_stats->sock_count); // increase the global counter - mI->m_peer_number = m_stats->sock_number.fetch_add(1); // use, and increase the generated number + ++(m_state->sock_count); // increase the global counter + mI->m_peer_number = m_state->sock_number.fetch_add(1); // use, and increase the generated number std::string remote_addr_str = "?"; try { boost::system::error_code e; remote_addr_str = socket().remote_endpoint(e).address().to_string(); } catch(...){} ; - _note("Spawned connection #"<<mI->m_peer_number<<" to " << remote_addr_str << " currently we have sockets count:" << m_stats->sock_count); + _note("Spawned connection #"<<mI->m_peer_number<<" to " << remote_addr_str << " currently we have sockets count:" << m_state->sock_count); } connection_basic::~connection_basic() noexcept(false) { - --(m_stats->sock_count); + --(m_state->sock_count); std::string remote_addr_str = "?"; try { boost::system::error_code e; remote_addr_str = socket().remote_endpoint(e).address().to_string(); } catch(...){} ; diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index eb0b0ad65..7bedb18ac 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -46,8 +46,7 @@ namespace { void operator()(BIO* ptr) const noexcept { - if (ptr) - BIO_free(ptr); + BIO_free(ptr); } }; using openssl_bio = std::unique_ptr<BIO, openssl_bio_free>; @@ -56,12 +55,43 @@ namespace { void operator()(EVP_PKEY* ptr) const noexcept { - if (ptr) - EVP_PKEY_free(ptr); + EVP_PKEY_free(ptr); } }; using openssl_pkey = std::unique_ptr<EVP_PKEY, openssl_pkey_free>; + struct openssl_rsa_free + { + void operator()(RSA* ptr) const noexcept + { + RSA_free(ptr); + } + }; + using openssl_rsa = std::unique_ptr<RSA, openssl_rsa_free>; + + struct openssl_bignum_free + { + void operator()(BIGNUM* ptr) const noexcept + { + BN_free(ptr); + } + }; + using openssl_bignum = std::unique_ptr<BIGNUM, openssl_bignum_free>; + + boost::system::error_code load_ca_file(boost::asio::ssl::context& ctx, const std::string& path) + { + SSL_CTX* const ssl_ctx = ctx.native_handle(); // could be moved from context + if (ssl_ctx == nullptr) + return {boost::asio::error::invalid_argument}; + + if (!SSL_CTX_load_verify_locations(ssl_ctx, path.c_str(), nullptr)) + { + return boost::system::error_code{ + int(::ERR_get_error()), boost::asio::error::get_ssl_category() + }; + } + return boost::system::error_code{}; + } } namespace epee @@ -69,6 +99,7 @@ namespace epee namespace net_utils { + // https://stackoverflow.com/questions/256405/programmatically-create-x509-certificate-using-openssl bool create_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert) { @@ -81,19 +112,37 @@ bool create_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert) } openssl_pkey pkey_deleter{pkey}; - RSA *rsa = RSA_generate_key(4096, RSA_F4, NULL, NULL); + openssl_rsa rsa{RSA_new()}; if (!rsa) { + MERROR("Error allocating RSA private key"); + return false; + } + + openssl_bignum exponent{BN_new()}; + if (!exponent) + { + MERROR("Error allocating exponent"); + return false; + } + + BN_set_word(exponent.get(), RSA_F4); + + if (RSA_generate_key_ex(rsa.get(), 4096, exponent.get(), nullptr) != 1) + { MERROR("Error generating RSA private key"); return false; } - if (EVP_PKEY_assign_RSA(pkey, rsa) <= 0) // The RSA will be automatically freed when the EVP_PKEY structure is freed. + + if (EVP_PKEY_assign_RSA(pkey, rsa.get()) <= 0) { MERROR("Error assigning RSA private key"); - RSA_free(rsa); return false; } + // the RSA key is now managed by the EVP_PKEY structure + (void)rsa.release(); + cert = X509_new(); if (!cert) { @@ -122,22 +171,34 @@ bool create_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert) return true; } -ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &private_key_and_certificate_path, std::list<std::string> allowed_certificates, std::vector<std::vector<uint8_t>> allowed_fingerprints, bool allow_any_cert) +ssl_options_t::ssl_options_t(std::vector<std::vector<std::uint8_t>> fingerprints, std::string ca_path) + : fingerprints_(std::move(fingerprints)), + ca_path(std::move(ca_path)), + auth(), + support(ssl_support_t::e_ssl_support_enabled), + verification(ssl_verification_t::user_certificates) { - ssl_context_t ssl_context{boost::asio::ssl::context(boost::asio::ssl::context::tlsv12), std::move(allowed_certificates), std::move(allowed_fingerprints)}; + std::sort(fingerprints_.begin(), fingerprints_.end()); +} + +boost::asio::ssl::context ssl_options_t::create_context() const +{ + boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12}; + if (!bool(*this)) + return ssl_context; // only allow tls v1.2 and up - ssl_context.context.set_options(boost::asio::ssl::context::default_workarounds); - ssl_context.context.set_options(boost::asio::ssl::context::no_sslv2); - ssl_context.context.set_options(boost::asio::ssl::context::no_sslv3); - ssl_context.context.set_options(boost::asio::ssl::context::no_tlsv1); - ssl_context.context.set_options(boost::asio::ssl::context::no_tlsv1_1); + ssl_context.set_options(boost::asio::ssl::context::default_workarounds); + ssl_context.set_options(boost::asio::ssl::context::no_sslv2); + ssl_context.set_options(boost::asio::ssl::context::no_sslv3); + ssl_context.set_options(boost::asio::ssl::context::no_tlsv1); + ssl_context.set_options(boost::asio::ssl::context::no_tlsv1_1); // only allow a select handful of tls v1.3 and v1.2 ciphers to be used - SSL_CTX_set_cipher_list(ssl_context.context.native_handle(), "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305"); + SSL_CTX_set_cipher_list(ssl_context.native_handle(), "ECDHE-ECDSA-CHACHA20-POLY1305-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305"); // set options on the SSL context for added security - SSL_CTX *ctx = ssl_context.context.native_handle(); + SSL_CTX *ctx = ssl_context.native_handle(); CHECK_AND_ASSERT_THROW_MES(ctx, "Failed to get SSL context"); SSL_CTX_clear_options(ctx, SSL_OP_LEGACY_SERVER_CONNECT); // SSL_CTX_SET_OPTIONS(3) SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF); // https://stackoverflow.com/questions/22378442 @@ -153,10 +214,29 @@ ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &priv #ifdef SSL_OP_NO_COMPRESSION SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION); #endif - ssl_context.context.set_default_verify_paths(); - CHECK_AND_ASSERT_THROW_MES(private_key_and_certificate_path.first.empty() == private_key_and_certificate_path.second.empty(), "private key and certificate must be either both given or both empty"); - if (private_key_and_certificate_path.second.empty()) + switch (verification) + { + case ssl_verification_t::system_ca: + ssl_context.set_default_verify_paths(); + break; + case ssl_verification_t::user_certificates: + ssl_context.set_verify_depth(0); + /* fallthrough */ + case ssl_verification_t::user_ca: + if (!ca_path.empty()) + { + const boost::system::error_code err = load_ca_file(ssl_context, ca_path); + if (err) + throw boost::system::system_error{err, "Failed to load user CA file at " + ca_path}; + } + break; + default: + break; + } + + CHECK_AND_ASSERT_THROW_MES(auth.private_key_path.empty() == auth.certificate_path.empty(), "private key and certificate must be either both given or both empty"); + if (auth.private_key_path.empty()) { EVP_PKEY *pkey; X509 *cert; @@ -167,19 +247,15 @@ ssl_context_t create_ssl_context(const std::pair<std::string, std::string> &priv EVP_PKEY_free(pkey); } else - { - ssl_context.context.use_private_key_file(private_key_and_certificate_path.first, boost::asio::ssl::context::pem); - ssl_context.context.use_certificate_file(private_key_and_certificate_path.second, boost::asio::ssl::context::pem); - } - ssl_context.allow_any_cert = allow_any_cert; + auth.use_ssl_certificate(ssl_context); return ssl_context; } -void use_ssl_certificate(ssl_context_t &ssl_context, const std::pair<std::string, std::string> &private_key_and_certificate_path) +void ssl_authentication_t::use_ssl_certificate(boost::asio::ssl::context &ssl_context) const { - ssl_context.context.use_private_key_file(private_key_and_certificate_path.first, boost::asio::ssl::context::pem); - ssl_context.context.use_certificate_file(private_key_and_certificate_path.second, boost::asio::ssl::context::pem); + ssl_context.use_private_key_file(private_key_path, boost::asio::ssl::context::pem); + ssl_context.use_certificate_chain_file(certificate_path); } bool is_ssl(const unsigned char *data, size_t len) @@ -202,23 +278,44 @@ bool is_ssl(const unsigned char *data, size_t len) return false; } -bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const ssl_context_t &ssl_context) +bool ssl_options_t::has_strong_verification(boost::string_ref host) const noexcept { - X509_STORE_CTX *sctx = ctx.native_handle(); - if (!sctx) - { - MERROR("Error getting verify_context handle"); - return false; - } - X509 *cert =X509_STORE_CTX_get_current_cert(sctx); - if (!cert) + // onion and i2p addresses contain information about the server cert + // which both authenticates and encrypts + if (host.ends_with(".onion") || host.ends_with(".i2p")) + return true; + switch (verification) { - MERROR("No certificate found in verify_context"); - return false; + default: + case ssl_verification_t::none: + case ssl_verification_t::system_ca: + return false; + case ssl_verification_t::user_certificates: + case ssl_verification_t::user_ca: + break; } + return true; +} +bool ssl_options_t::has_fingerprint(boost::asio::ssl::verify_context &ctx) const +{ // can we check the certificate against a list of fingerprints? - if (!ssl_context.allowed_fingerprints.empty()) { + if (!fingerprints_.empty()) { + X509_STORE_CTX *sctx = ctx.native_handle(); + if (!sctx) + { + MERROR("Error getting verify_context handle"); + return false; + } + + X509* cert = nullptr; + const STACK_OF(X509)* chain = X509_STORE_CTX_get_chain(sctx); + if (!chain || sk_X509_num(chain) < 1 || !(cert = sk_X509_value(chain, 0))) + { + MERROR("No certificate found in verify_context"); + return false; + } + // buffer for the certificate digest and the size of the result std::vector<uint8_t> digest(EVP_MAX_MD_SIZE); unsigned int size{ 0 }; @@ -232,74 +329,63 @@ bool is_certificate_allowed(boost::asio::ssl::verify_context &ctx, const ssl_con // strip unnecessary bytes from the digest digest.resize(size); - // is the certificate fingerprint inside the list of allowed fingerprints? - if (std::find(ssl_context.allowed_fingerprints.begin(), ssl_context.allowed_fingerprints.end(), digest) != ssl_context.allowed_fingerprints.end()) - return true; - } - - if (!ssl_context.allowed_certificates.empty()) { - BIO *bio_cert = BIO_new(BIO_s_mem()); - bool success = PEM_write_bio_X509(bio_cert, cert); - if (!success) - { - BIO_free(bio_cert); - MERROR("Failed to print certificate"); - return false; - } - BUF_MEM *buf = NULL; - BIO_get_mem_ptr(bio_cert, &buf); - if (!buf || !buf->data || !buf->length) - { - BIO_free(bio_cert); - MERROR("Failed to write certificate: " << ERR_get_error()); - return false; - } - std::string certificate(std::string(buf->data, buf->length)); - BIO_free(bio_cert); - if (std::find(ssl_context.allowed_certificates.begin(), ssl_context.allowed_certificates.end(), certificate) != ssl_context.allowed_certificates.end()) - return true; + return std::binary_search(fingerprints_.begin(), fingerprints_.end(), digest); } - // if either checklist is non-empty we must have failed it - return ssl_context.allowed_fingerprints.empty() && ssl_context.allowed_certificates.empty(); + return false; } -bool ssl_handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const epee::net_utils::ssl_context_t &ssl_context) +bool ssl_options_t::handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket> &socket, boost::asio::ssl::stream_base::handshake_type type, const std::string& host) const { - bool verified = false; socket.next_layer().set_option(boost::asio::ip::tcp::no_delay(true)); - socket.set_verify_mode(boost::asio::ssl::verify_peer); - socket.set_verify_callback([&](bool preverified, boost::asio::ssl::verify_context &ctx) + + /* Using system-wide CA store for client verification is funky - there is + no expected hostname for server to verify against. If server doesn't have + specific whitelisted certificates for client, don't require client to + send certificate at all. */ + const bool no_verification = verification == ssl_verification_t::none || + (type == boost::asio::ssl::stream_base::server && fingerprints_.empty() && ca_path.empty()); + + /* According to OpenSSL documentation (and SSL specifications), server must + always send certificate unless "anonymous" cipher mode is used which are + disabled by default. Either way, the certificate is never inspected. */ + if (no_verification) + socket.set_verify_mode(boost::asio::ssl::verify_none); + else { - if (!preverified) + socket.set_verify_mode(boost::asio::ssl::verify_peer | boost::asio::ssl::verify_fail_if_no_peer_cert); + + // in case server is doing "virtual" domains, set hostname + SSL* const ssl_ctx = socket.native_handle(); + if (type == boost::asio::ssl::stream_base::client && !host.empty() && ssl_ctx) + SSL_set_tlsext_host_name(ssl_ctx, host.c_str()); + + socket.set_verify_callback([&](const bool preverified, boost::asio::ssl::verify_context &ctx) { - const int err = X509_STORE_CTX_get_error(ctx.native_handle()); - const int depth = X509_STORE_CTX_get_error_depth(ctx.native_handle()); - if (err != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || depth != 0) + // preverified means it passed system or user CA check. System CA is never loaded + // when fingerprints are whitelisted. + const bool verified = preverified && + (verification != ssl_verification_t::system_ca || host.empty() || boost::asio::ssl::rfc2818_verification(host)(preverified, ctx)); + + if (!verified && !has_fingerprint(ctx)) { - MERROR("Invalid SSL certificate, error " << err << " at depth " << depth << ", connection dropped"); - return false; + // autodetect will reconnect without SSL - warn and keep connection encrypted + if (support != ssl_support_t::e_ssl_support_autodetect) + { + MERROR("SSL certificate is not in the allowed list, connection droppped"); + return false; + } + MWARNING("SSL peer has not been verified"); } - } - if (!ssl_context.allow_any_cert && !is_certificate_allowed(ctx, ssl_context)) - { - MERROR("Certificate is not in the allowed list, connection droppped"); - return false; - } - verified = true; - return true; - }); + return true; + }); + } boost::system::error_code ec; socket.handshake(type, ec); if (ec) { - MERROR("handshake failed, connection dropped: " << ec.message()); - return false; - } - if (!ssl_context.allow_any_cert && !verified) - { - MERROR("Peer did not provide a certificate in the allowed list, connection dropped"); + MERROR("SSL handshake failed, connection dropped: " << ec.message()); return false; } MDEBUG("SSL handshake success"); diff --git a/contrib/gitian/gitian-linux.yml b/contrib/gitian/gitian-linux.yml index 3bb25c314..67f174fec 100644 --- a/contrib/gitian/gitian-linux.yml +++ b/contrib/gitian/gitian-linux.yml @@ -137,7 +137,7 @@ script: | if [ -d "$EXTRA_INCLUDES" ]; then export HOST_ID_SALT="$EXTRA_INCLUDES" fi - make ${MAKEOPTS} -C ${BASEPREFIX} HOST="${i}" -j 4 V=1 + make ${MAKEOPTS} -C ${BASEPREFIX} HOST="${i}" V=1 unset HOST_ID_SALT done diff --git a/contrib/gitian/gitian-win.yml b/contrib/gitian/gitian-win.yml index fef5567f9..1eb558300 100644 --- a/contrib/gitian/gitian-win.yml +++ b/contrib/gitian/gitian-win.yml @@ -108,7 +108,7 @@ script: | if [ -d "$EXTRA_INCLUDES" ]; then export HOST_ID_SALT="$EXTRA_INCLUDES" fi - make ${MAKEOPTS} -C ${BASEPREFIX} HOST="${i}" -j 4 V=1 + make ${MAKEOPTS} -C ${BASEPREFIX} HOST="${i}" V=1 unset HOST_ID_SALT done diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index d2fe39fc2..2c40b5a78 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1514,6 +1514,20 @@ public: virtual bool check_pruning() = 0; /** + * @brief get the max block size + */ + virtual uint64_t get_max_block_size() = 0; + + /** + * @brief add a new max block size + * + * The max block size will be the maximum of sz and the current block size + * + * @param: sz the block size + */ + + virtual void add_max_block_size(uint64_t sz) = 0; + /** * @brief runs a function over all txpool transactions * * The subclass should run the passed function for each txpool tx it has diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 9f71fd068..a07e9ac55 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -2513,6 +2513,58 @@ std::vector<uint64_t> BlockchainLMDB::get_block_info_64bit_fields(uint64_t start return ret; } +uint64_t BlockchainLMDB::get_max_block_size() +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(properties) + MDB_val_str(k, "max_block_size"); + MDB_val v; + int result = mdb_cursor_get(m_cur_properties, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) + return std::numeric_limits<uint64_t>::max(); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to retrieve max block size: ", result).c_str())); + if (v.mv_size != sizeof(uint64_t)) + throw0(DB_ERROR("Failed to retrieve or create max block size: unexpected value size")); + uint64_t max_block_size; + memcpy(&max_block_size, v.mv_data, sizeof(max_block_size)); + TXN_POSTFIX_RDONLY(); + return max_block_size; +} + +void BlockchainLMDB::add_max_block_size(uint64_t sz) +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + mdb_txn_cursors *m_cursors = &m_wcursors; + + CURSOR(properties) + + MDB_val_str(k, "max_block_size"); + MDB_val v; + int result = mdb_cursor_get(m_cur_properties, &k, &v, MDB_SET); + if (result && result != MDB_NOTFOUND) + throw0(DB_ERROR(lmdb_error("Failed to retrieve max block size: ", result).c_str())); + uint64_t max_block_size = 0; + if (result == 0) + { + if (v.mv_size != sizeof(uint64_t)) + throw0(DB_ERROR("Failed to retrieve or create max block size: unexpected value size")); + memcpy(&max_block_size, v.mv_data, sizeof(max_block_size)); + } + if (sz > max_block_size) + max_block_size = sz; + v.mv_data = (void*)&max_block_size; + v.mv_size = sizeof(max_block_size); + result = mdb_cursor_put(m_cur_properties, &k, &v, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to set max_block_size: ", result).c_str())); +} + + std::vector<uint64_t> BlockchainLMDB::get_block_weights(uint64_t start_height, size_t count) const { return get_block_info_64bit_fields(start_height, count, offsetof(mdb_block_info, bi_weight)); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 2f89b77ac..f6b00817d 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -400,6 +400,9 @@ private: std::vector<uint64_t> get_block_info_64bit_fields(uint64_t start_height, size_t count, off_t offset) const; + uint64_t get_max_block_size(); + void add_max_block_size(uint64_t sz); + // fix up anything that may be wrong due to past bugs virtual void fixup(); diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index 7916364c5..04fad26a4 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -149,6 +149,9 @@ public: virtual bool update_pruning() { return true; } virtual bool check_pruning() { return true; } virtual void prune_outputs(uint64_t amount) {} + + virtual uint64_t get_max_block_size() { return 100000000; } + virtual void add_max_block_size(uint64_t sz) { } }; } diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 36080aade..8e13f2c04 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -611,24 +611,6 @@ int main(int argc, char* argv[]) } already_pruned = true; } - if (n == 0) - { - const uint64_t blockchain_height = core_storage[0]->get_current_blockchain_height(); - const crypto::hash hash = core_storage[0]->get_block_id_by_height(blockchain_height - 1); - cryptonote::block block; - if (core_storage[0]->get_block_by_hash(hash, block)) - { - if (block.major_version < 10) - { - time_t now = time(NULL); - if (now < 1555286400) // 15 april 2019 - { - MERROR("Pruning before v10 will confuse peers. Wait for v10 first"); - return 1; - } - } - } - } } core_storage[0]->deinit(); core_storage[0].reset(NULL); diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 4cc84bf4a..33c26277e 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -205,7 +205,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' char buf[8]; unsigned int i; for (i=0; i<24; i++) { - sprintf(buf, "\t%02d:00", i); + sprintf(buf, "\t%02u:00", i); std::cout << buf; } } diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 1a1155c7c..5e03bf897 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -33,7 +33,7 @@ #include <stdlib.h> #include "include_base_utils.h" #include "common/threadpool.h" -#include <random> +#include "crypto/crypto.h" #include <boost/thread/mutex.hpp> #include <boost/algorithm/string/join.hpp> #include <boost/optional.hpp> @@ -517,10 +517,7 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std std::vector<std::vector<std::string> > records; records.resize(dns_urls.size()); - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<int> dis(0, dns_urls.size() - 1); - size_t first_index = dis(gen); + size_t first_index = crypto::rand_idx(dns_urls.size()); // send all requests in parallel std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false); diff --git a/src/common/perf_timer.cpp b/src/common/perf_timer.cpp index dda498088..189eb85eb 100644 --- a/src/common/perf_timer.cpp +++ b/src/common/perf_timer.cpp @@ -88,7 +88,7 @@ namespace tools namespace tools { -el::Level performance_timer_log_level = el::Level::Debug; +el::Level performance_timer_log_level = el::Level::Info; static __thread std::vector<LoggingPerformanceTimer*> *performance_timers = NULL; @@ -97,8 +97,8 @@ void set_performance_timer_log_level(el::Level level) if (level != el::Level::Debug && level != el::Level::Trace && level != el::Level::Info && level != el::Level::Warning && level != el::Level::Error && level != el::Level::Fatal) { - MERROR("Wrong log level: " << el::LevelHelper::convertToString(level) << ", using Debug"); - level = el::Level::Debug; + MERROR("Wrong log level: " << el::LevelHelper::convertToString(level) << ", using Info"); + level = el::Level::Info; } performance_timer_log_level = level; } diff --git a/src/common/util.cpp b/src/common/util.cpp index 728efc294..3388974ce 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -641,16 +641,16 @@ std::string get_nix_version_display_string() return res; } - std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name) + std::error_code replace_file(const std::string& old_name, const std::string& new_name) { int code; #if defined(WIN32) // Maximizing chances for success std::wstring wide_replacement_name; - try { wide_replacement_name = string_tools::utf8_to_utf16(replacement_name); } + try { wide_replacement_name = string_tools::utf8_to_utf16(old_name); } catch (...) { return std::error_code(GetLastError(), std::system_category()); } std::wstring wide_replaced_name; - try { wide_replaced_name = string_tools::utf8_to_utf16(replaced_name); } + try { wide_replaced_name = string_tools::utf8_to_utf16(new_name); } catch (...) { return std::error_code(GetLastError(), std::system_category()); } DWORD attributes = ::GetFileAttributesW(wide_replaced_name.c_str()); @@ -662,7 +662,7 @@ std::string get_nix_version_display_string() bool ok = 0 != ::MoveFileExW(wide_replacement_name.c_str(), wide_replaced_name.c_str(), MOVEFILE_REPLACE_EXISTING); code = ok ? 0 : static_cast<int>(::GetLastError()); #else - bool ok = 0 == std::rename(replacement_name.c_str(), replaced_name.c_str()); + bool ok = 0 == std::rename(old_name.c_str(), new_name.c_str()); code = ok ? 0 : errno; #endif return std::error_code(code, std::system_category()); diff --git a/src/common/util.h b/src/common/util.h index 77a5a9af6..f6d5c9b1f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -145,7 +145,7 @@ namespace tools bool create_directories_if_necessary(const std::string& path); /*! \brief std::rename wrapper for nix and something strange for windows. */ - std::error_code replace_file(const std::string& replacement_name, const std::string& replaced_name); + std::error_code replace_file(const std::string& old_name, const std::string& new_name); bool sanitize_locale(); diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 22b182ab0..bac456f60 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -35,6 +35,7 @@ #include <boost/optional.hpp> #include <type_traits> #include <vector> +#include <random> #include "common/pod-class.h" #include "memwipe.h" @@ -162,6 +163,32 @@ namespace crypto { return res; } + /* UniformRandomBitGenerator using crypto::rand<uint64_t>() + */ + struct random_device + { + typedef uint64_t result_type; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return result_type(-1); } + result_type operator()() const { return crypto::rand<result_type>(); } + }; + + /* Generate a random value between range_min and range_max + */ + template<typename T> + typename std::enable_if<std::is_integral<T>::value, T>::type rand_range(T range_min, T range_max) { + crypto::random_device rd; + std::uniform_int_distribution<T> dis(range_min, range_max); + return dis(rd); + } + + /* Generate a random index between 0 and sz-1 + */ + template<typename T> + typename std::enable_if<std::is_unsigned<T>::value, T>::type rand_idx(T sz) { + return crypto::rand_range<T>(0, sz-1); + } + /* Generate a new key pair */ inline secret_key generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key = secret_key(), bool recover = false) { diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h index 03caafbb0..20d92bdf1 100644 --- a/src/cryptonote_basic/cryptonote_basic.h +++ b/src/cryptonote_basic/cryptonote_basic.h @@ -422,6 +422,8 @@ namespace cryptonote FIELDS(*static_cast<block_header *>(this)) FIELD(miner_tx) FIELD(tx_hashes) + if (tx_hashes.size() > CRYPTONOTE_MAX_TX_PER_BLOCK) + return false; END_SERIALIZE() }; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp index e336cc1d1..d8de65b81 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.cpp +++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp @@ -76,11 +76,6 @@ namespace cryptonote { return CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; } //----------------------------------------------------------------------------------------------- - size_t get_max_block_size() - { - return CRYPTONOTE_MAX_BLOCK_SIZE; - } - //----------------------------------------------------------------------------------------------- size_t get_max_tx_size() { return CRYPTONOTE_MAX_TX_SIZE; diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h index 036273f0e..c7198a16f 100644 --- a/src/cryptonote_basic/cryptonote_basic_impl.h +++ b/src/cryptonote_basic/cryptonote_basic_impl.h @@ -87,7 +87,6 @@ namespace cryptonote { /* Cryptonote helper functions */ /************************************************************************/ size_t get_min_block_weight(uint8_t version); - size_t get_max_block_size(); size_t get_max_tx_size(); bool get_block_reward(size_t median_weight, size_t current_block_weight, uint64_t already_generated_coins, uint64_t &reward, uint8_t version); uint8_t get_account_address_checksum(const public_address_outer_blob& bl); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 094057b1f..566622c1a 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -1065,8 +1065,6 @@ namespace cryptonote // prefix get_transaction_prefix_hash(t, hashes[0]); - transaction &tt = const_cast<transaction&>(t); - const blobdata blob = tx_to_blob(t); const unsigned int unprunable_size = t.unprunable_size; const unsigned int prefix_size = t.prefix_size; @@ -1090,7 +1088,14 @@ namespace cryptonote // we still need the size if (blob_size) - *blob_size = get_object_blobsize(t); + { + if (!t.is_blob_size_valid()) + { + t.blob_size = blob.size(); + t.set_blob_size_valid(true); + } + *blob_size = t.blob_size; + } return true; } @@ -1143,21 +1148,37 @@ namespace cryptonote return blob; } //--------------------------------------------------------------- - bool calculate_block_hash(const block& b, crypto::hash& res) + bool calculate_block_hash(const block& b, crypto::hash& res, const blobdata *blob) { + blobdata bd; + if (!blob) + { + bd = block_to_blob(b); + blob = &bd; + } + + bool hash_result = get_object_hash(get_block_hashing_blob(b), res); + if (!hash_result) + return false; + + if (b.miner_tx.vin.size() == 1 && b.miner_tx.vin[0].type() == typeid(cryptonote::txin_gen)) + { + const cryptonote::txin_gen &txin_gen = boost::get<cryptonote::txin_gen>(b.miner_tx.vin[0]); + if (txin_gen.height != 202612) + return true; + } + // EXCEPTION FOR BLOCK 202612 const std::string correct_blob_hash_202612 = "3a8a2b3a29b50fc86ff73dd087ea43c6f0d6b8f936c849194d5c84c737903966"; const std::string existing_block_id_202612 = "bbd604d2ba11ba27935e006ed39c9bfdd99b76bf4a50654bc1e1e61217962698"; - crypto::hash block_blob_hash = get_blob_hash(block_to_blob(b)); + crypto::hash block_blob_hash = get_blob_hash(*blob); if (string_tools::pod_to_hex(block_blob_hash) == correct_blob_hash_202612) { string_tools::hex_to_pod(existing_block_id_202612, res); return true; } - bool hash_result = get_object_hash(get_block_hashing_blob(b), res); - if (hash_result) { // make sure that we aren't looking at a block with the 202612 block id but not the correct blobdata if (string_tools::pod_to_hex(res) == existing_block_id_202612) @@ -1200,9 +1221,9 @@ namespace cryptonote bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height) { // block 202612 bug workaround - const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; if (height == 202612) { + static const std::string longhash_202612 = "84f64766475d51837ac9efbef1926486e58563c95a19fef4aec3254f03000000"; string_tools::hex_to_pod(longhash_202612, res); return true; } @@ -1239,7 +1260,7 @@ namespace cryptonote return p; } //--------------------------------------------------------------- - bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash *block_hash) { std::stringstream ss; ss << b_blob; @@ -1248,9 +1269,26 @@ namespace cryptonote CHECK_AND_ASSERT_MES(r, false, "Failed to parse block from blob"); b.invalidate_hashes(); b.miner_tx.invalidate_hashes(); + if (block_hash) + { + calculate_block_hash(b, *block_hash, &b_blob); + ++block_hashes_calculated_count; + b.hash = *block_hash; + b.set_hash_valid(true); + } return true; } //--------------------------------------------------------------- + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b) + { + return parse_and_validate_block_from_blob(b_blob, b, NULL); + } + //--------------------------------------------------------------- + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash &block_hash) + { + return parse_and_validate_block_from_blob(b_blob, b, &block_hash); + } + //--------------------------------------------------------------- blobdata block_to_blob(const block& b) { return t_serializable_object_to_blob(b); @@ -1286,6 +1324,7 @@ namespace cryptonote crypto::hash get_tx_tree_hash(const block& b) { std::vector<crypto::hash> txs_ids; + txs_ids.reserve(1 + b.tx_hashes.size()); crypto::hash h = null_hash; size_t bl_sz = 0; get_transaction_hash(b.miner_tx, h, bl_sz); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 40a9907be..c9de2a56e 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -114,12 +114,14 @@ namespace cryptonote crypto::hash get_pruned_transaction_hash(const transaction& t, const crypto::hash &pruned_data_hash); blobdata get_block_hashing_blob(const block& b); - bool calculate_block_hash(const block& b, crypto::hash& res); + bool calculate_block_hash(const block& b, crypto::hash& res, const blobdata *blob = NULL); bool get_block_hash(const block& b, crypto::hash& res); crypto::hash get_block_hash(const block& b); bool get_block_longhash(const block& b, crypto::hash& res, uint64_t height); crypto::hash get_block_longhash(const block& b, uint64_t height); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash *block_hash); bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b); + bool parse_and_validate_block_from_blob(const blobdata& b_blob, block& b, crypto::hash &block_hash); bool get_inputs_money_amount(const transaction& tx, uint64_t& money); uint64_t get_outs_money_amount(const transaction& tx); bool check_inputs_types_supported(const transaction& tx); @@ -140,6 +142,16 @@ namespace cryptonote std::string print_money(uint64_t amount, unsigned int decimal_point = -1); //--------------------------------------------------------------- template<class t_object> + bool t_serializable_object_from_blob(t_object& to, const blobdata& b_blob) + { + std::stringstream ss; + ss << b_blob; + binary_archive<false> ba(ss); + bool r = ::serialization::serialize(ba, to); + return r; + } + //--------------------------------------------------------------- + template<class t_object> bool t_serializable_object_to_blob(const t_object& to, blobdata& b_blob) { std::stringstream ss; diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 4e2edc20f..e6c6bddb6 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -106,6 +106,7 @@ namespace cryptonote m_thread_index(0), m_phandler(phandler), m_height(0), + m_threads_active(0), m_pausers_count(0), m_threads_total(0), m_starter_nonce(0), @@ -264,8 +265,8 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_threads_lock); boost::interprocess::ipcdetail::atomic_write32(&m_stop, 1); - for(boost::thread& th: m_threads) - th.join(); + while (m_threads_active > 0) + misc_utils::sleep_no_w(100); m_threads.clear(); } boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); @@ -447,15 +448,17 @@ namespace cryptonote // In case background mining was active and the miner threads are waiting // on the background miner to signal start. - m_is_background_mining_started_cond.notify_all(); - - for(boost::thread& th: m_threads) - th.join(); + while (m_threads_active > 0) + { + m_is_background_mining_started_cond.notify_all(); + misc_utils::sleep_no_w(100); + } // The background mining thread could be sleeping for a long time, so we // interrupt it just in case m_background_mining_thread.interrupt(); m_background_mining_thread.join(); + m_is_background_mining_enabled = false; MINFO("Mining has been stopped, " << m_threads.size() << " finished" ); m_threads.clear(); @@ -573,7 +576,8 @@ namespace cryptonote //we lucky! ++m_config.current_extra_message_index; MGINFO_GREEN("Found block " << get_block_hash(b) << " at height " << height << " for difficulty: " << local_diff); - if(!m_phandler->handle_block_found(b)) + cryptonote::block_verification_context bvc; + if(!m_phandler->handle_block_found(b, bvc) || !bvc.m_added_to_main_chain) { --m_config.current_extra_message_index; }else @@ -589,6 +593,7 @@ namespace cryptonote } slow_hash_free_state(); MGINFO("Miner thread stopped ["<< th_local_index << "]"); + --m_threads_active; return true; } //----------------------------------------------------------------------------------------------------- @@ -747,10 +752,10 @@ namespace cryptonote uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); uint8_t process_percentage = get_percent_of_total(process_diff, total_diff); - MGINFO("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); + MDEBUG("idle percentage is " << unsigned(idle_percentage) << "\%, miner percentage is " << unsigned(process_percentage) << "\%, ac power : " << on_ac_power); if( idle_percentage + process_percentage < get_idle_threshold() || !on_ac_power ) { - MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); + MINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining stopping, thanks for your contribution!"); m_is_background_mining_started = false; // reset process times @@ -788,10 +793,10 @@ namespace cryptonote uint64_t idle_diff = (current_idle_time - prev_idle_time); uint8_t idle_percentage = get_percent_of_total(idle_diff, total_diff); - MGINFO("idle percentage is " << unsigned(idle_percentage)); + MDEBUG("idle percentage is " << unsigned(idle_percentage)); if( idle_percentage >= get_idle_threshold() && on_ac_power ) { - MGINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); + MINFO("cpu is " << unsigned(idle_percentage) << "% idle, idle threshold is " << unsigned(get_idle_threshold()) << "\%, ac power : " << on_ac_power << ", background mining started, good luck!"); m_is_background_mining_started = true; m_is_background_mining_started_cond.notify_all(); @@ -1049,7 +1054,12 @@ namespace cryptonote if (boost::logic::indeterminate(on_battery)) { - LOG_ERROR("couldn't query power status from " << power_supply_class_path); + static bool error_shown = false; + if (!error_shown) + { + LOG_ERROR("couldn't query power status from " << power_supply_class_path); + error_shown = true; + } } return on_battery; diff --git a/src/cryptonote_basic/miner.h b/src/cryptonote_basic/miner.h index 08b1bd7f1..285075f51 100644 --- a/src/cryptonote_basic/miner.h +++ b/src/cryptonote_basic/miner.h @@ -34,6 +34,7 @@ #include <boost/logic/tribool_fwd.hpp> #include <atomic> #include "cryptonote_basic.h" +#include "verification_context.h" #include "difficulty.h" #include "math_helper.h" #ifdef _WIN32 @@ -45,7 +46,7 @@ namespace cryptonote struct i_miner_handler { - virtual bool handle_block_found(block& b) = 0; + virtual bool handle_block_found(block& b, block_verification_context &bvc) = 0; virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) = 0; protected: ~i_miner_handler(){}; @@ -125,6 +126,7 @@ namespace cryptonote uint64_t m_height; volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; + std::atomic<uint32_t> m_threads_active; std::atomic<int32_t> m_pausers_count; epee::critical_section m_miners_count_lock; diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index b6087de22..56b6a63b7 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -37,9 +37,9 @@ #define CRYPTONOTE_DNS_TIMEOUT_MS 20000 #define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 -#define CRYPTONOTE_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! #define CRYPTONOTE_GETBLOCKTEMPLATE_MAX_BLOCK_SIZE 196608 //size of block (bytes) that is the maximum that miners will produce -#define CRYPTONOTE_MAX_TX_SIZE 1000000000 +#define CRYPTONOTE_MAX_TX_SIZE 1000000 +#define CRYPTONOTE_MAX_TX_PER_BLOCK 0x10000000 #define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 #define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 60 #define CURRENT_TRANSACTION_VERSION 2 diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 83d3044f8..7ef8f8c45 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -1008,7 +1008,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, //------------------------------------------------------------------ // This function attempts to switch to an alternate chain, returning // boolean based on success therein. -bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain) +bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain) { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1109,7 +1109,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<blocks_ext_by_hash:: //------------------------------------------------------------------ // This function calculates the difficulty target for the block being added to // an alternate chain. -difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const +difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const { if (m_fixed_difficulty) { @@ -1351,7 +1351,7 @@ uint64_t Blockchain::get_current_cumulative_block_weight_median() const // in a lot of places. That flag is not referenced in any of the code // nor any of the makefiles, howeve. Need to look into whether or not it's // necessary at all. -bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +bool Blockchain::create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) { LOG_PRINT_L3("Blockchain::" << __func__); size_t median_weight; @@ -1361,8 +1361,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m m_tx_pool.lock(); const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); }); CRITICAL_REGION_LOCAL(m_blockchain_lock); - height = m_db->height(); - if (m_btc_valid) { + if (m_btc_valid && !from_block) { // The pool cookie is atomic. The lack of locking is OK, as if it changes // just as we compare it, we'll just use a slightly old template, but // this would be the case anyway if we'd lock, and the change happened @@ -1372,16 +1371,79 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m m_btc.timestamp = time(NULL); // update timestamp unconditionally b = m_btc; diffic = m_btc_difficulty; + height = m_btc_height; expected_reward = m_btc_expected_reward; return true; } - MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie())); + MDEBUG("Not using cached template: address " << (!memcmp(&miner_address, &m_btc_address, sizeof(cryptonote::account_public_address))) << ", nonce " << (m_btc_nonce == ex_nonce) << ", cookie " << (m_btc_pool_cookie == m_tx_pool.cookie()) << ", from_block " << (!!from_block)); invalidate_block_template_cache(); } - b.major_version = m_hardfork->get_current_version(); - b.minor_version = m_hardfork->get_ideal_version(); - b.prev_id = get_tail_id(); + if (from_block) + { + //build alternative subchain, front -> mainchain, back -> alternative head + //block is not related with head of main chain + //first of all - look in alternative chains container + auto it_prev = m_alternative_chains.find(*from_block); + bool parent_in_main = m_db->block_exists(*from_block); + if(it_prev == m_alternative_chains.end() && !parent_in_main) + { + MERROR("Unknown from block"); + return false; + } + + //we have new block in alternative chain + std::list<blocks_ext_by_hash::const_iterator> alt_chain; + block_verification_context bvc = boost::value_initialized<block_verification_context>(); + std::vector<uint64_t> timestamps; + if (!build_alt_chain(*from_block, alt_chain, timestamps, bvc)) + return false; + + if (parent_in_main) + { + cryptonote::block prev_block; + CHECK_AND_ASSERT_MES(get_block_by_hash(*from_block, prev_block), false, "From block not found"); // TODO + uint64_t from_block_height = cryptonote::get_block_height(prev_block); + height = from_block_height + 1; + } + else + { + height = alt_chain.back()->second.height + 1; + } + b.major_version = m_hardfork->get_ideal_version(height); + b.minor_version = m_hardfork->get_ideal_version(); + b.prev_id = *from_block; + + // cheat and use the weight of the block we start from, virtually certain to be acceptable + // and use 1.9 times rather than 2 times so we're even more sure + if (parent_in_main) + { + median_weight = m_db->get_block_weight(height - 1); + already_generated_coins = m_db->get_block_already_generated_coins(height - 1); + } + else + { + median_weight = it_prev->second.block_cumulative_weight - it_prev->second.block_cumulative_weight / 20; + already_generated_coins = alt_chain.back()->second.already_generated_coins; + } + + // FIXME: consider moving away from block_extended_info at some point + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = b; + bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(*from_block) + 1; + + diffic = get_next_difficulty_for_alternative_chain(alt_chain, bei); + } + else + { + height = m_db->height(); + b.major_version = m_hardfork->get_current_version(); + b.minor_version = m_hardfork->get_ideal_version(); + b.prev_id = get_tail_id(); + median_weight = m_current_block_cumul_weight_limit / 2; + diffic = get_difficulty_for_next_block(); + already_generated_coins = m_db->get_block_already_generated_coins(height - 1); + } b.timestamp = time(NULL); uint64_t median_ts; @@ -1390,15 +1452,11 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m b.timestamp = median_ts; } - diffic = get_difficulty_for_next_block(); CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); - median_weight = m_current_block_cumul_weight_limit / 2; - already_generated_coins = m_db->get_block_already_generated_coins(height - 1); - size_t txs_weight; uint64_t fee; - if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, m_hardfork->get_current_version())) + if (!m_tx_pool.fill_block_template(b, median_weight, already_generated_coins, txs_weight, fee, expected_reward, b.major_version)) { return false; } @@ -1461,7 +1519,7 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m block weight, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block weight */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob weight - uint8_t hf_version = m_hardfork->get_current_version(); + uint8_t hf_version = b.major_version; size_t max_outs = hf_version >= 4 ? 1 : 11; bool r = construct_miner_tx(height, median_weight, already_generated_coins, txs_weight, fee, miner_address, b.miner_tx, ex_nonce, max_outs, hf_version); CHECK_AND_ASSERT_MES(r, false, "Failed to construct miner tx, first chance"); @@ -1516,16 +1574,22 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m ", cumulative weight " << cumulative_weight << " is now good"); #endif - cache_block_template(b, miner_address, ex_nonce, diffic, expected_reward, pool_cookie); + if (!from_block) + cache_block_template(b, miner_address, ex_nonce, diffic, height, expected_reward, pool_cookie); return true; } LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); return false; } //------------------------------------------------------------------ +bool Blockchain::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) +{ + return create_block_template(b, NULL, miner_address, diffic, height, expected_reward, ex_nonce); +} +//------------------------------------------------------------------ // for an alternate chain, get the timestamps from the main chain to complete // the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. -bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) +bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps) const { LOG_PRINT_L3("Blockchain::" << __func__); @@ -1545,6 +1609,52 @@ bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vect return true; } //------------------------------------------------------------------ +bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> ×tamps, block_verification_context& bvc) const +{ + //build alternative subchain, front -> mainchain, back -> alternative head + blocks_ext_by_hash::const_iterator alt_it = m_alternative_chains.find(prev_id); + timestamps.clear(); + while(alt_it != m_alternative_chains.end()) + { + alt_chain.push_front(alt_it); + timestamps.push_back(alt_it->second.bl.timestamp); + alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); + } + + // if block to be added connects to known blocks that aren't part of the + // main chain -- that is, if we're adding on to an alternate chain + if(!alt_chain.empty()) + { + // make sure alt chain doesn't somehow start past the end of the main chain + CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height"); + + // make sure that the blockchain contains the block that should connect + // this alternate chain with it. + if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) + { + MERROR("alternate chain does not appear to connect to main chain..."); + return false; + } + + // make sure block connects correctly to the main chain + auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); + CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); + complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); + } + // if block not associated with known alternate chain + else + { + // if block parent is not part of main chain or an alternate chain, + // we ignore it + bool parent_in_main = m_db->block_exists(prev_id); + CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main"); + + complete_timestamps_vector(m_db->get_block_height(prev_id), timestamps); + } + + return true; +} +//------------------------------------------------------------------ // If a block is to be added and its parent block is not the current // main chain top block, then we need to see if we know about its parent block. // If its parent block is part of a known forked chain, then we need to see @@ -1589,47 +1699,18 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id if(it_prev != m_alternative_chains.end() || parent_in_main) { //we have new block in alternative chain - - //build alternative subchain, front -> mainchain, back -> alternative head - blocks_ext_by_hash::iterator alt_it = it_prev; //m_alternative_chains.find() - std::list<blocks_ext_by_hash::iterator> alt_chain; + std::list<blocks_ext_by_hash::const_iterator> alt_chain; std::vector<uint64_t> timestamps; - while(alt_it != m_alternative_chains.end()) - { - alt_chain.push_front(alt_it); - timestamps.push_back(alt_it->second.bl.timestamp); - alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id); - } - - // if block to be added connects to known blocks that aren't part of the - // main chain -- that is, if we're adding on to an alternate chain - if(!alt_chain.empty()) - { - // make sure alt chain doesn't somehow start past the end of the main chain - CHECK_AND_ASSERT_MES(m_db->height() > alt_chain.front()->second.height, false, "main blockchain wrong height"); - - // make sure that the blockchain contains the block that should connect - // this alternate chain with it. - if (!m_db->block_exists(alt_chain.front()->second.bl.prev_id)) - { - MERROR("alternate chain does not appear to connect to main chain..."); - return false; - } - - // make sure block connects correctly to the main chain - auto h = m_db->get_block_hash_from_height(alt_chain.front()->second.height - 1); - CHECK_AND_ASSERT_MES(h == alt_chain.front()->second.bl.prev_id, false, "alternative chain has wrong connection to main chain"); - complete_timestamps_vector(m_db->get_block_height(alt_chain.front()->second.bl.prev_id), timestamps); - } - // if block not associated with known alternate chain - else - { - // if block parent is not part of main chain or an alternate chain, - // we ignore it - CHECK_AND_ASSERT_MES(parent_in_main, false, "internal error: broken imperative condition: parent_in_main"); + if (!build_alt_chain(b.prev_id, alt_chain, timestamps, bvc)) + return false; - complete_timestamps_vector(m_db->get_block_height(b.prev_id), timestamps); - } + // FIXME: consider moving away from block_extended_info at some point + block_extended_info bei = boost::value_initialized<block_extended_info>(); + bei.bl = b; + const uint64_t prev_height = alt_chain.size() ? it_prev->second.height : m_db->get_block_height(b.prev_id); + bei.height = prev_height + 1; + uint64_t block_reward = get_outs_money_amount(b.miner_tx); + bei.already_generated_coins = block_reward + (alt_chain.size() ? it_prev->second.already_generated_coins : m_db->get_block_already_generated_coins(prev_height)); // verify that the block's timestamp is within the acceptable range // (not earlier than the median of the last X blocks) @@ -1640,11 +1721,6 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id return false; } - // FIXME: consider moving away from block_extended_info at some point - block_extended_info bei = boost::value_initialized<block_extended_info>(); - bei.bl = b; - bei.height = alt_chain.size() ? it_prev->second.height + 1 : m_db->get_block_height(b.prev_id) + 1; - bool is_a_checkpoint; if(!m_checkpoints.check_block(bei.height, id, is_a_checkpoint)) { @@ -3176,6 +3252,7 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const if (version >= HF_VERSION_DYNAMIC_FEE) { median = m_current_block_cumul_weight_limit / 2; + const uint64_t blockchain_height = m_db->height(); already_generated_coins = blockchain_height ? m_db->get_block_already_generated_coins(blockchain_height - 1) : 0; if (!get_block_reward(median, 1, already_generated_coins, base_reward, version)) return false; @@ -3688,6 +3765,8 @@ leave: //TODO: why is this done? make sure that keeping invalid blocks makes sense. add_block_as_invalid(bl, id); MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions"); + MERROR_VER("tx_index " << tx_index << ", m_blocks_txs_check " << m_blocks_txs_check.size() << ":"); + for (const auto &h: m_blocks_txs_check) MERROR_VER(" " << h); bvc.m_verifivation_failed = true; return_tx_to_pool(txs); goto leave; @@ -3817,12 +3896,6 @@ leave: //------------------------------------------------------------------ bool Blockchain::prune_blockchain(uint32_t pruning_seed) { - uint8_t hf_version = m_hardfork->get_current_version(); - if (hf_version < 10) - { - MERROR("Most of the network will only be ready for pruned blockchains from v10, not pruning"); - return false; - } return m_db->prune_blockchain(pruning_seed); } //------------------------------------------------------------------ @@ -3872,6 +3945,8 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti LOG_PRINT_L3("Blockchain::" << __func__); + m_db->block_txn_start(false); + // when we reach this, the last hf version is not yet written to the db const uint64_t db_height = m_db->height(); const uint8_t hf_version = get_current_hard_fork_version(); @@ -3934,6 +4009,10 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti if (long_term_effective_median_block_weight) *long_term_effective_median_block_weight = m_long_term_effective_median_block_weight; + m_db->add_max_block_size(m_current_block_cumul_weight_limit); + + m_db->block_txn_stop(); + return true; } //------------------------------------------------------------------ @@ -4332,8 +4411,9 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete for (unsigned int j = 0; j < batches; j++, ++blockidx) { block &block = blocks[blockidx]; + crypto::hash block_hash; - if (!parse_and_validate_block_from_blob(it->block, block)) + if (!parse_and_validate_block_from_blob(it->block, block, block_hash)) return false; // check first block and skip all blocks if its not chained properly @@ -4346,7 +4426,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete return true; } } - if (have_block(get_block_hash(block))) + if (have_block(block_hash)) blocks_exist = true; std::advance(it, 1); @@ -4356,11 +4436,12 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete for (unsigned i = 0; i < extra && !blocks_exist; i++, blockidx++) { block &block = blocks[blockidx]; + crypto::hash block_hash; - if (!parse_and_validate_block_from_blob(it->block, block)) + if (!parse_and_validate_block_from_blob(it->block, block, block_hash)) return false; - if (have_block(get_block_hash(block))) + if (have_block(block_hash)) blocks_exist = true; std::advance(it, 1); @@ -4888,13 +4969,14 @@ void Blockchain::invalidate_block_template_cache() m_btc_valid = false; } -void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie) +void Blockchain::cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t pool_cookie) { MDEBUG("Setting block template cache"); m_btc = b; m_btc_address = address; m_btc_nonce = nonce; m_btc_difficulty = diff; + m_btc_height = height; m_btc_expected_reward = expected_reward; m_btc_pool_cookie = pool_cookie; m_btc_valid = true; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 2cd4dc31b..3588bbd1b 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -336,6 +336,7 @@ namespace cryptonote * @brief creates a new block to mine against * * @param b return-by-reference block to be filled in + * @param from_block optional block hash to start mining from (main chain tip if NULL) * @param miner_address address new coins for the block will go to * @param di return-by-reference tells the miner what the difficulty target is * @param height return-by-reference tells the miner what height it's mining against @@ -345,6 +346,7 @@ namespace cryptonote * @return true if block template filled in successfully, else false */ bool create_block_template(block& b, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + bool create_block_template(block& b, const crypto::hash *from_block, const account_public_address& miner_address, difficulty_type& di, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** * @brief checks if a block is known about with a given hash @@ -1094,6 +1096,7 @@ namespace cryptonote account_public_address m_btc_address; blobdata m_btc_nonce; difficulty_type m_btc_difficulty; + uint64_t m_btc_height; uint64_t m_btc_pool_cookie; uint64_t m_btc_expected_reward; bool m_btc_valid; @@ -1179,7 +1182,7 @@ namespace cryptonote * * @return false if the reorganization fails, otherwise true */ - bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::iterator>& alt_chain, bool discard_disconnected_chain); + bool switch_to_alternative_blockchain(std::list<blocks_ext_by_hash::const_iterator>& alt_chain, bool discard_disconnected_chain); /** * @brief removes the most recent block from the blockchain @@ -1233,6 +1236,18 @@ namespace cryptonote bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); /** + * @brief builds a list of blocks connecting a block to the main chain + * + * @param prev_id the block hash of the tip of the alt chain + * @param alt_chain the chain to be added to + * @param timestamps returns the timestamps of previous blocks + * @param bvc the block verification context for error return + * + * @return true on success, false otherwise + */ + bool build_alt_chain(const crypto::hash &prev_id, std::list<blocks_ext_by_hash::const_iterator>& alt_chain, std::vector<uint64_t> ×tamps, block_verification_context& bvc) const; + + /** * @brief gets the difficulty requirement for a new block on an alternate chain * * @param alt_chain the chain to be added to @@ -1240,7 +1255,7 @@ namespace cryptonote * * @return the difficulty requirement */ - difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::iterator>& alt_chain, block_extended_info& bei) const; + difficulty_type get_next_difficulty_for_alternative_chain(const std::list<blocks_ext_by_hash::const_iterator>& alt_chain, block_extended_info& bei) const; /** * @brief sanity checks a miner transaction before validating an entire block @@ -1400,7 +1415,7 @@ namespace cryptonote * * @return true unless start_height is greater than the current blockchain height */ - bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps); + bool complete_timestamps_vector(uint64_t start_height, std::vector<uint64_t>& timestamps) const; /** * @brief calculate the block weight limit for the next block to be added @@ -1464,6 +1479,6 @@ namespace cryptonote * * At some point, may be used to push an update to miners */ - void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t expected_reward, uint64_t pool_cookie); + void cache_block_template(const block &b, const cryptonote::account_public_address &address, const blobdata &nonce, const difficulty_type &diff, uint64_t height, uint64_t expected_reward, uint64_t pool_cookie); }; } // namespace cryptonote diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index da413bbe2..91dea4982 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -62,6 +62,9 @@ DISABLE_VS_WARNINGS(4355) #define BAD_SEMANTICS_TXES_MAX_SIZE 100 +// basically at least how many bytes the block itself serializes to without the miner tx +#define BLOCK_SIZE_SANITY_LEEWAY 100 + namespace cryptonote { const command_line::arg_descriptor<bool, false> arg_testnet_on = { @@ -1265,6 +1268,11 @@ namespace cryptonote return m_blockchain_storage.create_block_template(b, adr, diffic, height, expected_reward, ex_nonce); } //----------------------------------------------------------------------------------------------- + bool core::get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) + { + return m_blockchain_storage.create_block_template(b, prev_block, adr, diffic, height, expected_reward, ex_nonce); + } + //----------------------------------------------------------------------------------------------- bool core::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const { return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); @@ -1318,9 +1326,9 @@ namespace cryptonote return bce; } //----------------------------------------------------------------------------------------------- - bool core::handle_block_found(block& b) + bool core::handle_block_found(block& b, block_verification_context &bvc) { - block_verification_context bvc = boost::value_initialized<block_verification_context>(); + bvc = boost::value_initialized<block_verification_context>(); m_miner.pause(); std::vector<block_complete_entry> blocks; try @@ -1370,7 +1378,7 @@ namespace cryptonote m_pprotocol->relay_block(arg, exclude_context); } - return bvc.m_added_to_main_chain; + return true; } //----------------------------------------------------------------------------------------------- void core::on_synchronized() @@ -1417,22 +1425,26 @@ namespace cryptonote { TRY_ENTRY(); - // load json & DNS checkpoints every 10min/hour respectively, - // and verify them with respect to what blocks we already have - CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); - bvc = boost::value_initialized<block_verification_context>(); - if(block_blob.size() > get_max_block_size()) + + if (!check_incoming_block_size(block_blob)) { - LOG_PRINT_L1("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); bvc.m_verifivation_failed = true; return false; } + if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff) + MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit"); + + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + block lb; if (!b) { - if(!parse_and_validate_block_from_blob(block_blob, lb)) + crypto::hash block_hash; + if(!parse_and_validate_block_from_blob(block_blob, lb, block_hash)) { LOG_PRINT_L1("Failed to parse and validate new block"); bvc.m_verifivation_failed = true; @@ -1452,9 +1464,13 @@ namespace cryptonote // block_blob bool core::check_incoming_block_size(const blobdata& block_blob) const { - if(block_blob.size() > get_max_block_size()) + // note: we assume block weight is always >= block blob size, so we check incoming + // blob size against the block weight limit, which acts as a sanity check without + // having to parse/weigh first; in fact, since the block blob is the block header + // plus the tx hashes, the weight will typically be much larger than the blob size + if(block_blob.size() > m_blockchain_storage.get_current_cumulative_block_weight_limit() + BLOCK_SIZE_SANITY_LEEWAY) { - LOG_PRINT_L1("WRONG BLOCK BLOB, too big size " << block_blob.size() << ", rejected"); + LOG_PRINT_L1("WRONG BLOCK BLOB, sanity check failed on size " << block_blob.size() << ", rejected"); return false; } return true; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 356265dd6..2fcf26a17 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -195,10 +195,11 @@ namespace cryptonote * the network. * * @param b the block found + * @param bvc returns the block verification flags * * @return true if the block was added to the main chain, otherwise false */ - virtual bool handle_block_found( block& b); + virtual bool handle_block_found(block& b, block_verification_context &bvc); /** * @copydoc Blockchain::create_block_template @@ -206,6 +207,7 @@ namespace cryptonote * @note see Blockchain::create_block_template */ virtual bool get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); + virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** * @brief called when a transaction is relayed diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 32f0afceb..b7a50783a 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -48,6 +48,17 @@ #define MONERO_DEFAULT_LOG_CATEGORY "net.cn" #define MLOG_P2P_MESSAGE(x) MCINFO("net.p2p.msg", context << x) +#define MLOGIF_P2P_MESSAGE(init, test, x) \ + do { \ + const auto level = el::Level::Info; \ + const char *cat = "net.p2p.msg"; \ + if (ELPP->vRegistry()->allowed(level, cat)) { \ + init; \ + if (test) \ + el::base::Writer(level, __FILE__, __LINE__, ELPP_FUNC, el::base::DispatchAction::NormalLog).construct(cat) << x; \ + } \ + } while(0) + #define MLOG_PEER_STATE(x) \ MCINFO(MONERO_DEFAULT_LOG_CATEGORY, context << "[" << epee::string_tools::to_string_hex(context.m_pruning_seed) << "] state: " << x << " in state " << cryptonote::get_protocol_state_string(context.m_state)) @@ -418,7 +429,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_BLOCK (" << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -488,7 +499,7 @@ namespace cryptonote template<class t_core> int t_cryptonote_protocol_handler<t_core>::handle_notify_new_fluffy_block(int command, NOTIFY_NEW_FLUFFY_BLOCK::request& arg, cryptonote_connection_context& context) { - MLOG_P2P_MESSAGE("Received NOTIFY_NEW_FLUFFY_BLOCK (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); + MLOGIF_P2P_MESSAGE(crypto::hash hash; cryptonote::block b; bool ret = cryptonote::parse_and_validate_block_from_blob(arg.b.block, b, &hash);, ret, "Received NOTIFY_NEW_FLUFFY_BLOCK " << hash << " (height " << arg.current_blockchain_height << ", " << arg.b.txs.size() << " txes)"); if(context.m_state != cryptonote_connection_context::state_normal) return 1; if(!is_synchronized()) // can happen if a peer connection goes to normal but another thread still hasn't finished adding queued blocks @@ -858,6 +869,9 @@ namespace cryptonote int t_cryptonote_protocol_handler<t_core>::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& context) { MLOG_P2P_MESSAGE("Received NOTIFY_NEW_TRANSACTIONS (" << arg.txs.size() << " txes)"); + for (const auto &blob: arg.txs) + MLOGIF_P2P_MESSAGE(cryptonote::transaction tx; crypto::hash hash; bool ret = cryptonote::parse_and_validate_tx_from_blob(blob, tx, hash);, ret, "Including transaction " << hash); + if(context.m_state != cryptonote_connection_context::state_normal) return 1; @@ -995,7 +1009,8 @@ namespace cryptonote return 1; } - if(!parse_and_validate_block_from_blob(block_entry.block, b)) + crypto::hash block_hash; + if(!parse_and_validate_block_from_blob(block_entry.block, b, block_hash)) { LOG_ERROR_CCONTEXT("sent wrong block: failed to parse and validate block: " << epee::string_tools::buff_to_hex_nodelimer(block_entry.block) << ", dropping connection"); @@ -1014,7 +1029,6 @@ namespace cryptonote if (start_height == std::numeric_limits<uint64_t>::max()) start_height = boost::get<txin_gen>(b.miner_tx.vin[0]).height; - const crypto::hash block_hash = get_block_hash(b); auto req_it = context.m_requested_objects.find(block_hash); if(req_it == context.m_requested_objects.end()) { @@ -1121,13 +1135,13 @@ namespace cryptonote << ", we need " << previous_height); block new_block; - if (!parse_and_validate_block_from_blob(blocks.back().block, new_block)) + crypto::hash last_block_hash; + if (!parse_and_validate_block_from_blob(blocks.back().block, new_block, last_block_hash)) { MERROR(context << "Failed to parse block, but it should already have been parsed"); m_block_queue.remove_spans(span_connection_id, start_height); continue; } - const crypto::hash last_block_hash = cryptonote::get_block_hash(new_block); if (m_core.have_block(last_block_hash)) { const uint64_t subchain_height = start_height + blocks.size(); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index c9ec5109e..5901be662 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -551,11 +551,12 @@ bool t_rpc_command_executor::mining_status() { tools::msg_writer() << " Ignore battery: " << (mres.bg_ignore_battery ? "yes" : "no"); } - if (!mining_busy && mres.active) + if (!mining_busy && mres.active && mres.speed > 0 && mres.block_target > 0 && mres.difficulty > 0) { - uint64_t daily = 86400ull / mres.block_target * mres.block_reward; - uint64_t monthly = 86400ull / mres.block_target * 30.5 * mres.block_reward; - uint64_t yearly = 86400ull / mres.block_target * 356 * mres.block_reward; + double ratio = mres.speed * mres.block_target / mres.difficulty; + uint64_t daily = 86400ull / mres.block_target * mres.block_reward * ratio; + uint64_t monthly = 86400ull / mres.block_target * 30.5 * mres.block_reward * ratio; + uint64_t yearly = 86400ull / mres.block_target * 356 * mres.block_reward * ratio; tools::msg_writer() << "Expected: " << cryptonote::print_money(daily) << " monero daily, " << cryptonote::print_money(monthly) << " monero monthly, " << cryptonote::print_money(yearly) << " yearly"; } @@ -2235,7 +2236,7 @@ bool t_rpc_command_executor::check_blockchain_pruning() if (res.pruning_seed) { - tools::success_msg_writer() << "Blockchain pruning checked"; + tools::success_msg_writer() << "Blockchain is pruned"; } else { diff --git a/src/device/device.hpp b/src/device/device.hpp index 2e485b1d6..866e2c676 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -76,6 +76,7 @@ namespace hw { class i_device_callback { public: virtual void on_button_request(uint64_t code=0) {} + virtual void on_button_pressed() {} virtual boost::optional<epee::wipeable_string> on_pin_request() { return boost::none; } virtual boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) { return boost::none; } virtual void on_progress(const device_progress& event) {} diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index f07e0eaae..721bed9ca 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -85,7 +85,18 @@ namespace hw { void device_io_hid::connect(void *params) { hid_conn_params *p = (struct hid_conn_params*)params; - this->connect(p->vid, p->pid, p->interface_number, p->usage_page); + if (!this->connect(p->vid, p->pid, p->interface_number, p->usage_page)) { + ASSERT_X(false, "No device found"); + } + } + + void device_io_hid::connect(const std::vector<hid_conn_params> &hcpV) { + for (auto p: hcpV) { + if (this->connect(p.vid, p.pid, p.interface_number, p.usage_page)) { + return; + } + } + ASSERT_X(false, "No device found"); } hid_device_info *device_io_hid::find_device(hid_device_info *devices_list, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { @@ -124,14 +135,17 @@ namespace hw { return result; } - void device_io_hid::connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { + hid_device *device_io_hid::connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page) { hid_device_info *hwdev_info_list; hid_device *hwdev; this->disconnect(); hwdev_info_list = hid_enumerate(vid, pid); - ASSERT_X(hwdev_info_list, "Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device)); + if (!hwdev_info_list) { + MDEBUG("Unable to enumerate device "+std::to_string(vid)+":"+std::to_string(vid)+ ": "+ safe_hid_error(this->usb_device)); + return NULL; + } hwdev = NULL; if (hid_device_info *device = find_device(hwdev_info_list, interface_number, usage_page)) { hwdev = hid_open_path(device->path); @@ -141,6 +155,7 @@ namespace hw { this->usb_vid = vid; this->usb_pid = pid; this->usb_device = hwdev; + return hwdev; } diff --git a/src/device/device_io_hid.hpp b/src/device/device_io_hid.hpp index ed22058d6..96cb8d993 100644 --- a/src/device/device_io_hid.hpp +++ b/src/device/device_io_hid.hpp @@ -98,7 +98,8 @@ namespace hw { void init(); void connect(void *params); - void connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page); + void connect(const std::vector<hid_conn_params> &conn); + hid_device *connect(unsigned int vid, unsigned int pid, boost::optional<int> interface_number, boost::optional<unsigned short> usage_page); bool connected() const; int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len, bool user_input); void disconnect(); diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index d6033e189..200370564 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -389,10 +389,15 @@ namespace hw { MDEBUG( "Device "<<this->id <<" HIDUSB inited"); return true; } + + static const std::vector<hw::io::hid_conn_params> known_devices { + {0x2c97, 0x0001, 0, 0xffa0}, + {0x2c97, 0x0004, 0, 0xffa0}, + }; bool device_ledger::connect(void) { this->disconnect(); - hw_device.connect(0x2c97, 0x0001, 0, 0xffa0); + hw_device.connect(known_devices); this->reset(); #ifdef DEBUG_HWDEVICE cryptonote::account_public_address pubkey; diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index b4a80cf2c..b1022dd9c 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -137,7 +137,7 @@ namespace trezor { } auto current_time = std::chrono::steady_clock::now(); - if (current_time - m_last_live_refresh_time <= std::chrono::seconds(20)) + if (current_time - m_last_live_refresh_time <= std::chrono::minutes(5)) { continue; } diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index f3d15c5e2..58abde1d1 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -43,7 +43,7 @@ namespace trezor { const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080}; - device_trezor_base::device_trezor_base(): m_callback(nullptr) { + device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success) { #ifdef WITH_TREZOR_DEBUGGING m_debug = false; #endif @@ -275,6 +275,12 @@ namespace trezor { // Later if needed this generic message handler can be replaced by a pointer to // a protocol message handler which by default points to the device class which implements // the default handler. + + if (m_last_msg_type == messages::MessageType_ButtonRequest){ + on_button_pressed(); + } + m_last_msg_type = input.m_type; + switch(input.m_type){ case messages::MessageType_ButtonRequest: on_button_request(input, dynamic_cast<const messages::common::ButtonRequest*>(input.m_msg.get())); @@ -413,6 +419,11 @@ namespace trezor { resp = read_raw(); } + void device_trezor_base::on_button_pressed() + { + TREZOR_CALLBACK(on_button_pressed); + } + void device_trezor_base::on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg) { MDEBUG("on_pin_request"); diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 8c3c14b29..c106d2099 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -98,6 +98,7 @@ namespace trezor { std::shared_ptr<messages::management::Features> m_features; // features from the last device reset boost::optional<epee::wipeable_string> m_pin; boost::optional<epee::wipeable_string> m_passphrase; + messages::MessageType m_last_msg_type; cryptonote::network_type network_type; @@ -311,6 +312,7 @@ namespace trezor { // Protocol callbacks void on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg); + void on_button_pressed(); void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); void on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg); void on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg); diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 991ba3395..dd9b0b52f 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -223,6 +223,11 @@ namespace trezor{ msg = msg_wrap; } + static void assert_port_number(uint32_t port) + { + CHECK_AND_ASSERT_THROW_MES(port >= 1024 && port < 65535, "Invalid port number: " << port); + } + Transport::Transport(): m_open_counter(0) { } @@ -263,6 +268,29 @@ namespace trezor{ const char * BridgeTransport::PATH_PREFIX = "bridge:"; + BridgeTransport::BridgeTransport( + boost::optional<std::string> device_path, + boost::optional<std::string> bridge_host): + m_device_path(device_path), + m_bridge_host(bridge_host ? bridge_host.get() : DEFAULT_BRIDGE), + m_response(boost::none), + m_session(boost::none), + m_device_info(boost::none) + { + const char *env_bridge_port = nullptr; + if (!bridge_host && (env_bridge_port = getenv("TREZOR_BRIDGE_PORT")) != nullptr) + { + uint16_t bridge_port; + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::get_xtype_from_string(bridge_port, env_bridge_port), "Invalid bridge port: " << env_bridge_port); + assert_port_number(bridge_port); + + m_bridge_host = std::string("127.0.0.1:") + boost::lexical_cast<std::string>(env_bridge_port); + MDEBUG("Bridge host: " << m_bridge_host); + } + + m_http_client.set_server(m_bridge_host, boost::none, epee::net_utils::ssl_support_t::e_ssl_support_disabled); + } + std::string BridgeTransport::get_path() const { if (!m_device_path){ return ""; @@ -401,28 +429,40 @@ namespace trezor{ const char * UdpTransport::DEFAULT_HOST = "127.0.0.1"; const int UdpTransport::DEFAULT_PORT = 21324; + static void parse_udp_path(std::string &host, int &port, std::string path) + { + if (boost::starts_with(path, UdpTransport::PATH_PREFIX)) + { + path = path.substr(strlen(UdpTransport::PATH_PREFIX)); + } + + auto delim = path.find(':'); + if (delim == std::string::npos) { + host = path; + } else { + host = path.substr(0, delim); + port = std::stoi(path.substr(delim + 1)); + } + } + UdpTransport::UdpTransport(boost::optional<std::string> device_path, boost::optional<std::shared_ptr<Protocol>> proto) : m_io_service(), m_deadline(m_io_service) { + m_device_host = DEFAULT_HOST; m_device_port = DEFAULT_PORT; + const char *env_trezor_path = nullptr; + if (device_path) { - const std::string device_str = device_path.get(); - auto delim = device_str.find(':'); - if (delim == std::string::npos) { - m_device_host = device_str; - } else { - m_device_host = device_str.substr(0, delim); - m_device_port = std::stoi(device_str.substr(delim + 1)); - } + parse_udp_path(m_device_host, m_device_port, device_path.get()); + } else if ((env_trezor_path = getenv("TREZOR_PATH")) != nullptr && boost::starts_with(env_trezor_path, UdpTransport::PATH_PREFIX)){ + parse_udp_path(m_device_host, m_device_port, std::string(env_trezor_path)); + MDEBUG("Applied TREZOR_PATH: " << m_device_host << ":" << m_device_port); } else { m_device_host = DEFAULT_HOST; } - if (m_device_port <= 1024 || m_device_port > 65535){ - throw std::invalid_argument("Port number invalid"); - } - + assert_port_number((uint32_t)m_device_port); if (m_device_host != "localhost" && m_device_host != DEFAULT_HOST){ throw std::invalid_argument("Local endpoint allowed only"); } diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp index 2945b3184..cde862547 100644 --- a/src/device_trezor/trezor/transport.hpp +++ b/src/device_trezor/trezor/transport.hpp @@ -163,15 +163,7 @@ namespace trezor { public: BridgeTransport( boost::optional<std::string> device_path = boost::none, - boost::optional<std::string> bridge_host = boost::none): - m_device_path(device_path), - m_bridge_host(bridge_host ? bridge_host.get() : DEFAULT_BRIDGE), - m_response(boost::none), - m_session(boost::none), - m_device_info(boost::none) - { - m_http_client.set_server(m_bridge_host, boost::none, epee::net_utils::ssl_support_t::e_ssl_support_disabled); - } + boost::optional<std::string> bridge_host = boost::none); virtual ~BridgeTransport() = default; diff --git a/src/lmdb/database.cpp b/src/lmdb/database.cpp index c6b244671..ccab1902a 100644 --- a/src/lmdb/database.cpp +++ b/src/lmdb/database.cpp @@ -46,7 +46,7 @@ namespace lmdb { namespace { - constexpr const std::size_t max_resize = 1 * 1024 * 1024 * 1024; // 1 GB + constexpr const mdb_size_t max_resize = 1 * 1024 * 1024 * 1024; // 1 GB void acquire_context(context& ctx) noexcept { while (ctx.lock.test_and_set()); @@ -136,7 +136,7 @@ namespace lmdb MDB_envinfo info{}; MONERO_LMDB_CHECK(mdb_env_info(handle(), &info)); - const std::size_t resize = std::min(info.me_mapsize, max_resize); + const mdb_size_t resize = std::min(info.me_mapsize, max_resize); const int err = mdb_env_set_mapsize(handle(), info.me_mapsize + resize); ctx.lock.clear(); if (err) diff --git a/src/lmdb/value_stream.cpp b/src/lmdb/value_stream.cpp index 1024deb06..604140e47 100644 --- a/src/lmdb/value_stream.cpp +++ b/src/lmdb/value_stream.cpp @@ -36,9 +36,9 @@ namespace lmdb { namespace stream { - std::size_t count(MDB_cursor* cur) + mdb_size_t count(MDB_cursor* cur) { - std::size_t out = 0; + mdb_size_t out = 0; if (cur) { const int rc = mdb_cursor_count(cur, &out); diff --git a/src/lmdb/value_stream.h b/src/lmdb/value_stream.h index c9977221f..01090aa67 100644 --- a/src/lmdb/value_stream.h +++ b/src/lmdb/value_stream.h @@ -43,7 +43,7 @@ namespace lmdb \throw std::system_error if unexpected LMDB error. \return 0 if `cur == nullptr`, otherwise count of values at current key. */ - std::size_t count(MDB_cursor* cur); + mdb_size_t count(MDB_cursor* cur); /*! Calls `mdb_cursor_get` and does some error checking. diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f0aef384f..7d13b3216 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -1259,7 +1259,7 @@ namespace nodetool } } else - random_index = crypto::rand<size_t>() % filtered.size(); + random_index = crypto::rand_idx(filtered.size()); CHECK_AND_ASSERT_MES(random_index < filtered.size(), false, "random_index < filtered.size() failed!!"); random_index = filtered[random_index]; @@ -1313,7 +1313,7 @@ namespace nodetool return true; size_t try_count = 0; - size_t current_index = crypto::rand<size_t>()%m_seed_nodes.size(); + size_t current_index = crypto::rand_idx(m_seed_nodes.size()); const net_server& server = m_network_zones.at(epee::net_utils::zone::public_).m_net_server; while(true) { diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index ebe0268d8..52814af94 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -398,7 +398,7 @@ namespace nodetool return false; } - size_t random_index = crypto::rand<size_t>() % m_peers_gray.size(); + size_t random_index = crypto::rand_idx(m_peers_gray.size()); peers_indexed::index<by_time>::type& by_time_index = m_peers_gray.get<by_time>(); pe = *epee::misc_utils::move_it_backward(--by_time_index.end(), random_index); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 8c201097e..bd1455c43 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -91,7 +91,7 @@ namespace cryptonote command_line::add_arg(desc, arg_rpc_ssl); command_line::add_arg(desc, arg_rpc_ssl_private_key); command_line::add_arg(desc, arg_rpc_ssl_certificate); - command_line::add_arg(desc, arg_rpc_ssl_allowed_certificates); + command_line::add_arg(desc, arg_rpc_ssl_ca_certificates); command_line::add_arg(desc, arg_rpc_ssl_allowed_fingerprints); command_line::add_arg(desc, arg_rpc_ssl_allow_any_cert); command_line::add_arg(desc, arg_bootstrap_daemon_address); @@ -149,36 +149,38 @@ namespace cryptonote if (rpc_config->login) http_login.emplace(std::move(rpc_config->login->username), std::move(rpc_config->login->password).password()); - epee::net_utils::ssl_support_t ssl_support; - const std::string ssl = command_line::get_arg(vm, arg_rpc_ssl); - if (!epee::net_utils::ssl_support_from_string(ssl_support, ssl)) + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect; + if (command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert)) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else { - MFATAL("Invalid RPC SSL support: " << ssl); - return false; + std::string ssl_ca_path = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); + const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() }; + std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + + if (!ssl_ca_path.empty() || !ssl_allowed_fingerprints.empty()) + ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(ssl_ca_path)}; } - const std::string ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); - const std::string ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); - const std::vector<std::string> ssl_allowed_certificate_paths = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates); - std::list<std::string> ssl_allowed_certificates; - for (const std::string &path: ssl_allowed_certificate_paths) + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + command_line::get_arg(vm, arg_rpc_ssl_private_key), command_line::get_arg(vm, arg_rpc_ssl_certificate) + }; + + // user specified CA file or fingeprints implies enabled SSL by default + if (ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) { - ssl_allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, ssl_allowed_certificates.back())) + const std::string ssl = command_line::get_arg(vm, arg_rpc_ssl); + if (!epee::net_utils::ssl_support_from_string(ssl_options.support, ssl)) { - MERROR("Failed to load certificate: " << path); - ssl_allowed_certificates.back() = std::string(); + MFATAL("Invalid RPC SSL support: " << ssl); + return false; } } - const std::vector<std::string> ssl_allowed_fingerprint_strings = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); - std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ ssl_allowed_fingerprint_strings.size() }; - std::transform(ssl_allowed_fingerprint_strings.begin(), ssl_allowed_fingerprint_strings.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); - const bool ssl_allow_any_cert = command_line::get_arg(vm, arg_rpc_ssl_allow_any_cert); - auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; return epee::http_server_impl_base<core_rpc_server, connection_context>::init( - rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), - ssl_support, std::make_pair(ssl_private_key, ssl_certificate), std::move(ssl_allowed_certificates), std::move(ssl_allowed_fingerprints), ssl_allow_any_cert + rng, std::move(port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -200,7 +202,9 @@ namespace cryptonote if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_HEIGHT>(invoke_http_mode::JON, "/getheight", req, res, r)) return r; - res.height = m_core.get_current_blockchain_height(); + crypto::hash hash; + m_core.get_blockchain_top(res.height, hash); + res.hash = string_tools::pod_to_hex(hash); res.status = CORE_RPC_STATUS_OK; return true; } @@ -247,7 +251,6 @@ namespace cryptonote res.cumulative_difficulty, res.wide_cumulative_difficulty, res.cumulative_difficulty_top64); res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); - res.status = CORE_RPC_STATUS_OK; res.start_time = restricted ? 0 : (uint64_t)m_core.get_start_time(); res.free_space = restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); res.offline = m_core.offline(); @@ -265,6 +268,8 @@ namespace cryptonote res.database_size = round_up(res.database_size, 5ull* 1024 * 1024 * 1024); res.update_available = restricted ? false : m_core.is_update_available(); res.version = restricted ? "" : MONERO_VERSION; + + res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -491,6 +496,7 @@ namespace cryptonote cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request req_bin; req_bin.outputs = req.outputs; + req_bin.get_txid = req.get_txid; cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response res_bin; if(!m_core.get_outs(req_bin, res_bin)) { @@ -646,30 +652,61 @@ namespace cryptonote e.prunable_hash = epee::string_tools::pod_to_hex(std::get<2>(tx)); if (req.split || req.prune || std::get<3>(tx).empty()) { + // use splitted form with pruned and prunable (filled only when prune=false and the daemon has it), leaving as_hex as empty e.pruned_as_hex = string_tools::buff_to_hex_nodelimer(std::get<1>(tx)); if (!req.prune) e.prunable_as_hex = string_tools::buff_to_hex_nodelimer(std::get<3>(tx)); - } - else - { - cryptonote::blobdata tx_data; - if (req.prune) - tx_data = std::get<1>(tx); - else - tx_data = std::get<1>(tx) + std::get<3>(tx); - e.as_hex = string_tools::buff_to_hex_nodelimer(tx_data); - if (req.decode_as_json && !tx_data.empty()) + if (req.decode_as_json) { + cryptonote::blobdata tx_data; cryptonote::transaction t; - if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + if (req.prune || std::get<3>(tx).empty()) { - if (req.prune) + // decode pruned tx to JSON + tx_data = std::get<1>(tx); + if (cryptonote::parse_and_validate_tx_base_from_blob(tx_data, t)) { pruned_transaction pruned_tx{t}; e.as_json = obj_to_json_str(pruned_tx); } else + { + res.status = "Failed to parse and validate pruned tx from blob"; + return true; + } + } + else + { + // decode full tx to JSON + tx_data = std::get<1>(tx) + std::get<3>(tx); + if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + { e.as_json = obj_to_json_str(t); + } + else + { + res.status = "Failed to parse and validate tx from blob"; + return true; + } + } + } + } + else + { + // use non-splitted form, leaving pruned_as_hex and prunable_as_hex as empty + cryptonote::blobdata tx_data = std::get<1>(tx) + std::get<3>(tx); + e.as_hex = string_tools::buff_to_hex_nodelimer(tx_data); + if (req.decode_as_json) + { + cryptonote::transaction t; + if (cryptonote::parse_and_validate_tx_from_blob(tx_data, t)) + { + e.as_json = obj_to_json_str(t); + } + else + { + res.status = "Failed to parse and validate tx from blob"; + return true; } } } @@ -939,6 +976,7 @@ namespace cryptonote const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); res.is_background_mining_enabled = lMiner.get_is_background_mining_enabled(); + store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64); res.block_target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; if ( lMiner.is_mining() ) { @@ -1222,7 +1260,17 @@ namespace cryptonote cryptonote::blobdata blob_reserve; blob_reserve.resize(req.reserve_size, 0); cryptonote::difficulty_type wdiff; - if(!m_core.get_block_template(b, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) + crypto::hash prev_block; + if (!req.prev_block.empty()) + { + if (!epee::string_tools::hex_to_pod(req.prev_block, prev_block)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Invalid prev_block"; + return false; + } + } + if(!m_core.get_block_template(b, req.prev_block.empty() ? NULL : &prev_block, info.address, wdiff, res.height, res.expected_reward, blob_reserve)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; @@ -1308,7 +1356,8 @@ namespace cryptonote return false; } - if(!m_core.handle_block_found(b)) + block_verification_context bvc; + if(!m_core.handle_block_found(b, bvc)) { error_resp.code = CORE_RPC_ERROR_CODE_BLOCK_NOT_ACCEPTED; error_resp.message = "Block not accepted"; @@ -1340,15 +1389,17 @@ namespace cryptonote template_req.reserve_size = 1; template_req.wallet_address = req.wallet_address; + template_req.prev_block = req.prev_block; submit_req.push_back(boost::value_initialized<std::string>()); res.height = m_core.get_blockchain_storage().get_current_blockchain_height(); - bool r; + bool r = CORE_RPC_STATUS_OK; for(size_t i = 0; i < req.amount_of_blocks; i++) { r = on_getblocktemplate(template_req, template_res, error_resp, ctx); res.status = template_res.status; + template_req.prev_block.clear(); if (!r) return false; @@ -1366,6 +1417,7 @@ namespace cryptonote error_resp.message = "Wrong block blob"; return false; } + b.nonce = req.starting_nonce; miner::find_nonce_for_given_block(b, template_res.difficulty, template_res.height); submit_req.front() = string_tools::buff_to_hex_nodelimer(block_to_blob(b)); @@ -1374,6 +1426,8 @@ namespace cryptonote if (!r) return false; + res.blocks.push_back(epee::string_tools::pod_to_hex(get_block_hash(b))); + template_req.prev_block = res.blocks.back(); res.height = template_res.height; } @@ -1718,67 +1772,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - PERF_TIMER(on_get_info_json); - bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_GET_INFO>(invoke_http_mode::JON_RPC, "get_info", req, res, r)) - { - res.bootstrap_daemon_address = m_bootstrap_daemon_address; - crypto::hash top_hash; - m_core.get_blockchain_top(res.height_without_bootstrap, top_hash); - ++res.height_without_bootstrap; // turn top block height into blockchain height - res.was_bootstrap_ever_used = true; - return r; - } - - const bool restricted = m_restricted && ctx; - - crypto::hash top_hash; - m_core.get_blockchain_top(res.height, top_hash); - ++res.height; // turn top block height into blockchain height - res.top_block_hash = string_tools::pod_to_hex(top_hash); - res.target_height = m_core.get_target_blockchain_height(); - store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), - res.difficulty, res.wide_difficulty, res.difficulty_top64); - res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; - res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase - res.tx_pool_size = m_core.get_pool_transactions_count(); - res.alt_blocks_count = restricted ? 0 : m_core.get_blockchain_storage().get_alternative_blocks_count(); - uint64_t total_conn = restricted ? 0 : m_p2p.get_public_connections_count(); - res.outgoing_connections_count = restricted ? 0 : m_p2p.get_public_outgoing_connections_count(); - res.incoming_connections_count = restricted ? 0 : (total_conn - res.outgoing_connections_count); - res.rpc_connections_count = restricted ? 0 : get_connections_count(); - res.white_peerlist_size = restricted ? 0 : m_p2p.get_public_white_peers_count(); - res.grey_peerlist_size = restricted ? 0 : m_p2p.get_public_gray_peers_count(); - - cryptonote::network_type net_type = nettype(); - res.mainnet = net_type == MAINNET; - res.testnet = net_type == TESTNET; - res.stagenet = net_type == STAGENET; - res.nettype = net_type == MAINNET ? "mainnet" : net_type == TESTNET ? "testnet" : net_type == STAGENET ? "stagenet" : "fakechain"; - - store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1), - res.cumulative_difficulty, res.wide_cumulative_difficulty, res.cumulative_difficulty_top64); - res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit(); - res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median(); - res.status = CORE_RPC_STATUS_OK; - res.start_time = restricted ? 0 : (uint64_t)m_core.get_start_time(); - res.free_space = restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); - res.offline = m_core.offline(); - res.bootstrap_daemon_address = restricted ? "" : m_bootstrap_daemon_address; - res.height_without_bootstrap = restricted ? 0 : res.height; - if (restricted) - res.was_bootstrap_ever_used = false; - else - { - boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); - res.was_bootstrap_ever_used = m_was_bootstrap_ever_used; - } - res.database_size = m_core.get_blockchain_storage().get_db().get_database_size(); - if (restricted) - res.database_size = round_up(res.database_size, 5ull * 1024 * 1024 * 1024); - res.update_available = restricted ? false : m_core.is_update_available(); - res.version = restricted ? "" : MONERO_VERSION; - return true; + return on_get_info(req, res, ctx); } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_hard_fork_info(const COMMAND_RPC_HARD_FORK_INFO::request& req, COMMAND_RPC_HARD_FORK_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) @@ -2436,9 +2430,9 @@ namespace cryptonote , "" }; - const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_certificates = { - "rpc-ssl-allowed-certificates" - , "List of paths to PEM format certificates of allowed peers (all allowed if empty)" + const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_ssl_ca_certificates = { + "rpc-ssl-ca-certificates" + , "Path to file containing concatenated PEM format certificate(s) to replace system CA(s)." }; const command_line::arg_descriptor<std::vector<std::string>> core_rpc_server::arg_rpc_ssl_allowed_fingerprints = { @@ -2448,7 +2442,7 @@ namespace cryptonote const command_line::arg_descriptor<bool> core_rpc_server::arg_rpc_ssl_allow_any_cert = { "rpc-ssl-allow-any-cert" - , "Allow any peer certificate, rather than just those on the allowed list" + , "Allow any peer certificate" , false }; diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 8f5d83f1b..a42ca2494 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -60,7 +60,7 @@ namespace cryptonote static const command_line::arg_descriptor<std::string> arg_rpc_ssl; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key; static const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate; - static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates; + static const command_line::arg_descriptor<std::string> arg_rpc_ssl_ca_certificates; static const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints; static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address; diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1f14267f6..d2aba8d67 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -102,11 +102,13 @@ namespace cryptonote uint64_t height; std::string status; bool untrusted; + std::string hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) KV_SERIALIZE(status) KV_SERIALIZE(untrusted) + KV_SERIALIZE(hash) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -528,9 +530,11 @@ namespace cryptonote struct request_t { std::vector<get_outputs_out> outputs; + bool get_txid; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(outputs) + KV_SERIALIZE(get_txid) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -821,6 +825,9 @@ namespace cryptonote uint8_t bg_target; uint32_t block_target; uint64_t block_reward; + uint64_t difficulty; + std::string wide_difficulty; + uint64_t difficulty_top64; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -836,6 +843,9 @@ namespace cryptonote KV_SERIALIZE(bg_target) KV_SERIALIZE(block_target) KV_SERIALIZE(block_reward) + KV_SERIALIZE(difficulty) + KV_SERIALIZE(wide_difficulty) + KV_SERIALIZE(difficulty_top64) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -896,10 +906,12 @@ namespace cryptonote { uint64_t reserve_size; //max 255 bytes std::string wallet_address; + std::string prev_block; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(reserve_size) KV_SERIALIZE(wallet_address) + KV_SERIALIZE(prev_block) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -956,10 +968,14 @@ namespace cryptonote { uint64_t amount_of_blocks; std::string wallet_address; + std::string prev_block; + uint32_t starting_nonce; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount_of_blocks) KV_SERIALIZE(wallet_address) + KV_SERIALIZE(prev_block) + KV_SERIALIZE_OPT(starting_nonce, (uint32_t)0) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -967,10 +983,12 @@ namespace cryptonote struct response_t { uint64_t height; + std::vector<std::string> blocks; std::string status; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) + KV_SERIALIZE(blocks) KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 8974bd1e0..2e134931f 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -235,6 +235,7 @@ namespace const char* USAGE_MMS_AUTO_CONFIG("mms auto_config <auto_config_token>"); const char* USAGE_PRINT_RING("print_ring <key_image> | <txid>"); const char* USAGE_SET_RING("set_ring <filename> | ( <key_image> absolute|relative <index> [<index>...] )"); + const char* USAGE_UNSET_RING("unset_ring <txid> | ( <key_image> [<key_image>...] )"); const char* USAGE_SAVE_KNOWN_RINGS("save_known_rings"); const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent <amount>/<offset> | <filename> [add]"); const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent <amount>/<offset>"); @@ -242,6 +243,8 @@ namespace const char* USAGE_FREEZE("freeze <key_image>"); const char* USAGE_THAW("thaw <key_image>"); const char* USAGE_FROZEN("frozen <key_image>"); + const char* USAGE_NET_STATS("net_stats"); + const char* USAGE_WELCOME("welcome"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command>]"); @@ -1870,6 +1873,38 @@ bool simple_wallet::set_ring(const std::vector<std::string> &args) return true; } +bool simple_wallet::unset_ring(const std::vector<std::string> &args) +{ + crypto::hash txid; + std::vector<crypto::key_image> key_images; + + if (args.size() < 1) + { + PRINT_USAGE(USAGE_UNSET_RING); + return true; + } + + key_images.resize(args.size()); + for (size_t i = 0; i < args.size(); ++i) + { + if (!epee::string_tools::hex_to_pod(args[i], key_images[i])) + { + fail_msg_writer() << tr("Invalid key image or txid"); + return true; + } + } + static_assert(sizeof(crypto::hash) == sizeof(crypto::key_image), "hash and key_image must have the same size"); + memcpy(&txid, &key_images[0], sizeof(txid)); + + if (!m_wallet->unset_ring(key_images) && !m_wallet->unset_ring(txid)) + { + fail_msg_writer() << tr("failed to unset ring"); + return true; + } + + return true; +} + bool simple_wallet::blackball(const std::vector<std::string> &args) { uint64_t amount = std::numeric_limits<uint64_t>::max(), offset, num_offsets; @@ -2098,6 +2133,31 @@ bool simple_wallet::frozen(const std::vector<std::string> &args) return true; } +bool simple_wallet::net_stats(const std::vector<std::string> &args) +{ + message_writer() << std::to_string(m_wallet->get_bytes_sent()) + tr(" bytes sent"); + message_writer() << std::to_string(m_wallet->get_bytes_received()) + tr(" bytes received"); + return true; +} + +bool simple_wallet::welcome(const std::vector<std::string> &args) +{ + message_writer() << tr("Welcome to Monero, the private cryptocurrency."); + message_writer() << ""; + message_writer() << tr("Monero, like Bitcoin, is a cryptocurrency. That is, it is digital money."); + message_writer() << tr("Unlike Bitcoin, your Monero transactions and balance stay private, and not visible to the world by default."); + message_writer() << tr("However, you have the option of making those available to select parties, if you choose to."); + message_writer() << ""; + message_writer() << tr("Monero protects your privacy on the blockchain, and while Monero strives to improve all the time,"); + message_writer() << tr("no privacy technology can be 100% perfect, Monero included."); + message_writer() << tr("Monero cannot protect you from malware, and it may not be as effective as we hope against powerful adversaries."); + message_writer() << tr("Flaws in Monero may be discovered in the future, and attacks may be developed to peek under some"); + message_writer() << tr("of the layers of privacy Monero provides. Be safe and practice defense in depth."); + message_writer() << ""; + message_writer() << tr("Welcome to Monero and financial privacy. For more information, see https://getmonero.org/"); + return true; +} + bool simple_wallet::version(const std::vector<std::string> &args) { message_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; @@ -2592,6 +2652,31 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::set_setup_background_mining(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + tools::wallet2::BackgroundMiningSetupType setup = tools::wallet2::BackgroundMiningMaybe; + if (args[1] == "yes" || args[1] == "1") + setup = tools::wallet2::BackgroundMiningYes; + else if (args[1] == "no" || args[1] == "0") + setup = tools::wallet2::BackgroundMiningNo; + else + { + fail_msg_writer() << tr("invalid argument: must be either 1/yes or 0/no"); + return true; + } + m_wallet->setup_background_mining(setup); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + if (setup == tools::wallet2::BackgroundMiningYes) + start_background_mining(); + else + stop_background_mining(); + } + return true; +} + bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -3069,6 +3154,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::set_ring, this, _1), tr(USAGE_SET_RING), tr("Set the ring used for a given key image, so it can be reused in a fork")); + m_cmd_binder.set_handler("unset_ring", + boost::bind(&simple_wallet::unset_ring, this, _1), + tr(USAGE_UNSET_RING), + tr("Unsets the ring used for a given key image or transaction")); m_cmd_binder.set_handler("save_known_rings", boost::bind(&simple_wallet::save_known_rings, this, _1), tr(USAGE_SAVE_KNOWN_RINGS), @@ -3097,6 +3186,14 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::frozen, this, _1), tr(USAGE_FROZEN), tr("Checks whether a given output is currently frozen by key image")); + m_cmd_binder.set_handler("net_stats", + boost::bind(&simple_wallet::net_stats, this, _1), + tr(USAGE_NET_STATS), + tr("Prints simple network stats")); + m_cmd_binder.set_handler("welcome", + boost::bind(&simple_wallet::welcome, this, _1), + tr(USAGE_WELCOME), + tr("Prints basic info about Monero for first time users")); m_cmd_binder.set_handler("version", boost::bind(&simple_wallet::version, this, _1), tr(USAGE_VERSION), @@ -3125,6 +3222,13 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) case tools::wallet2::AskPasswordOnAction: ask_password_string = "action"; break; case tools::wallet2::AskPasswordToDecrypt: ask_password_string = "decrypt"; break; } + std::string setup_background_mining_string = "invalid"; + switch (m_wallet->setup_background_mining()) + { + case tools::wallet2::BackgroundMiningMaybe: setup_background_mining_string = "maybe"; break; + case tools::wallet2::BackgroundMiningYes: setup_background_mining_string = "yes"; break; + case tools::wallet2::BackgroundMiningNo: setup_background_mining_string = "no"; break; + } success_msg_writer() << "seed = " << seed_language; success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members(); @@ -3151,6 +3255,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "segregation-height = " << m_wallet->segregation_height(); success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs(); success_msg_writer() << "track-uses = " << m_wallet->track_uses(); + success_msg_writer() << "setup-background-mining = " << setup_background_mining_string + tr(" (set this to support the network and to get a chance to receive new monero)"); success_msg_writer() << "device_name = " << m_wallet->device_name(); return true; } @@ -3208,6 +3313,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer")); CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); } fail_msg_writer() << tr("set: unrecognized argument(s)"); @@ -3403,10 +3509,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; epee::wipeable_string multisig_keys; + epee::wipeable_string password; if (!handle_command_line(vm)) return false; + bool welcome = false; + if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_device.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_spend_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_multisig_keys.empty()) + (!m_generate_from_json.empty()) > 1) { fail_msg_writer() << tr("can't specify more than one of --generate-new-wallet=\"wallet_name\", --wallet-file=\"wallet_name\", --generate-from-view-key=\"wallet_name\", --generate-from-spend-key=\"wallet_name\", --generate-from-keys=\"wallet_name\", --generate-from-multisig-keys=\"wallet_name\", --generate-from-json=\"jsonfilename\" and --generate-from-device=\"wallet_name\""); @@ -3510,7 +3619,6 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } } - epee::wipeable_string password; if (!m_generate_from_view_key.empty()) { m_wallet_file = m_generate_from_view_key; @@ -3565,6 +3673,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_spend_key.empty()) { @@ -3585,6 +3694,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, m_recovery_key, true, false, ""); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_keys.empty()) { @@ -3662,6 +3772,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } // Asks user for all the data required to merge secret keys from multisig wallets into one master wallet, which then gets full control of the multisig wallet. The resulting wallet will be the same as any other regular wallet. @@ -3795,6 +3906,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; } else if (!m_generate_from_json.empty()) @@ -3821,6 +3933,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto r = new_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); password = *r; + welcome = true; // if no block_height is specified, assume its a new account and start it "now" if(m_wallet->get_refresh_from_block_height() == 0) { { @@ -3852,6 +3965,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) 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")); password = *r; + welcome = true; } if (m_restoring && m_generate_from_json.empty() && m_generate_from_device.empty()) @@ -3955,8 +4069,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("can't specify --subaddress-lookahead and --wallet-file at the same time"); return false; } - bool r = open_wallet(vm); + auto r = open_wallet(vm); CHECK_AND_ASSERT_MES(r, false, tr("failed to open account")); + password = *r; } if (!m_wallet) { @@ -3972,6 +4087,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet->callback(this); + check_background_mining(password); + + if (welcome) + message_writer(console_color_yellow, true) << tr("If you are new to Monero, type \"welcome\" for a brief overview."); + return true; } //---------------------------------------------------------------------------------------------------- @@ -4341,12 +4461,12 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr return std::move(password); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) +boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { if (!tools::wallet2::wallet_valid_path_format(m_wallet_file)) { fail_msg_writer() << tr("wallet file path not valid: ") << m_wallet_file; - return false; + return {}; } bool keys_file_exists; @@ -4356,7 +4476,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) if(!keys_file_exists) { fail_msg_writer() << tr("Key file not found. Failed to open wallet"); - return false; + return {}; } epee::wipeable_string password; @@ -4367,7 +4487,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) password = std::move(std::move(rc.second).password()); if (!m_wallet) { - return false; + return {}; } m_wallet->callback(this); @@ -4393,7 +4513,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) { bool is_deterministic; { - SCOPED_WALLET_UNLOCK(); + SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return {};); is_deterministic = m_wallet->is_deterministic(); } if (is_deterministic) @@ -4402,7 +4522,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) "a deprecated version of the wallet. Please proceed to upgrade your wallet.\n"); std::string mnemonic_language = get_mnemonic_language(); if (mnemonic_language.empty()) - return false; + return {}; m_wallet->set_seed_language(mnemonic_language); m_wallet->rewrite(m_wallet_file, password); @@ -4434,14 +4554,14 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) if (password_is_correct) fail_msg_writer() << boost::format(tr("You may want to remove the file \"%s\" and try again")) % m_wallet_file; } - return false; + return {}; } success_msg_writer() << "**********************************************************************\n" << tr("Use the \"help\" command to see the list of available commands.\n") << tr("Use \"help <command>\" to see a command's documentation.\n") << "**********************************************************************"; - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::close_wallet() @@ -4522,7 +4642,118 @@ bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std } return true; } +//---------------------------------------------------------------------------------------------------- +void simple_wallet::start_background_mining() +{ + COMMAND_RPC_MINING_STATUS::request reqq; + COMMAND_RPC_MINING_STATUS::response resq; + bool r = m_wallet->invoke_http_json("/mining_status", reqq, resq); + std::string err = interpret_rpc_response(r, resq.status); + if (!r) + return; + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to query mining status: ") << err; + return; + } + if (!resq.is_background_mining_enabled) + { + COMMAND_RPC_START_MINING::request req; + COMMAND_RPC_START_MINING::response res; + req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + req.threads_count = 1; + req.do_background_mining = true; + req.ignore_battery = false; + bool r = m_wallet->invoke_http_json("/start_mining", req, res); + std::string err = interpret_rpc_response(r, res.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to setup background mining: ") << err; + return; + } + } + success_msg_writer() << tr("Background mining enabled. Thank you for supporting the Monero network."); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::stop_background_mining() +{ + COMMAND_RPC_MINING_STATUS::request reqq; + COMMAND_RPC_MINING_STATUS::response resq; + bool r = m_wallet->invoke_http_json("/mining_status", reqq, resq); + if (!r) + return; + std::string err = interpret_rpc_response(r, resq.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to query mining status: ") << err; + return; + } + if (resq.is_background_mining_enabled) + { + COMMAND_RPC_STOP_MINING::request req; + COMMAND_RPC_STOP_MINING::response res; + bool r = m_wallet->invoke_http_json("/stop_mining", req, res); + std::string err = interpret_rpc_response(r, res.status); + if (!err.empty()) + { + fail_msg_writer() << tr("Failed to setup background mining: ") << err; + return; + } + } + message_writer(console_color_red, false) << tr("Background mining not enabled. Run \"set setup-background-mining 1\" to change."); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::check_background_mining(const epee::wipeable_string &password) +{ + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) + { + message_writer(console_color_red, false) << tr("Background mining not enabled. Run \"set setup-background-mining 1\" to change."); + return; + } + + if (!m_wallet->is_trusted_daemon()) + { + message_writer() << tr("Using an untrusted daemon, skipping background mining check"); + return; + } + + COMMAND_RPC_MINING_STATUS::request req; + COMMAND_RPC_MINING_STATUS::response res; + bool r = m_wallet->invoke_http_json("/mining_status", req, res); + std::string err = interpret_rpc_response(r, res.status); + bool is_background_mining_enabled = false; + if (err.empty()) + is_background_mining_enabled = res.is_background_mining_enabled; + + if (is_background_mining_enabled) + { + // already active, nice + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); + m_wallet->rewrite(m_wallet_file, password); + start_background_mining(); + return; + } + if (res.active) + return; + if (setup == tools::wallet2::BackgroundMiningMaybe) + { + message_writer() << tr("The daemon is not set up to background mine."); + message_writer() << tr("With background mining enabled, the daemon will mine when idle and not on batttery."); + message_writer() << tr("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); + std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): ")); + if (std::cin.eof() || !command_line::is_yes(accepted)) { + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningNo); + m_wallet->rewrite(m_wallet_file, password); + message_writer(console_color_red, false) << tr("Background mining not enabled. Set setup-background-mining to 1 to change."); + return; + } + m_wallet->setup_background_mining(tools::wallet2::BackgroundMiningYes); + m_wallet->rewrite(m_wallet_file, password); + start_background_mining(); + } +} //---------------------------------------------------------------------------------------------------- bool simple_wallet::start_mining(const std::vector<std::string>& args) { @@ -4834,8 +5065,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo LOCK_IDLE_SCOPE(); crypto::hash transfer_hash_pre{}; - uint64_t height_pre, height_post; - + uint64_t height_pre = 0, height_post; if (reset != ResetNone) { if (reset == ResetSoftKeepKI) @@ -4948,10 +5178,15 @@ bool simple_wallet::show_balance_unlocked(bool detailed) success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account]; success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag); + uint64_t blocks_to_unlock; + uint64_t unlocked_balance = m_wallet->unlocked_balance(m_current_subaddress_account, &blocks_to_unlock); + std::string unlock_time_message; + if (blocks_to_unlock > 0) + unlock_time_message = (boost::format(" (%lu block(s) to unlock)") % blocks_to_unlock).str(); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " - << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; + << tr("unlocked balance: ") << print_money(unlocked_balance) << unlock_time_message << extra; std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(m_current_subaddress_account); if (!detailed || balance_per_subaddress.empty()) return true; success_msg_writer() << tr("Balance per address:"); @@ -4963,7 +5198,7 @@ bool simple_wallet::show_balance_unlocked(bool detailed) cryptonote::subaddress_index subaddr_index = {m_current_subaddress_account, i.first}; std::string address_str = m_wallet->get_subaddress_as_str(subaddr_index).substr(0, 6); uint64_t num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&subaddr_index](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == subaddr_index; }); - success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first]) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index); + success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %7u %21s")) % i.first % address_str % print_money(i.second) % print_money(unlocked_balance_per_subaddress[i.first].first) % num_unspent_outputs % m_wallet->get_subaddress_label(subaddr_index); } return true; } @@ -5297,7 +5532,7 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending } const cryptonote::tx_source_entry& source = *sptr; - ostr << boost::format(tr("\nInput %llu/%llu: amount=%s")) % (i + 1) % tx.vin.size() % print_money(source.amount); + ostr << boost::format(tr("\nInput %llu/%llu (%s): amount=%s")) % (i + 1) % tx.vin.size() % epee::string_tools::pod_to_hex(in_key.k_image) % print_money(source.amount); // convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets); // get block heights from which those ring member keys originated @@ -6654,7 +6889,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, { const tx_destination_entry &entry = cd.splitted_dsts[d]; std::string address, standard_address = get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr); - if (has_encrypted_payment_id && !entry.is_subaddress) + if (has_encrypted_payment_id && !entry.is_subaddress && standard_address != entry.original) { address = get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8); address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c9a5c55e8..76d446ba5 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -102,7 +102,7 @@ namespace cryptonote boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const epee::wipeable_string &multisig_keys, const std::string &old_language); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm); - bool open_wallet(const boost::program_options::variables_map& vm); + boost::optional<epee::wipeable_string> open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); bool viewkey(const std::vector<std::string> &args = std::vector<std::string>()); @@ -143,6 +143,7 @@ namespace cryptonote bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); @@ -234,6 +235,7 @@ namespace cryptonote bool mms(const std::vector<std::string>& args); bool print_ring(const std::vector<std::string>& args); bool set_ring(const std::vector<std::string>& args); + bool unset_ring(const std::vector<std::string>& args); bool save_known_rings(const std::vector<std::string>& args); bool blackball(const std::vector<std::string>& args); bool unblackball(const std::vector<std::string>& args); @@ -241,6 +243,8 @@ namespace cryptonote bool freeze(const std::vector<std::string>& args); bool thaw(const std::vector<std::string>& args); bool frozen(const std::vector<std::string>& args); + bool net_stats(const std::vector<std::string>& args); + bool welcome(const std::vector<std::string>& args); bool version(const std::vector<std::string>& args); bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func); @@ -297,6 +301,13 @@ namespace cryptonote */ void commit_or_save(std::vector<tools::wallet2::pending_tx>& ptx_vector, bool do_not_relay); + /*! + * \brief checks whether background mining is enabled, and asks to configure it if not + */ + void check_background_mining(const epee::wipeable_string &password); + void start_background_mining(); + void stop_background_mining(); + //----------------- i_wallet2_callback --------------------- virtual void on_new_block(uint64_t height, const cryptonote::block& block); virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index c1303c225..032b873d6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -249,6 +249,13 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } + virtual void on_device_button_pressed() + { + if (m_listener) { + m_listener->onDeviceButtonPressed(); + } + } + virtual boost::optional<epee::wipeable_string> on_device_pin_request() { if (m_listener) { @@ -449,6 +456,11 @@ WalletImpl::~WalletImpl() close(false); // do not store wallet as part of the closing activities // Stop refresh thread stopRefresh(); + + if (m_wallet2Callback->getListener()) { + m_wallet2Callback->getListener()->onSetWallet(nullptr); + } + LOG_PRINT_L1(__FUNCTION__ << " finished"); } diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index ee1d6ae79..0af3b1867 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -37,6 +37,7 @@ #include <set> #include <ctime> #include <iostream> +#include <stdexcept> // Public interface for libwallet library namespace Monero { @@ -337,6 +338,7 @@ protected: bool m_indeterminate; }; +struct Wallet; struct WalletListener { virtual ~WalletListener() = 0; @@ -381,7 +383,12 @@ struct WalletListener /** * @brief called by device if the action is required */ - virtual void onDeviceButtonRequest(uint64_t code) {} + virtual void onDeviceButtonRequest(uint64_t code) { (void)code; } + + /** + * @brief called by device if the button was pressed + */ + virtual void onDeviceButtonPressed() { } /** * @brief called by device when PIN is needed @@ -401,7 +408,12 @@ struct WalletListener /** * @brief Signalizes device operation progress */ - virtual void onDeviceProgress(const DeviceProgress & event) {}; + virtual void onDeviceProgress(const DeviceProgress & event) { (void)event; }; + + /** + * @brief If the listener is created before the wallet this enables to set created wallet object + */ + virtual void onSetWallet(Wallet * wallet) { (void)wallet; }; }; @@ -440,8 +452,8 @@ struct Wallet //! returns both error and error string atomically. suggested to use in instead of status() and errorString() virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; virtual bool setPassword(const std::string &password) = 0; - virtual bool setDevicePin(const std::string &password) { return false; }; - virtual bool setDevicePassphrase(const std::string &password) { return false; }; + virtual bool setDevicePin(const std::string &pin) { (void)pin; return false; }; + virtual bool setDevicePassphrase(const std::string &passphrase) { (void)passphrase; return false; }; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } virtual std::string path() const = 0; @@ -1020,9 +1032,10 @@ struct WalletManager * \param password Password of wallet file * \param nettype Network type * \param kdf_rounds Number of rounds for key derivation function + * \param listener Wallet listener to set to the wallet after creation * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) */ - virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; + virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) = 0; Wallet * openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated { return openWallet(path, password, testnet ? TESTNET : MAINNET); @@ -1134,6 +1147,7 @@ struct WalletManager * \param restoreHeight restore from start height (0 sets to current height) * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value) * \param kdf_rounds Number of rounds for key derivation function + * \param listener Wallet listener to set to the wallet after creation * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromDevice(const std::string &path, @@ -1142,7 +1156,8 @@ struct WalletManager const std::string &deviceName, uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1) = 0; + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) = 0; /*! * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index f584e88ac..ef2ed2015 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -57,9 +57,14 @@ Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::stri return wallet; } -Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds) +Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, WalletListener * listener) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + wallet->setListener(listener); + if (listener){ + listener->onSetWallet(wallet); + } + wallet->open(path, password); //Refresh addressBook wallet->addressBook()->refresh(); @@ -122,9 +127,15 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, const std::string &deviceName, uint64_t restoreHeight, const std::string &subaddressLookahead, - uint64_t kdf_rounds) + uint64_t kdf_rounds, + WalletListener * listener) { WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + wallet->setListener(listener); + if (listener){ + listener->onSetWallet(wallet); + } + if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } else { diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 0c83d794f..235f96e17 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -40,7 +40,7 @@ class WalletManagerImpl : public WalletManager public: Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) override; - Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1) override; + Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) override; virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, @@ -72,7 +72,8 @@ public: const std::string &deviceName, uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1) override; + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) override; virtual bool closeWallet(Wallet *wallet, bool store = true) override; bool walletExists(const std::string &path) override; bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index b69022af4..8da95de7b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -281,7 +281,7 @@ bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::t return true; } -bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images) { MDB_txn *txn; int dbr; @@ -294,17 +294,10 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); tx_active = true; - for (const auto &in: tx.vin) + for (const crypto::key_image &key_image: key_images) { - if (in.type() != typeid(cryptonote::txin_to_key)) - continue; - const auto &txin = boost::get<cryptonote::txin_to_key>(in); - const uint32_t ring_size = txin.key_offsets.size(); - if (ring_size == 1) - continue; - MDB_val key, data; - std::string key_ciphertext = encrypt(txin.k_image, chacha_key); + std::string key_ciphertext = encrypt(key_image, chacha_key); key.mv_data = (void*)key_ciphertext.data(); key.mv_size = key_ciphertext.size(); @@ -314,7 +307,7 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote continue; THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); - MDEBUG("Removing ring data for key image " << txin.k_image); + MDEBUG("Removing ring data for key image " << key_image); dbr = mdb_del(txn, dbi_rings, &key, NULL); THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to remove ring to database: " + std::string(mdb_strerror(dbr))); } @@ -325,6 +318,23 @@ bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote return true; } +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + std::vector<crypto::key_image> key_images; + key_images.reserve(tx.vin.size()); + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + key_images.push_back(txin.k_image); + } + return remove_rings(chacha_key, key_images); +} + bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) { MDB_txn *txn; diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 7b448b0d7..9c7e624bc 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -45,6 +45,7 @@ namespace tools ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool remove_rings(const crypto::chacha_key &chacha_key, const std::vector<crypto::key_image> &key_images); bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 0e82f1a91..b288994a5 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -243,9 +243,10 @@ struct options { const command_line::arg_descriptor<std::string> daemon_ssl = {"daemon-ssl", tools::wallet2::tr("Enable SSL on daemon RPC connections: enabled|disabled|autodetect"), "autodetect"}; const command_line::arg_descriptor<std::string> daemon_ssl_private_key = {"daemon-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> daemon_ssl_certificate = {"daemon-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; - const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_certificates = {"daemon-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers")}; + const command_line::arg_descriptor<std::string> daemon_ssl_ca_certificates = {"daemon-ssl-ca-certificates", tools::wallet2::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s).")}; const command_line::arg_descriptor<std::vector<std::string>> daemon_ssl_allowed_fingerprints = {"daemon-ssl-allowed-fingerprints", tools::wallet2::tr("List of valid fingerprints of allowed RPC servers")}; const command_line::arg_descriptor<bool> daemon_ssl_allow_any_cert = {"daemon-ssl-allow-any-cert", tools::wallet2::tr("Allow any SSL certificate from the daemon"), false}; + const command_line::arg_descriptor<bool> daemon_ssl_allow_chained = {"daemon-ssl-allow-chained", tools::wallet2::tr("Allow user (via --daemon-ssl-ca-certificates) chain certificates"), false}; const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<std::string, false, true, 2> shared_ringdb_dir = { @@ -264,6 +265,7 @@ struct options { const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""}; const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""}; const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; + const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) @@ -314,6 +316,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl const uint64_t kdf_rounds = command_line::get_arg(vm, opts.kdf_rounds); THROW_WALLET_EXCEPTION_IF(kdf_rounds == 0, tools::error::wallet_internal_error, "KDF rounds must not be 0"); + const bool use_proxy = command_line::has_arg(vm, opts.proxy); auto daemon_address = command_line::get_arg(vm, opts.daemon_address); auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); @@ -321,13 +324,37 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path); auto daemon_ssl_private_key = command_line::get_arg(vm, opts.daemon_ssl_private_key); auto daemon_ssl_certificate = command_line::get_arg(vm, opts.daemon_ssl_certificate); - auto daemon_ssl_allowed_certificates = command_line::get_arg(vm, opts.daemon_ssl_allowed_certificates); + auto daemon_ssl_ca_file = command_line::get_arg(vm, opts.daemon_ssl_ca_certificates); auto daemon_ssl_allowed_fingerprints = command_line::get_arg(vm, opts.daemon_ssl_allowed_fingerprints); auto daemon_ssl_allow_any_cert = command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert); auto daemon_ssl = command_line::get_arg(vm, opts.daemon_ssl); - epee::net_utils::ssl_support_t ssl_support; - THROW_WALLET_EXCEPTION_IF(!epee::net_utils::ssl_support_from_string(ssl_support, daemon_ssl), tools::error::wallet_internal_error, - tools::wallet2::tr("Invalid argument for ") + std::string(opts.daemon_ssl.name)); + + // user specified CA file or fingeprints implies enabled SSL by default + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert)) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else if (!daemon_ssl_ca_file.empty() || !daemon_ssl_allowed_fingerprints.empty()) + { + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; + std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); + + ssl_options = epee::net_utils::ssl_options_t{ + std::move(ssl_allowed_fingerprints), std::move(daemon_ssl_ca_file) + }; + + if (command_line::get_arg(vm, opts.daemon_ssl_allow_chained)) + ssl_options.verification = epee::net_utils::ssl_verification_t::user_ca; + } + + if (ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, opts.daemon_ssl)) + { + THROW_WALLET_EXCEPTION_IF(!epee::net_utils::ssl_support_from_string(ssl_options.support, daemon_ssl), tools::error::wallet_internal_error, + tools::wallet2::tr("Invalid argument for ") + std::string(opts.daemon_ssl.name)); + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(daemon_ssl_private_key), std::move(daemon_ssl_certificate) + }; THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port, tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once")); @@ -357,22 +384,24 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl if (daemon_address.empty()) daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - boost::asio::ip::tcp::endpoint proxy{}; - if (command_line::has_arg(vm, opts.proxy)) { - namespace ip = boost::asio::ip; const boost::string_ref real_daemon = boost::string_ref{daemon_address}.substr(0, daemon_address.rfind(':')); - // onion and i2p addresses contain information about the server cert - // which both authenticates and encrypts - const bool unencrypted_proxy = - !real_daemon.ends_with(".onion") && !real_daemon.ends_with(".i2p") && - daemon_ssl_allowed_certificates.empty() && daemon_ssl_allowed_fingerprints.empty(); + const bool verification_required = + ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled || use_proxy; + THROW_WALLET_EXCEPTION_IF( - unencrypted_proxy, + verification_required && !ssl_options.has_strong_verification(real_daemon), tools::error::wallet_internal_error, - std::string{"Use of --"} + opts.proxy.name + " requires --" + opts.daemon_ssl_allowed_certificates.name + " or --" + opts.daemon_ssl_allowed_fingerprints.name + " or use of a .onion/.i2p domain" + tools::wallet2::tr("Enabling --") + std::string{use_proxy ? opts.proxy.name : opts.daemon_ssl.name} + tools::wallet2::tr(" requires --") + + opts.daemon_ssl_ca_certificates.name + tools::wallet2::tr(" or --") + opts.daemon_ssl_allowed_fingerprints.name + tools::wallet2::tr(" or use of a .onion/.i2p domain") ); + } + + boost::asio::ip::tcp::endpoint proxy{}; + if (use_proxy) + { + namespace ip = boost::asio::ip; const auto proxy_address = command_line::get_arg(vm, opts.proxy); @@ -416,28 +445,17 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl catch (const std::exception &e) { } } - std::list<std::string> ssl_allowed_certificates; - for (const std::string &path: daemon_ssl_allowed_certificates) - { - ssl_allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, ssl_allowed_certificates.back())) - { - MERROR("Failed to load certificate: " << path); - ssl_allowed_certificates.back() = std::string(); - } - } - - std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints{ daemon_ssl_allowed_fingerprints.size() }; - std::transform(daemon_ssl_allowed_fingerprints.begin(), daemon_ssl_allowed_fingerprints.end(), ssl_allowed_fingerprints.begin(), epee::from_hex::vector); - std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(nettype, kdf_rounds, unattended)); - wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, ssl_support, std::make_pair(daemon_ssl_private_key, daemon_ssl_certificate), ssl_allowed_certificates, ssl_allowed_fingerprints, daemon_ssl_allow_any_cert); + wallet->init(std::move(daemon_address), std::move(login), std::move(proxy), 0, *trusted_daemon, std::move(ssl_options)); boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); wallet->get_message_store().set_options(vm); wallet->device_name(device_name); wallet->device_derivation_path(device_derivation_path); + if (command_line::get_arg(vm, opts.no_dns)) + wallet->enable_dns(false); + try { if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) @@ -978,6 +996,12 @@ void wallet_device_callback::on_button_request(uint64_t code) wallet->on_device_button_request(code); } +void wallet_device_callback::on_button_pressed() +{ + if (wallet) + wallet->on_device_button_pressed(); +} + boost::optional<epee::wipeable_string> wallet_device_callback::on_pin_request() { if (wallet) @@ -1032,6 +1056,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_segregation_height(0), m_ignore_fractional_outputs(true), m_track_uses(false), + m_setup_background_mining(BackgroundMiningMaybe), m_is_initialized(false), m_kdf_rounds(kdf_rounds), is_old_file_format(false), @@ -1057,7 +1082,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended): m_encrypt_keys_after_refresh(boost::none), m_unattended(unattended), m_devices_registered(false), - m_device_last_key_image_sync(0) + m_device_last_key_image_sync(0), + m_use_dns(true) { } @@ -1100,9 +1126,10 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.daemon_ssl); command_line::add_arg(desc_params, opts.daemon_ssl_private_key); command_line::add_arg(desc_params, opts.daemon_ssl_certificate); - command_line::add_arg(desc_params, opts.daemon_ssl_allowed_certificates); + command_line::add_arg(desc_params, opts.daemon_ssl_ca_certificates); command_line::add_arg(desc_params, opts.daemon_ssl_allowed_fingerprints); command_line::add_arg(desc_params, opts.daemon_ssl_allow_any_cert); + command_line::add_arg(desc_params, opts.daemon_ssl_allow_chained); command_line::add_arg(desc_params, opts.testnet); command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.shared_ringdb_dir); @@ -1111,6 +1138,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.hw_device); command_line::add_arg(desc_params, opts.hw_device_derivation_path); command_line::add_arg(desc_params, opts.tx_notify); + command_line::add_arg(desc_params, opts.no_dns); } std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -1154,10 +1182,7 @@ std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::varia } //---------------------------------------------------------------------------------------------------- -bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, bool trusted_daemon, - epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, - const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, - bool allow_any_cert) +bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) { if(m_http_client.is_connected()) m_http_client.disconnect(); @@ -1166,17 +1191,17 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u m_trusted_daemon = trusted_daemon; MINFO("setting daemon to " << get_daemon_address()); - return m_http_client.set_server(get_daemon_address(), get_daemon_login(), ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); + return m_http_client.set_server(get_daemon_address(), get_daemon_login(), std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- -bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_support_t ssl_support, const std::pair<std::string, std::string> &private_key_and_certificate_path, const std::list<std::string> &allowed_certificates, const std::vector<std::vector<uint8_t>> &allowed_fingerprints, bool allow_any_cert) +bool wallet2::init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login, boost::asio::ip::tcp::endpoint proxy, uint64_t upper_transaction_weight_limit, bool trusted_daemon, epee::net_utils::ssl_options_t ssl_options) { m_checkpoints.init_default_checkpoints(m_nettype); m_is_initialized = true; m_upper_transaction_weight_limit = upper_transaction_weight_limit; if (proxy != boost::asio::ip::tcp::endpoint{}) m_http_client.set_connector(net::socks::connector{std::move(proxy)}); - return set_daemon(daemon_address, daemon_login, trusted_daemon, ssl_support, private_key_and_certificate_path, allowed_certificates, allowed_fingerprints, allow_any_cert); + return set_daemon(daemon_address, daemon_login, trusted_daemon, std::move(ssl_options)); } //---------------------------------------------------------------------------------------------------- bool wallet2::is_deterministic() const @@ -2275,6 +2300,12 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans add_rings(tx); } //---------------------------------------------------------------------------------------------------- +bool wallet2::should_skip_block(const cryptonote::block &b, uint64_t height) const +{ + // seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup + return !(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height); +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache) { THROW_WALLET_EXCEPTION_IF(bche.txs.size() + 1 != parsed_block.o_indices.indices.size(), error::wallet_internal_error, @@ -2284,7 +2315,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) + if (!should_skip_block(b, height)) { TIME_MEASURE_START(miner_tx_handle_time); if (m_refresh_type != RefreshNoCoinbase) @@ -2348,9 +2379,7 @@ void wallet2::get_short_chain_history(std::list<crypto::hash>& ids, uint64_t gra //---------------------------------------------------------------------------------------------------- void wallet2::parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const { - error = !cryptonote::parse_and_validate_block_from_blob(blob, bl); - if (!error) - bl_id = get_block_hash(bl); + error = !cryptonote::parse_and_validate_block_from_blob(blob, bl, bl_id); } //---------------------------------------------------------------------------------------------------- void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices) @@ -2416,6 +2445,11 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry { THROW_WALLET_EXCEPTION_IF(parsed_blocks[i].txes.size() != parsed_blocks[i].block.tx_hashes.size(), error::wallet_internal_error, "Mismatched parsed_blocks[i].txes.size() and parsed_blocks[i].block.tx_hashes.size()"); + if (should_skip_block(parsed_blocks[i].block, start_height + i)) + { + txidx += 1 + parsed_blocks[i].block.tx_hashes.size(); + continue; + } if (m_refresh_type != RefreshNoCoinbase) tpool.submit(&waiter, [&, i, txidx](){ cache_tx_data(parsed_blocks[i].block.miner_tx, get_transaction_hash(parsed_blocks[i].block.miner_tx), tx_cache_data[txidx]); }); ++txidx; @@ -2444,6 +2478,8 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry for (size_t i = 0; i < tx_cache_data.size(); ++i) { + if (tx_cache_data[i].empty()) + continue; tpool.submit(&waiter, [&hwdev, &gender, &tx_cache_data, i]() { auto &slot = tx_cache_data[i]; boost::unique_lock<hw::device> hwdev_lock(hwdev); @@ -2462,6 +2498,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry if (o.target.type() == typeid(cryptonote::txout_to_key)) { std::vector<crypto::key_derivation> additional_derivations; + additional_derivations.reserve(tx_cache_data[txidx].additional.size()); for (const auto &iod: tx_cache_data[txidx].additional) additional_derivations.push_back(iod.derivation); const auto &key = boost::get<txout_to_key>(o.target).key; @@ -2479,6 +2516,12 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry txidx = 0; for (size_t i = 0; i < blocks.size(); ++i) { + if (should_skip_block(parsed_blocks[i].block, start_height + i)) + { + txidx += 1 + parsed_blocks[i].block.tx_hashes.size(); + continue; + } + if (m_refresh_type != RefreshType::RefreshNoCoinbase) { THROW_WALLET_EXCEPTION_IF(txidx >= tx_cache_data.size(), error::wallet_internal_error, "txidx out of range"); @@ -3522,6 +3565,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_setup_background_mining); + json.AddMember("setup_background_mining", value2, json.GetAllocator()); + value2.SetUint(m_subaddress_lookahead_major); json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator()); @@ -3673,6 +3719,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_segregation_height = 0; m_ignore_fractional_outputs = true; m_track_uses = false; + m_setup_background_mining = BackgroundMiningMaybe; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_original_keys_available = false; @@ -3827,6 +3874,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_ignore_fractional_outputs = field_ignore_fractional_outputs; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false); m_track_uses = field_track_uses; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, setup_background_mining, BackgroundMiningSetupType, Int, false, BackgroundMiningMaybe); + m_setup_background_mining = field_setup_background_mining; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); m_subaddress_lookahead_major = field_subaddress_lookahead_major; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); @@ -4128,6 +4177,17 @@ bool wallet2::query_device(hw::device::device_type& device_type, const std::stri return true; } +void wallet2::init_type(hw::device::device_type device_type) +{ + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = false; + m_multisig_threshold = 0; + m_multisig_signers.clear(); + m_original_keys_available = false; + m_key_device_type = device_type; +} + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file @@ -4197,18 +4257,15 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); m_account.finalize_multisig(spend_public_key); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; + // Not possible to restore a multisig wallet that is able to activate the MMS + // (because the original keys are not (yet) part of the restore info), so + // keep m_original_keys_available to false + init_type(hw::device::device_type::SOFTWARE); m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; - m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); - // Not possible to restore a multisig wallet that is able to activate the MMS - // (because the original keys are not (yet) part of the restore info) - m_original_keys_available = false; - create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); setup_new_blockchain(); @@ -4241,13 +4298,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip crypto::secret_key retval = m_account.generate(recovery_param, recover, two_random); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; + init_type(hw::device::device_type::SOFTWARE); setup_keys(password); // calculate a starting refresh height @@ -4330,13 +4381,9 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& } m_account.create_from_viewkey(account_public_address, viewkey); - m_account_public_address = account_public_address; + init_type(hw::device::device_type::SOFTWARE); m_watch_only = true; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; + m_account_public_address = account_public_address; setup_keys(password); create_keys_file(wallet_, true, password, m_nettype != MAINNET || create_address_file); @@ -4371,13 +4418,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& } m_account.create_from_keys(account_public_address, spendkey, viewkey); + init_type(hw::device::device_type::SOFTWARE); m_account_public_address = account_public_address; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; - m_key_device_type = hw::device::device_type::SOFTWARE; setup_keys(password); create_keys_file(wallet_, false, password, create_address_file); @@ -4412,13 +4454,7 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p hwdev.set_callback(get_device_callback()); m_account.create_from_device(hwdev); - m_key_device_type = m_account.get_device().get_type(); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; - m_multisig = false; - m_multisig_threshold = 0; - m_multisig_signers.clear(); - m_original_keys_available = false; + init_type(m_account.get_device().get_type()); setup_keys(password); m_device_name = device_name; @@ -4550,10 +4586,9 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, "Failed to create multisig wallet due to bad keys"); memwipe(&spend_skey, sizeof(rct::key)); - m_account_public_address = m_account.get_keys().m_account_address; - m_watch_only = false; + init_type(hw::device::device_type::SOFTWARE); + m_original_keys_available = true; m_multisig = true; - m_key_device_type = hw::device::device_type::SOFTWARE; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; ++m_multisig_rounds_passed; @@ -5441,13 +5476,19 @@ uint64_t wallet2::balance(uint32_t index_major) const return amount; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance(uint32_t index_major) const +uint64_t wallet2::unlocked_balance(uint32_t index_major, uint64_t *blocks_to_unlock) const { uint64_t amount = 0; + if (blocks_to_unlock) + *blocks_to_unlock = 0; if(m_light_wallet) return m_light_wallet_balance; for (const auto& i : unlocked_balance_per_subaddress(index_major)) - amount += i.second; + { + amount += i.second.first; + if (blocks_to_unlock && i.second.second > *blocks_to_unlock) + *blocks_to_unlock = i.second.second; + } return amount; } //---------------------------------------------------------------------------------------------------- @@ -5480,18 +5521,36 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo return amount_per_subaddr; } //---------------------------------------------------------------------------------------------------- -std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const +std::map<uint32_t, std::pair<uint64_t, uint64_t>> wallet2::unlocked_balance_per_subaddress(uint32_t index_major) const { - std::map<uint32_t, uint64_t> amount_per_subaddr; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> amount_per_subaddr; + const uint64_t blockchain_height = get_blockchain_current_height(); for(const transfer_details& td: m_transfers) { - if(td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen && is_transfer_unlocked(td)) + if(td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen) { + uint64_t amount = 0, blocks_to_unlock = 0; + if (is_transfer_unlocked(td)) + { + amount = td.amount(); + blocks_to_unlock = 0; + } + else + { + uint64_t unlock_height = td.m_block_height + std::max<uint64_t>(CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS); + if (td.m_tx.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && td.m_tx.unlock_time > unlock_height) + unlock_height = td.m_tx.unlock_time; + blocks_to_unlock = unlock_height > blockchain_height ? unlock_height - blockchain_height : 0; + amount = 0; + } auto found = amount_per_subaddr.find(td.m_subaddr_index.minor); if (found == amount_per_subaddr.end()) - amount_per_subaddr[td.m_subaddr_index.minor] = td.amount(); + amount_per_subaddr[td.m_subaddr_index.minor] = std::make_pair(amount, blocks_to_unlock); else - found->second += td.amount(); + { + found->second.first += amount; + found->second.second = std::max(found->second.second, blocks_to_unlock); + } } } return amount_per_subaddr; @@ -5505,11 +5564,18 @@ uint64_t wallet2::balance_all() const return r; } //---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance_all() const +uint64_t wallet2::unlocked_balance_all(uint64_t *blocks_to_unlock) const { uint64_t r = 0; + if (blocks_to_unlock) + *blocks_to_unlock = 0; for (uint32_t index_major = 0; index_major < get_num_subaddress_accounts(); ++index_major) - r += unlocked_balance(index_major); + { + uint64_t local_blocks_to_unlock; + r += unlocked_balance(index_major, blocks_to_unlock ? &local_blocks_to_unlock : NULL); + if (blocks_to_unlock) + *blocks_to_unlock = std::max(*blocks_to_unlock, local_blocks_to_unlock); + } return r; } //---------------------------------------------------------------------------------------------------- @@ -5719,7 +5785,7 @@ namespace { CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty"); - size_t idx = crypto::rand<size_t>() % vec.size(); + size_t idx = crypto::rand_idx(vec.size()); return pop_index (vec, idx); } @@ -5822,7 +5888,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve } else { - idx = crypto::rand<size_t>() % candidates.size(); + idx = crypto::rand_idx(candidates.size()); } return pop_index (unused_indices, candidates[idx]); } @@ -7040,6 +7106,43 @@ bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uin catch (const std::exception &e) { return false; } } +bool wallet2::unset_ring(const std::vector<crypto::key_image> &key_images) +{ + if (!m_ringdb) + return false; + + try { return m_ringdb->remove_rings(get_ringdb_key(), key_images); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::unset_ring(const crypto::hash &txid) +{ + if (!m_ringdb) + return false; + + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = true; + m_daemon_rpc_mutex.lock(); + bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to get transaction from daemon"); + if (res.txs.empty()) + return false; + THROW_WALLET_EXCEPTION_IF(res.txs.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::transaction tx; + crypto::hash tx_hash; + if (!get_pruned_tx(res.txs.front(), tx, tx_hash)) + return false; + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + try { return m_ringdb->remove_rings(get_ringdb_key(), tx); } + catch (const std::exception &e) { return false; } +} + bool wallet2::find_and_save_rings(bool force) { if (!force && m_ring_history_saved) @@ -7474,7 +7577,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (n_rct == 0) return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0; MDEBUG("Picking 1/" << n_rct << " in " << (last_block_offset - first_block_offset + 1) << " blocks centered around " << block_offset + rct_start_height); - return first_rct + crypto::rand<uint64_t>() % n_rct; + return first_rct + crypto::rand_idx(n_rct); }; size_t num_selected_transfers = 0; @@ -9055,7 +9158,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // throw if attempting a transaction with no money THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); std::map<uint32_t, uint64_t> balance_per_subaddr = balance_per_subaddress(subaddr_account); if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked balance @@ -9073,7 +9176,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (uint32_t index_minor : subaddr_indices) { balance_subtotal += balance_per_subaddr[index_minor]; - unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor]; + unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor].first; } THROW_WALLET_EXCEPTION_IF(needed_money + min_fee > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); @@ -9139,7 +9242,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp { auto sort_predicate = [&unlocked_balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) { - return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.first]; + return unlocked_balance_per_subaddr[x.first].first > unlocked_balance_per_subaddr[y.first].first; }; std::sort(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), sort_predicate); std::sort(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), sort_predicate); @@ -9530,14 +9633,16 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, s change -= r.second.first; MDEBUG("Adding " << cryptonote::print_money(change) << " expected change"); + // for all txes that have actual change, check change is coming back to the sending wallet for (const pending_tx &ptx: ptx_vector) - THROW_WALLET_EXCEPTION_IF(ptx.change_dts.addr != ptx_vector[0].change_dts.addr, error::wallet_internal_error, - "Change goes to several different addresses"); - const auto it = m_subaddresses.find(ptx_vector[0].change_dts.addr.m_spend_public_key); - THROW_WALLET_EXCEPTION_IF(change > 0 && it == m_subaddresses.end(), error::wallet_internal_error, "Change address is not ours"); - - required[ptx_vector[0].change_dts.addr].first += change; - required[ptx_vector[0].change_dts.addr].second = ptx_vector[0].change_dts.is_subaddress; + { + if (ptx.change_dts.amount == 0) + continue; + THROW_WALLET_EXCEPTION_IF(m_subaddresses.find(ptx.change_dts.addr.m_spend_public_key) == m_subaddresses.end(), + error::wallet_internal_error, "Change address is not ours"); + required[ptx.change_dts.addr].first += ptx.change_dts.amount; + required[ptx.change_dts.addr].second = ptx.change_dts.is_subaddress; + } for (const auto &r: required) { @@ -9603,7 +9708,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below if (unused_transfer_dust_indices_per_subaddr.count(0) == 1 && unused_transfer_dust_indices_per_subaddr.size() > 1) unused_transfer_dust_indices_per_subaddr.erase(0); auto i = unused_transfer_dust_indices_per_subaddr.begin(); - std::advance(i, crypto::rand<size_t>() % unused_transfer_dust_indices_per_subaddr.size()); + std::advance(i, crypto::rand_idx(unused_transfer_dust_indices_per_subaddr.size())); unused_transfers_indices = i->second.first; unused_dust_indices = i->second.second; LOG_PRINT_L2("Spending from subaddress index " << i->first); @@ -10584,13 +10689,13 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de check_tx_key_helper(tx, derivation, additional_derivations, address, received); in_pool = res.txs.front().in_pool; - confirmations = (uint64_t)-1; + confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - (res.txs.front().block_height + 1); + confirmations = bc_height - res.txs.front().block_height; } } @@ -10786,13 +10891,13 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account return false; in_pool = res.txs.front().in_pool; - confirmations = (uint64_t)-1; + confirmations = 0; if (!in_pool) { std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - confirmations = bc_height - (res.txs.front().block_height + 1); + confirmations = bc_height - res.txs.front().block_height; } return true; @@ -12774,8 +12879,7 @@ uint64_t wallet2::get_segregation_fork_height() const if (m_segregation_height > 0) return m_segregation_height; - static const bool use_dns = true; - if (use_dns) + if (m_use_dns) { // All four MoneroPulse domains have DNSSEC on and valid static const std::vector<std::string> dns_urls = { @@ -12855,6 +12959,12 @@ void wallet2::on_device_button_request(uint64_t code) m_callback->on_device_button_request(code); } //---------------------------------------------------------------------------------------------------- +void wallet2::on_device_button_pressed() +{ + if (nullptr != m_callback) + m_callback->on_device_button_pressed(); +} +//---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> wallet2::on_device_pin_request() { if (nullptr != m_callback) @@ -12953,4 +13063,14 @@ void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const c m_transfers[it->second].m_key_image_known = true; } } +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_bytes_sent() const +{ + return m_http_client.get_bytes_sent(); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_bytes_received() const +{ + return m_http_client.get_bytes_received(); +} } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2eb7de94a..39380c9df 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -105,6 +105,7 @@ namespace tools virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {} // Device callbacks virtual void on_device_button_request(uint64_t code) {} + virtual void on_device_button_pressed() {} virtual boost::optional<epee::wipeable_string> on_device_pin_request() { return boost::none; } virtual boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device) { return boost::none; } virtual void on_device_progress(const hw::device_progress& event) {}; @@ -118,6 +119,7 @@ namespace tools public: wallet_device_callback(wallet2 * wallet): wallet(wallet) {}; void on_button_request(uint64_t code=0) override; + void on_button_pressed() override; boost::optional<epee::wipeable_string> on_pin_request() override; boost::optional<epee::wipeable_string> on_passphrase_request(bool on_device) override; void on_progress(const hw::device_progress& event) override; @@ -194,6 +196,12 @@ namespace tools AskPasswordToDecrypt = 2, }; + enum BackgroundMiningSetupType { + BackgroundMiningMaybe = 0, + BackgroundMiningYes = 1, + BackgroundMiningNo = 2, + }; + static const char* tr(const char* str); static bool has_testnet_option(const boost::program_options::variables_map& vm); @@ -533,6 +541,8 @@ namespace tools std::vector<cryptonote::tx_extra_field> tx_extra_fields; std::vector<is_out_data> primary; std::vector<is_out_data> additional; + + bool empty() const { return tx_extra_fields.empty() && primary.empty() && additional.empty(); } }; /*! @@ -687,16 +697,10 @@ namespace tools boost::asio::ip::tcp::endpoint proxy = {}, uint64_t upper_transaction_weight_limit = 0, bool trusted_daemon = true, - epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, - const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, - const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, - bool allow_any_cert = false); + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); bool set_daemon(std::string daemon_address = "http://localhost:8080", boost::optional<epee::net_utils::http::login> daemon_login = boost::none, bool trusted_daemon = true, - epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, - const std::pair<std::string, std::string> &private_key_and_certificate_path = {}, - const std::list<std::string> &allowed_certificates = {}, const std::vector<std::vector<uint8_t>> &allowed_fingerprints = {}, - bool allow_any_cert = false); + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); void stop() { m_run.store(false, std::memory_order_relaxed); m_message_store.stop(); } @@ -771,13 +775,13 @@ namespace tools // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; - uint64_t unlocked_balance(uint32_t subaddr_index_major) const; + uint64_t unlocked_balance(uint32_t subaddr_index_major, uint64_t *blocks_to_unlock = NULL) const; // locked & unlocked balance per subaddress of given or current subaddress account std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major) const; - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major) const; // all locked & unlocked balances of all subaddress accounts uint64_t balance_all() const; - uint64_t unlocked_balance_all() const; + uint64_t unlocked_balance_all(uint64_t *blocks_to_unlock = NULL) const; template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, @@ -1014,6 +1018,8 @@ namespace tools void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } + void setup_background_mining(BackgroundMiningSetupType value) { m_setup_background_mining = value; } const std::string & device_name() const { return m_device_name; } void device_name(const std::string & device_name) { m_device_name = device_name; } const std::string & device_derivation_path() const { return m_device_derivation_path; } @@ -1247,6 +1253,8 @@ namespace tools bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs); bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs); bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + bool unset_ring(const std::vector<crypto::key_image> &key_images); + bool unset_ring(const crypto::hash &txid); bool find_and_save_rings(bool force = true); bool blackball_output(const std::pair<uint64_t, uint64_t> &output); @@ -1262,6 +1270,9 @@ namespace tools bool frozen(const crypto::key_image &ki) const; bool frozen(const transfer_details &td) const; + uint64_t get_bytes_sent() const; + uint64_t get_bytes_received() const; + // MMS ------------------------------------------------------------------------------------------------- mms::message_store& get_message_store() { return m_message_store; }; const mms::message_store& get_message_store() const { return m_message_store; }; @@ -1279,6 +1290,7 @@ namespace tools void hash_m_transfer(const transfer_details & transfer, crypto::hash &hash) const; uint64_t hash_m_transfers(int64_t transfer_height, crypto::hash &hash) const; void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash); + void enable_dns(bool enable) { m_use_dns = enable; } private: /*! @@ -1296,6 +1308,7 @@ namespace tools */ bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password); void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); + bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; @@ -1362,11 +1375,13 @@ namespace tools void cache_tx_data(const cryptonote::transaction& tx, const crypto::hash &txid, tx_cache_data &tx_cache_data) const; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> create_output_tracker_cache() const; + void init_type(hw::device::device_type device_type); void setup_new_blockchain(); void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file); wallet_device_callback * get_device_callback(); void on_device_button_request(uint64_t code); + void on_device_button_pressed(); boost::optional<epee::wipeable_string> on_device_pin_request(); boost::optional<epee::wipeable_string> on_device_passphrase_request(bool on_device); void on_device_progress(const hw::device_progress& event); @@ -1450,6 +1465,7 @@ namespace tools uint64_t m_segregation_height; bool m_ignore_fractional_outputs; bool m_track_uses; + BackgroundMiningSetupType m_setup_background_mining; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; @@ -1457,6 +1473,7 @@ namespace tools std::string m_device_name; std::string m_device_derivation_path; uint64_t m_device_last_key_image_sync; + bool m_use_dns; // Aux transaction data from device std::unordered_map<crypto::hash, std::string> m_tx_device; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 95cda7f1e..71c64d3c1 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -68,7 +68,7 @@ namespace const command_line::arg_descriptor<std::string> arg_rpc_ssl = {"rpc-ssl", tools::wallet2::tr("Enable SSL on wallet RPC connections: enabled|disabled|autodetect"), "autodetect"}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_private_key = {"rpc-ssl-private-key", tools::wallet2::tr("Path to a PEM format private key"), ""}; const command_line::arg_descriptor<std::string> arg_rpc_ssl_certificate = {"rpc-ssl-certificate", tools::wallet2::tr("Path to a PEM format certificate"), ""}; - const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_certificates = {"rpc-ssl-allowed-certificates", tools::wallet2::tr("List of paths to PEM format certificates of allowed RPC servers (all allowed if empty)")}; + const command_line::arg_descriptor<std::string> arg_rpc_ssl_ca_certificates = {"rpc-ssl-ca-certificates", tools::wallet2::tr("Path to file containing concatenated PEM format certificate(s) to replace system CA(s).")}; const command_line::arg_descriptor<std::vector<std::string>> arg_rpc_ssl_allowed_fingerprints = {"rpc-ssl-allowed-fingerprints", tools::wallet2::tr("List of certificate fingerprints to allow")}; constexpr const char default_rpc_username[] = "monero"; @@ -247,40 +247,102 @@ namespace tools auto rpc_ssl_private_key = command_line::get_arg(vm, arg_rpc_ssl_private_key); auto rpc_ssl_certificate = command_line::get_arg(vm, arg_rpc_ssl_certificate); - auto rpc_ssl_allowed_certificates = command_line::get_arg(vm, arg_rpc_ssl_allowed_certificates); + auto rpc_ssl_ca_file = command_line::get_arg(vm, arg_rpc_ssl_ca_certificates); auto rpc_ssl_allowed_fingerprints = command_line::get_arg(vm, arg_rpc_ssl_allowed_fingerprints); auto rpc_ssl = command_line::get_arg(vm, arg_rpc_ssl); - epee::net_utils::ssl_support_t rpc_ssl_support; - if (!epee::net_utils::ssl_support_from_string(rpc_ssl_support, rpc_ssl)) + epee::net_utils::ssl_options_t rpc_ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + + if (!rpc_ssl_ca_file.empty() || !rpc_ssl_allowed_fingerprints.empty()) { - MERROR("Invalid argument for " << std::string(arg_rpc_ssl.name)); - return false; + std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() }; + std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + + rpc_ssl_options = epee::net_utils::ssl_options_t{ + std::move(allowed_fingerprints), std::move(rpc_ssl_ca_file) + }; } - std::list<std::string> allowed_certificates; - for (const std::string &path: rpc_ssl_allowed_certificates) + + // user specified CA file or fingeprints implies enabled SSL by default + if (rpc_ssl_options.verification != epee::net_utils::ssl_verification_t::user_certificates || !command_line::is_arg_defaulted(vm, arg_rpc_ssl)) { - allowed_certificates.push_back({}); - if (!epee::file_io_utils::load_file_to_string(path, allowed_certificates.back())) - { - MERROR("Failed to load certificate: " << path); - allowed_certificates.back() = std::string(); - } + if (!epee::net_utils::ssl_support_from_string(rpc_ssl_options.support, rpc_ssl)) + { + MERROR("Invalid argument for " << std::string(arg_rpc_ssl.name)); + return false; + } } - std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() }; - std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector); + rpc_ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(rpc_ssl_private_key), std::move(rpc_ssl_certificate) + }; m_auto_refresh_period = DEFAULT_AUTO_REFRESH_PERIOD; m_last_auto_refresh_time = boost::posix_time::min_date_time; + check_background_mining(); + m_net_server.set_threads_prefix("RPC"); auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login), - rpc_ssl_support, std::make_pair(rpc_ssl_private_key, rpc_ssl_certificate), std::move(allowed_certificates), std::move(allowed_fingerprints) + std::move(rpc_ssl_options) ); } //------------------------------------------------------------------------------------------------------------------------------ + void wallet_rpc_server::check_background_mining() + { + if (!m_wallet) + return; + + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) + { + MLOG_RED(el::Level::Warning, "Background mining not enabled. Run \"set setup-background-mining 1\" in monero-wallet-cli to change."); + return; + } + + if (!m_wallet->is_trusted_daemon()) + { + MDEBUG("Using an untrusted daemon, skipping background mining check"); + return; + } + + cryptonote::COMMAND_RPC_MINING_STATUS::request req; + cryptonote::COMMAND_RPC_MINING_STATUS::response res; + bool r = m_wallet->invoke_http_json("/mining_status", req, res); + if (!r || res.status != CORE_RPC_STATUS_OK) + { + MERROR("Failed to query mining status: " << (r ? res.status : "No connection to daemon")); + return; + } + if (res.active || res.is_background_mining_enabled) + return; + + if (setup == tools::wallet2::BackgroundMiningMaybe) + { + MINFO("The daemon is not set up to background mine."); + MINFO("With background mining enabled, the daemon will mine when idle and not on batttery."); + MINFO("Enabling this supports the network you are using, and makes you eligible for receiving new monero"); + MINFO("Set setup-background-mining to 1 in monero-wallet-cli to change."); + return; + } + + cryptonote::COMMAND_RPC_START_MINING::request req2; + cryptonote::COMMAND_RPC_START_MINING::response res2; + req2.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + req2.threads_count = 1; + req2.do_background_mining = true; + req2.ignore_battery = false; + r = m_wallet->invoke_http_json("/start_mining", req2, res); + if (!r || res2.status != CORE_RPC_STATUS_OK) + { + MERROR("Failed to setup background mining: " << (r ? res.status : "No connection to daemon")); + return; + } + + MINFO("Background mining enabled. The daemon will mine when idle and not on batttery."); + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::not_open(epee::json_rpc::error& er) { er.code = WALLET_RPC_ERROR_CODE_NOT_OPEN; @@ -385,10 +447,10 @@ namespace tools try { res.balance = req.all_accounts ? m_wallet->balance_all() : m_wallet->balance(req.account_index); - res.unlocked_balance = req.all_accounts ? m_wallet->unlocked_balance_all() : m_wallet->unlocked_balance(req.account_index); + res.unlocked_balance = req.all_accounts ? m_wallet->unlocked_balance_all(&res.blocks_to_unlock) : m_wallet->unlocked_balance(req.account_index, &res.blocks_to_unlock); res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images(); std::map<uint32_t, std::map<uint32_t, uint64_t>> balance_per_subaddress_per_account; - std::map<uint32_t, std::map<uint32_t, uint64_t>> unlocked_balance_per_subaddress_per_account; + std::map<uint32_t, std::map<uint32_t, std::pair<uint64_t, uint64_t>>> unlocked_balance_per_subaddress_per_account; if (req.all_accounts) { for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) @@ -408,7 +470,7 @@ namespace tools { uint32_t account_index = p.first; std::map<uint32_t, uint64_t> balance_per_subaddress = p.second; - std::map<uint32_t, uint64_t> unlocked_balance_per_subaddress = unlocked_balance_per_subaddress_per_account[account_index]; + std::map<uint32_t, std::pair<uint64_t, uint64_t>> unlocked_balance_per_subaddress = unlocked_balance_per_subaddress_per_account[account_index]; std::set<uint32_t> address_indices; if (!req.all_accounts && !req.address_indices.empty()) { @@ -427,7 +489,8 @@ namespace tools cryptonote::subaddress_index index = {info.account_index, info.address_index}; info.address = m_wallet->get_subaddress_as_str(index); info.balance = balance_per_subaddress[i]; - info.unlocked_balance = unlocked_balance_per_subaddress[i]; + info.unlocked_balance = unlocked_balance_per_subaddress[i].first; + info.blocks_to_unlock = unlocked_balance_per_subaddress[i].second; info.label = m_wallet->get_subaddress_label(index); info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); res.per_subaddress.emplace_back(std::move(info)); @@ -1173,7 +1236,7 @@ namespace tools { const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; std::string address = cryptonote::get_account_address_as_str(m_wallet->nettype(), entry.is_subaddress, entry.addr); - if (has_encrypted_payment_id && !entry.is_subaddress) + if (has_encrypted_payment_id && !entry.is_subaddress && address != entry.original) address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.addr, payment_id8); auto i = dests.find(entry.addr); if (i == dests.end()) @@ -2916,7 +2979,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_languages(const wallet_rpc::COMMAND_RPC_GET_LANGUAGES::request& req, wallet_rpc::COMMAND_RPC_GET_LANGUAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) { - crypto::ElectrumWords::get_language_list(res.languages); + crypto::ElectrumWords::get_language_list(res.languages, true); + crypto::ElectrumWords::get_language_list(res.languages_local, false); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2947,14 +3011,19 @@ namespace tools std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename); { std::vector<std::string> languages; - crypto::ElectrumWords::get_language_list(languages); + crypto::ElectrumWords::get_language_list(languages, false); std::vector<std::string>::iterator it; it = std::find(languages.begin(), languages.end(), req.language); if (it == languages.end()) { + crypto::ElectrumWords::get_language_list(languages, true); + it = std::find(languages.begin(), languages.end(), req.language); + } + if (it == languages.end()) + { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Unknown language"; + er.message = "Unknown language: " + req.language; return false; } } @@ -4048,13 +4117,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - epee::net_utils::ssl_support_t ssl_support; - if (!epee::net_utils::ssl_support_from_string(ssl_support, req.ssl_support)) - { - er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; - er.message = std::string("Invalid ssl support mode"); - return false; - } + std::vector<std::vector<uint8_t>> ssl_allowed_fingerprints; ssl_allowed_fingerprints.reserve(req.ssl_allowed_fingerprints.size()); for (const std::string &fp: req.ssl_allowed_fingerprints) @@ -4064,7 +4127,31 @@ namespace tools for (auto c: fp) v.push_back(c); } - if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, ssl_support, std::make_pair(req.ssl_private_key_path, req.ssl_certificate_path), req.ssl_allowed_certificates, ssl_allowed_fingerprints, req.ssl_allow_any_cert)) + + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (req.ssl_allow_any_cert) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else if (!ssl_allowed_fingerprints.empty() || !req.ssl_ca_file.empty()) + ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(req.ssl_ca_file)}; + + if (!epee::net_utils::ssl_support_from_string(ssl_options.support, req.ssl_support)) + { + er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; + er.message = std::string("Invalid ssl support mode"); + return false; + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(req.ssl_private_key_path), std::move(req.ssl_certificate_path) + }; + + if (ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled && !ssl_options.has_strong_verification(boost::string_ref{})) + { + er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; + er.message = "SSL is enabled but no user certificate or fingerprints were provided"; + } + + if (!m_wallet->set_daemon(req.address, boost::none, req.trusted, std::move(ssl_options))) { er.code = WALLET_RPC_ERROR_CODE_NO_DAEMON_CONNECTION; er.message = std::string("Unable to set daemon"); @@ -4272,7 +4359,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_rpc_ssl); command_line::add_arg(desc_params, arg_rpc_ssl_private_key); command_line::add_arg(desc_params, arg_rpc_ssl_certificate); - command_line::add_arg(desc_params, arg_rpc_ssl_allowed_certificates); + command_line::add_arg(desc_params, arg_rpc_ssl_ca_certificates); command_line::add_arg(desc_params, arg_rpc_ssl_allowed_fingerprints); daemonizer::init_options(hidden_options, desc_params); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index fb0c48a80..7d2272dd0 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -254,6 +254,8 @@ namespace tools bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); + void check_background_mining(); + wallet2 *m_wallet; std::string m_wallet_dir; tools::private_file rpc_login_file; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 298f34f66..bb360ae01 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 8 +#define WALLET_RPC_VERSION_MINOR 9 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -81,6 +81,7 @@ namespace wallet_rpc uint64_t unlocked_balance; std::string label; uint64_t num_unspent_outputs; + uint64_t blocks_to_unlock; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) @@ -90,6 +91,7 @@ namespace wallet_rpc KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(label) KV_SERIALIZE(num_unspent_outputs) + KV_SERIALIZE(blocks_to_unlock) END_KV_SERIALIZE_MAP() }; @@ -99,12 +101,14 @@ namespace wallet_rpc uint64_t unlocked_balance; bool multisig_import_needed; std::vector<per_subaddress_info> per_subaddress; + uint64_t blocks_to_unlock; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(multisig_import_needed) KV_SERIALIZE(per_subaddress) + KV_SERIALIZE(blocks_to_unlock) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -1999,9 +2003,11 @@ namespace wallet_rpc struct response_t { std::vector<std::string> languages; + std::vector<std::string> languages_local; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(languages) + KV_SERIALIZE(languages_local) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -2446,7 +2452,7 @@ namespace wallet_rpc std::string ssl_support; // disabled, enabled, autodetect std::string ssl_private_key_path; std::string ssl_certificate_path; - std::list<std::string> ssl_allowed_certificates; + std::string ssl_ca_file; std::vector<std::string> ssl_allowed_fingerprints; bool ssl_allow_any_cert; @@ -2456,7 +2462,7 @@ namespace wallet_rpc KV_SERIALIZE_OPT(ssl_support, (std::string)"autodetect") KV_SERIALIZE(ssl_private_key_path) KV_SERIALIZE(ssl_certificate_path) - KV_SERIALIZE(ssl_allowed_certificates) + KV_SERIALIZE(ssl_ca_file) KV_SERIALIZE(ssl_allowed_fingerprints) KV_SERIALIZE_OPT(ssl_allow_any_cert, false) END_KV_SERIALIZE_MAP() diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 7b3fdfe57..f193133da 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -195,8 +195,10 @@ static void test(test_t t, uint64_t blocks) int main() { + TRY_ENTRY(); test(test_max, 2 * LONG_TERM_BLOCK_WEIGHT_WINDOW); test(test_lcg, 9 * LONG_TERM_BLOCK_WEIGHT_WINDOW); test(test_min, 1 * LONG_TERM_BLOCK_WEIGHT_WINDOW); return 0; + CATCH_ENTRY_L0("main", 1); } diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index 616774d18..d9cee34c1 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -135,7 +135,7 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono } } - MINFO("Selected " << i << " from tx: " << dump_keys(td.m_txid.data) + MDEBUG("Selected " << i << " from tx: " << dump_keys(td.m_txid.data) << " ki: " << dump_keys(td.m_key_image.data) << " amnt: " << td.amount() << " rct: " << td.is_rct() diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp index 9985b8710..11ce0bd73 100644 --- a/tests/difficulty/difficulty.cpp +++ b/tests/difficulty/difficulty.cpp @@ -36,6 +36,7 @@ #include <algorithm> #include <stdexcept> +#include "misc_log_ex.h" #include "cryptonote_config.h" #include "cryptonote_basic/difficulty.h" @@ -82,6 +83,8 @@ static int test_wide_difficulty(const char *filename) } int main(int argc, char *argv[]) { + TRY_ENTRY(); + if (argc < 2) { cerr << "Wrong arguments" << endl; return 1; @@ -136,4 +139,6 @@ int main(int argc, char *argv[]) { data.clear(fstream::badbit); } return 0; + + CATCH_ENTRY_L0("main", 1); } diff --git a/tests/functional_tests/bans.py b/tests/functional_tests/bans.py new file mode 100755 index 000000000..bb3051a6a --- /dev/null +++ b/tests/functional_tests/bans.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2019 The Monero Project +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are +# permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of +# conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list +# of conditions and the following disclaimer in the documentation and/or other +# materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be +# used to endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import time + +"""Test peer baning RPC calls + +Test the following RPCs: + - set_bans + - get_bans + +""" + +from framework.daemon import Daemon + +class BanTest(): + def run_test(self): + print 'Testing bans' + + daemon = Daemon() + res = daemon.get_bans() + assert 'bans' not in res or len(res.bans) == 0 + + daemon.set_bans([{'host': '1.2.3.4', 'ban': True, 'seconds': 100}]) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '1.2.3.4' + assert res.bans[0].seconds >= 98 and res.bans[0].seconds <= 100 # allow for slow RPC + + daemon.set_bans([{'host': '5.6.7.8', 'ban': True, 'seconds': 100}]) + res = daemon.get_bans() + assert len(res.bans) == 2 + for i in range(2): + assert res.bans[i].host == '1.2.3.4' or res.bans[i].host == '5.6.7.8' + assert res.bans[i].seconds >= 7 and res.bans[0].seconds <= 100 # allow for slow RPC + + daemon.set_bans([{'host': '1.2.3.4', 'ban': False}]) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 98 and res.bans[0].seconds <= 100 # allow for slow RPC + + time.sleep(2) + + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 96 and res.bans[0].seconds <= 98 # allow for slow RPC + + daemon.set_bans([{'host': '3.4.5.6', 'ban': False}]) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 96 and res.bans[0].seconds <= 98 # allow for slow RPC + + daemon.set_bans([{'host': '3.4.5.6', 'ban': True, 'seconds': 2}]) + res = daemon.get_bans() + assert len(res.bans) == 2 + for i in range(2): + assert res.bans[i].host == '5.6.7.8' or res.bans[i].host == '3.4.5.6' + if res.bans[i].host == '5.6.7.8': + assert res.bans[i].seconds >= 96 and res.bans[0].seconds <= 98 # allow for slow RPC + else: + assert res.bans[i].seconds >= 1 and res.bans[0].seconds <= 2 # allow for slow RPC + + time.sleep(2) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 94 and res.bans[0].seconds <= 96 # allow for slow RPC + + daemon.set_bans([{'host': '5.6.7.8', 'ban': True, 'seconds': 20}]) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 18 and res.bans[0].seconds <= 20 # allow for slow RPC + + daemon.set_bans([{'host': '5.6.7.8', 'ban': True, 'seconds': 200}]) + res = daemon.get_bans() + assert len(res.bans) == 1 + assert res.bans[0].host == '5.6.7.8' + assert res.bans[0].seconds >= 198 and res.bans[0].seconds <= 200 # allow for slow RPC + + daemon.set_bans([{'host': '5.6.7.8', 'ban': False}]) + res = daemon.get_bans() + assert 'bans' not in res or len(res.bans) == 0 + + +if __name__ == '__main__': + BanTest().run_test() diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py index 3c4bc264f..56164600d 100755 --- a/tests/functional_tests/blockchain.py +++ b/tests/functional_tests/blockchain.py @@ -46,6 +46,7 @@ from framework.daemon import Daemon class BlockchainTest(): def run_test(self): self._test_generateblocks(5) + self._test_alt_chains() def _test_generateblocks(self, blocks): assert blocks >= 2 @@ -152,6 +153,163 @@ class BlockchainTest(): except: ok = True assert ok + # get transactions + res = daemon.get_info() + assert res.height == height + blocks - 1 + nblocks = height + blocks - 1 + res = daemon.getblockheadersrange(0, nblocks - 1) + assert len(res.headers) == nblocks + assert res.headers[-1] == block_header + txids = [x.miner_tx_hash for x in res.headers] + res = daemon.get_transactions(txs_hashes = txids) + assert len(res.txs) == nblocks + assert not 'missed_txs' in res or len(res.missed_txs) == 0 + running_output_index = 0 + for i in range(len(txids)): + tx = res.txs[i] + assert tx.tx_hash == txids[i] + assert not tx.double_spend_seen + assert not tx.in_pool + assert tx.block_height == i + if i > 0: + for idx in tx.output_indices: + assert idx == running_output_index + running_output_index += 1 + res_out = daemon.get_outs([{'amount': 0, 'index': i} for i in tx.output_indices], get_txid = True) + assert len(res_out.outs) == len(tx.output_indices) + for out in res_out.outs: + assert len(out.key) == 64 + assert len(out.mask) == 64 + assert not out.unlocked + assert out.height == i + 1 + assert out.txid == txids[i + 1] + + for i in range(height + nblocks - 1): + res_sum = daemon.get_coinbase_tx_sum(i, 1) + res_header = daemon.getblockheaderbyheight(i) + assert res_sum.emission_amount == res_header.block_header.reward + + res = daemon.get_coinbase_tx_sum(0, 1) + assert res.emission_amount == 17592186044415 + assert res.fee_amount == 0 + sum_blocks = height + nblocks - 1 + res = daemon.get_coinbase_tx_sum(0, sum_blocks) + extrapolated = 17592186044415 + 17592186044415 * 2 * (sum_blocks - 1) + assert res.emission_amount < extrapolated and res.emission_amount > extrapolated - 1e12 + assert res.fee_amount == 0 + sum_blocks_emission = res.emission_amount + res = daemon.get_coinbase_tx_sum(1, sum_blocks) + assert res.emission_amount == sum_blocks_emission - 17592186044415 + assert res.fee_amount == 0 + + res = daemon.get_output_distribution([0, 1, 17592186044415], 0, 0) + assert len(res.distributions) == 3 + for a in range(3): + assert res.distributions[a].amount == [0, 1, 17592186044415][a] + assert res.distributions[a].start_height == 0 + assert res.distributions[a].base == 0 + assert len(res.distributions[a].distribution) == height + nblocks - 1 + assert res.distributions[a].binary == False + for i in range(height + nblocks - 1): + assert res.distributions[a].distribution[i] == (1 if i > 0 and a == 0 else 1 if a == 2 and i == 0 else 0) + + res = daemon.get_output_histogram([], min_count = 0, max_count = 0) + assert len(res.histogram) == 2 + for i in range(2): + assert res.histogram[i].amount in [0, 17592186044415] + assert res.histogram[i].total_instances in [height + nblocks - 2, 1] + assert res.histogram[i].unlocked_instances == 0 + assert res.histogram[i].recent_instances == 0 + + def _test_alt_chains(self): + print('Testing alt chains') + daemon = Daemon() + res = daemon.get_alt_blocks_hashes() + starting_alt_blocks = res.blks_hashes if 'blks_hashes' in res else [] + res = daemon.get_info() + root_block_hash = res.top_block_hash + height = res.height + prev_hash = res.top_block_hash + res_template = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm') + nonce = 0 + + # 5 siblings + alt_blocks = [None] * 5 + for i in range(len(alt_blocks)): + res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1, prev_block = prev_hash, starting_nonce = nonce) + assert res.height == height + assert len(res.blocks) == 1 + txid = res.blocks[0] + res = daemon.getblockheaderbyhash(txid) + nonce = res.block_header.nonce + print('mined ' + ('alt' if res.block_header.orphan_status else 'tip') + ' block ' + str(height) + ', nonce ' + str(nonce)) + assert res.block_header.prev_hash == prev_hash + assert res.block_header.orphan_status == (i > 0) + alt_blocks[i] = txid + nonce += 1 + + print 'mining 3 on 1' + # three more on [1] + chain1 = [] + res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 3, prev_block = alt_blocks[1], starting_nonce = nonce) + assert res.height == height + 3 + assert len(res.blocks) == 3 + blk_hash = res.blocks[2] + res = daemon.getblockheaderbyhash(blk_hash) + nonce = res.block_header.nonce + assert not res.block_header.orphan_status + nonce += 1 + chain1.append(blk_hash) + chain1.append(res.block_header.prev_hash) + + print('Checking alt blocks match') + res = daemon.get_alt_blocks_hashes() + assert len(res.blks_hashes) == len(starting_alt_blocks) + 4 + for txid in alt_blocks: + assert txid in res.blks_hashes or txid == alt_blocks[1] + + print 'mining 4 on 3' + # 4 more on [3], the chain will reorg when we mine the 4th + top_block_hash = blk_hash + prev_block = alt_blocks[3] + for i in range(4): + res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1, prev_block = prev_block) + assert res.height == height + 1 + i + assert len(res.blocks) == 1 + prev_block = res.blocks[-1] + res = daemon.getblockheaderbyhash(res.blocks[-1]) + assert res.block_header.orphan_status == (i < 3) + + res = daemon.get_info() + assert res.height == ((height + 4) if i < 3 else height + 5) + assert res.top_block_hash == (top_block_hash if i < 3 else prev_block) + + res = daemon.get_info() + assert res.height == height + 5 + assert res.top_block_hash == prev_block + + print('Checking alt blocks match') + res = daemon.get_alt_blocks_hashes() + blks_hashes = res.blks_hashes + assert len(blks_hashes) == len(starting_alt_blocks) + 7 + for txid in alt_blocks: + assert txid in blks_hashes or txid == alt_blocks[3] + for txid in chain1: + assert txid in blks_hashes + + res = daemon.get_alternate_chains() + assert len(res.chains) == 4 + tips = [chain.block_hash for chain in res.chains] + for txid in tips: + assert txid in blks_hashes + for chain in res.chains: + assert chain.length in [1, 4] + assert chain.length == len(chain.block_hashes) + assert chain.height == height + chain.length - 1 # all happen start at the same height + assert chain.main_chain_parent_block == root_block_hash + for txid in [alt_blocks[0], alt_blocks[2], alt_blocks[4]]: + assert len([chain for chain in res.chains if chain.block_hash == txid]) == 1 + if __name__ == '__main__': BlockchainTest().run_test() diff --git a/tests/functional_tests/cold_signing.py b/tests/functional_tests/cold_signing.py index 6895aec60..e5430f87c 100755 --- a/tests/functional_tests/cold_signing.py +++ b/tests/functional_tests/cold_signing.py @@ -89,6 +89,12 @@ class ColdSigningTest(): dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde' + self.hot_wallet.refresh() + res = self.hot_wallet.export_outputs() + self.cold_wallet.import_outputs(res.outputs_data_hex) + res = self.cold_wallet.export_key_images(True) + self.hot_wallet.import_key_images(res.signed_key_images, offset = res.offset) + res = self.hot_wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False) assert len(res.tx_hash) == 32*2 txid = res.tx_hash @@ -104,6 +110,22 @@ class ColdSigningTest(): unsigned_txset = res.unsigned_txset print 'Signing transaction with cold wallet' + res = self.cold_wallet.describe_transfer(unsigned_txset = unsigned_txset) + assert len(res.desc) == 1 + desc = res.desc[0] + assert desc.amount_in >= amount + fee + assert desc.amount_out == desc.amount_in - fee + assert desc.ring_size == 11 + assert desc.unlock_time == 0 + assert desc.payment_id == payment_id + assert desc.change_amount == desc.amount_in - 1000000000000 - fee + assert desc.change_address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert desc.fee == fee + assert len(desc.recipients) == 1 + rec = desc.recipients[0] + assert rec.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert rec.amount == 1000000000000 + res = self.cold_wallet.sign_transfer(unsigned_txset) assert len(res.signed_txset) > 0 signed_txset = res.signed_txset diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index f2fef7e95..83b75a088 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -133,3 +133,5 @@ if len(FAIL) == 0: print('Done, ' + str(len(PASS)) + '/' + str(len(tests)) + ' tests passed') else: print('Done, ' + str(len(FAIL)) + '/' + str(len(tests)) + ' tests failed: ' + string.join(FAIL, ', ')) + +sys.exit(0 if len(FAIL) == 0 else 1) diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index a0e8551cd..a61f30001 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -129,6 +129,7 @@ class MultisigTest(): addresses.append(res.address) for i in range(N_total): assert addresses[i] == expected_address + self.wallet_address = expected_address for i in range(N_total): res = self.wallet[i].is_multisig() @@ -181,6 +182,22 @@ class MultisigTest(): for i in range(len(signers[1:])): print('Signing multisig transaction with wallet ' + str(signers[i+1])) + res = self.wallet[signers[i+1]].describe_transfer(multisig_txset = multisig_txset) + assert len(res.desc) == 1 + desc = res.desc[0] + assert desc.amount_in >= amount + fee + assert desc.amount_out == desc.amount_in - fee + assert desc.ring_size == 11 + assert desc.unlock_time == 0 + assert desc.payment_id == '0000000000000000' + assert desc.change_amount == desc.amount_in - 1000000000000 - fee + assert desc.change_address == self.wallet_address + assert desc.fee == fee + assert len(desc.recipients) == 1 + rec = desc.recipients[0] + assert rec.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert rec.amount == 1000000000000 + res = self.wallet[signers[i+1]].sign_multisig(multisig_txset) multisig_txset = res.tx_data_hex assert len(res.tx_hash_list if 'tx_hash_list' in res else []) == (i == len(signers[1:]) - 1) diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index b7a85f1d6..050277c51 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -42,6 +42,7 @@ class TransferTest(): self.mine() self.transfer() self.check_get_bulk_payments() + self.check_double_spend_detection() def create(self): print 'Creating wallets' @@ -62,9 +63,14 @@ class TransferTest(): print("Mining some blocks") daemon = Daemon() + res = daemon.get_info() + height = res.height + daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) for i in range(len(self.wallet)): self.wallet[i].refresh() + res = self.wallet[i].get_height() + assert res.height == height + 80 def transfer(self): daemon = Daemon() @@ -169,6 +175,27 @@ class TransferTest(): assert e.double_spend_seen == False assert e.confirmations == 1 + res = self.wallet[0].get_height() + wallet_height = res.height + res = self.wallet[0].get_transfer_by_txid(txid) + assert len(res.transfers) == 1 + assert res.transfers[0] == res.transfer + t = res.transfer + assert t.txid == txid + assert t.payment_id == payment_id + assert t.height == wallet_height - 1 + assert t.timestamp > 0 + assert t.amount == 0 # to self, so it's just "pay a fee" really + assert t.fee == fee + assert t.note == '' + assert len(t.destinations) == 1 + assert t.destinations[0] == {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + assert t.type == 'out' + assert t.unlock_time == 0 + assert t.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert t.double_spend_seen == False + assert t.confirmations == 1 + res = self.wallet[0].get_balance() assert res.balance == running_balances[0] assert res.unlocked_balance <= res.balance @@ -483,5 +510,65 @@ class TransferTest(): res = self.wallet[2].get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64]) assert len(res.payments) >= 1 # one tx was sent + def check_double_spend_detection(self): + print('Checking double spend detection') + txes = [[None, None], [None, None]] + for i in range(2): + self.wallet[0].restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted') + self.wallet[0].refresh() + res = self.wallet[0].get_balance() + unlocked_balance = res.unlocked_balance + res = self.wallet[0].sweep_all(address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', do_not_relay = True, get_tx_hex = True) + assert len(res.tx_hash_list) == 1 + assert len(res.tx_hash_list[0]) == 32*2 + txes[i][0] = res.tx_hash_list[0] + assert len(res.fee_list) == 1 + assert res.fee_list[0] > 0 + assert len(res.amount_list) == 1 + assert res.amount_list[0] == unlocked_balance - res.fee_list[0] + assert len(res.tx_blob_list) > 0 + assert len(res.tx_blob_list[0]) > 0 + assert not 'tx_metadata_list' in res or len(res.tx_metadata_list) == 0 + assert not 'multisig_txset' in res or len(res.multisig_txset) == 0 + assert not 'unsigned_txset' in res or len(res.unsigned_txset) == 0 + assert len(res.tx_blob_list) == 1 + txes[i][1] = res.tx_blob_list[0] + + daemon = Daemon() + res = daemon.send_raw_transaction(txes[0][1]) + assert res.not_relayed == False + assert res.low_mixin == False + assert res.double_spend == False + assert res.invalid_input == False + assert res.invalid_output == False + assert res.too_big == False + assert res.overspend == False + assert res.fee_too_low == False + assert res.not_rct == False + + res = daemon.get_transactions([txes[0][0]]) + assert len(res.txs) >= 1 + tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0] + assert tx.in_pool + assert not tx.double_spend_seen + + res = daemon.send_raw_transaction(txes[1][1]) + assert res.not_relayed == False + assert res.low_mixin == False + assert res.double_spend == True + assert res.invalid_input == False + assert res.invalid_output == False + assert res.too_big == False + assert res.overspend == False + assert res.fee_too_low == False + assert res.not_rct == False + + res = daemon.get_transactions([txes[0][0]]) + assert len(res.txs) >= 1 + tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0] + assert tx.in_pool + assert tx.double_spend_seen + + if __name__ == '__main__': TransferTest().run_test() diff --git a/tests/functional_tests/wallet_address.py b/tests/functional_tests/wallet_address.py index 66a1633ca..cb9c52e7a 100755 --- a/tests/functional_tests/wallet_address.py +++ b/tests/functional_tests/wallet_address.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +#encoding=utf-8 # Copyright (c) 2019 The Monero Project # @@ -45,6 +46,8 @@ class WalletAddressTest(): self.check_main_address() self.check_keys() self.create_subaddresses() + self.open_close() + self.languages() def create(self): print 'Creating wallet' @@ -148,5 +151,52 @@ class WalletAddressTest(): res = wallet.get_address_index('82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf') assert res.index == {'major': 1, 'minor': 0} + def open_close(self): + print 'Testing open/close' + wallet = Wallet() + + res = wallet.get_address() + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + + wallet.close_wallet() + ok = False + try: res = wallet.get_address() + except: ok = True + assert ok + + wallet.restore_deterministic_wallet(seed = 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout') + res = wallet.get_address() + assert res.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + + wallet.close_wallet() + ok = False + try: wallet.get_address() + except: ok = True + assert ok + + wallet.restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted') + res = wallet.get_address() + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + + def languages(self): + print('Testing languages') + wallet = Wallet() + res = wallet.get_languages() + assert 'English' in res.languages + assert 'English' in res.languages_local + assert 'Dutch' in res.languages + assert 'Nederlands' in res.languages_local + assert 'Japanese' in res.languages + assert u'日本語' in res.languages_local + try: wallet.close_wallet() + except: pass + languages = res.languages + for language in languages: + print 'Creating ' + str(language) + ' wallet' + wallet.create_wallet(filename = '', language = language) + res = wallet.query_key('mnemonic') + wallet.close_wallet() + + if __name__ == '__main__': WalletAddressTest().run_test() diff --git a/tests/hash-target.cpp b/tests/hash-target.cpp index 12acc5a67..1e988c302 100644 --- a/tests/hash-target.cpp +++ b/tests/hash-target.cpp @@ -32,6 +32,7 @@ #include <cstdlib> #include <cstring> #include <limits> +#include "misc_log_ex.h" #include "crypto/hash.h" #include "cryptonote_basic/difficulty.h" @@ -39,6 +40,7 @@ using namespace std; using cryptonote::check_hash; int main(int argc, char *argv[]) { + TRY_ENTRY(); crypto::hash h; for (cryptonote::difficulty_type diff = 1;; diff += 1 + (diff >> 8)) { for (uint16_t b = 0; b < 256; b++) { @@ -83,4 +85,5 @@ int main(int argc, char *argv[]) { } } return 0; + CATCH_ENTRY_L0("main", 1); } diff --git a/tests/hash/main.cpp b/tests/hash/main.cpp index adf1bd9c4..d62098a60 100644 --- a/tests/hash/main.cpp +++ b/tests/hash/main.cpp @@ -35,6 +35,7 @@ #include <string> #include <cfenv> +#include "misc_log_ex.h" #include "warnings.h" #include "crypto/hash.h" #include "crypto/variant2_int_sqrt.h" @@ -89,6 +90,8 @@ int test_variant2_int_sqrt(); int test_variant2_int_sqrt_ref(); int main(int argc, char *argv[]) { + TRY_ENTRY(); + hash_f *f; hash_func *hf; fstream input; @@ -183,6 +186,7 @@ int main(int argc, char *argv[]) { } } return error ? 1 : 0; + CATCH_ENTRY_L0("main", 1); } #if defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64)) diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 02faae50d..c34da04b7 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -1139,6 +1139,8 @@ TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) int main(int argc, char** argv) { + TRY_ENTRY(); + tools::on_startup(); // we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR" @@ -1173,4 +1175,5 @@ int main(int argc, char** argv) ::testing::InitGoogleTest(&argc, argv); Monero::WalletManagerFactory::setLogLevel(Monero::WalletManagerFactory::LogLevel_Max); return RUN_ALL_TESTS(); + CATCH_ENTRY_L0("main", 1); } diff --git a/tests/trezor/daemon.cpp b/tests/trezor/daemon.cpp index 5e987793a..41af93f3f 100644 --- a/tests/trezor/daemon.cpp +++ b/tests/trezor/daemon.cpp @@ -101,6 +101,9 @@ void mock_daemon::load_params(boost::program_options::variables_map const & vm) mock_daemon::~mock_daemon() { + if(m_http_client.is_connected()) + m_http_client.disconnect(); + if (!m_terminated) { try @@ -134,11 +137,14 @@ void mock_daemon::init() if(m_http_client.is_connected()) m_http_client.disconnect(); - CHECK_AND_ASSERT_THROW_MES(m_http_client.set_server(rpc_addr(), boost::none), "RPC client init fail"); + CHECK_AND_ASSERT_THROW_MES(m_http_client.set_server(rpc_addr(), boost::none, epee::net_utils::ssl_support_t::e_ssl_support_disabled), "RPC client init fail"); } void mock_daemon::deinit() { + if(m_http_client.is_connected()) + m_http_client.disconnect(); + try { m_rpc_server.deinit(); diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index 310fa45f1..31c2471ec 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -60,13 +60,14 @@ namespace #define HW_TREZOR_NAME "Trezor" #define TREZOR_ACCOUNT_ORDERING &m_miner_account, &m_alice_account, &m_bob_account, &m_eve_account -#define TREZOR_COMMON_TEST_CASE(genclass, CORE, BASE) \ +#define TREZOR_COMMON_TEST_CASE(genclass, CORE, BASE) do { \ rollback_chain(CORE, BASE.head_block()); \ { \ genclass ctest; \ BASE.fork(ctest); \ GENERATE_AND_PLAY_INSTANCE(genclass, ctest, *(CORE)); \ - } + } \ +} while(0) #define TREZOR_SETUP_CHAIN(NAME) do { \ ++tests_count; \ @@ -83,6 +84,11 @@ static device_trezor_test *ensure_trezor_test_device(); static void rollback_chain(cryptonote::core * core, const cryptonote::block & head); static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain, const po::variables_map & vm_core); +static long get_env_long(const char * flag_name, boost::optional<long> def = boost::none){ + const char *env_data = getenv(flag_name); + return env_data ? atol(env_data) : (def ? def.get() : 0); +} + int main(int argc, char* argv[]) { TRY_ENTRY(); @@ -127,12 +133,13 @@ int main(int argc, char* argv[]) const bool heavy_tests = command_line::get_arg(vm, arg_heavy_tests); const bool fix_chain = command_line::get_arg(vm, arg_fix_chain); - hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device()); - // hw::trezor::register_all(); // We use our shim instead. + hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device()); // shim device for call tracking // Bootstrapping common chain & accounts - const uint8_t initial_hf = 9; - const uint8_t max_hf = 10; + const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", 11); + const uint8_t max_hf = (uint8_t)get_env_long("TEST_MAX_HF", 11); + MINFO("Test versions " << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); + MINFO("Testing hardforks [" << (int)initial_hf << ", " << (int)max_hf << "]"); cryptonote::core core_obj(nullptr); cryptonote::core * const core = &core_obj; @@ -150,16 +157,20 @@ int main(int argc, char* argv[]) mock_daemon::default_options(vm_core); // Transaction tests - for(uint8_t hf=initial_hf; hf <= max_hf; ++hf) + for(uint8_t hf=initial_hf; hf <= max_hf + 1; ++hf) { - MDEBUG("Transaction tests for HF " << (int)hf); - if (hf > initial_hf) + if (hf > initial_hf || hf > max_hf) { daemon->stop_and_deinit(); daemon = nullptr; trezor_base.daemon(nullptr); + if (hf > max_hf) + { + break; + } } + MDEBUG("Transaction tests for HF " << (int)hf); trezor_base.set_hard_fork(hf); TREZOR_SETUP_CHAIN(std::string("HF") + std::to_string((int)hf)); @@ -196,7 +207,6 @@ int main(int argc, char* argv[]) TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo, core, trezor_base); } - daemon->stop(); core->deinit(); el::Level level = (failed_tests.empty() ? el::Level::Info : el::Level::Error); MLOG(level, "\nREPORT:"); @@ -225,6 +235,7 @@ static void rollback_chain(cryptonote::core * core, const cryptonote::block & he crypto::hash head_hash = get_block_hash(head), cur_hash{}; uint64_t height = get_block_height(head), cur_height=0; + MDEBUG("Rollbacking to " << height << " to hash " << head_hash); do { core->get_blockchain_top(cur_height, cur_hash); @@ -593,7 +604,7 @@ gen_trezor_base::gen_trezor_base(){ gen_trezor_base::gen_trezor_base(const gen_trezor_base &other): m_generator(other.m_generator), m_bt(other.m_bt), m_miner_account(other.m_miner_account), m_bob_account(other.m_bob_account), m_alice_account(other.m_alice_account), m_eve_account(other.m_eve_account), - m_hard_forks(other.m_hard_forks), m_trezor(other.m_trezor), m_rct_config(other.m_rct_config), + m_hard_forks(other.m_hard_forks), m_trezor(other.m_trezor), m_rct_config(other.m_rct_config), m_top_hard_fork(other.m_top_hard_fork), m_heavy_tests(other.m_heavy_tests), m_test_get_tx_key(other.m_test_get_tx_key), m_live_refresh_enabled(other.m_live_refresh_enabled), m_network_type(other.m_network_type), m_daemon(other.m_daemon) { @@ -625,6 +636,7 @@ void gen_trezor_base::fork(gen_trezor_base & other) other.m_events = m_events; other.m_head = m_head; other.m_hard_forks = m_hard_forks; + other.m_top_hard_fork = m_top_hard_fork; other.m_trezor_path = m_trezor_path; other.m_heavy_tests = m_heavy_tests; other.m_rct_config = m_rct_config; @@ -806,8 +818,6 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) // RCT transactions, wallets have to be used wallet_tools::process_transactions(m_wl_alice.get(), events, blk_5r, m_bt); wallet_tools::process_transactions(m_wl_bob.get(), events, blk_5r, m_bt); - MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get())); - MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get())); // Send Alice -> Bob, manually constructed. Simple TX test, precondition. cryptonote::transaction tx_1; @@ -827,7 +837,12 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(resx, "tx_1 semantics failed"); CHECK_AND_ASSERT_THROW_MES(resy, "tx_1 non-semantics failed"); - REWIND_BLOCKS_HF(events, blk_6r, blk_6, m_miner_account, CUR_HF); + REWIND_BLOCKS_N_HF(events, blk_6r, blk_6, m_miner_account, 10, CUR_HF); + wallet_tools::process_transactions(m_wl_alice.get(), events, blk_6, m_bt); + wallet_tools::process_transactions(m_wl_bob.get(), events, blk_6, m_bt); + MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get())); + MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get())); + m_head = blk_6r; m_events = events; return true; @@ -889,15 +904,44 @@ void gen_trezor_base::load(std::vector<test_event_entry>& events) MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get())); } +void gen_trezor_base::rewind_blocks(std::vector<test_event_entry>& events, size_t rewind_n, uint8_t hf) +{ + auto & generator = m_generator; // macro shortcut + REWIND_BLOCKS_N_HF(events, blk_new, m_head, m_miner_account, rewind_n, hf); + m_head = blk_new; + m_events = events; + MDEBUG("Blocks rewound: " << rewind_n << ", #blocks: " << num_blocks(events) << ", #events: " << events.size()); + + wallet_tools::process_transactions(m_wl_alice.get(), events, m_head, m_bt); + wallet_tools::process_transactions(m_wl_bob.get(), events, m_head, m_bt); +} + void gen_trezor_base::fix_hf(std::vector<test_event_entry>& events) { // If current test requires higher hard-fork, move it up const auto current_hf = m_hard_forks.back().first; - if (m_rct_config.bp_version == 2 && current_hf < 10){ + + if (current_hf > m_top_hard_fork) + { + throw std::runtime_error("Generated chain hardfork is higher than desired maximum"); + } + + if (m_rct_config.bp_version == 2 && m_top_hard_fork < 10) + { + throw std::runtime_error("Desired maximum is too low for BPv2"); + } + + if (current_hf < m_top_hard_fork) + { auto hardfork_height = num_blocks(events); - ADD_HARDFORK(m_hard_forks, 10, hardfork_height); + ADD_HARDFORK(m_hard_forks, m_top_hard_fork, hardfork_height); add_top_hfork(events, m_hard_forks); - MDEBUG("Hardfork height: " << hardfork_height); + MDEBUG("Hardfork added at height: " << hardfork_height << ", from " << (int)current_hf << " to " << (int)m_top_hard_fork); + + if (current_hf < 10) + { // buffer blocks, add 10 to apply v10 rules + rewind_blocks(events, 10, m_top_hard_fork); + } } } @@ -934,10 +978,9 @@ void gen_trezor_base::add_transactions_to_events( { // If current test requires higher hard-fork, move it up const auto current_hf = m_hard_forks.back().first; - const uint8_t tx_hf = m_rct_config.bp_version == 2 ? 10 : 9; - if (tx_hf > current_hf){ - throw std::runtime_error("Too late for HF change"); - } + const uint8_t tx_hf = m_top_hard_fork; + CHECK_AND_ASSERT_THROW_MES(tx_hf <= current_hf, "Too late for HF change: " << (int)tx_hf << " current: " << (int)current_hf); + CHECK_AND_ASSERT_THROW_MES(m_rct_config.bp_version < 2 || tx_hf >= 10, "HF too low for BPv2: " << (int)tx_hf); std::list<cryptonote::transaction> tx_list; for(const auto & tx : txs) @@ -1406,7 +1449,7 @@ tsx_builder * tsx_builder::construct_pending_tx(tools::wallet2::pending_tx &ptx, ptx.construction_data.extra = tx.extra; ptx.construction_data.unlock_time = 0; ptx.construction_data.use_rct = true; - ptx.construction_data.use_bulletproofs = true; + ptx.construction_data.rct_config = m_rct_config; ptx.construction_data.dests = m_destinations_orig; ptx.construction_data.subaddr_account = 0; @@ -1808,7 +1851,7 @@ bool wallet_api_tests::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(w->init(daemon()->rpc_addr(), 0), "Wallet init fail"); CHECK_AND_ASSERT_THROW_MES(w->refresh(), "Refresh fail"); uint64_t balance = w->balance(0); - MINFO("Balance: " << balance); + MDEBUG("Balance: " << balance); CHECK_AND_ASSERT_THROW_MES(w->status() == Monero::PendingTransaction::Status_Ok, "Status nok"); auto addr = get_address(m_eve_account); diff --git a/tests/trezor/trezor_tests.h b/tests/trezor/trezor_tests.h index bed49fec4..46eb5e6a5 100644 --- a/tests/trezor/trezor_tests.h +++ b/tests/trezor/trezor_tests.h @@ -84,6 +84,8 @@ public: virtual void mine_and_test(std::vector<test_event_entry>& events); + virtual void rewind_blocks(std::vector<test_event_entry>& events, size_t rewind_n, uint8_t hf); + virtual void set_hard_fork(uint8_t hf); crypto::hash head_hash() const { return get_block_hash(m_head); } diff --git a/tests/unit_tests/hmac_keccak.cpp b/tests/unit_tests/hmac_keccak.cpp index cb35d272a..3898d4a7a 100644 --- a/tests/unit_tests/hmac_keccak.cpp +++ b/tests/unit_tests/hmac_keccak.cpp @@ -88,7 +88,8 @@ static void test_keccak_hmac(const size_t * chunks) uint8_t key_buff[1024]; uint8_t res_exp[32]; uint8_t res_comp[32]; - const size_t len_chunks = chunks ? sizeof(chunks) / sizeof(*chunks) : 0; + size_t len_chunks = 0; + for(; chunks && chunks[len_chunks] > 0; ++len_chunks); for (size_t i = 0; i < (sizeof(keccak_hmac_vectors) / sizeof(*keccak_hmac_vectors)); i++) { @@ -124,29 +125,29 @@ static void test_keccak_hmac(const size_t * chunks) TEST(keccak_hmac, ) { - test_keccak_hmac({}); + test_keccak_hmac(nullptr); } TEST(keccak_hmac, 1) { - static const size_t chunks[] = {1}; + static const size_t chunks[] = {1, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 1_20) { - static const size_t chunks[] = {1, 20}; + static const size_t chunks[] = {1, 20, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 136_1) { - static const size_t chunks[] = {136, 1}; + static const size_t chunks[] = {136, 1, 0}; test_keccak_hmac(chunks); } TEST(keccak_hmac, 137_1) { - static const size_t chunks[] = {137, 1}; + static const size_t chunks[] = {137, 1, 0}; test_keccak_hmac(chunks); } diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index ce6ff3551..bf1368618 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -76,6 +76,11 @@ public: while (count-- && start_height < blocks.size()) ret.push_back(blocks[start_height++].long_term_weight); return ret; } + virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override { + crypto::hash hash = crypto::null_hash; + *(uint64_t*)&hash = height; + return hash; + } virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override { uint64_t h = height(); crypto::hash top = crypto::null_hash; diff --git a/utils/python-rpc/console b/utils/python-rpc/console new file mode 100755 index 000000000..a4ec6a9ad --- /dev/null +++ b/utils/python-rpc/console @@ -0,0 +1,3 @@ +#!/bin/sh + +env python -i $(dirname $0)/console.py "$@" diff --git a/utils/python-rpc/console.py b/utils/python-rpc/console.py index ab0d9f27f..57a04528b 100755 --- a/utils/python-rpc/console.py +++ b/utils/python-rpc/console.py @@ -4,46 +4,85 @@ from __future__ import print_function import sys import subprocess import socket -from framework import rpc -from framework import wallet -from framework import daemon +import urlparse +import framework.rpc +import framework.daemon +import framework.wallet -USAGE = 'usage: python -i console.py <port>' -try: - port = int(sys.argv[1]) -except: - print(USAGE) - sys.exit(1) - -# check for open port -s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) -s.settimeout(1) -if s.connect_ex(('127.0.0.1', port)) != 0: - print('No wallet or daemon RPC on port ' + str(port)) - sys.exit(1) -s.close() - -# both wallet and daemon have a get_version JSON RPC -rpc = rpc.JSONRPC('{protocol}://{host}:{port}'.format(protocol='http', host='127.0.0.1', port=port)) -get_version = { - 'method': 'get_version', - 'jsonrpc': '2.0', - 'id': '0' -} -try: - res = rpc.send_json_rpc_request(get_version) -except Exception, e: - print('Failed to call version RPC: ' + str(e)) - sys.exit(1) +USAGE = 'usage: python -i console.py [[[scheme]<host>:]<port> [[[scheme]<host>:]<port>...]]' +daemons = [] +wallets = [] +rpcs = [] +for n in range(1, len(sys.argv)): + scheme='http' + host='127.0.0.1' + port=None + try: + try: + port = int(sys.argv[n]) + except: + t = urlparse.urlparse(sys.argv[n], allow_fragments = False) + scheme = t.scheme or scheme + host = t.hostname or host + port = t.port or port + if scheme != 'http' and scheme != 'https': + raise Exception(USAGE) + if port <= 0 or port > 65535: + raise Exception(USAGE) + except Exception, e: + print('Error: ' + str(e)) + raise Exception(USAGE) + + # check for open port + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(1) + if s.connect_ex((host, port)) != 0: + raise Exception('No wallet or daemon RPC on port ' + str(port)) + s.close() + + # both wallet and daemon have a get_version JSON RPC + rpc = framework.rpc.JSONRPC('{protocol}://{host}:{port}'.format(protocol=scheme, host=host, port=port)) + get_version = { + 'method': 'get_version', + 'jsonrpc': '2.0', + 'id': '0' + } + try: + res = rpc.send_json_rpc_request(get_version) + except Exception, e: + raise Exception('Failed to call version RPC: ' + str(e)) -if 'version' not in res: - print('Server is not a monero process') - sys.exit(1) + if 'version' not in res: + raise Exception('Server is not a Monero process') -if 'status' in res: - rpc = daemon.Daemon(port=port) + if 'status' in res: + daemons.append(framework.daemon.Daemon(port=port)) + rpcs.append(daemons[-1]) + else: + wallets.append(framework.wallet.Wallet(port=port)) + rpcs.append(wallets[-1]) + +# add tab completion if we can: https://stackoverflow.com/questions/246725 +try: + import readline +except: + pass else: - rpc = wallet.Wallet(port=port) + import rlcompleter + readline.parse_and_bind('tab: complete') + +if len(daemons) == 1: + daemon = daemons[0] +if len(wallets) == 1: + wallet = wallets[0] -print('Connected to %s RPC on port %u' % ('daemon' if 'status' in res else 'wallet', port)) -print('The \'rpc\' object may now be used to use the API') +didx = 0 +widx = 0 +for rpc in rpcs: + if type(rpc) == framework.daemon.Daemon: + var = "daemon" if len(daemons) == 1 else "daemons[" + str(didx) + "]" + didx += 1 + else: + var = "wallet" if len(wallets) == 1 else "wallets[" + str(widx) + "]" + widx += 1 + print('Variable \'%s\' connected to %s RPC on %s:%u' % (var, 'daemon' if type(rpc) == framework.daemon.Daemon else 'wallet', rpc.host ,rpc.port)) diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index f60fe62db..f4d5e90f0 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -33,14 +33,17 @@ from .rpc import JSONRPC class Daemon(object): def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): + self.host = host + self.port = port self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx)) - def getblocktemplate(self, address): + def getblocktemplate(self, address, prev_block = ""): getblocktemplate = { 'method': 'getblocktemplate', 'params': { 'wallet_address': address, - 'reserve_size' : 1 + 'reserve_size' : 1, + 'prev_block' : prev_block, }, 'jsonrpc': '2.0', 'id': '0' @@ -143,13 +146,15 @@ class Daemon(object): } return self.rpc.send_json_rpc_request(hard_fork_info) - def generateblocks(self, address, blocks=1): + def generateblocks(self, address, blocks=1, prev_block = "", starting_nonce = 0): generateblocks = { 'method': 'generateblocks', 'params': { 'amount_of_blocks' : blocks, 'reserve_size' : 20, - 'wallet_address': address + 'wallet_address': address, + 'prev_block': prev_block, + 'starting_nonce': starting_nonce, }, 'jsonrpc': '2.0', 'id': '0' @@ -217,3 +222,110 @@ class Daemon(object): 'id': '0' } return self.rpc.send_json_rpc_request(get_version) + + def get_bans(self): + get_bans = { + 'method': 'get_bans', + 'params': { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_bans) + + def set_bans(self, bans = []): + set_bans = { + 'method': 'set_bans', + 'params': { + 'bans': bans + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(set_bans) + + def get_transactions(self, txs_hashes = [], decode_as_json = False, prune = False, split = False): + get_transactions = { + 'txs_hashes': txs_hashes, + 'decode_as_json': decode_as_json, + 'prune': prune, + 'split': split, + } + return self.rpc.send_request('/get_transactions', get_transactions) + + def get_outs(self, outputs = [], get_txid = False): + get_outs = { + 'outputs': outputs, + 'get_txid': get_txid, + } + return self.rpc.send_request('/get_outs', get_outs) + + def get_coinbase_tx_sum(self, height, count): + get_coinbase_tx_sum = { + 'method': 'get_coinbase_tx_sum', + 'params': { + 'height': height, + 'count': count, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_coinbase_tx_sum) + + def get_output_distribution(self, amounts = [], from_height = 0, to_height = 0, cumulative = False, binary = False, compress = False): + get_output_distribution = { + 'method': 'get_output_distribution', + 'params': { + 'amounts': amounts, + 'from_height': from_height, + 'to_height': to_height, + 'cumulative': cumulative, + 'binary': binary, + 'compress': compress, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_output_distribution) + + def get_output_histogram(self, amounts = [], min_count = 0, max_count = 0, unlocked = False, recent_cutoff = 0): + get_output_histogram = { + 'method': 'get_output_histogram', + 'params': { + 'amounts': amounts, + 'min_count': min_count, + 'max_count': max_count, + 'unlocked': unlocked, + 'recent_cutoff': recent_cutoff, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_output_histogram) + + def set_log_level(self, level): + set_log_level = { + 'level': level, + } + return self.rpc.send_request('/set_log_level', set_log_level) + + def set_log_categories(self, categories = ''): + set_log_categories = { + 'categories': categories, + } + return self.rpc.send_request('/set_log_categories', set_log_categories) + + def get_alt_blocks_hashes(self): + get_alt_blocks_hashes = { + } + return self.rpc.send_request('/get_alt_blocks_hashes', get_alt_blocks_hashes) + + def get_alternate_chains(self): + get_alternate_chains = { + 'method': 'get_alternate_chains', + 'params': { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_alternate_chains) diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index ea683b8c5..a80aaefec 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -33,6 +33,8 @@ from .rpc import JSONRPC class Wallet(object): def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): + self.host = host + self.port = port self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18090+idx)) def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1): @@ -89,6 +91,18 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(transfer) + def get_transfer_by_txid(self, txid, account_index = 0): + get_transfer_by_txid = { + 'method': 'get_transfer_by_txid', + 'params': { + 'txid': txid, + 'account_index': account_index, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_transfer_by_txid) + def get_bulk_payments(self, payment_ids = [], min_block_height = 0): get_bulk_payments = { 'method': 'get_bulk_payments', @@ -101,24 +115,25 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(get_bulk_payments) - def describe_transfer(self, unsigned_txset): + def describe_transfer(self, unsigned_txset = '', multisig_txset = ''): describe_transfer = { 'method': 'describe_transfer', 'params': { 'unsigned_txset': unsigned_txset, + 'multisig_txset': multisig_txset, }, 'jsonrpc': '2.0', 'id': '0' } return self.rpc.send_json_rpc_request(describe_transfer) - def create_wallet(self, index=''): + def create_wallet(self, filename='', password = '', language = 'English'): create_wallet = { 'method': 'create_wallet', 'params': { - 'filename': 'testWallet' + index, - 'password' : '', - 'language' : 'English' + 'filename': filename, + 'password': password, + 'language': language }, 'jsonrpc': '2.0', 'id': '0' @@ -146,11 +161,23 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(sweep_dust) - def sweep_all(self, address): + def sweep_all(self, address = '', account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, outputs = 1, unlock_time = 0, payment_id = '', get_tx_keys = False, below_amount = 0, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False): sweep_all = { 'method': 'sweep_all', 'params' : { - 'address' : '' + 'address' : address, + 'account_index' : account_index, + 'subaddr_indices' : subaddr_indices, + 'priority' : priority, + 'ring_size' : ring_size, + 'outputs' : outputs, + 'unlock_time' : unlock_time, + 'payment_id' : payment_id, + 'get_tx_keys' : get_tx_keys, + 'below_amount' : below_amount, + 'do_not_relay' : do_not_relay, + 'get_tx_hex' : get_tx_hex, + 'get_tx_metadata' : get_tx_metadata, }, 'jsonrpc': '2.0', 'id': '0' @@ -591,6 +618,79 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(verify) + def get_height(self): + get_height = { + 'method': 'get_height', + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_height) + + def relay_tx(self, hex_): + relay_tx = { + 'method': 'relay_tx', + 'params': { + 'hex': hex_, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(relay_tx) + + def get_languages(self): + get_languages = { + 'method': 'get_languages', + 'params': { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_languages) + + def export_outputs(self): + export_outputs = { + 'method': 'export_outputs', + 'params': { + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(export_outputs) + + def import_outputs(self, outputs_data_hex): + import_outputs = { + 'method': 'import_outputs', + 'params': { + 'outputs_data_hex': outputs_data_hex + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(import_outputs) + + def export_key_images(self, all_ = False): + export_key_images = { + 'method': 'export_key_images', + 'params': { + 'all': all_ + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(export_key_images) + + def import_key_images(self, signed_key_images, offset = 0): + import_key_images = { + 'method': 'import_key_images', + 'params': { + 'offset': offset, + 'signed_key_images': signed_key_images, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(import_key_images) + def get_version(self): get_version = { 'method': 'get_version', |