diff options
131 files changed, 3935 insertions, 1719 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 637e9c4e6..d63b50510 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -331,6 +331,10 @@ if (UNIX AND NOT APPLE) endif() # Handle OpenSSL, used for sha256sum on binary updates and light wallet ssl http +if (CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthreads") +endif () + if (APPLE AND NOT IOS) if (NOT OpenSSL_DIR) EXECUTE_PROCESS(COMMAND brew --prefix openssl @@ -675,6 +679,8 @@ elseif(FREEBSD) elseif(DRAGONFLY) find_library(COMPAT compat) set(EXTRA_LIBRARIES execinfo ${COMPAT}) +elseif(CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") + set(EXTRA_LIBRARIES socket nsl resolv) elseif(NOT MSVC) find_library(RT rt) set(EXTRA_LIBRARIES ${RT}) @@ -688,8 +694,8 @@ if(USE_READLINE) if(READLINE_FOUND AND GNU_READLINE_FOUND) add_definitions(-DHAVE_READLINE) include_directories(${Readline_INCLUDE_DIR}) - list(APPEND EXTRA_LIBRARIES ${Readline_LIBRARY}) message(STATUS "Found readline library at: ${Readline_ROOT_DIR}") + set(EPEE_READLINE epee_readline) else() message(STATUS "Could not find GNU readline library so building without readline support") endif() @@ -698,7 +704,7 @@ endif() if(ANDROID) set(ATOMIC libatomic.a) endif() -if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND ARCH_WIDTH EQUAL "32" AND NOT IOS) +if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND ARCH_WIDTH EQUAL "32" AND NOT IOS AND NOT FREEBSD) find_library(ATOMIC atomic) list(APPEND EXTRA_LIBRARIES ${ATOMIC}) endif() @@ -212,7 +212,7 @@ invokes cmake commands as needed. make release-test - *NOTE*: `coretests` test may take a few hours to complete. + *NOTE*: `core_tests` test may take a few hours to complete. * **Optional**: to build binaries suitable for debugging: @@ -350,6 +350,8 @@ We expect to add Monero into the ports tree in the near future, which will aid i ### On OpenBSD: +#### OpenBSD < 6.2 + This has been tested on OpenBSD 5.8. You will need to add a few packages to your system. `pkg_add db cmake gcc gcc-libs g++ miniupnpc gtest`. @@ -363,6 +365,82 @@ You will have to add the serialization, date_time, and regex modules to Boost wh To build: `env CC=egcc CXX=eg++ CPP=ecpp DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/path/to/the/boost/you/built make release-static-64` +#### OpenBSD >= 6.2 + +You will need to add a few packages to your system. Choose version 4 for db. `pkg_add db cmake miniupnpc zeromq`. + +The doxygen and graphviz packages are optional and require the xbase set. + + +Build the Boost library using clang. This guide is derived from: https://github.com/bitcoin/bitcoin/blob/master/doc/build-openbsd.md + +We assume you are compiling with a non-root user and you have `doas` enabled. + +Note: do not use the boost package provided by OpenBSD, as we are installing boost to `/usr/local`. + +``` +# Create boost building directory +mkdir ~/boost +cd ~/boost + +# Fetch boost source +ftp -o boost_1_64_0.tar.bz2 https://netcologne.dl.sourceforge.net/project/boost/boost/1.64.0/boost_1_64_0.tar.bz2 + +# MUST output: (SHA256) boost_1_64_0.tar.bz2: OK +echo "7bcc5caace97baa948931d712ea5f37038dbb1c5d89b43ad4def4ed7cb683332 boost_1_64_0.tar.bz2" | sha256 -c +tar xfj boost_1_64_0.tar.bz2 + +# Fetch a boost patch, required for OpenBSD +ftp -o boost.patch https://raw.githubusercontent.com/openbsd/ports/bee9e6df517077a7269ff0dfd57995f5c6a10379/devel/boost/patches/patch-boost_test_impl_execution_monitor_ipp +cd boost_1_64_0 +patch -p0 < ../boost.patch + +# Start building boost +echo 'using clang : : c++ : <cxxflags>"-fvisibility=hidden -fPIC" <linkflags>"" <archiver>"ar" <striper>"strip" <ranlib>"ranlib" <rc>"" : ;' > user-config.jam +./bootstrap.sh --without-icu --with-libraries=chrono,filesystem,program_options,system,thread,test,date_time,regex,serialization --with-toolset=clang +./b2 toolset=clang cxxflags="-stdlib=libc++" linkflags="-stdlib=libc++" +doas ./b2 -d0 runtime-link=shared threadapi=pthread threading=multi link=static variant=release --layout=tagged --build-type=complete --user-config=user-config.jam -sNO_BZIP2=1 --prefix=/usr/local install +``` + +Build cppzmq + +Build the cppzmq bindings. + +We assume you are compiling with a non-root user and you have `doas` enabled. + +``` +# Create cppzmq building directory +mkdir ~/cppzmq +cd ~/cppzmq + +# Fetch cppzmq source +ftp -o cppzmq-4.2.2.tar.gz https://github.com/zeromq/cppzmq/archive/v4.2.2.tar.gz + +# MUST output: (SHA256) cppzmq-4.2.2.tar.gz: OK +echo "3ef50070ac5877c06c6bb25091028465020e181bbfd08f110294ed6bc419737d cppzmq-4.2.2.tar.gz" | sha256 -c +tar xfz cppzmq-4.2.2.tar.gz + +# Start building cppzmq +cd cppzmq-4.2.2 +mkdir build +cd build +cmake .. +doas make install +``` + +Build monero: `env DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/usr/local make release-static` + +### On Solaris: + +The default Solaris linker can't be used, you have to install GNU ld, then run cmake manually with the path to your copy of GNU ld: + + mkdir -p build/release + cd build/release + cmake -DCMAKE_LINKER=/path/to/ld -D CMAKE_BUILD_TYPE=Release ../.. + cd ../.. + +Then you can run make as usual. + ### On Linux for Android (using docker): # Build image (select android64.Dockerfile for aarch64) diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index 03f143fe4..33fec8ec5 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -213,6 +213,12 @@ namespace net_utils int get_binded_port(){return m_port;} + long get_connections_count() const + { + auto connections_count = (m_sock_count > 0) ? (m_sock_count - 1) : 0; // Socket count minus listening socket + return connections_count; + } + boost::asio::io_service& get_io_service(){return io_service_;} struct idle_callback_conext_base diff --git a/contrib/epee/include/net/http_base.h b/contrib/epee/include/net/http_base.h index a66fb7c23..a66fb7c23 100755..100644 --- a/contrib/epee/include/net/http_base.h +++ b/contrib/epee/include/net/http_base.h diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 80a4504e3..d77c84c28 100755..100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -66,8 +66,6 @@ namespace epee namespace net_utils { -using namespace std; - /*struct url { public: diff --git a/contrib/epee/include/net/http_protocol_handler.h b/contrib/epee/include/net/http_protocol_handler.h index 652d8ff6f..652d8ff6f 100755..100644 --- a/contrib/epee/include/net/http_protocol_handler.h +++ b/contrib/epee/include/net/http_protocol_handler.h diff --git a/contrib/epee/include/net/http_protocol_handler.inl b/contrib/epee/include/net/http_protocol_handler.inl index c3350bf73..c3350bf73 100755..100644 --- a/contrib/epee/include/net/http_protocol_handler.inl +++ b/contrib/epee/include/net/http_protocol_handler.inl diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 429e3e1af..429e3e1af 100755..100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h diff --git a/contrib/epee/include/net/http_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h index 0788c6a4b..8b8e31b51 100755..100644 --- a/contrib/epee/include/net/http_server_impl_base.h +++ b/contrib/epee/include/net/http_server_impl_base.h @@ -117,6 +117,11 @@ namespace epee return m_net_server.get_binded_port(); } + long get_connections_count() const + { + return m_net_server.get_connections_count(); + } + protected: net_utils::boosted_tcp_server<net_utils::http::http_custom_handler<t_connection_context> > m_net_server; }; diff --git a/contrib/epee/include/net/levin_base.h b/contrib/epee/include/net/levin_base.h index d630bff19..7d060f5ef 100644 --- a/contrib/epee/include/net/levin_base.h +++ b/contrib/epee/include/net/levin_base.h @@ -87,6 +87,7 @@ namespace levin virtual void on_connection_new(t_connection_context& context){}; virtual void on_connection_close(t_connection_context& context){}; + virtual ~levin_commands_handler(){} }; #define LEVIN_OK 0 diff --git a/contrib/epee/include/net/levin_client_async.h b/contrib/epee/include/net/levin_client_async.h index 337d345c4..6c8f9bcb3 100644 --- a/contrib/epee/include/net/levin_client_async.h +++ b/contrib/epee/include/net/levin_client_async.h @@ -52,6 +52,7 @@ namespace levin class levin_client_async { levin_commands_handler* m_pcommands_handler; + void (*commands_handler_destroy)(levin_commands_handler*); volatile uint32_t m_is_stop; volatile uint32_t m_threads_count; ::critical_section m_send_lock; @@ -85,9 +86,9 @@ namespace levin ::critical_section m_connection_lock; net_utils::blocked_mode_client m_transport; public: - levin_client_async():m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) + levin_client_async():m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) {} - levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) + levin_client_async(const levin_client_async& /*v*/):m_pcommands_handler(NULL), commands_handler_destroy(NULL), m_is_stop(0), m_threads_count(0), m_invoke_data_ready(0), m_invoke_is_active(0) {} ~levin_client_async() { @@ -97,11 +98,16 @@ namespace levin while(boost::interprocess::ipcdetail::atomic_read32(&m_threads_count)) ::Sleep(100); + + set_handler(NULL); } - void set_handler(levin_commands_handler* phandler) + void set_handler(levin_commands_handler* phandler, void (*destroy)(levin_commands_handler*) = NULL) { + if (commands_handler_destroy && m_pcommands_handler) + (*commands_handler_destroy)(m_pcommands_handler); m_pcommands_handler = phandler; + m_pcommands_handler_destroy = destroy; } bool connect(uint32_t ip, uint32_t port, uint32_t timeout) diff --git a/contrib/epee/include/net/levin_protocol_handler.h b/contrib/epee/include/net/levin_protocol_handler.h index fbc9727e2..b3a75bedc 100644 --- a/contrib/epee/include/net/levin_protocol_handler.h +++ b/contrib/epee/include/net/levin_protocol_handler.h @@ -43,6 +43,8 @@ namespace levin struct protocl_handler_config { levin_commands_handler<t_connection_context>* m_pcommands_handler; + void (*m_pcommands_handler_destroy)(levin_commands_handler<t_connection_context>*); + ~protocl_handler_config() { if (m_pcommands_handler && m_pcommands_handler_destroy) (*m_pcommands_handler_destroy)(m_pcommands_handler); } }; template<class t_connection_context = net_utils::connection_context_base> diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h index 779f4e78f..7ad6d198b 100644 --- a/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/net/levin_protocol_handler_async.h @@ -72,9 +72,11 @@ class async_protocol_handler_config friend class async_protocol_handler<t_connection_context>; + levin_commands_handler<t_connection_context>* m_pcommands_handler; + void (*m_pcommands_handler_destroy)(levin_commands_handler<t_connection_context>*); + public: typedef t_connection_context connection_context; - levin_commands_handler<t_connection_context>* m_pcommands_handler; uint64_t m_max_packet_size; uint64_t m_invoke_timeout; @@ -91,8 +93,9 @@ public: template<class callback_t> bool for_connection(const boost::uuids::uuid &connection_id, callback_t cb); size_t get_connections_count(); + void set_handler(levin_commands_handler<t_connection_context>* handler, void (*destroy)(levin_commands_handler<t_connection_context>*) = NULL); - async_protocol_handler_config():m_pcommands_handler(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE) + async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE) {} void del_out_connections(size_t count); }; @@ -832,6 +835,15 @@ size_t async_protocol_handler_config<t_connection_context>::get_connections_coun } //------------------------------------------------------------------------------------------ template<class t_connection_context> +void async_protocol_handler_config<t_connection_context>::set_handler(levin_commands_handler<t_connection_context>* handler, void (*destroy)(levin_commands_handler<t_connection_context>*)) +{ + if (m_pcommands_handler && m_pcommands_handler_destroy) + (*m_pcommands_handler_destroy)(m_pcommands_handler); + m_pcommands_handler = handler; + m_pcommands_handler_destroy = destroy; +} +//------------------------------------------------------------------------------------------ +template<class t_connection_context> int async_protocol_handler_config<t_connection_context>::notify(int command, const std::string& in_buff, boost::uuids::uuid connection_id) { async_protocol_handler<t_connection_context>* aph; diff --git a/contrib/epee/include/storages/portable_storage_to_bin.h b/contrib/epee/include/storages/portable_storage_to_bin.h index 5695143b0..38e9f9098 100644 --- a/contrib/epee/include/storages/portable_storage_to_bin.h +++ b/contrib/epee/include/storages/portable_storage_to_bin.h @@ -47,6 +47,9 @@ namespace epee PRAGMA_WARNING_PUSH PRAGMA_GCC("GCC diagnostic ignored \"-Wstrict-aliasing\"") +#ifdef __clang__ + PRAGMA_GCC("GCC diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"") +#endif template<class t_stream> size_t pack_varint(t_stream& strm, size_t val) { //the first two bits always reserved for size information diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 3b41f415e..bd6714791 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -26,10 +26,9 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp) if (USE_READLINE AND GNU_READLINE_FOUND) - add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp readline_buffer.cpp) -else() - add_library(epee STATIC hex.cpp http_auth.cpp mlog.cpp net_utils_base.cpp string_tools.cpp) + add_library(epee_readline STATIC readline_buffer.cpp) endif() # Build and install libepee if we're building for GUI @@ -41,6 +40,10 @@ if (BUILD_GUI_DEPS) endif() install(TARGETS epee ARCHIVE DESTINATION ${lib_folder}) + if (USE_READLINE AND GNU_READLINE_FOUND) + install(TARGETS epee_readline + ARCHIVE DESTINATION ${lib_folder}) + endif() endif() target_link_libraries(epee @@ -51,3 +54,11 @@ target_link_libraries(epee PRIVATE ${OPENSSL_LIBRARIES} ${EXTRA_LIBRARIES}) + +if (USE_READLINE AND GNU_READLINE_FOUND) + target_link_libraries(epee_readline + PUBLIC + easylogging + PRIVATE + ${Readline_LIBRARY}) +endif() diff --git a/contrib/epee/tests/src/net/test_net.h b/contrib/epee/tests/src/net/test_net.h index 5b21036bb..2e1b1e5fd 100644 --- a/contrib/epee/tests/src/net/test_net.h +++ b/contrib/epee/tests/src/net/test_net.h @@ -155,7 +155,7 @@ namespace tests bool init(const std::string& bind_port = "", const std::string& bind_ip = "0.0.0.0") { - m_net_server.get_config_object().m_pcommands_handler = this; + m_net_server.get_config_object().set_handler(this); m_net_server.get_config_object().m_invoke_timeout = 1000; LOG_PRINT_L0("Binding on " << bind_ip << ":" << bind_port); return m_net_server.init_server(bind_port, bind_ip); diff --git a/external/db_drivers/liblmdb/mdb.c b/external/db_drivers/liblmdb/mdb.c index b3de9702f..87b244ce7 100644 --- a/external/db_drivers/liblmdb/mdb.c +++ b/external/db_drivers/liblmdb/mdb.c @@ -304,7 +304,8 @@ union semun { # else # define MDB_USE_ROBUST 1 /* glibc < 2.12 only provided _np API */ -# if defined(__GLIBC__) && GLIBC_VER < 0x02000c +# if (defined(__GLIBC__) && GLIBC_VER < 0x02000c) || \ + (defined(PTHREAD_MUTEX_ROBUST_NP) && !defined(PTHREAD_MUTEX_ROBUST)) # define PTHREAD_MUTEX_ROBUST PTHREAD_MUTEX_ROBUST_NP # define pthread_mutexattr_setrobust(attr, flag) pthread_mutexattr_setrobust_np(attr, flag) # define pthread_mutex_consistent(mutex) pthread_mutex_consistent_np(mutex) @@ -4977,6 +4978,13 @@ mdb_env_setup_locks(MDB_env *env, char *lpath, int mode, int *excl) #else /* MDB_USE_POSIX_MUTEX: */ pthread_mutexattr_t mattr; + /* Solaris needs this before initing a robust mutex. Otherwise + * it may skip the init and return EBUSY "seems someone already + * inited" or EINVAL "it was inited differently". + */ + memset(env->me_txns->mti_rmutex, 0, sizeof(*env->me_txns->mti_rmutex)); + memset(env->me_txns->mti_wmutex, 0, sizeof(*env->me_txns->mti_wmutex)); + if ((rc = pthread_mutexattr_init(&mattr)) || (rc = pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED)) #ifdef MDB_ROBUST_SUPPORTED diff --git a/external/easylogging++/CMakeLists.txt b/external/easylogging++/CMakeLists.txt index ae7d931cf..97d0bf571 100644 --- a/external/easylogging++/CMakeLists.txt +++ b/external/easylogging++/CMakeLists.txt @@ -51,6 +51,7 @@ if (BUILD_GUI_DEPS) set(lib_folder lib) endif() install(TARGETS easylogging - ARCHIVE DESTINATION ${lib_folder}) + ARCHIVE DESTINATION ${lib_folder} + LIBRARY DESTINATION ${lib_folder}) endif() diff --git a/external/easylogging++/easylogging++.h b/external/easylogging++/easylogging++.h index c55cce755..469cf9eec 100644 --- a/external/easylogging++/easylogging++.h +++ b/external/easylogging++/easylogging++.h @@ -99,6 +99,11 @@ #else # define ELPP_OS_FREEBSD 0 #endif +#if (defined(__OpenBSD__)) +# define ELPP_OS_OPENBSD 1 +#else +# define ELPP_OS_OPENBSD 0 +#endif #if (defined(__sun)) # define ELPP_OS_SOLARIS 1 #else @@ -110,7 +115,7 @@ # define ELPP_OS_DRAGONFLY 0 #endif // Unix -#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_SOLARIS || ELPP_OS_DRAGONFLY) && (!ELPP_OS_WINDOWS)) +#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_SOLARIS || ELPP_OS_DRAGONFLY || ELPP_OS_OPENBSD) && (!ELPP_OS_WINDOWS)) # define ELPP_OS_UNIX 1 #else # define ELPP_OS_UNIX 0 @@ -195,7 +200,7 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre # define ELPP_INTERNAL_INFO(lvl, msg) #endif // (defined(ELPP_DEBUG_INFO)) #if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) -# if (ELPP_COMPILER_GCC && !ELPP_MINGW) +# if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_OS_OPENBSD) # define ELPP_STACKTRACE 1 # else # define ELPP_STACKTRACE 0 diff --git a/external/miniupnpc/CMakeLists.txt b/external/miniupnpc/CMakeLists.txt index 4c01b6d06..bc9685699 100644 --- a/external/miniupnpc/CMakeLists.txt +++ b/external/miniupnpc/CMakeLists.txt @@ -36,6 +36,9 @@ if (NOT WIN32) # add_definitions (-D_POSIX_C_SOURCE=200112L) add_definitions (-D_XOPEN_SOURCE=600) endif (NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND NOT CMAKE_SYSTEM_NAME STREQUAL "DragonFly") + if (CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") + add_definitions (-D__EXTENSIONS__ -std=c99) + endif () else (NOT WIN32) add_definitions (-D_WIN32_WINNT=0x0501) # XP or higher for getnameinfo and friends endif (NOT WIN32) diff --git a/external/unbound/CMakeLists.txt b/external/unbound/CMakeLists.txt index 3dae8b425..a2e475232 100644 --- a/external/unbound/CMakeLists.txt +++ b/external/unbound/CMakeLists.txt @@ -44,7 +44,12 @@ endif () set(RETSIGTYPE void) +if(CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") +add_definitions(-D_XOPEN_SOURCE=600) +else() add_definitions(-D_GNU_SOURCE) +endif() +add_definitions(-std=c99) option(USE_ECDSA "Use ECDSA algorithms" ON) option(USE_SHA2 "Enable SHA2 support" ON) @@ -60,6 +65,15 @@ if (CMAKE_USE_WIN32_THREADS_INIT) else () set(HAVE_WINDOWS_THREADS 0) endif () + +# determine if we have libressl +check_symbol_exists(LIBRESSL_VERSION_TEXT "openssl/opensslv.h" HAVE_LIBRESSL) +# check if we have found HAVE_DECL_REALLOCARRAY already, so we can safely undefine and redefine it with value 1 +if (HAVE_LIBRESSL AND HAVE_DECL_REALLOCARRAY) + unset(HAVE_DECL_REALLOCARRAY CACHE) + add_definitions(-DHAVE_DECL_REALLOCARRAY=1) +endif () + configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h") diff --git a/external/unbound/configure_checks.cmake b/external/unbound/configure_checks.cmake index 258f28138..2acf556d1 100644 --- a/external/unbound/configure_checks.cmake +++ b/external/unbound/configure_checks.cmake @@ -49,6 +49,11 @@ if (WIN32) iphlpapi ws2_32) endif () +if (CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") + set(CMAKE_REQUIRED_LIBRARIES + socket + nsl) +endif () check_function_exists(_beginthreadex HAVE__BEGINTHREADEX) check_function_exists(arc4random HAVE_ARC4RANDOM) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index d62a250ff..c3f6e3d87 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -208,7 +208,7 @@ uint64_t BlockchainDB::add_block( const block& blk time1 = epee::misc_utils::get_tick_count(); add_transaction(blk_hash, blk.miner_tx); int tx_i = 0; - crypto::hash tx_hash = null_hash; + crypto::hash tx_hash = crypto::null_hash; for (const transaction& tx : txs) { tx_hash = blk.tx_hashes[tx_i]; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 838385e8a..79676b808 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -147,8 +147,9 @@ struct txpool_tx_meta_t uint8_t kept_by_block; uint8_t relayed; uint8_t do_not_relay; + uint8_t double_spend_seen: 1; - uint8_t padding[77]; // till 192 bytes + uint8_t padding[76]; // till 192 bytes }; #define DBF_SAFE 1 @@ -1314,7 +1315,7 @@ public: /** * @brief get the number of transactions in the txpool */ - virtual uint64_t get_txpool_tx_count() const = 0; + virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const = 0; /** * @brief check whether a txid is in the txpool @@ -1369,7 +1370,7 @@ public: * * @return false if the function returns false for any transaction, otherwise true */ - virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const = 0; + virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const = 0; /** * @brief runs a function over all key images stored diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 5bd02bcf7..865558e07 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -34,6 +34,7 @@ #include <cstring> // memcpy #include <random> +#include "common/util.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "crypto/crypto.h" #include "profile_tools.h" @@ -48,6 +49,7 @@ #endif using epee::string_tools::pod_to_hex; +using namespace crypto; // Increase when the DB changes in a non backward compatible way, and there // is no automatic conversion, so that a full resync is needed. @@ -1135,6 +1137,11 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) if ((result = mdb_env_set_maxdbs(m_env, 20))) throw0(DB_ERROR(lmdb_error("Failed to set max number of dbs: ", result).c_str())); + int threads = tools::get_max_concurrency(); + if (threads > 110 && /* maxreaders default is 126, leave some slots for other read processes */ + (result = mdb_env_set_maxreaders(m_env, threads+16))) + throw0(DB_ERROR(lmdb_error("Failed to set max number of readers: ", result).c_str())); + size_t mapsize = DEFAULT_MAPSIZE; if (db_flags & DBF_FAST) @@ -1522,21 +1529,49 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash &txid, const txpool_tx_ } } -uint64_t BlockchainLMDB::get_txpool_tx_count() const +uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); - TXN_PREFIX_RDONLY(); int result; + uint64_t num_entries = 0; - MDB_stat db_stats; - if ((result = mdb_stat(m_txn, m_txpool_meta, &db_stats))) - throw0(DB_ERROR(lmdb_error("Failed to query m_txpool_meta: ", result).c_str())); + TXN_PREFIX_RDONLY(); + + if (include_unrelayed_txes) + { + // No filtering, we can get the number of tx the "fast" way + MDB_stat db_stats; + if ((result = mdb_stat(m_txn, m_txpool_meta, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txpool_meta: ", result).c_str())); + num_entries = db_stats.ms_entries; + } + else + { + // Filter unrelayed tx out of the result, so we need to loop over transactions and check their meta data + RCURSOR(txpool_meta); + RCURSOR(txpool_blob); + MDB_val k; + MDB_val v; + MDB_cursor_op op = MDB_FIRST; + while (1) + { + result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, op); + op = MDB_NEXT; + if (result == MDB_NOTFOUND) + break; + if (result) + throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str())); + const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data; + if (!meta.do_not_relay) + ++num_entries; + } + } TXN_POSTFIX_RDONLY(); - return db_stats.ms_entries; + return num_entries; } bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const @@ -1633,7 +1668,7 @@ cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid return bd; } -bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob) const +bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1657,6 +1692,9 @@ bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, throw0(DB_ERROR(lmdb_error("Failed to enumerate txpool tx metadata: ", result).c_str())); const crypto::hash txid = *(const crypto::hash*)k.mv_data; const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data; + if (!include_unrelayed_txes && meta.do_not_relay) + // Skipping that tx + continue; const cryptonote::blobdata *passed_bd = NULL; cryptonote::blobdata bd; if (include_blob) diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 98571a7f8..fce8f29ed 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -243,13 +243,13 @@ public: virtual void add_txpool_tx(const transaction &tx, const txpool_tx_meta_t& meta); virtual void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& meta); - virtual uint64_t get_txpool_tx_count() const; + virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const; virtual bool txpool_has_tx(const crypto::hash &txid) const; virtual void remove_txpool_tx(const crypto::hash& txid); virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; - virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false) const; + virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, bool include_unrelayed_txes = true) const; virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const; diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 60672eeda..78bb51ab6 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -30,6 +30,7 @@ #include "blocksdat_file.h" #include "common/command_line.h" #include "cryptonote_core/tx_pool.h" +#include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/blockchain_db.h" #include "blockchain_db/db_types.h" #include "version.h" @@ -66,21 +67,16 @@ int main(int argc, char* argv[]) const command_line::arg_descriptor<std::string> arg_output_file = {"output-file", "Specify output file", "", true}; const command_line::arg_descriptor<std::string> arg_log_level = {"log-level", "0-4 or categories", ""}; const command_line::arg_descriptor<uint64_t> arg_block_stop = {"block-stop", "Stop at block number", block_stop}; - const command_line::arg_descriptor<bool> arg_testnet_on = { - "testnet" - , "Run on testnet." - , false - }; const command_line::arg_descriptor<std::string> arg_database = { "database", available_dbs.c_str(), default_db_type }; const command_line::arg_descriptor<bool> arg_blocks_dat = {"blocksdat", "Output in blocks.dat format", blocks_dat}; - command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, default_data_path.string()); - command_line::add_arg(desc_cmd_sett, command_line::arg_testnet_data_dir, default_testnet_data_path.string()); + command_line::add_arg(desc_cmd_sett, cryptonote::arg_data_dir, default_data_path.string()); + command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_data_dir, default_testnet_data_path.string()); command_line::add_arg(desc_cmd_sett, arg_output_file); - command_line::add_arg(desc_cmd_sett, arg_testnet_on); + command_line::add_arg(desc_cmd_sett, cryptonote::arg_testnet_on); command_line::add_arg(desc_cmd_sett, arg_log_level); command_line::add_arg(desc_cmd_sett, arg_database); command_line::add_arg(desc_cmd_sett, arg_block_stop); @@ -117,12 +113,12 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Starting..."); - bool opt_testnet = command_line::get_arg(vm, arg_testnet_on); + bool opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool opt_blocks_dat = command_line::get_arg(vm, arg_blocks_dat); std::string m_config_folder; - auto data_dir_arg = opt_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + auto data_dir_arg = opt_testnet ? cryptonote::arg_testnet_data_dir : cryptonote::arg_data_dir; m_config_folder = command_line::get_arg(vm, data_dir_arg); std::string db_type = command_line::get_arg(vm, arg_database); diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 2a956dbdb..a50b0bad6 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -256,7 +256,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path seek_height = start_height; BootstrapFile bootstrap; - streampos pos; + std::streampos pos; // BootstrapFile bootstrap(import_file_path); uint64_t total_source_blocks = bootstrap.count_blocks(import_file_path, pos, seek_height); MINFO("bootstrap file last block number: " << total_source_blocks-1 << " (zero-based height) total blocks: " << total_source_blocks); @@ -585,11 +585,6 @@ int main(int argc, char* argv[]) const command_line::arg_descriptor<uint64_t> arg_batch_size = {"batch-size", "", db_batch_size}; const command_line::arg_descriptor<uint64_t> arg_pop_blocks = {"pop-blocks", "Remove blocks from end of blockchain", num_blocks}; const command_line::arg_descriptor<bool> arg_drop_hf = {"drop-hard-fork", "Drop hard fork subdbs", false}; - const command_line::arg_descriptor<bool> arg_testnet_on = { - "testnet" - , "Run on testnet." - , false - }; const command_line::arg_descriptor<bool> arg_count_blocks = { "count-blocks" , "Count blocks in bootstrap file and exit" @@ -674,8 +669,8 @@ int main(int argc, char* argv[]) } } - opt_testnet = command_line::get_arg(vm, arg_testnet_on); - auto data_dir_arg = opt_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + opt_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); + auto data_dir_arg = opt_testnet ? cryptonote::arg_testnet_data_dir : cryptonote::arg_data_dir; m_config_folder = command_line::get_arg(vm, data_dir_arg); db_arg_str = command_line::get_arg(vm, arg_database); diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp index a004d3547..50b56eab9 100644 --- a/src/blockchain_utilities/bootstrap_file.cpp +++ b/src/blockchain_utilities/bootstrap_file.cpp @@ -134,8 +134,7 @@ bool BootstrapFile::initialize_file() bbi.block_last_pos = 0; buffer_type buffer2; - boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>* output_stream_header; - output_stream_header = new boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>>(buffer2); + boost::iostreams::stream<boost::iostreams::back_insert_device<buffer_type>> output_stream_header(buffer2); uint32_t bd_size = 0; @@ -147,8 +146,8 @@ bool BootstrapFile::initialize_file() { throw std::runtime_error("Error in serialization of bootstrap::file_info size"); } - *output_stream_header << blob; - *output_stream_header << bd; + output_stream_header << blob; + output_stream_header << bd; bd = t_serializable_object_to_blob(bbi); MDEBUG("bootstrap::blocks_info size: " << bd.size()); @@ -158,12 +157,12 @@ bool BootstrapFile::initialize_file() { throw std::runtime_error("Error in serialization of bootstrap::blocks_info size"); } - *output_stream_header << blob; - *output_stream_header << bd; + output_stream_header << blob; + output_stream_header << bd; - output_stream_header->flush(); - *output_stream_header << std::string(header_size-buffer2.size(), 0); // fill in rest with null bytes - output_stream_header->flush(); + output_stream_header.flush(); + output_stream_header << std::string(header_size-buffer2.size(), 0); // fill in rest with null bytes + output_stream_header.flush(); std::copy(buffer2.begin(), buffer2.end(), std::ostreambuf_iterator<char>(*m_raw_data_file)); return true; @@ -221,7 +220,7 @@ void BootstrapFile::write_block(block& block) // now add all regular transactions for (const auto& tx_id : block.tx_hashes) { - if (tx_id == null_hash) + if (tx_id == crypto::null_hash) { throw std::runtime_error("Aborting: tx == null_hash"); } @@ -433,7 +432,7 @@ uint64_t BootstrapFile::count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) { - streampos dummy_pos; + std::streampos dummy_pos; uint64_t dummy_height = 0; return count_blocks(import_file_path, dummy_pos, dummy_height); } @@ -441,7 +440,7 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path) // If seek_height is non-zero on entry, return a stream position <= this height when finished. // And return the actual height corresponding to this position. Allows the caller to locate its // starting position without having to reread the entire file again. -uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, streampos &start_pos, uint64_t& seek_height) +uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::streampos &start_pos, uint64_t& seek_height) { boost::filesystem::path raw_file_path(import_file_path); boost::system::error_code ec; diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index c3969a357..0926ee2e5 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -57,7 +57,7 @@ class BootstrapFile public: uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit); - uint64_t count_blocks(const std::string& dir_path, streampos& start_pos, uint64_t& seek_height); + uint64_t count_blocks(const std::string& dir_path, std::streampos& start_pos, uint64_t& seek_height); uint64_t count_blocks(const std::string& dir_path); uint64_t seek_to_first_chunk(std::ifstream& import_file); diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index bea392db0..9be08958c 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -140,8 +140,8 @@ namespace cryptonote { if (testnet) { - // just use the genesis block on testnet ADD_CHECKPOINT(0, "48ca7cd3c8de5b6a4d53d2861fbdaedca141553559f9be9520068053cda8430b"); + ADD_CHECKPOINT(1000000, "46b690b710a07ea051bc4a6b6842ac37be691089c0f7758cfeec4d5fc0b4a258"); return true; } ADD_CHECKPOINT(1, "771fbcd656ec1464d3a02ead5e18644030007a0fc664c0a964d30922821a8148"); diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 666b3267f..4b9ca9559 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -36,10 +36,6 @@ #include "cryptonote_config.h" #include "string_tools.h" -#ifdef HAVE_READLINE - #include "readline_buffer.h" -#endif - namespace command_line { namespace @@ -50,29 +46,29 @@ namespace command_line } } - std::string input_line(const std::string& prompt) + bool is_yes(const std::string& str) { -#ifdef HAVE_READLINE - rdln::suspend_readline pause_readline; -#endif - std::cout << prompt; - - std::string buf; - std::getline(std::cin, buf); + if (str == "y" || str == "Y") + return true; - return epee::string_tools::trim(buf); + boost::algorithm::is_iequal ignore_case{}; + if (boost::algorithm::equals("yes", str, ignore_case)) + return true; + if (boost::algorithm::equals(command_line::tr("yes"), str, ignore_case)) + return true; + return false; } - bool is_yes(const std::string& str) + bool is_no(const std::string& str) { - if (str == "y" || str == "Y") + if (str == "n" || str == "N") return true; boost::algorithm::is_iequal ignore_case{}; - if (boost::algorithm::equals("yes", str, ignore_case)) + if (boost::algorithm::equals("no", str, ignore_case)) return true; - if (boost::algorithm::equals(command_line::tr("yes"), str, ignore_case)) + if (boost::algorithm::equals(command_line::tr("no"), str, ignore_case)) return true; return false; @@ -80,49 +76,4 @@ namespace command_line const arg_descriptor<bool> arg_help = {"help", "Produce help message"}; const arg_descriptor<bool> arg_version = {"version", "Output version information"}; - const arg_descriptor<std::string> arg_data_dir = {"data-dir", "Specify data directory"}; - const arg_descriptor<std::string> arg_testnet_data_dir = {"testnet-data-dir", "Specify testnet data directory"}; - const arg_descriptor<bool> arg_test_drop_download = {"test-drop-download", "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)"}; - const arg_descriptor<uint64_t> arg_test_drop_download_height = {"test-drop-download-height", "Like test-drop-download but disards only after around certain height", 0}; - const arg_descriptor<int> arg_test_dbg_lock_sleep = {"test-dbg-lock-sleep", "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests."}; - const arg_descriptor<bool, false> arg_testnet_on = { - "testnet" - , "Run on testnet. The wallet must be launched with --testnet flag." - , false - }; - const arg_descriptor<bool> arg_dns_checkpoints = { - "enforce-dns-checkpointing" - , "checkpoints from DNS server will be enforced" - , false - }; - const command_line::arg_descriptor<uint64_t> arg_fast_block_sync = { - "fast-block-sync" - , "Sync up most of the way by using embedded, known block hashes." - , 1 - }; - const command_line::arg_descriptor<uint64_t> arg_prep_blocks_threads = { - "prep-blocks-threads" - , "Max number of threads to use when preparing block hashes in groups." - , 4 - }; - const command_line::arg_descriptor<uint64_t> arg_show_time_stats = { - "show-time-stats" - , "Show time-stats when processing blocks/txs and disk synchronization." - , 0 - }; - const command_line::arg_descriptor<size_t> arg_block_sync_size = { - "block-sync-size" - , "How many blocks to sync at once during chain synchronization (0 = adaptive)." - , 0 - }; - const command_line::arg_descriptor<std::string> arg_check_updates = { - "check-updates" - , "Check for new versions of monero: [disabled|notify|download|update]" - , "notify" - }; - const arg_descriptor<bool> arg_fluffy_blocks = { - "fluffy-blocks" - , "Relay blocks as fluffy blocks where possible (automatic on testnet)" - , false - }; } diff --git a/src/common/command_line.h b/src/common/command_line.h index 04fd70be5..c2bac9cc8 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -41,10 +41,10 @@ namespace command_line { - std::string input_line(const std::string& prompt); - //! \return True if `str` is `is_iequal("y" || "yes" || `tr("yes"))`. bool is_yes(const std::string& str); + //! \return True if `str` is `is_iequal("n" || "no" || `tr("no"))`. + bool is_no(const std::string& str); template<typename T, bool required = false> struct arg_descriptor; @@ -211,17 +211,4 @@ namespace command_line extern const arg_descriptor<bool> arg_help; extern const arg_descriptor<bool> arg_version; - extern const arg_descriptor<std::string> arg_data_dir; - extern const arg_descriptor<std::string> arg_testnet_data_dir; - extern const arg_descriptor<bool> arg_test_drop_download; - extern const arg_descriptor<uint64_t> arg_test_drop_download_height; - extern const arg_descriptor<int> arg_test_dbg_lock_sleep; - extern const arg_descriptor<bool, false> arg_testnet_on; - extern const arg_descriptor<bool> arg_dns_checkpoints; - extern const arg_descriptor<uint64_t> arg_fast_block_sync; - extern const arg_descriptor<uint64_t> arg_prep_blocks_threads; - extern const arg_descriptor<uint64_t> arg_show_time_stats; - extern const arg_descriptor<size_t> arg_block_sync_size; - extern const arg_descriptor<std::string> arg_check_updates; - extern const arg_descriptor<bool> arg_fluffy_blocks; } diff --git a/src/common/int-util.h b/src/common/int-util.h index 34288805a..7cec571ad 100644 --- a/src/common/int-util.h +++ b/src/common/int-util.h @@ -40,6 +40,10 @@ #include <byteswap.h> #endif +#if defined(__sun) && defined(__SVR4) +#include <endian.h> +#endif + #if defined(_MSC_VER) #include <stdlib.h> diff --git a/src/common/password.cpp b/src/common/password.cpp index 5c04023f4..5d56464a5 100644 --- a/src/common/password.cpp +++ b/src/common/password.cpp @@ -88,13 +88,11 @@ namespace { pass.back() = '\0'; pass.resize(pass.size() - 1); - std::cout << "\b \b"; } } else { pass.push_back(ch); - std::cout << '*'; } } @@ -150,13 +148,11 @@ namespace { aPass.back() = '\0'; aPass.resize(aPass.size() - 1); - std::cout << "\b \b"; } } else { aPass.push_back(ch); - std::cout << '*'; } } @@ -242,9 +238,6 @@ namespace tools boost::optional<password_container> password_container::prompt(const bool verify, const char *message) { -#ifdef HAVE_READLINE - rdln::suspend_readline pause_readline; -#endif password_container pass1{}; password_container pass2{}; if (is_cin_tty() ? read_from_tty(verify, message, pass1.m_password, pass2.m_password) : read_from_file(pass1.m_password)) @@ -253,7 +246,7 @@ namespace tools return boost::none; } - boost::optional<login> login::parse(std::string&& userpass, bool verify, const char* message) + boost::optional<login> login::parse(std::string&& userpass, bool verify, const std::function<boost::optional<password_container>(bool)> &prompt) { login out{}; password_container wipe{std::move(userpass)}; @@ -261,7 +254,7 @@ namespace tools const auto loc = wipe.password().find(':'); if (loc == std::string::npos) { - auto result = tools::password_container::prompt(verify, message); + auto result = prompt(verify); if (!result) return boost::none; diff --git a/src/common/password.h b/src/common/password.h index 12f715df4..ba1c30a28 100644 --- a/src/common/password.h +++ b/src/common/password.h @@ -82,7 +82,7 @@ namespace tools \return The username and password, or boost::none if `password_container::prompt` fails. */ - static boost::optional<login> parse(std::string&& userpass, bool verify, const char* message = "Password"); + static boost::optional<login> parse(std::string&& userpass, bool verify, const std::function<boost::optional<password_container>(bool)> &prompt); login(const login&) = delete; login(login&&) = default; diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 41d0c25e0..20c5765b0 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -39,7 +39,7 @@ namespace tools threadpool::threadpool() : running(true), active(0) { boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - max = tools::get_max_concurrency() * 2; + max = tools::get_max_concurrency(); size_t i = max; while(i--) { threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this))); @@ -74,7 +74,7 @@ void threadpool::submit(waiter *obj, std::function<void()> f) { } int threadpool::get_max_concurrency() { - return max / 2; + return max; } void threadpool::waiter::wait() { diff --git a/src/common/updates.cpp b/src/common/updates.cpp index 8a057b1cf..141330c2c 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -100,7 +100,7 @@ namespace tools { const char *base = user ? "https://downloads.getmonero.org/" : "http://updates.getmonero.org/"; #ifdef _WIN32 - static const char extension[] = ".zip"; + static const char *extension = strncmp(buildtag.c_str(), "install-", 8) ? ".zip" : ".exe"; #else static const char extension[] = ".tar.bz2"; #endif diff --git a/src/common/util.cpp b/src/common/util.cpp index 30746f680..a13ac6e50 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -405,7 +405,7 @@ namespace tools #else std::string get_nix_version_display_string() { - utsname un; + struct utsname un; if(uname(&un) < 0) return std::string("*nix: failed to get os version"); @@ -549,6 +549,13 @@ std::string get_nix_version_display_string() if (!strcmp(ver, "2.25")) MCLOG_RED(el::Level::Warning, "global", "Running with glibc " << ver << ", hangs may occur - change glibc version if possible"); #endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000 + SSL_library_init(); +#else + OPENSSL_init_ssl(0, NULL); +#endif + return true; } void set_strict_default_file_permissions(bool strict) diff --git a/src/common/util.h b/src/common/util.h index 2e4d6e917..1aac026c1 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -148,9 +148,10 @@ namespace tools } return r; #else - /* Only blocks SIGINT and SIGTERM */ + /* Only blocks SIGINT, SIGTERM and SIGPIPE */ signal(SIGINT, posix_handler); signal(SIGTERM, posix_handler); + signal(SIGPIPE, SIG_IGN); m_handler = t; return true; #endif diff --git a/src/crypto/initializer.h b/src/crypto/initializer.h index 619038ae6..eb1d1c069 100644 --- a/src/crypto/initializer.h +++ b/src/crypto/initializer.h @@ -31,8 +31,13 @@ #pragma once #if defined(__GNUC__) +#if defined(__sun) && defined(__SVR4) +#define INITIALIZER(name) __attribute__((constructor)) static void name(void) +#define FINALIZER(name) __attribute__((destructor)) static void name(void) +#else #define INITIALIZER(name) __attribute__((constructor(101))) static void name(void) #define FINALIZER(name) __attribute__((destructor(101))) static void name(void) +#endif #define REGISTER_FINALIZER(name) ((void) 0) #elif defined(_MSC_VER) diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index 090d563a2..fc6d487c2 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -2,6 +2,8 @@ // 19-Nov-11 Markku-Juhani O. Saarinen <mjos@iki.fi> // A baseline Keccak (3rd round) implementation. +#include <stdio.h> +#include <stdlib.h> #include "hash-ops.h" #include "keccak.h" @@ -73,12 +75,18 @@ void keccakf(uint64_t st[25], int rounds) // compute a keccak hash (md) of given byte length from "in" typedef uint64_t state_t[25]; -int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) +void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) { state_t st; uint8_t temp[144]; size_t i, rsiz, rsizw; + if (mdlen <= 0 || mdlen > 200 || sizeof(st) != 200) + { + fprintf(stderr, "Bad keccak use"); + abort(); + } + rsiz = sizeof(state_t) == mdlen ? HASH_DATA_AREA : 200 - 2 * mdlen; rsizw = rsiz / 8; @@ -91,6 +99,12 @@ int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) } // last block and padding + if (inlen >= sizeof(temp) || inlen > rsiz || rsiz - inlen + inlen + 1 >= sizeof(temp) || rsiz == 0 || rsiz - 1 >= sizeof(temp) || rsizw * 8 > sizeof(temp)) + { + fprintf(stderr, "Bad keccak use"); + abort(); + } + memcpy(temp, in, inlen); temp[inlen++] = 1; memset(temp + inlen, 0, rsiz - inlen); @@ -102,8 +116,6 @@ int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) keccakf(st, KECCAK_ROUNDS); memcpy(md, st, mdlen); - - return 0; } void keccak1600(const uint8_t *in, size_t inlen, uint8_t *md) diff --git a/src/crypto/keccak.h b/src/crypto/keccak.h index fbd8e1904..fb9d8bd04 100644 --- a/src/crypto/keccak.h +++ b/src/crypto/keccak.h @@ -16,7 +16,7 @@ #endif // compute a keccak hash (md) of given byte length from "in" -int keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); +void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen); // update the state void keccakf(uint64_t st[25], int norounds); diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index d09f4c432..3c760493f 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -45,6 +45,8 @@ using namespace epee; // #define ENABLE_HASH_CASH_INTEGRITY_CHECK +using namespace crypto; + static const uint64_t valid_decomposed_outputs[] = { (uint64_t)1, (uint64_t)2, (uint64_t)3, (uint64_t)4, (uint64_t)5, (uint64_t)6, (uint64_t)7, (uint64_t)8, (uint64_t)9, // 1 piconero (uint64_t)10, (uint64_t)20, (uint64_t)30, (uint64_t)40, (uint64_t)50, (uint64_t)60, (uint64_t)70, (uint64_t)80, (uint64_t)90, @@ -992,7 +994,7 @@ namespace cryptonote block_hashes_cached = block_hashes_cached_count; } //--------------------------------------------------------------- - crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase) + crypto::secret_key encrypt_key(crypto::secret_key key, const std::string &passphrase) { crypto::hash hash; crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); @@ -1000,7 +1002,7 @@ namespace cryptonote return key; } //--------------------------------------------------------------- - crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase) + crypto::secret_key decrypt_key(crypto::secret_key key, const std::string &passphrase) { crypto::hash hash; crypto::cn_slow_hash(passphrase.data(), passphrase.size(), hash); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index f88310c4c..aebeaa6f4 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -226,8 +226,8 @@ namespace cryptonote bool is_valid_decomposed_amount(uint64_t amount); void get_hash_stats(uint64_t &tx_hashes_calculated, uint64_t &tx_hashes_cached, uint64_t &block_hashes_calculated, uint64_t & block_hashes_cached); - crypto::secret_key encrypt_key(const crypto::secret_key &key, const std::string &passphrase); - crypto::secret_key decrypt_key(const crypto::secret_key &key, const std::string &passphrase); + crypto::secret_key encrypt_key(crypto::secret_key key, const std::string &passphrase); + crypto::secret_key decrypt_key(crypto::secret_key key, const std::string &passphrase); #define CHECKED_GET_SPECIFIC_VARIANT(variant_var, specific_type, variable_name, fail_return_val) \ CHECK_AND_ASSERT_MES(variant_var.type() == typeid(specific_type), fail_return_val, "wrong variant type: " << variant_var.type().name() << ", expected " << typeid(specific_type).name()); \ specific_type& variable_name = boost::get<specific_type>(variant_var); diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 0aafe24e1..c90ab0f03 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -53,6 +53,19 @@ #include <TargetConditionals.h> #endif +#ifdef __FreeBSD__ +#include <devstat.h> +#include <errno.h> +#include <fcntl.h> +#include <machine/apm_bios.h> +#include <stdio.h> +#include <sys/resource.h> +#include <sys/sysctl.h> +#include <sys/times.h> +#include <sys/types.h> +#include <unistd.h> +#endif + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "miner" @@ -735,8 +748,6 @@ namespace cryptonote #elif defined(__linux__) - const std::string STR_CPU("cpu"); - const std::size_t STR_CPU_LEN = STR_CPU.size(); const std::string STAT_FILE_PATH = "/proc/stat"; if( !epee::file_io_utils::is_file_exist(STAT_FILE_PATH) ) @@ -785,9 +796,36 @@ namespace cryptonote return true; + #elif defined(__FreeBSD__) + + struct statinfo s; + size_t n = sizeof(s.cp_time); + if( sysctlbyname("kern.cp_time", s.cp_time, &n, NULL, 0) == -1 ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\"): " << strerror(errno)); + return false; + } + if( n != sizeof(s.cp_time) ) + { + LOG_ERROR("sysctlbyname(\"kern.cp_time\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(s.cp_time) + << " bytes."); + return false; + } + + idle_time = s.cp_time[CP_IDLE]; + total_time = + s.cp_time[CP_USER] + + s.cp_time[CP_NICE] + + s.cp_time[CP_SYS] + + s.cp_time[CP_INTR] + + s.cp_time[CP_IDLE]; + + return true; + #endif - return false; // unsupported systemm.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- bool miner::get_process_time(uint64_t& total_time) @@ -807,7 +845,7 @@ namespace cryptonote return true; } - #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) + #elif (defined(__linux__) && defined(_SC_CLK_TCK)) || defined(__APPLE__) || defined(__FreeBSD__) struct tms tms; if ( times(&tms) != (clock_t)-1 ) @@ -818,7 +856,7 @@ namespace cryptonote #endif - return false; // unsupported system.. + return false; // unsupported system } //----------------------------------------------------------------------------------------------------- uint8_t miner::get_percent_of_total(uint64_t other, uint64_t total) @@ -929,6 +967,70 @@ namespace cryptonote } return on_battery; + #elif defined(__FreeBSD__) + int ac; + size_t n = sizeof(ac); + if( sysctlbyname("hw.acpi.acline", &ac, &n, NULL, 0) == -1 ) + { + if( errno != ENOENT ) + { + LOG_ERROR("Cannot query battery status: " + << "sysctlbyname(\"hw.acpi.acline\"): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + // If sysctl fails with ENOENT, then try querying /dev/apm. + + static const char* dev_apm = "/dev/apm"; + const int fd = open(dev_apm, O_RDONLY); + if( fd == -1 ) { + LOG_ERROR("Cannot query battery status: " + << "open(): " << dev_apm << ": " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + apm_info info; + if( ioctl(fd, APMIO_GETINFO, &info) == -1 ) { + close(fd); + LOG_ERROR("Cannot query battery status: " + << "ioctl(" << dev_apm << ", APMIO_GETINFO): " << strerror(errno)); + return boost::logic::tribool(boost::logic::indeterminate); + } + + close(fd); + + // See apm(8). + switch( info.ai_acline ) + { + case 0: // off-line + case 2: // backup power + return boost::logic::tribool(true); + case 1: // on-line + return boost::logic::tribool(false); + } + switch( info.ai_batt_stat ) + { + case 0: // high + case 1: // low + case 2: // critical + return boost::logic::tribool(true); + case 3: // charging + return boost::logic::tribool(false); + } + + LOG_ERROR("Cannot query battery status: " + << "sysctl hw.acpi.acline is not available and /dev/apm returns " + << "unexpected ac-line status (" << info.ai_acline << ") and " + << "battery status (" << info.ai_batt_stat << ")."); + return boost::logic::tribool(boost::logic::indeterminate); + } + if( n != sizeof(ac) ) + { + LOG_ERROR("sysctlbyname(\"hw.acpi.acline\") output is unexpectedly " + << n << " bytes instead of the expected " << sizeof(ac) << " bytes."); + return boost::logic::tribool(boost::logic::indeterminate); + } + return boost::logic::tribool(ac == 0); #endif LOG_ERROR("couldn't query power status"); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9d89c6280..3d586a704 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -61,6 +61,8 @@ #define FIND_BLOCKCHAIN_SUPPLEMENT_MAX_SIZE (100*1024*1024) // 100 MB +using namespace crypto; + //#include "serialization/json_archive.h" /* TODO: @@ -3166,9 +3168,9 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids) cryptonote::transaction tx; size_t blob_size; uint64_t fee; - bool relayed, do_not_relay; + bool relayed, do_not_relay, double_spend_seen; MINFO("Removing txid " << txid << " from the pool"); - if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay)) + if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen)) { MERROR("Failed to remove txid " << txid << " from the pool"); res = false; @@ -3351,7 +3353,7 @@ leave: transaction tx; size_t blob_size = 0; uint64_t fee = 0; - bool relayed = false, do_not_relay = false; + bool relayed = false, do_not_relay = false, double_spend_seen = false; TIME_MEASURE_START(aa); // XXX old code does not check whether tx exists @@ -3368,7 +3370,7 @@ leave: TIME_MEASURE_START(bb); // get transaction with hash <tx_id> from tx_pool - if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay)) + if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen)) { MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); bvc.m_verifivation_failed = true; @@ -4211,9 +4213,9 @@ void Blockchain::remove_txpool_tx(const crypto::hash &txid) m_db->remove_txpool_tx(txid); } -uint64_t Blockchain::get_txpool_tx_count() const +uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const { - return m_db->get_txpool_tx_count(); + return m_db->get_txpool_tx_count(include_unrelayed_txes); } txpool_tx_meta_t Blockchain::get_txpool_tx_meta(const crypto::hash& txid) const @@ -4231,9 +4233,9 @@ cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) co return m_db->get_txpool_tx_blob(txid); } -bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob) const +bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, bool include_unrelayed_txes) const { - return m_db->for_all_txpool_txes(f, include_blob); + return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes); } void Blockchain::set_user_options(uint64_t maxthreads, uint64_t blocks_per_sync, blockchain_db_sync_mode sync_mode, bool fast_sync) @@ -4381,12 +4383,12 @@ void Blockchain::load_compiled_in_block_hashes() size_t blob_size; uint64_t fee; - bool relayed, do_not_relay; + bool relayed, do_not_relay, double_spend_seen; transaction pool_tx; for(const transaction &tx : txs) { crypto::hash tx_hash = get_transaction_hash(tx); - m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay); + m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay, double_spend_seen); } } } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index f64bd35e3..e0936da8f 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -635,16 +635,6 @@ namespace cryptonote uint64_t get_current_cumulative_blocksize_limit() const; /** - * @brief checks if the blockchain is currently being stored - * - * Note: this should be meaningless in cases where Blockchain is not - * directly managing saving the blockchain to disk. - * - * @return true if Blockchain is having the chain stored currently, else false - */ - bool is_storing_blockchain()const{return false;} - - /** * @brief gets the difficulty of the block with a given height * * @param i the height @@ -947,11 +937,11 @@ namespace cryptonote void add_txpool_tx(transaction &tx, const txpool_tx_meta_t &meta); void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t &meta); void remove_txpool_tx(const crypto::hash &txid); - uint64_t get_txpool_tx_count() const; + uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const; txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const; bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const; cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const; - bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const; + bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = true) const; bool is_within_compiled_block_hash_area(uint64_t height) const; bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 61f844612..5c181208f 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -38,6 +38,7 @@ using namespace epee; #include "common/updates.h" #include "common/download.h" #include "common/threadpool.h" +#include "common/command_line.h" #include "warnings.h" #include "crypto/crypto.h" #include "cryptonote_config.h" @@ -61,6 +62,69 @@ DISABLE_VS_WARNINGS(4355) namespace cryptonote { + const command_line::arg_descriptor<std::string> arg_data_dir = { + "data-dir" + , "Specify data directory" + }; + const command_line::arg_descriptor<std::string> arg_testnet_data_dir = { + "testnet-data-dir" + , "Specify testnet data directory" + }; + const command_line::arg_descriptor<bool, false> arg_testnet_on = { + "testnet" + , "Run on testnet. The wallet must be launched with --testnet flag." + , false + }; + + static const command_line::arg_descriptor<bool> arg_test_drop_download = { + "test-drop-download" + , "For net tests: in download, discard ALL blocks instead checking/saving them (very fast)" + }; + static const command_line::arg_descriptor<uint64_t> arg_test_drop_download_height = { + "test-drop-download-height" + , "Like test-drop-download but disards only after around certain height" + , 0 + }; + static const command_line::arg_descriptor<int> arg_test_dbg_lock_sleep = { + "test-dbg-lock-sleep" + , "Sleep time in ms, defaults to 0 (off), used to debug before/after locking mutex. Values 100 to 1000 are good for tests." + , 0 + }; + static const command_line::arg_descriptor<bool> arg_dns_checkpoints = { + "enforce-dns-checkpointing" + , "checkpoints from DNS server will be enforced" + , false + }; + static const command_line::arg_descriptor<uint64_t> arg_fast_block_sync = { + "fast-block-sync" + , "Sync up most of the way by using embedded, known block hashes." + , 1 + }; + static const command_line::arg_descriptor<uint64_t> arg_prep_blocks_threads = { + "prep-blocks-threads" + , "Max number of threads to use when preparing block hashes in groups." + , 4 + }; + static const command_line::arg_descriptor<uint64_t> arg_show_time_stats = { + "show-time-stats" + , "Show time-stats when processing blocks/txs and disk synchronization." + , 0 + }; + static const command_line::arg_descriptor<size_t> arg_block_sync_size = { + "block-sync-size" + , "How many blocks to sync at once during chain synchronization (0 = adaptive)." + , 0 + }; + static const command_line::arg_descriptor<std::string> arg_check_updates = { + "check-updates" + , "Check for new versions of monero: [disabled|notify|download|update]" + , "notify" + }; + static const command_line::arg_descriptor<bool> arg_fluffy_blocks = { + "fluffy-blocks" + , "Relay blocks as fluffy blocks where possible (automatic on testnet)" + , false + }; //----------------------------------------------------------------------------------------------- core::core(i_cryptonote_protocol* pprotocol): @@ -148,20 +212,21 @@ namespace cryptonote //----------------------------------------------------------------------------------- void core::init_options(boost::program_options::options_description& desc) { - command_line::add_arg(desc, command_line::arg_data_dir, tools::get_default_data_dir()); - command_line::add_arg(desc, command_line::arg_testnet_data_dir, (boost::filesystem::path(tools::get_default_data_dir()) / "testnet").string()); + command_line::add_arg(desc, arg_data_dir, tools::get_default_data_dir()); + command_line::add_arg(desc, arg_testnet_data_dir, (boost::filesystem::path(tools::get_default_data_dir()) / "testnet").string()); - command_line::add_arg(desc, command_line::arg_test_drop_download); - command_line::add_arg(desc, command_line::arg_test_drop_download_height); + command_line::add_arg(desc, arg_test_drop_download); + command_line::add_arg(desc, arg_test_drop_download_height); - command_line::add_arg(desc, command_line::arg_testnet_on); - command_line::add_arg(desc, command_line::arg_dns_checkpoints); - command_line::add_arg(desc, command_line::arg_prep_blocks_threads); - command_line::add_arg(desc, command_line::arg_fast_block_sync); - command_line::add_arg(desc, command_line::arg_show_time_stats); - command_line::add_arg(desc, command_line::arg_block_sync_size); - command_line::add_arg(desc, command_line::arg_check_updates); - command_line::add_arg(desc, command_line::arg_fluffy_blocks); + command_line::add_arg(desc, arg_testnet_on); + command_line::add_arg(desc, arg_dns_checkpoints); + command_line::add_arg(desc, arg_prep_blocks_threads); + command_line::add_arg(desc, arg_fast_block_sync); + command_line::add_arg(desc, arg_show_time_stats); + command_line::add_arg(desc, arg_block_sync_size); + command_line::add_arg(desc, arg_check_updates); + command_line::add_arg(desc, arg_fluffy_blocks); + command_line::add_arg(desc, arg_test_dbg_lock_sleep); // we now also need some of net_node's options (p2p bind arg, for separate data dir) command_line::add_arg(desc, nodetool::arg_testnet_p2p_bind_port, false); @@ -173,9 +238,9 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::handle_command_line(const boost::program_options::variables_map& vm) { - m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + m_testnet = command_line::get_arg(vm, arg_testnet_on); - auto data_dir_arg = m_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + auto data_dir_arg = m_testnet ? arg_testnet_data_dir : arg_data_dir; m_config_folder = command_line::get_arg(vm, data_dir_arg); auto data_dir = boost::filesystem::path(m_config_folder); @@ -196,13 +261,15 @@ namespace cryptonote } - set_enforce_dns_checkpoints(command_line::get_arg(vm, command_line::arg_dns_checkpoints)); - test_drop_download_height(command_line::get_arg(vm, command_line::arg_test_drop_download_height)); - m_fluffy_blocks_enabled = m_testnet || get_arg(vm, command_line::arg_fluffy_blocks); + set_enforce_dns_checkpoints(command_line::get_arg(vm, arg_dns_checkpoints)); + test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height)); + m_fluffy_blocks_enabled = m_testnet || get_arg(vm, arg_fluffy_blocks); - if (command_line::get_arg(vm, command_line::arg_test_drop_download) == true) + if (command_line::get_arg(vm, arg_test_drop_download) == true) test_drop_download(); + epee::debug::g_test_dbg_lock_sleep() = command_line::get_arg(vm, arg_test_dbg_lock_sleep); + return true; } //----------------------------------------------------------------------------------------------- @@ -268,7 +335,7 @@ namespace cryptonote m_fakechain = test_options != NULL; bool r = handle_command_line(vm); - bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + bool testnet = command_line::get_arg(vm, arg_testnet_on); auto p2p_bind_arg = testnet ? nodetool::arg_testnet_p2p_bind_port : nodetool::arg_p2p_bind_port; std::string m_port = command_line::get_arg(vm, p2p_bind_arg); std::string m_config_folder_mempool = m_config_folder; @@ -281,9 +348,9 @@ namespace cryptonote std::string db_type = command_line::get_arg(vm, cryptonote::arg_db_type); std::string db_sync_mode = command_line::get_arg(vm, cryptonote::arg_db_sync_mode); bool db_salvage = command_line::get_arg(vm, cryptonote::arg_db_salvage) != 0; - bool fast_sync = command_line::get_arg(vm, command_line::arg_fast_block_sync) != 0; - uint64_t blocks_threads = command_line::get_arg(vm, command_line::arg_prep_blocks_threads); - std::string check_updates_string = command_line::get_arg(vm, command_line::arg_check_updates); + bool fast_sync = command_line::get_arg(vm, arg_fast_block_sync) != 0; + uint64_t blocks_threads = command_line::get_arg(vm, arg_prep_blocks_threads); + std::string check_updates_string = command_line::get_arg(vm, arg_check_updates); boost::filesystem::path folder(m_config_folder); if (m_fakechain) @@ -409,11 +476,11 @@ namespace cryptonote // transactions in the pool that do not conform to the current fork m_mempool.validate(m_blockchain_storage.get_current_hard_fork_version()); - bool show_time_stats = command_line::get_arg(vm, command_line::arg_show_time_stats) != 0; + bool show_time_stats = command_line::get_arg(vm, arg_show_time_stats) != 0; m_blockchain_storage.set_show_time_stats(show_time_stats); CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage"); - block_sync_size = command_line::get_arg(vm, command_line::arg_block_sync_size); + block_sync_size = command_line::get_arg(vm, arg_block_sync_size); MGINFO("Loading checkpoints"); @@ -498,8 +565,8 @@ namespace cryptonote return false; } - tx_hash = null_hash; - tx_prefixt_hash = null_hash; + tx_hash = crypto::null_hash; + tx_prefixt_hash = crypto::null_hash; if(!parse_tx_from_blob(tx, tx_hash, tx_prefixt_hash, tx_blob)) { @@ -1182,21 +1249,21 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transactions(std::list<transaction>& txs) const + bool core::get_pool_transactions(std::list<transaction>& txs, bool include_sensitive_data) const { - m_mempool.get_transactions(txs); + m_mempool.get_transactions(txs, include_sensitive_data); return true; } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const + bool core::get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive_data) const { - m_mempool.get_transaction_hashes(txs); + m_mempool.get_transaction_hashes(txs, include_sensitive_data); return true; } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transaction_stats(struct txpool_stats& stats) const + bool core::get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_data) const { - m_mempool.get_transaction_stats(stats); + m_mempool.get_transaction_stats(stats, include_sensitive_data); return true; } //----------------------------------------------------------------------------------------------- @@ -1210,9 +1277,9 @@ namespace cryptonote return m_mempool.have_tx(id); } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const + bool core::get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data) const { - return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos); + return m_mempool.get_transactions_and_spent_keys_info(tx_infos, key_image_infos, include_sensitive_data); } //----------------------------------------------------------------------------------------------- bool core::get_pool_for_rpc(std::vector<cryptonote::rpc::tx_in_pool>& tx_infos, cryptonote::rpc::key_images_with_tx_hashes& key_image_infos) const @@ -1269,6 +1336,7 @@ namespace cryptonote m_fork_moaner.do_call(boost::bind(&core::check_fork_time, this)); m_txpool_auto_relayer.do_call(boost::bind(&core::relay_txpool_transactions, this)); m_check_updates_interval.do_call(boost::bind(&core::check_updates, this)); + m_check_disk_space_interval.do_call(boost::bind(&core::check_disk_space, this)); m_miner.on_idle(); m_mempool.on_idle(); return true; @@ -1400,6 +1468,17 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::check_disk_space() + { + uint64_t free_space = get_free_space(); + if (free_space < 1ull * 1024 * 1024 * 1024) // 1 GB + { + const el::Level level = el::Level::Warning; + MCLOG_RED(level, "global", "Free space is below 1 GB on " << m_config_folder); + } + return true; + } + //----------------------------------------------------------------------------------------------- void core::set_target_blockchain_height(uint64_t target_blockchain_height) { m_target_blockchain_height = target_blockchain_height; @@ -1415,6 +1494,13 @@ namespace cryptonote return get_blockchain_storage().prevalidate_block_hashes(height, hashes); } //----------------------------------------------------------------------------------------------- + uint64_t core::get_free_space() const + { + boost::filesystem::path path(m_config_folder); + boost::filesystem::space_info si = boost::filesystem::space(path); + return si.available; + } + //----------------------------------------------------------------------------------------------- std::time_t core::get_start_time() const { return start_time; diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 7340e1024..905e67f6d 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -41,6 +41,7 @@ #include "storages/portable_storage_template_helper.h" #include "common/download.h" #include "common/threadpool.h" +#include "common/command_line.h" #include "tx_pool.h" #include "blockchain.h" #include "cryptonote_basic/miner.h" @@ -58,6 +59,10 @@ namespace cryptonote const std::pair<uint8_t, uint64_t> *hard_forks; }; + extern const command_line::arg_descriptor<std::string> arg_data_dir; + extern const command_line::arg_descriptor<std::string> arg_testnet_data_dir; + extern const command_line::arg_descriptor<bool, false> arg_testnet_on; + /************************************************************************/ /* */ /************************************************************************/ @@ -420,11 +425,12 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions + * @param include_unrelayed_txes include unrelayed txes in result * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transactions(std::list<transaction>& txs) const; - + bool get_pool_transactions(std::list<transaction>& txs, bool include_unrelayed_txes = true) const; + /** * @copydoc tx_memory_pool::get_txpool_backlog * @@ -434,17 +440,19 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions + * @param include_unrelayed_txes include unrelayed txes in result * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs) const; + bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const; /** * @copydoc tx_memory_pool::get_transactions + * @param include_unrelayed_txes include unrelayed txes in result * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transaction_stats(struct txpool_stats& stats) const; + bool get_pool_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const; /** * @copydoc tx_memory_pool::get_transaction @@ -455,10 +463,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info + * @param include_unrelayed_txes include unrelayed txes in result * * @note see tx_memory_pool::get_pool_transactions_and_spent_keys_info */ - bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; + bool get_pool_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_unrelayed_txes = true) const; /** * @copydoc tx_memory_pool::get_pool_for_rpc @@ -757,6 +766,13 @@ namespace cryptonote */ uint64_t prevalidate_block_hashes(uint64_t height, const std::list<crypto::hash> &hashes); + /** + * @brief get free disk space on the blockchain partition + * + * @return free space in bytes + */ + uint64_t get_free_space() const; + private: /** @@ -916,6 +932,13 @@ namespace cryptonote */ bool check_updates(); + /** + * @brief checks free disk space + * + * @return true on success, false otherwise + */ + bool check_disk_space(); + bool m_test_drop_download = true; //!< whether or not to drop incoming blocks (for testing) uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so @@ -939,6 +962,7 @@ namespace cryptonote epee::math_helper::once_a_time_seconds<60*60*2, true> m_fork_moaner; //!< interval for checking HardFork status epee::math_helper::once_a_time_seconds<60*2, false> m_txpool_auto_relayer; //!< interval for checking re-relaying txpool transactions epee::math_helper::once_a_time_seconds<60*60*12, true> m_check_updates_interval; //!< interval for checking for new versions + epee::math_helper::once_a_time_seconds<60*10, true> m_check_disk_space_interval; //!< interval for checking for disk space std::atomic<bool> m_starter_message_showed; //!< has the "daemon will sync now" message been shown? diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 96f6ee872..feefc1592 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -40,6 +40,8 @@ using namespace epee; #include "crypto/hash.h" #include "ringct/rctSigs.h" +using namespace crypto; + namespace cryptonote { //--------------------------------------------------------------- @@ -160,12 +162,6 @@ namespace cryptonote //--------------------------------------------------------------- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct) { - if (destinations.empty()) - { - LOG_ERROR("The destinations must be non-empty"); - return false; - } - std::vector<rct::key> amount_keys; tx.set_null(); amount_keys.clear(); @@ -174,9 +170,8 @@ namespace cryptonote tx.unlock_time = unlock_time; tx.extra = extra; - keypair txkey = keypair::generate(); - remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); - add_tx_pub_key_to_extra(tx, txkey.pub); + keypair txkey; + txkey.sec = rct::rct2sk(rct::skGen()); tx_key = txkey.sec; // if we have a stealth payment id, find it and encrypt it with the tx key now @@ -323,9 +318,13 @@ namespace cryptonote if (num_stdaddresses == 0 && num_subaddresses == 1) { txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec))); - remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); - add_tx_pub_key_to_extra(tx, txkey.pub); } + else + { + txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(txkey.sec))); + } + remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key)); + add_tx_pub_key_to_extra(tx, txkey.pub); std::vector<crypto::public_key> additional_tx_public_keys; additional_tx_keys.clear(); @@ -348,9 +347,11 @@ namespace cryptonote keypair additional_txkey; if (need_additional_txkeys) { - additional_txkey = keypair::generate(); + additional_txkey.sec = rct::rct2sk(rct::skGen()); if (dst_entr.is_subaddress) additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec))); + else + additional_txkey.pub = rct::rct2pk(rct::scalarmultBase(rct::sk2rct(additional_txkey.sec))); } bool r; @@ -393,7 +394,6 @@ namespace cryptonote } remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys)); - add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); LOG_PRINT_L2("tx pubkey: " << txkey.pub); if (need_additional_txkeys) @@ -401,6 +401,7 @@ namespace cryptonote LOG_PRINT_L2("additional tx pubkeys: "); for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) LOG_PRINT_L2(additional_tx_public_keys[i]); + add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys); } //check money diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 9a3b2484d..8d9a1e332 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -53,6 +53,20 @@ namespace cryptonote rct::key mask; //ringct amount mask void push_output(uint64_t idx, const crypto::public_key &k, uint64_t amount) { outputs.push_back(std::make_pair(idx, rct::ctkey({rct::pk2rct(k), rct::zeroCommit(amount)}))); } + + BEGIN_SERIALIZE_OBJECT() + FIELD(outputs) + FIELD(real_output) + FIELD(real_out_tx_key) + FIELD(real_out_additional_tx_keys) + FIELD(real_output_in_tx_index) + FIELD(amount) + FIELD(rct) + FIELD(mask) + + if (real_output >= outputs.size()) + return false; + END_SERIALIZE() }; struct tx_destination_entry diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 9071c330c..e6f217463 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -51,6 +51,8 @@ DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated +using namespace crypto; + namespace cryptonote { namespace @@ -187,6 +189,7 @@ namespace cryptonote { if(have_tx_keyimges_as_spent(tx)) { + mark_double_spend(tx); LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images"); tvc.m_verifivation_failed = true; tvc.m_double_spend = true; @@ -228,6 +231,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.do_not_relay = do_not_relay; + meta.double_spend_seen = have_tx_keyimges_as_spent(tx); memset(meta.padding, 0, sizeof(meta.padding)); try { @@ -266,6 +270,7 @@ namespace cryptonote meta.last_relayed_time = time(NULL); meta.relayed = relayed; meta.do_not_relay = do_not_relay; + meta.double_spend_seen = false; memset(meta.padding, 0, sizeof(meta.padding)); try @@ -354,7 +359,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay) + bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen) { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -377,6 +382,7 @@ namespace cryptonote fee = meta.fee; relayed = meta.relayed; do_not_relay = meta.do_not_relay; + double_spend_seen = meta.double_spend_seen; // remove first, in case this throws, so key images aren't removed m_blockchain.remove_txpool_tx(id); @@ -432,7 +438,7 @@ namespace cryptonote remove.insert(txid); } return true; - }); + }, false); if (!remove.empty()) { @@ -494,7 +500,7 @@ namespace cryptonote } } return true; - }); + }, false); return true; } //--------------------------------------------------------------------------------- @@ -521,14 +527,14 @@ namespace cryptonote } } //--------------------------------------------------------------------------------- - size_t tx_memory_pool::get_transactions_count() const + size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - return m_blockchain.get_txpool_tx_count(); + return m_blockchain.get_txpool_tx_count(include_unrelayed_txes); } //--------------------------------------------------------------------------------- - void tx_memory_pool::get_transactions(std::list<transaction>& txs) const + void tx_memory_pool::get_transactions(std::list<transaction>& txs, bool include_unrelayed_txes) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -542,20 +548,20 @@ namespace cryptonote } txs.push_back(tx); return true; - }, true); + }, true, include_unrelayed_txes); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs) const + void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ txs.push_back(txid); return true; - }); + }, false, include_unrelayed_txes); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const + void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -563,16 +569,16 @@ namespace cryptonote m_blockchain.for_all_txpool_txes([&backlog, now](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ backlog.push_back({meta.blob_size, meta.fee, meta.receive_time - now}); return true; - }); + }, false, include_unrelayed_txes); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats) const + void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); const uint64_t now = time(NULL); std::map<uint64_t, txpool_histo> agebytes; - stats.txs_total = m_blockchain.get_txpool_tx_count(); + stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes); std::vector<uint32_t> sizes; sizes.reserve(stats.txs_total); m_blockchain.for_all_txpool_txes([&stats, &sizes, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ @@ -594,8 +600,10 @@ namespace cryptonote uint64_t age = now - meta.receive_time + (now == meta.receive_time); agebytes[age].txs++; agebytes[age].bytes += meta.blob_size; + if (meta.double_spend_seen) + ++stats.num_double_spends; return true; - }); + }, false, include_unrelayed_txes); stats.bytes_med = epee::misc_utils::median(sizes); if (stats.txs_total > 1) { @@ -642,13 +650,14 @@ namespace cryptonote } //------------------------------------------------------------------ //TODO: investigate whether boolean return is appropriate - bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const + bool tx_memory_pool::get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ + m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos, include_sensitive_data](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ tx_info txi; txi.id_hash = epee::string_tools::pod_to_hex(txid); + txi.tx_blob = *bd; transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { @@ -664,14 +673,18 @@ namespace cryptonote txi.max_used_block_id_hash = epee::string_tools::pod_to_hex(meta.max_used_block_id); txi.last_failed_height = meta.last_failed_height; txi.last_failed_id_hash = epee::string_tools::pod_to_hex(meta.last_failed_id); - txi.receive_time = meta.receive_time; + // In restricted mode we do not include this data: + txi.receive_time = include_sensitive_data ? meta.receive_time : 0; txi.relayed = meta.relayed; - txi.last_relayed_time = meta.last_relayed_time; + // In restricted mode we do not include this data: + txi.last_relayed_time = include_sensitive_data ? meta.last_relayed_time : 0; txi.do_not_relay = meta.do_not_relay; + txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(txi); return true; - }, true); + }, true, include_sensitive_data); + txpool_tx_meta_t meta; for (const key_images_container::value_type& kee : m_spent_key_images) { const crypto::key_image& k_image = kee.first; const std::unordered_set<crypto::hash>& kei_image_set = kee.second; @@ -679,9 +692,26 @@ namespace cryptonote ki.id_hash = epee::string_tools::pod_to_hex(k_image); for (const crypto::hash& tx_id_hash : kei_image_set) { + if (!include_sensitive_data) + { + try + { + meta = m_blockchain.get_txpool_tx_meta(tx_id_hash); + if (!meta.relayed) + // Do not include that transaction if in restricted mode and it's not relayed + continue; + } + catch (const std::exception &e) + { + MERROR("Failed to get tx meta from txpool: " << e.what()); + return false; + } + } ki.txs_hashes.push_back(epee::string_tools::pod_to_hex(tx_id_hash)); } - key_image_infos.push_back(ki); + // Only return key images for which we have at least one tx that we can show for them + if (!ki.txs_hashes.empty()) + key_image_infos.push_back(ki); } return true; } @@ -712,9 +742,10 @@ namespace cryptonote txi.relayed = meta.relayed; txi.last_relayed_time = meta.last_relayed_time; txi.do_not_relay = meta.do_not_relay; + txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(txi); return true; - }, true); + }, true, false); for (const key_images_container::value_type& kee : m_spent_key_images) { std::vector<crypto::hash> tx_hashes; @@ -843,7 +874,10 @@ namespace cryptonote } //if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure if(m_blockchain.have_tx_keyimges_as_spent(tx)) + { + txd.double_spend_seen = true; return false; + } //transaction is ok. return true; @@ -871,6 +905,39 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- + void tx_memory_pool::mark_double_spend(const transaction &tx) + { + CRITICAL_REGION_LOCAL(m_transactions_lock); + CRITICAL_REGION_LOCAL1(m_blockchain); + LockedTXN lock(m_blockchain); + for(size_t i = 0; i!= tx.vin.size(); i++) + { + CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void()); + const key_images_container::const_iterator it = m_spent_key_images.find(itk.k_image); + if (it != m_spent_key_images.end()) + { + for (const crypto::hash &txid: it->second) + { + txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(txid); + if (!meta.double_spend_seen) + { + MDEBUG("Marking " << txid << " as double spending " << itk.k_image); + meta.double_spend_seen = true; + try + { + m_blockchain.update_txpool_tx(txid, meta); + } + catch (const std::exception &e) + { + MERROR("Failed to update tx meta: " << e.what()); + // continue, not fatal + } + } + } + } + } + } + //--------------------------------------------------------------------------------- std::string tx_memory_pool::print_pool(bool short_format) const { std::stringstream ss; @@ -890,6 +957,7 @@ namespace cryptonote ss << "blob_size: " << meta.blob_size << std::endl << "fee: " << print_money(meta.fee) << std::endl << "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl + << "double_spend_seen: " << (meta.double_spend_seen ? 'T' : 'F') << std::endl << "max_used_block_height: " << meta.max_used_block_height << std::endl << "max_used_block_id: " << meta.max_used_block_id << std::endl << "last_failed_height: " << meta.last_failed_height << std::endl @@ -1044,7 +1112,7 @@ namespace cryptonote remove.insert(txid); } return true; - }); + }, false); size_t n_removed = 0; if (!remove.empty()) diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 3e4ccb338..d657c6554 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -137,10 +137,11 @@ namespace cryptonote * @param fee the transaction fee * @param relayed return-by-reference was transaction relayed to us by the network? * @param do_not_relay return-by-reference is transaction not to be relayed to the network? + * @param double_spend_seen return-by-reference was a double spend seen for that transaction? * * @return true unless the transaction cannot be found in the pool */ - bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay); + bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen); /** * @brief checks if the pool has a transaction with the given hash @@ -233,29 +234,37 @@ namespace cryptonote * @brief get a list of all transactions in the pool * * @param txs return-by-reference the list of transactions + * @param include_unrelayed_txes include unrelayed txes in the result + * */ - void get_transactions(std::list<transaction>& txs) const; + void get_transactions(std::list<transaction>& txs, bool include_unrelayed_txes = true) const; /** * @brief get a list of all transaction hashes in the pool * * @param txs return-by-reference the list of transactions + * @param include_unrelayed_txes include unrelayed txes in the result + * */ - void get_transaction_hashes(std::vector<crypto::hash>& txs) const; + void get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const; /** * @brief get (size, fee, receive time) for all transaction in the pool * * @param txs return-by-reference that data + * @param include_unrelayed_txes include unrelayed txes in the result + * */ - void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog) const; + void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes = true) const; /** * @brief get a summary statistics of all transaction hashes in the pool * * @param stats return-by-reference the pool statistics + * @param include_unrelayed_txes include unrelayed txes in the result + * */ - void get_transaction_stats(struct txpool_stats& stats) const; + void get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const; /** * @brief get information about all transactions and key images in the pool @@ -264,10 +273,11 @@ namespace cryptonote * * @param tx_infos return-by-reference the transactions' information * @param key_image_infos return-by-reference the spent key images' information + * @param include_sensitive_data include unrelayed txes and fields that are sensitive to the node privacy * * @return true */ - bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos) const; + bool get_transactions_and_spent_keys_info(std::vector<tx_info>& tx_infos, std::vector<spent_key_image_info>& key_image_infos, bool include_sensitive_data = true) const; /** * @brief get information about all transactions and key images in the pool @@ -308,6 +318,7 @@ namespace cryptonote * nonzero fee * hasn't been relayed too recently * isn't old enough that relaying it is considered harmful + * Note a transaction can be "relayable" even if do_not_relay is true * * @param txs return-by-reference the transactions and their hashes * @@ -327,7 +338,7 @@ namespace cryptonote * * @return the number of transactions in the pool */ - size_t get_transactions_count() const; + size_t get_transactions_count(bool include_unrelayed_txes = true) const; /** * @brief get a string containing human-readable pool information @@ -391,6 +402,8 @@ namespace cryptonote time_t last_relayed_time; //!< the last time the transaction was relayed to the network bool relayed; //!< whether or not the transaction has been relayed to the network bool do_not_relay; //!< to avoid relay this transaction to the network + + bool double_spend_seen; //!< true iff another tx was seen double spending this one }; private: @@ -478,6 +491,11 @@ namespace cryptonote */ bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const; + /** + * @brief mark all transactions double spending the one passed + */ + void mark_double_spend(const transaction &tx); + //TODO: confirm the below comments and investigate whether or not this // is the desired behavior //! map key images to transactions which spent them diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index d16e5d7e5..73433a8d8 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -52,6 +52,7 @@ #define BLOCK_QUEUE_SIZE_THRESHOLD (100*1024*1024) // MB #define REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD (5 * 1000000) // microseconds #define IDLE_PEER_KICK_TIME (600 * 1000000) // microseconds +#define PASSIVE_PEER_KICK_TIME (60 * 1000000) // microseconds namespace cryptonote { @@ -296,9 +297,9 @@ namespace cryptonote m_core.set_target_blockchain_height((hshd.current_height)); int64_t diff = static_cast<int64_t>(hshd.current_height) - static_cast<int64_t>(m_core.get_current_blockchain_height()); uint64_t abs_diff = std::abs(diff); - uint64_t max_block_height = max(hshd.current_height,m_core.get_current_blockchain_height()); + uint64_t max_block_height = std::max(hshd.current_height,m_core.get_current_blockchain_height()); uint64_t last_block_v1 = m_core.get_testnet() ? 624633 : 1009826; - uint64_t diff_v2 = max_block_height > last_block_v1 ? min(abs_diff, max_block_height - last_block_v1) : 0; + uint64_t diff_v2 = max_block_height > last_block_v1 ? std::min(abs_diff, max_block_height - last_block_v1) : 0; MCLOG(is_inital ? el::Level::Info : el::Level::Debug, "global", context << "Sync data returned a new top block candidate: " << m_core.get_current_blockchain_height() << " -> " << hshd.current_height << " [Your node is " << abs_diff << " blocks (" << ((abs_diff - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) @@ -1184,13 +1185,15 @@ skip: std::vector<boost::uuids::uuid> kick_connections; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool { - if (context.m_state == cryptonote_connection_context::state_synchronizing) + if (context.m_state == cryptonote_connection_context::state_synchronizing || context.m_state == cryptonote_connection_context::state_before_handshake) { + const bool passive = context.m_state == cryptonote_connection_context::state_before_handshake; const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); const boost::posix_time::time_duration dt = now - context.m_last_request_time; - if (dt.total_microseconds() > IDLE_PEER_KICK_TIME) + const int64_t threshold = passive ? PASSIVE_PEER_KICK_TIME : IDLE_PEER_KICK_TIME; + if (dt.total_microseconds() > threshold) { - MINFO(context << " kicking idle peer"); + MINFO(context << " kicking " << (passive ? "passive" : "idle") << " peer"); kick_connections.push_back(context.m_connection_id); } } diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index d0fc1d846..2f9c2b2f9 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -94,6 +94,8 @@ target_link_libraries(daemon daemonizer serialization daemon_rpc_server + epee + ${EPEE_READLINE} version ${Boost_CHRONO_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -102,6 +104,7 @@ target_link_libraries(daemon ${Boost_SYSTEM_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${ZMQ_LIB} + ${Readline_LIBRARY} ${EXTRA_LIBRARIES}) set_property(TARGET daemon PROPERTY diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index faa620c54..cf7d5f8ab 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -88,7 +88,7 @@ t_daemon::t_daemon( ) : mp_internals{new t_internals{vm}} { - bool testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); if (testnet) { zmq_rpc_bind_port = command_line::get_arg(vm, daemon_args::arg_zmq_testnet_rpc_bind_port); diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 5d548f410..ae83943b6 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -82,7 +82,6 @@ int main(int argc, char const * argv[]) command_line::add_arg(visible_options, daemon_args::arg_os_version); bf::path default_conf = default_data_dir / std::string(CRYPTONOTE_NAME ".conf"); command_line::add_arg(visible_options, daemon_args::arg_config_file, default_conf.string()); - command_line::add_arg(visible_options, command_line::arg_test_dbg_lock_sleep); // Settings bf::path default_log = default_data_dir / std::string(CRYPTONOTE_NAME ".log"); @@ -144,8 +143,6 @@ int main(int argc, char const * argv[]) return 0; } - epee::debug::g_test_dbg_lock_sleep() = command_line::get_arg(vm, command_line::arg_test_dbg_lock_sleep); - std::string db_type = command_line::get_arg(vm, cryptonote::arg_db_type); // verify that blockchaindb type is valid @@ -156,9 +153,9 @@ int main(int argc, char const * argv[]) return 0; } - bool testnet_mode = command_line::get_arg(vm, command_line::arg_testnet_on); + bool testnet_mode = command_line::get_arg(vm, cryptonote::arg_testnet_on); - auto data_dir_arg = testnet_mode ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + auto data_dir_arg = testnet_mode ? cryptonote::arg_testnet_data_dir : cryptonote::arg_data_dir; // data_dir // default: e.g. ~/.bitmonero/ or ~/.bitmonero/testnet @@ -250,7 +247,12 @@ int main(int argc, char const * argv[]) if (command_line::has_arg(vm, arg.rpc_login)) { login = tools::login::parse( - command_line::get_arg(vm, arg.rpc_login), false, "Daemon client password" + command_line::get_arg(vm, arg.rpc_login), false, [](bool verify) { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + return tools::password_container::prompt(verify, "Daemon client password"); + } ); if (!login) { diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2c11dcb31..d7ee28baa 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -814,7 +814,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() { } else { - if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK) + if (!m_rpc_server->on_get_transaction_pool(req, res, false) || res.status != CORE_RPC_STATUS_OK) { tools::fail_msg_writer() << make_error(fail_message, res.status); return true; @@ -840,6 +840,7 @@ bool t_rpc_command_executor::print_transaction_pool_long() { << "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl << "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl + << "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl << "max_used_block_height: " << tx_info.max_used_block_height << std::endl << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl << "last_failed_height: " << tx_info.last_failed_height << std::endl @@ -898,7 +899,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() { } else { - if (!m_rpc_server->on_get_transaction_pool(req, res) || res.status != CORE_RPC_STATUS_OK) + if (!m_rpc_server->on_get_transaction_pool(req, res, false) || res.status != CORE_RPC_STATUS_OK) { tools::fail_msg_writer() << make_error(fail_message, res.status); return true; @@ -922,6 +923,7 @@ bool t_rpc_command_executor::print_transaction_pool_short() { << "relayed: " << [&](const cryptonote::tx_info &tx_info)->std::string { if (!tx_info.relayed) return "no"; return boost::lexical_cast<std::string>(tx_info.last_relayed_time) + " (" + get_human_time_ago(tx_info.last_relayed_time, now) + ")"; } (tx_info) << std::endl << "do_not_relay: " << (tx_info.do_not_relay ? 'T' : 'F') << std::endl << "kept_by_block: " << (tx_info.kept_by_block ? 'T' : 'F') << std::endl + << "double_spend_seen: " << (tx_info.double_spend_seen ? 'T' : 'F') << std::endl << "max_used_block_height: " << tx_info.max_used_block_height << std::endl << "max_used_block_id: " << tx_info.max_used_block_id_hash << std::endl << "last_failed_height: " << tx_info.last_failed_height << std::endl @@ -954,7 +956,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { else { memset(&res.pool_stats, 0, sizeof(res.pool_stats)); - if (!m_rpc_server->on_get_transaction_pool_stats(req, res) || res.status != CORE_RPC_STATUS_OK) + if (!m_rpc_server->on_get_transaction_pool_stats(req, res, false) || res.status != CORE_RPC_STATUS_OK) { tools::fail_msg_writer() << make_error(fail_message, res.status); return true; @@ -984,7 +986,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { tools::msg_writer() << n_transactions << " tx(es), " << res.pool_stats.bytes_total << " bytes total (min " << res.pool_stats.bytes_min << ", max " << res.pool_stats.bytes_max << ", avg " << avg_bytes << ", median " << res.pool_stats.bytes_med << ")" << std::endl << "fees " << cryptonote::print_money(res.pool_stats.fee_total) << " (avg " << cryptonote::print_money(n_transactions ? res.pool_stats.fee_total / n_transactions : 0) << " per tx" << ", " << cryptonote::print_money(res.pool_stats.bytes_total ? res.pool_stats.fee_total / res.pool_stats.bytes_total : 0) << " per byte)" << std::endl - << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message; + << res.pool_stats.num_double_spends << " double spends, " << res.pool_stats.num_not_relayed << " not relayed, " << res.pool_stats.num_failing << " failing, " << res.pool_stats.num_10m << " older than 10 minutes (oldest " << (res.pool_stats.oldest == 0 ? "-" : get_human_time_ago(res.pool_stats.oldest, now)) << "), " << backlog_message; if (n_transactions > 1 && res.pool_stats.histo.size()) { @@ -1009,7 +1011,7 @@ bool t_rpc_command_executor::print_transaction_pool_stats() { tools::msg_writer() << " Age Txes Bytes"; for (i=0; i<n; i++) { - tools::msg_writer() << get_time_hms(times[i]) << setw(8) << res.pool_stats.histo[i].txs << setw(12) << res.pool_stats.histo[i].bytes; + tools::msg_writer() << get_time_hms(times[i]) << std::setw(8) << res.pool_stats.histo[i].txs << std::setw(12) << res.pool_stats.histo[i].bytes; } } tools::msg_writer(); @@ -1302,6 +1304,7 @@ bool t_rpc_command_executor::start_save_graph() } } + tools::success_msg_writer() << "Saving graph is now on"; return true; } @@ -1327,6 +1330,7 @@ bool t_rpc_command_executor::stop_save_graph() return true; } } + tools::success_msg_writer() << "Saving graph is now off"; return true; } @@ -1493,6 +1497,7 @@ bool t_rpc_command_executor::flush_txpool(const std::string &txid) } } + tools::success_msg_writer() << "Pool successfully flushed"; return true; } @@ -1795,6 +1800,7 @@ bool t_rpc_command_executor::relay_tx(const std::string &txid) } } + tools::success_msg_writer() << "Transaction successfully relayed"; return true; } diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index 6ae33a743..1b14905f6 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -236,11 +236,13 @@ namespace crypto /*! * \brief Converts seed words to bytes (secret key). * \param words String containing the words separated by spaces. - * \param dst To put the secret key restored from the words. + * \param dst To put the secret data restored from the words. + * \param len The number of bytes to expect, 0 if unknown + * \param duplicate If true and len is not zero, we accept half the data, and duplicate it * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list */ - bool words_to_bytes(std::string words, crypto::secret_key& dst, + bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, std::string &language_name) { std::vector<std::string> seed; @@ -248,15 +250,23 @@ namespace crypto boost::algorithm::trim(words); boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on); - // error on non-compliant word list - if (seed.size() != seed_length/2 && seed.size() != seed_length && - seed.size() != seed_length + 1) - { + if (len % 4) return false; - } - // If it is seed with a checksum. - bool has_checksum = seed.size() == (seed_length + 1); + bool has_checksum = true; + if (len) + { + // error on non-compliant word list + const size_t expected = len * 8 * 3 / 32; + if (seed.size() != expected/2 && seed.size() != expected && + seed.size() != expected + 1) + { + return false; + } + + // If it is seed with a checksum. + has_checksum = seed.size() == (expected + 1); + } std::vector<uint32_t> matched_indices; Language::Base *language; @@ -290,32 +300,55 @@ namespace crypto if (!(val % word_list_length == w1)) return false; - memcpy(dst.data + i * 4, &val, 4); // copy 4 bytes to position + dst.append((const char*)&val, 4); // copy 4 bytes to position } - std::string wlist_copy = words; - if (seed.size() == seed_length/2) + if (len > 0 && duplicate) { - memcpy(dst.data+16, dst.data, 16); // if electrum 12-word seed, duplicate - wlist_copy += ' '; - wlist_copy += words; + const size_t expected = len * 3 / 32; + std::string wlist_copy = words; + if (seed.size() == expected/2) + { + dst.append(dst); // if electrum 12-word seed, duplicate + wlist_copy += ' '; + wlist_copy += words; + } } return true; } /*! + * \brief Converts seed words to bytes (secret key). + * \param words String containing the words separated by spaces. + * \param dst To put the secret key restored from the words. + * \param language_name Language of the seed as found gets written here. + * \return false if not a multiple of 3 words, or if word is not in the words list + */ + bool words_to_bytes(std::string words, crypto::secret_key& dst, + std::string &language_name) + { + std::string s; + if (!words_to_bytes(words, s, sizeof(dst), true, language_name)) + return false; + if (s.size() != sizeof(dst)) + return false; + dst = *(const crypto::secret_key*)s.data(); + return true; + } + + /*! * \brief Converts bytes (secret key) to seed words. * \param src Secret key * \param words Space delimited concatenated words get written here. * \param language_name Seed language name * \return true if successful false if not. Unsuccessful if wrong key size. */ - bool bytes_to_words(const crypto::secret_key& src, std::string& words, + bool bytes_to_words(const char *src, size_t len, std::string& words, const std::string &language_name) { - if (sizeof(src.data) % 4 != 0 || sizeof(src.data) == 0) return false; + if (len % 4 != 0 || len == 0) return false; Language::Base *language; if (language_name == "English") @@ -376,13 +409,13 @@ namespace crypto uint32_t word_list_length = word_list.size(); // 8 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626 - for (unsigned int i=0; i < sizeof(src.data)/4; i++, words += ' ') + for (unsigned int i=0; i < len/4; i++, words += ' ') { uint32_t w1, w2, w3; uint32_t val; - memcpy(&val, (src.data) + (i * 4), 4); + memcpy(&val, src + (i * 4), 4); w1 = val % word_list_length; w2 = ((val / word_list_length) + w1) % word_list_length; @@ -404,6 +437,12 @@ namespace crypto return true; } + bool bytes_to_words(const crypto::secret_key& src, std::string& words, + const std::string &language_name) + { + return bytes_to_words(src.data, sizeof(src), words, language_name); + } + /*! * \brief Gets a list of seed languages that are supported. * \param languages The vector is set to the list of languages. diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 94ce9c200..941768352 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -64,6 +64,17 @@ namespace crypto /*! * \brief Converts seed words to bytes (secret key). * \param words String containing the words separated by spaces. + * \param dst To put the secret data restored from the words. + * \param len The number of bytes to expect, 0 if unknown + * \param duplicate If true and len is not zero, we accept half the data, and duplicate it + * \param language_name Language of the seed as found gets written here. + * \return false if not a multiple of 3 words, or if word is not in the words list + */ + bool words_to_bytes(std::string words, std::string& dst, size_t len, bool duplicate, + std::string &language_name); + /*! + * \brief Converts seed words to bytes (secret key). + * \param words String containing the words separated by spaces. * \param dst To put the secret key restored from the words. * \param language_name Language of the seed as found gets written here. * \return false if not a multiple of 3 words, or if word is not in the words list @@ -72,6 +83,17 @@ namespace crypto std::string &language_name); /*! + * \brief Converts bytes to seed words. + * \param src Secret data + * \param len Secret data length in bytes (positive multiples of 4 only) + * \param words Space delimited concatenated words get written here. + * \param language_name Seed language name + * \return true if successful false if not. Unsuccessful if wrong key size. + */ + bool bytes_to_words(const char *src, size_t len, std::string& words, + const std::string &language_name); + + /*! * \brief Converts bytes (secret key) to seed words. * \param src Secret key * \param words Space delimited concatenated words get written here. diff --git a/src/p2p/connection_basic.cpp b/src/p2p/connection_basic.cpp index b95f36b99..8edd75b3e 100644 --- a/src/p2p/connection_basic.cpp +++ b/src/p2p/connection_basic.cpp @@ -158,7 +158,7 @@ connection_basic::connection_basic(boost::asio::io_service& io_service, std::ato ++ref_sock_count; // increase the global counter mI->m_peer_number = sock_number.fetch_add(1); // use, and increase the generated number - string remote_addr_str = "?"; + 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 p2p#"<<mI->m_peer_number<<" to " << remote_addr_str << " currently we have sockets count:" << m_ref_sock_count); @@ -166,7 +166,7 @@ connection_basic::connection_basic(boost::asio::io_service& io_service, std::ato } connection_basic::~connection_basic() noexcept(false) { - string remote_addr_str = "?"; + std::string remote_addr_str = "?"; m_ref_sock_count--; try { boost::system::error_code e; remote_addr_str = socket_.remote_endpoint(e).address().to_string(); } catch(...){} ; _note("Destructing connection p2p#"<<mI->m_peer_number << " to " << remote_addr_str); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 6162d649b..f64b29c1f 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -48,6 +48,7 @@ #include "net/local_ip.h" #include "crypto/crypto.h" #include "storages/levin_abstract_invoke2.h" +#include "cryptonote_core/cryptonote_core.h" // We have to look for miniupnpc headers in different places, dependent on if its compiled or external #ifdef UPNP_STATIC @@ -434,7 +435,7 @@ namespace nodetool bool node_server<t_payload_net_handler>::init(const boost::program_options::variables_map& vm) { std::set<std::string> full_addrs; - m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + m_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); if (m_testnet) { @@ -535,7 +536,7 @@ namespace nodetool bool res = handle_command_line(vm); CHECK_AND_ASSERT_MES(res, false, "Failed to handle command line"); - auto config_arg = m_testnet ? command_line::arg_testnet_data_dir : command_line::arg_data_dir; + auto config_arg = m_testnet ? cryptonote::arg_testnet_data_dir : cryptonote::arg_data_dir; m_config_folder = command_line::get_arg(vm, config_arg); if ((!m_testnet && m_port != std::to_string(::config::P2P_DEFAULT_PORT)) @@ -560,7 +561,7 @@ namespace nodetool //configure self m_net_server.set_threads_prefix("P2P"); - m_net_server.get_config_object().m_pcommands_handler = this; + m_net_server.get_config_object().set_handler(this); m_net_server.get_config_object().m_invoke_timeout = P2P_DEFAULT_INVOKE_TIMEOUT; m_net_server.set_connection_filter(this); diff --git a/src/ringct/rctOps.h b/src/ringct/rctOps.h index cb19bbbd6..412450c18 100644 --- a/src/ringct/rctOps.h +++ b/src/ringct/rctOps.h @@ -35,8 +35,6 @@ #define RCTOPS_H #include <cstddef> -#include <mutex> -#include <vector> #include <tuple> #include "crypto/generic-ops.h" @@ -57,9 +55,6 @@ extern "C" { #define DP(x) #endif -using namespace std; -using namespace crypto; - namespace rct { //Various key initialization functions @@ -99,13 +94,13 @@ namespace rct { key pkGen(); //generates a random secret and corresponding public key void skpkGen(key &sk, key &pk); - tuple<key, key> skpkGen(); + std::tuple<key, key> skpkGen(); //generates a <secret , public> / Pedersen commitment to the amount - tuple<ctkey, ctkey> ctskpkGen(xmr_amount amount); + std::tuple<ctkey, ctkey> ctskpkGen(xmr_amount amount); //generates C =aG + bH from b, a is random void genC(key & C, const key & a, xmr_amount amount); //this one is mainly for testing, can take arbitrary amounts.. - tuple<ctkey, ctkey> ctskpkGen(const key &bH); + std::tuple<ctkey, ctkey> ctskpkGen(const key &bH); // make a pedersen commitment with given key key commit(xmr_amount amount, const key &mask); // make a pedersen commitment with zero key diff --git a/src/ringct/rctSigs.h b/src/ringct/rctSigs.h index ca40ddd85..d158f06f0 100644 --- a/src/ringct/rctSigs.h +++ b/src/ringct/rctSigs.h @@ -36,7 +36,6 @@ #define RCTSIGS_H #include <cstddef> -#include <mutex> #include <vector> #include <tuple> @@ -61,9 +60,6 @@ extern "C" { -using namespace std; -using namespace crypto; - namespace rct { boroSig genBorromean(const key64 x, const key64 P1, const key64 P2, const bits indices); @@ -110,7 +106,7 @@ namespace rct { //populateFromBlockchain creates a keymatrix with "mixin" columns and one of the columns is inPk // the return value are the key matrix, and the index where inPk was put (random). void getKeyFromBlockchain(ctkey & a, size_t reference_index); - tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin); + std::tuple<ctkeyM, xmr_amount> populateFromBlockchain(ctkeyV inPk, int mixin); //RingCT protocol //genRct: @@ -122,10 +118,10 @@ namespace rct { //decodeRct: (c.f. http://eprint.iacr.org/2015/1098 section 5.1.1) // uses the attached ecdh info to find the amounts represented by each output commitment // must know the destination private key to find the correct amount, else will return a random number - rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk); - rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); - rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const vector<xmr_amount> & inamounts, const vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk); + rctSig genRct(const key &message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const ctkeyM &mixRing, const keyV &amount_keys, unsigned int index, ctkeyV &outSk); + rctSig genRct(const key &message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & amounts, const keyV &amount_keys, const int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const ctkeyV & inPk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, const keyV &amount_keys, xmr_amount txnFee, unsigned int mixin); + rctSig genRctSimple(const key & message, const ctkeyV & inSk, const keyV & destinations, const std::vector<xmr_amount> & inamounts, const std::vector<xmr_amount> & outamounts, xmr_amount txnFee, const ctkeyM & mixRing, const keyV &amount_keys, const std::vector<unsigned int> & index, ctkeyV &outSk); bool verRct(const rctSig & rv, bool semantics); static inline bool verRct(const rctSig & rv) { return verRct(rv, true) && verRct(rv, false); } bool verRctSimple(const rctSig & rv, bool semantics); diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index cc0000ad6..8147cb602 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -33,9 +33,7 @@ #define RCT_TYPES_H #include <cstddef> -#include <mutex> #include <vector> -#include <tuple> #include <iostream> #include <cinttypes> @@ -67,9 +65,6 @@ extern "C" { //for printing large ints -using namespace std; -using namespace crypto; - //Namespace specifically for ring ct code namespace rct { //basic ops containers @@ -89,8 +84,8 @@ namespace rct { bool operator==(const key &k) const { return !memcmp(bytes, k.bytes, sizeof(bytes)); } unsigned char bytes[32]; }; - typedef vector<key> keyV; //vector of keys - typedef vector<keyV> keyM; //matrix of keys (indexed by column first) + typedef std::vector<key> keyV; //vector of keys + typedef std::vector<keyV> keyM; //matrix of keys (indexed by column first) //containers For CT operations //if it's representing a private ctkey then "dest" contains the secret key of the address @@ -101,8 +96,8 @@ namespace rct { key dest; key mask; //C here if public }; - typedef vector<ctkey> ctkeyV; - typedef vector<ctkeyV> ctkeyM; + typedef std::vector<ctkey> ctkeyV; + typedef std::vector<ctkeyV> ctkeyM; //data for passing the amount to the receiver secretly // If the pedersen commitment to an amount is C = aG + bH, @@ -184,7 +179,7 @@ namespace rct { ctkeyM mixRing; //the set of all pubkeys / copy //pairs that you mix with keyV pseudoOuts; //C - for simple rct - vector<ecdhTuple> ecdhInfo; + std::vector<ecdhTuple> ecdhInfo; ctkeyV outPk; xmr_amount txnFee; // contains b @@ -245,8 +240,8 @@ namespace rct { } }; struct rctSigPrunable { - vector<rangeSig> rangeSigs; - vector<mgSig> MGs; // simple rct has N, full has 1 + std::vector<rangeSig> rangeSigs; + std::vector<mgSig> MGs; // simple rct has N, full has 1 template<bool W, template <bool> class Archive> bool serialize_rctsig_prunable(Archive<W> &ar, uint8_t type, size_t inputs, size_t outputs, size_t mixin) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b3ce30d0c..e9a6a18aa 100755..100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -36,6 +36,7 @@ using namespace epee; #include "common/updates.h" #include "common/download.h" #include "common/util.h" +#include "common/perf_timer.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" @@ -84,7 +85,7 @@ namespace cryptonote const boost::program_options::variables_map& vm ) { - m_testnet = command_line::get_arg(vm, command_line::arg_testnet_on); + m_testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); m_net_server.set_threads_prefix("RPC"); auto p2p_bind_arg = m_testnet ? arg_testnet_rpc_bind_port : arg_rpc_bind_port; @@ -105,30 +106,20 @@ namespace cryptonote ); } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::check_core_busy() - { - if(m_p2p.get_payload_object().get_core().get_blockchain_storage().is_storing_blockchain()) - { - return false; - } - return true; - } -#define CHECK_CORE_BUSY() do { if(!check_core_busy()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0) - //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_core_ready() { if(!m_p2p.get_payload_object().is_synchronized()) { return false; } - return check_core_busy(); + return true; } #define CHECK_CORE_READY() do { if(!check_core_ready()){res.status = CORE_RPC_STATUS_BUSY;return true;} } while(0) //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_height); res.height = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; return true; @@ -136,7 +127,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_info); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height @@ -150,6 +141,7 @@ namespace cryptonote uint64_t total_conn = m_p2p.get_connections_count(); res.outgoing_connections_count = m_p2p.get_outgoing_connections_count(); res.incoming_connections_count = total_conn - res.outgoing_connections_count; + res.rpc_connections_count = get_connections_count(); res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; @@ -157,6 +149,7 @@ namespace cryptonote res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; res.start_time = (uint64_t)m_core.get_start_time(); + res.free_space = m_restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -179,7 +172,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_blocks); std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > > bs; if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) @@ -238,7 +231,7 @@ namespace cryptonote } bool core_rpc_server::on_get_alt_blocks_hashes(const COMMAND_RPC_GET_ALT_BLOCKS_HASHES::request& req, COMMAND_RPC_GET_ALT_BLOCKS_HASHES::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_alt_blocks_hashes); std::list<block> blks; if(!m_core.get_alternative_blocks(blks)) @@ -261,7 +254,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_blocks_by_height); res.status = "Failed"; res.blocks.clear(); res.blocks.reserve(req.heights.size()); @@ -291,7 +284,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_hashes); NOTIFY_RESPONSE_CHAIN_ENTRY::request resp; resp.start_height = req.start_height; @@ -310,7 +303,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_random_outs(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_random_outs); res.status = "Failed"; if (m_restricted) @@ -349,7 +342,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_outs_bin); res.status = "Failed"; if (m_restricted) @@ -372,7 +365,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_outs); res.status = "Failed"; if (m_restricted) @@ -410,7 +403,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_random_rct_outs(const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request& req, COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_random_rct_outs); res.status = "Failed"; if(!m_core.get_random_rct_outs(req, res)) { @@ -434,7 +427,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_indexes); bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) { @@ -448,7 +441,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_get_transactions); std::vector<crypto::hash> vh; for(const auto& tx_hex_str: req.txs_hashes) { @@ -478,15 +471,17 @@ namespace cryptonote // try the pool for any missing txes size_t found_in_pool = 0; std::unordered_set<crypto::hash> pool_tx_hashes; + std::unordered_map<crypto::hash, bool> double_spend_seen; if (!missed_txs.empty()) { - std::list<transaction> pool_txs; - bool r = m_core.get_pool_transactions(pool_txs); + std::vector<tx_info> pool_tx_info; + std::vector<spent_key_image_info> pool_key_image_info; + bool r = m_core.get_pool_transactions_and_spent_keys_info(pool_tx_info, pool_key_image_info); if(r) { // sort to match original request std::list<transaction> sorted_txs; - std::list<cryptonote::transaction>::const_iterator i; + std::vector<tx_info>::const_iterator i; for (const crypto::hash &h: vh) { if (std::find(missed_txs.begin(), missed_txs.end(), h) == missed_txs.end()) @@ -500,11 +495,26 @@ namespace cryptonote sorted_txs.push_back(std::move(txs.front())); txs.pop_front(); } - else if ((i = std::find_if(pool_txs.begin(), pool_txs.end(), [h](cryptonote::transaction &tx) { return h == cryptonote::get_transaction_hash(tx); })) != pool_txs.end()) + else if ((i = std::find_if(pool_tx_info.begin(), pool_tx_info.end(), [h](const tx_info &txi) { return epee::string_tools::pod_to_hex(h) == txi.id_hash; })) != pool_tx_info.end()) { - sorted_txs.push_back(*i); + cryptonote::transaction tx; + if (!cryptonote::parse_and_validate_tx_from_blob(i->tx_blob, tx)) + { + res.status = "Failed to parse and validate tx from blob"; + return true; + } + sorted_txs.push_back(tx); missed_txs.remove(h); pool_tx_hashes.insert(h); + const std::string hash_string = epee::string_tools::pod_to_hex(h); + for (const auto &ti: pool_tx_info) + { + if (ti.id_hash == hash_string) + { + double_spend_seen.insert(std::make_pair(h, ti.double_spend_seen)); + break; + } + } ++found_in_pool; } } @@ -530,11 +540,21 @@ namespace cryptonote if (e.in_pool) { e.block_height = e.block_timestamp = std::numeric_limits<uint64_t>::max(); + if (double_spend_seen.find(tx_hash) != double_spend_seen.end()) + { + e.double_spend_seen = double_spend_seen[tx_hash]; + } + else + { + MERROR("Failed to determine double spend status for " << tx_hash); + e.double_spend_seen = false; + } } else { e.block_height = m_core.get_blockchain_storage().get_db().get_tx_block_height(tx_hash); e.block_timestamp = m_core.get_blockchain_storage().get_db().get_block_timestamp(e.block_height); + e.double_spend_seen = false; } // fill up old style responses too, in case an old wallet asks @@ -564,9 +584,9 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res) + bool core_rpc_server::on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, bool request_has_rpc_origin) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_is_key_image_spent); std::vector<crypto::key_image> key_images; for(const auto& ki_hex_str: req.key_images) { @@ -596,7 +616,7 @@ namespace cryptonote // check the pool too std::vector<cryptonote::tx_info> txs; std::vector<cryptonote::spent_key_image_info> ki; - r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki); + r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki, !request_has_rpc_origin || !m_restricted); if(!r) { res.status = "Failed"; @@ -629,6 +649,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res) { + PERF_TIMER(on_send_raw_tx); CHECK_CORE_READY(); std::string tx_blob; @@ -692,6 +713,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res) { + PERF_TIMER(on_start_mining); CHECK_CORE_READY(); cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_testnet, req.miner_address)) @@ -739,6 +761,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_mining(const COMMAND_RPC_STOP_MINING::request& req, COMMAND_RPC_STOP_MINING::response& res) { + PERF_TIMER(on_stop_mining); if(!m_core.get_miner().stop()) { res.status = "Failed, mining not stopped"; @@ -751,7 +774,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_mining_status); const miner& lMiner = m_core.get_miner(); res.active = lMiner.is_mining(); @@ -770,7 +793,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_save_bc); if( !m_core.get_blockchain_storage().store_blockchain() ) { res.status = "Error while storing blockhain"; @@ -782,6 +805,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res) { + PERF_TIMER(on_get_peer_list); std::list<nodetool::peerlist_entry> white_list; std::list<nodetool::peerlist_entry> gray_list; m_p2p.get_peerlist_manager().get_peerlist_full(gray_list, white_list); @@ -811,6 +835,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res) { + PERF_TIMER(on_set_log_hash_rate); if(m_core.get_miner().is_mining()) { m_core.get_miner().do_print_hashrate(req.visible); @@ -825,6 +850,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res) { + PERF_TIMER(on_set_log_level); if (req.level < 0 || req.level > 4) { res.status = "Error: log level not valid"; @@ -837,38 +863,40 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res) { + PERF_TIMER(on_set_log_categories); mlog_set_log(req.categories.c_str()); res.categories = mlog_get_categories(); res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res) + bool core_rpc_server::on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, bool request_has_rpc_origin) { - CHECK_CORE_BUSY(); - m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images); + PERF_TIMER(on_get_transaction_pool); + m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !m_restricted); res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res) + bool core_rpc_server::on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, bool request_has_rpc_origin) { - CHECK_CORE_BUSY(); - m_core.get_pool_transaction_hashes(res.tx_hashes); + PERF_TIMER(on_get_transaction_pool_hashes); + m_core.get_pool_transaction_hashes(res.tx_hashes, !request_has_rpc_origin || !m_restricted); res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res) + bool core_rpc_server::on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, bool request_has_rpc_origin) { - CHECK_CORE_BUSY(); - m_core.get_pool_transaction_stats(res.pool_stats); + PERF_TIMER(on_get_transaction_pool_stats); + m_core.get_pool_transaction_stats(res.pool_stats, !request_has_rpc_origin || !m_restricted); res.status = CORE_RPC_STATUS_OK; return true; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res) { + PERF_TIMER(on_stop_daemon); // FIXME: replace back to original m_p2p.send_stop_signal() after // investigating why that isn't working quite right. m_p2p.send_stop_signal(); @@ -878,7 +906,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res) { - CHECK_CORE_BUSY(); + PERF_TIMER(on_getblockcount); res.count = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; return true; @@ -886,12 +914,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy"; - return false; - } + PERF_TIMER(on_getblockhash); if(req.size() != 1) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; @@ -927,6 +950,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_getblocktemplate); if(!check_core_ready()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -968,7 +992,7 @@ namespace cryptonote } blobdata block_blob = t_serializable_object_to_blob(b); crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx); - if(tx_pub_key == null_pkey) + if(tx_pub_key == crypto::null_pkey) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = "Internal error: failed to create block template"; @@ -1001,6 +1025,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_submitblock); CHECK_CORE_READY(); if(req.size()!=1) { @@ -1057,6 +1082,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response) { + PERF_TIMER(fill_block_header_response); response.major_version = blk.major_version; response.minor_version = blk.minor_version; response.timestamp = blk.timestamp; @@ -1075,12 +1101,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_last_block_header); + CHECK_CORE_READY(); uint64_t last_block_height; crypto::hash last_block_hash; m_core.get_blockchain_top(last_block_height, last_block_hash); @@ -1104,12 +1126,7 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_hash(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HASH::response& res, epee::json_rpc::error& error_resp){ - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_block_header_by_hash); crypto::hash block_hash; bool hash_parsed = parse_hash256(req.hash, block_hash); if(!hash_parsed) @@ -1146,12 +1163,7 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_headers_range(const COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request& req, COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response& res, epee::json_rpc::error& error_resp){ - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_block_headers_range); const uint64_t bc_height = m_core.get_current_blockchain_height(); if (req.start_height >= bc_height || req.end_height >= bc_height || req.start_height > req.end_height) { @@ -1197,12 +1209,7 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block_header_by_height(const COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response& res, epee::json_rpc::error& error_resp){ - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_block_header_by_height); if(m_core.get_current_blockchain_height() <= req.height) { error_resp.code = CORE_RPC_ERROR_CODE_TOO_BIG_HEIGHT; @@ -1230,12 +1237,7 @@ namespace cryptonote } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_block(const COMMAND_RPC_GET_BLOCK::request& req, COMMAND_RPC_GET_BLOCK::response& res, epee::json_rpc::error& error_resp){ - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_block); crypto::hash block_hash; if (!req.hash.empty()) { @@ -1280,6 +1282,7 @@ namespace cryptonote error_resp.message = "Internal error: can't produce valid response."; return false; } + res.miner_tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(blk.miner_tx)); for (size_t n = 0; n < blk.tx_hashes.size(); ++n) { res.tx_hashes.push_back(epee::string_tools::pod_to_hex(blk.tx_hashes[n])); @@ -1292,12 +1295,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_connections(const COMMAND_RPC_GET_CONNECTIONS::request& req, COMMAND_RPC_GET_CONNECTIONS::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_connections); res.connections = m_p2p.get_payload_object().get_connections(); @@ -1308,12 +1306,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) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_info_json); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); @@ -1328,6 +1321,7 @@ namespace cryptonote uint64_t total_conn = m_p2p.get_connections_count(); res.outgoing_connections_count = m_p2p.get_outgoing_connections_count(); res.incoming_connections_count = total_conn - res.outgoing_connections_count; + res.rpc_connections_count = get_connections_count(); res.white_peerlist_size = m_p2p.get_peerlist_manager().get_white_peers_count(); res.grey_peerlist_size = m_p2p.get_peerlist_manager().get_gray_peers_count(); res.testnet = m_testnet; @@ -1335,17 +1329,13 @@ namespace cryptonote res.block_size_limit = m_core.get_blockchain_storage().get_current_cumulative_blocksize_limit(); res.status = CORE_RPC_STATUS_OK; res.start_time = (uint64_t)m_core.get_start_time(); + res.free_space = m_restricted ? std::numeric_limits<uint64_t>::max() : m_core.get_free_space(); return true; } //------------------------------------------------------------------------------------------------------------------------------ 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) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_hard_fork_info); const Blockchain &blockchain = m_core.get_blockchain_storage(); uint8_t version = req.version > 0 ? req.version : blockchain.get_next_hard_fork_version(); @@ -1358,12 +1348,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_bans(const COMMAND_RPC_GETBANS::request& req, COMMAND_RPC_GETBANS::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_bans); auto now = time(nullptr); std::map<std::string, time_t> blocked_hosts = m_p2p.get_blocked_hosts(); @@ -1387,12 +1372,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_bans(const COMMAND_RPC_SETBANS::request& req, COMMAND_RPC_SETBANS::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_set_bans); for (auto i = req.bans.begin(); i != req.bans.end(); ++i) { @@ -1422,12 +1402,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_flush_txpool(const COMMAND_RPC_FLUSH_TRANSACTION_POOL::request& req, COMMAND_RPC_FLUSH_TRANSACTION_POOL::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_flush_txpool); bool failed = false; std::list<crypto::hash> txids; @@ -1476,12 +1451,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_output_histogram(const COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request& req, COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_output_histogram); std::map<uint64_t, std::tuple<uint64_t, uint64_t, uint64_t>> histogram; try @@ -1508,6 +1478,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_version(const COMMAND_RPC_GET_VERSION::request& req, COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_get_version); res.version = CORE_RPC_VERSION; res.status = CORE_RPC_STATUS_OK; return true; @@ -1515,6 +1486,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_coinbase_tx_sum(const COMMAND_RPC_GET_COINBASE_TX_SUM::request& req, COMMAND_RPC_GET_COINBASE_TX_SUM::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_get_coinbase_tx_sum); std::pair<uint64_t, uint64_t> amounts = m_core.get_coinbase_tx_sum(req.height, req.count); res.emission_amount = amounts.first; res.fee_amount = amounts.second; @@ -1524,6 +1496,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_per_kb_fee_estimate(const COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request& req, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_get_per_kb_fee_estimate); res.fee = m_core.get_blockchain_storage().get_dynamic_per_kb_fee_estimate(req.grace_blocks); res.status = CORE_RPC_STATUS_OK; return true; @@ -1531,6 +1504,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_alternate_chains(const COMMAND_RPC_GET_ALTERNATE_CHAINS::request& req, COMMAND_RPC_GET_ALTERNATE_CHAINS::response& res, epee::json_rpc::error& error_resp) { + PERF_TIMER(on_get_alternate_chains); try { std::list<std::pair<Blockchain::block_extended_info, uint64_t>> chains = m_core.get_blockchain_storage().get_alternative_chains(); @@ -1549,6 +1523,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res) { + PERF_TIMER(on_get_limit); res.limit_down = epee::net_utils::connection_basic::get_rate_down_limit(); res.limit_up = epee::net_utils::connection_basic::get_rate_up_limit(); res.status = CORE_RPC_STATUS_OK; @@ -1557,6 +1532,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res) { + PERF_TIMER(on_set_limit); // -1 = reset to default // 0 = do not modify @@ -1596,6 +1572,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_out_peers(const COMMAND_RPC_OUT_PEERS::request& req, COMMAND_RPC_OUT_PEERS::response& res) { + PERF_TIMER(on_out_peers); size_t n_connections = m_p2p.get_outgoing_connections_count(); size_t n_delete = (n_connections > req.out_peers) ? n_connections - req.out_peers : 0; m_p2p.m_config.m_net_config.connections_count = req.out_peers; @@ -1607,6 +1584,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_start_save_graph(const COMMAND_RPC_START_SAVE_GRAPH::request& req, COMMAND_RPC_START_SAVE_GRAPH::response& res) { + PERF_TIMER(on_start_save_graph); m_p2p.set_save_graph(true); res.status = CORE_RPC_STATUS_OK; return true; @@ -1614,6 +1592,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_stop_save_graph(const COMMAND_RPC_STOP_SAVE_GRAPH::request& req, COMMAND_RPC_STOP_SAVE_GRAPH::response& res) { + PERF_TIMER(on_stop_save_graph); m_p2p.set_save_graph(false); res.status = CORE_RPC_STATUS_OK; return true; @@ -1621,6 +1600,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_update(const COMMAND_RPC_UPDATE::request& req, COMMAND_RPC_UPDATE::response& res) { + PERF_TIMER(on_update); static const char software[] = "monero"; #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); @@ -1715,12 +1695,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_relay_tx(const COMMAND_RPC_RELAY_TX::request& req, COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_relay_tx); bool failed = false; for (const auto &str: req.txids) @@ -1761,12 +1736,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_sync_info(const COMMAND_RPC_SYNC_INFO::request& req, COMMAND_RPC_SYNC_INFO::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_sync_info); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); @@ -1792,12 +1762,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_txpool_backlog(const COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response& res, epee::json_rpc::error& error_resp) { - if(!check_core_busy()) - { - error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; - error_resp.message = "Core is busy."; - return false; - } + PERF_TIMER(on_get_txpool_backlog); if (!m_core.get_txpool_backlog(res.backlog)) { @@ -1825,7 +1790,7 @@ namespace cryptonote const command_line::arg_descriptor<bool> core_rpc_server::arg_restricted_rpc = { "restricted-rpc" - , "Restrict RPC to view only commands" + , "Restrict RPC to view only commands and do not return privacy sensitive data in RPC calls" , false }; } // namespace cryptonote diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 73a308a72..7f252258c 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -122,7 +122,7 @@ namespace cryptonote MAP_JON_RPC_WE_IF("flush_txpool", on_flush_txpool, COMMAND_RPC_FLUSH_TRANSACTION_POOL, !m_restricted) MAP_JON_RPC_WE("get_output_histogram", on_get_output_histogram, COMMAND_RPC_GET_OUTPUT_HISTOGRAM) MAP_JON_RPC_WE("get_version", on_get_version, COMMAND_RPC_GET_VERSION) - MAP_JON_RPC_WE("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM) + MAP_JON_RPC_WE_IF("get_coinbase_tx_sum", on_get_coinbase_tx_sum, COMMAND_RPC_GET_COINBASE_TX_SUM, !m_restricted) MAP_JON_RPC_WE("get_fee_estimate", on_get_per_kb_fee_estimate, COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE) MAP_JON_RPC_WE_IF("get_alternate_chains",on_get_alternate_chains, COMMAND_RPC_GET_ALTERNATE_CHAINS, !m_restricted) MAP_JON_RPC_WE_IF("relay_tx", on_relay_tx, COMMAND_RPC_RELAY_TX, !m_restricted) @@ -137,7 +137,7 @@ namespace cryptonote bool on_get_blocks_by_height(const COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::request& req, COMMAND_RPC_GET_BLOCKS_BY_HEIGHT::response& res); bool on_get_hashes(const COMMAND_RPC_GET_HASHES_FAST::request& req, COMMAND_RPC_GET_HASHES_FAST::response& res); bool on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res); - bool on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res); + bool on_is_key_image_spent(const COMMAND_RPC_IS_KEY_IMAGE_SPENT::request& req, COMMAND_RPC_IS_KEY_IMAGE_SPENT::response& res, bool request_has_rpc_origin = true); bool on_get_indexes(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response& res); bool on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res); bool on_start_mining(const COMMAND_RPC_START_MINING::request& req, COMMAND_RPC_START_MINING::response& res); @@ -153,9 +153,9 @@ namespace cryptonote bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res); bool on_set_log_level(const COMMAND_RPC_SET_LOG_LEVEL::request& req, COMMAND_RPC_SET_LOG_LEVEL::response& res); bool on_set_log_categories(const COMMAND_RPC_SET_LOG_CATEGORIES::request& req, COMMAND_RPC_SET_LOG_CATEGORIES::response& res); - bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res); - bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res); - bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res); + bool on_get_transaction_pool(const COMMAND_RPC_GET_TRANSACTION_POOL::request& req, COMMAND_RPC_GET_TRANSACTION_POOL::response& res, bool request_has_rpc_origin = true); + bool on_get_transaction_pool_hashes(const COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_HASHES::response& res, bool request_has_rpc_origin = true); + bool on_get_transaction_pool_stats(const COMMAND_RPC_GET_TRANSACTION_POOL_STATS::request& req, COMMAND_RPC_GET_TRANSACTION_POOL_STATS::response& res, bool request_has_rpc_origin = true); bool on_stop_daemon(const COMMAND_RPC_STOP_DAEMON::request& req, COMMAND_RPC_STOP_DAEMON::response& res); bool on_get_limit(const COMMAND_RPC_GET_LIMIT::request& req, COMMAND_RPC_GET_LIMIT::response& res); bool on_set_limit(const COMMAND_RPC_SET_LIMIT::request& req, COMMAND_RPC_SET_LIMIT::response& res); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ee2a79eb4..d27d5611e 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 15 +#define CORE_RPC_VERSION_MINOR 16 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -566,6 +566,7 @@ namespace cryptonote std::string as_hex; std::string as_json; bool in_pool; + bool double_spend_seen; uint64_t block_height; uint64_t block_timestamp; std::vector<uint64_t> output_indices; @@ -575,6 +576,7 @@ namespace cryptonote KV_SERIALIZE(as_hex) KV_SERIALIZE(as_json) KV_SERIALIZE(in_pool) + KV_SERIALIZE(double_spend_seen) KV_SERIALIZE(block_height) KV_SERIALIZE(block_timestamp) KV_SERIALIZE(output_indices) @@ -917,6 +919,7 @@ namespace cryptonote uint64_t alt_blocks_count; uint64_t outgoing_connections_count; uint64_t incoming_connections_count; + uint64_t rpc_connections_count; uint64_t white_peerlist_size; uint64_t grey_peerlist_size; bool testnet; @@ -924,6 +927,7 @@ namespace cryptonote uint64_t cumulative_difficulty; uint64_t block_size_limit; uint64_t start_time; + uint64_t free_space; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) @@ -936,6 +940,7 @@ namespace cryptonote KV_SERIALIZE(alt_blocks_count) KV_SERIALIZE(outgoing_connections_count) KV_SERIALIZE(incoming_connections_count) + KV_SERIALIZE(rpc_connections_count) KV_SERIALIZE(white_peerlist_size) KV_SERIALIZE(grey_peerlist_size) KV_SERIALIZE(testnet) @@ -943,6 +948,7 @@ namespace cryptonote KV_SERIALIZE(cumulative_difficulty) KV_SERIALIZE(block_size_limit) KV_SERIALIZE(start_time) + KV_SERIALIZE(free_space) END_KV_SERIALIZE_MAP() }; }; @@ -1217,12 +1223,14 @@ namespace cryptonote { std::string status; block_header_response block_header; + std::string miner_tx_hash; std::vector<std::string> tx_hashes; std::string blob; std::string json; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(block_header) + KV_SERIALIZE(miner_tx_hash) KV_SERIALIZE(tx_hashes) KV_SERIALIZE(status) KV_SERIALIZE(blob) @@ -1357,6 +1365,8 @@ namespace cryptonote bool relayed; uint64_t last_relayed_time; bool do_not_relay; + bool double_spend_seen; + std::string tx_blob; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(id_hash) @@ -1372,6 +1382,8 @@ namespace cryptonote KV_SERIALIZE(relayed) KV_SERIALIZE(last_relayed_time) KV_SERIALIZE(do_not_relay) + KV_SERIALIZE(double_spend_seen) + KV_SERIALIZE(tx_blob) END_KV_SERIALIZE_MAP() }; @@ -1480,6 +1492,7 @@ namespace cryptonote uint32_t num_not_relayed; uint64_t histo_98pc; std::vector<txpool_histo> histo; + uint32_t num_double_spends; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(bytes_total) @@ -1494,6 +1507,7 @@ namespace cryptonote KV_SERIALIZE(num_not_relayed) KV_SERIALIZE(histo_98pc) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(histo) + KV_SERIALIZE(num_double_spends) END_KV_SERIALIZE_MAP() }; diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h index 00f1e0caa..581048eaf 100644 --- a/src/rpc/message_data_structs.h +++ b/src/rpc/message_data_structs.h @@ -95,6 +95,7 @@ namespace rpc uint64_t last_relayed_time; bool relayed; bool do_not_relay; + bool double_spend_seen; }; typedef std::unordered_map<crypto::key_image, std::vector<crypto::hash> > key_images_with_tx_hashes; diff --git a/src/rpc/rpc_args.cpp b/src/rpc/rpc_args.cpp index 93309bf3c..e03c5472d 100755..100644 --- a/src/rpc/rpc_args.cpp +++ b/src/rpc/rpc_args.cpp @@ -83,7 +83,9 @@ namespace cryptonote if (command_line::has_arg(vm, arg.rpc_login)) { - config.login = tools::login::parse(command_line::get_arg(vm, arg.rpc_login), true, "RPC server password"); + config.login = tools::login::parse(command_line::get_arg(vm, arg.rpc_login), true, [](bool verify) { + return tools::password_container::prompt(verify, "RPC server password"); + }); if (!config.login) return boost::none; diff --git a/src/rpc/rpc_args.h b/src/rpc/rpc_args.h index 72b5aa706..72b5aa706 100755..100644 --- a/src/rpc/rpc_args.h +++ b/src/rpc/rpc_args.h diff --git a/src/serialization/json_object.cpp b/src/serialization/json_object.cpp index a40821d19..6e6e51528 100644 --- a/src/serialization/json_object.cpp +++ b/src/serialization/json_object.cpp @@ -755,6 +755,7 @@ void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::tx_in_pool& tx INSERT_INTO_JSON_OBJECT(val, doc, last_relayed_time, tx.last_relayed_time); INSERT_INTO_JSON_OBJECT(val, doc, relayed, tx.relayed); INSERT_INTO_JSON_OBJECT(val, doc, do_not_relay, tx.do_not_relay); + INSERT_INTO_JSON_OBJECT(val, doc, double_spend_seen, tx.double_spend_seen); } @@ -777,6 +778,7 @@ void fromJsonValue(const rapidjson::Value& val, cryptonote::rpc::tx_in_pool& tx) GET_FROM_JSON_OBJECT(val, tx.last_relayed_time, last_relayed_time); GET_FROM_JSON_OBJECT(val, tx.relayed, relayed); GET_FROM_JSON_OBJECT(val, tx.do_not_relay, do_not_relay); + GET_FROM_JSON_OBJECT(val, tx.double_spend_seen, double_spend_seen); } void toJsonValue(rapidjson::Document& doc, const cryptonote::rpc::hard_fork_info& info, rapidjson::Value& val) diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index 869f5d10e..9e23f0791 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -43,6 +43,8 @@ #include <vector> #include <deque> #include <list> +#include <set> +#include <unordered_set> #include <string> #include <boost/type_traits/is_integral.hpp> #include <boost/type_traits/integral_constant.hpp> @@ -364,3 +366,4 @@ namespace serialization { #include "vector.h" #include "list.h" #include "pair.h" +#include "set.h" diff --git a/src/serialization/set.h b/src/serialization/set.h new file mode 100644 index 000000000..54b4eb3ab --- /dev/null +++ b/src/serialization/set.h @@ -0,0 +1,127 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + +#include "serialization.h" + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v); +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v); + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_set_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_set_element(Archive& ar, uint32_t& e) + { + ar.serialize_varint(e); + return true; + } + + template <typename Archive> + bool serialize_set_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class T> +bool do_serialize_set(Archive<false> &ar, T &v) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.stream().good()) + return false; + v.clear(); + + // very basic sanity check + if (ar.remaining_bytes() < cnt) { + ar.stream().setstate(std::ios::failbit); + return false; + } + + for (size_t i = 0; i < cnt; i++) { + if (i > 0) + ar.delimit_array(); + typename T::key_type k; + if (!::serialization::detail::serialize_set_element(ar, k)) + return false; + v.insert(std::move(k)); + if (!ar.stream().good()) + return false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize_set(Archive<true> &ar, T &v) +{ + size_t cnt = v.size(); + ar.begin_array(cnt); + bool first = true; + for (const typename T::key_type &k: v) { + if (!ar.stream().good()) + return false; + if (!first) + ar.delimit_array(); + if(!::serialization::detail::serialize_set_element(ar, const_cast<typename T::key_type&>(k))) + return false; + if (!ar.stream().good()) + return false; + first = false; + } + ar.end_array(); + return true; +} + +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<false> &ar, std::unordered_set<T> &v) { return do_serialize_set(ar, v); } +template <template <bool> class Archive, class T> +bool do_serialize(Archive<true> &ar, std::unordered_set<T> &v) { return do_serialize_set(ar, v); } diff --git a/src/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index b56085b8f..3ff4466fc 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -48,12 +48,14 @@ target_link_libraries(simplewallet cncrypto common mnemonics - p2p + epee + ${EPEE_READLINE} version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} + ${Readline_LIBRARY} ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) set_property(TARGET simplewallet diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index c936f481e..ff5255a80 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -49,7 +49,6 @@ #include "common/dns_utils.h" #include "common/base58.h" #include "common/scoped_message_writer.h" -#include "p2p/net_node.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "simplewallet.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -81,6 +80,8 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 +#define MIN_RING_SIZE 5 // Used to inform user about min ring size -- does not track actual protocol + #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" #define LOCK_IDLE_SCOPE() \ @@ -111,6 +112,7 @@ namespace const auto arg_wallet_file = wallet_args::arg_wallet_file(); const command_line::arg_descriptor<std::string> arg_generate_new_wallet = {"generate-new-wallet", sw::tr("Generate new wallet and save it to <arg>"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_view_key = {"generate-from-view-key", sw::tr("Generate incoming-only wallet from view key"), ""}; + const command_line::arg_descriptor<std::string> arg_generate_from_spend_key = {"generate-from-spend-key", sw::tr("Generate deterministic wallet from spend key"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_keys = {"generate-from-keys", sw::tr("Generate wallet from private keys"), ""}; const command_line::arg_descriptor<std::string> arg_generate_from_multisig_keys = {"generate-from-multisig-keys", sw::tr("Generate a master wallet from multisig wallet keys"), ""}; const auto arg_generate_from_json = wallet_args::arg_generate_from_json(); @@ -125,6 +127,37 @@ namespace const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; + std::string input_line(const std::string& prompt) + { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + std::cout << prompt; + + std::string buf; + std::getline(std::cin, buf); + + return epee::string_tools::trim(buf); + } + + boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify) + { +#ifdef HAVE_READLINE + rdln::suspend_readline pause_readline; +#endif + auto pwd_container = tools::password_container::prompt(verify, prompt); + if (!pwd_container) + { + tools::fail_msg_writer() << tr("failed to read wallet password"); + } + return pwd_container; + } + + boost::optional<tools::password_container> default_password_prompter(bool verify) + { + return password_prompter(verify ? tr("Enter new wallet password") : tr("Wallet password"), verify); + } + inline std::string interpret_rpc_response(bool ok, const std::string& status) { std::string err; @@ -161,20 +194,50 @@ namespace return tools::scoped_message_writer(console_color_red, true, sw::tr("Error: "), el::Level::Error); } - bool is_it_true(const std::string& s) + bool parse_bool(const std::string& s, bool& result) { if (s == "1" || command_line::is_yes(s)) + { + result = true; return true; + } + if (s == "0" || command_line::is_no(s)) + { + result = false; + return true; + } boost::algorithm::is_iequal ignore_case{}; - if (boost::algorithm::equals("true", s, ignore_case)) + if (boost::algorithm::equals("true", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case)) + { + result = true; return true; - if (boost::algorithm::equals(simple_wallet::tr("true"), s, ignore_case)) + } + if (boost::algorithm::equals("false", s, ignore_case) || boost::algorithm::equals(simple_wallet::tr("false"), s, ignore_case)) + { + result = false; return true; + } return false; } + template <typename F> + bool parse_bool_and_use(const std::string& s, F func) + { + bool r; + if (parse_bool(s, r)) + { + func(r); + return true; + } + else + { + fail_msg_writer() << tr("invalid argument: must be either 0/1, true/false, y/n, yes/no"); + return false; + } + } + const struct { const char *name; @@ -240,7 +303,7 @@ namespace << tr("Is this OK? (Y/n) ") ; // prompt the user for confirmation given the dns query and dnssec status - std::string confirm_dns_ok = command_line::input_line(prompt.str()); + std::string confirm_dns_ok = input_line(prompt.str()); if (std::cin.eof()) { return {}; @@ -418,7 +481,7 @@ bool simple_wallet::change_password(const std::vector<std::string> &args) } // prompts for a new password, pass true to verify the password - const auto pwd_container = tools::wallet2::password_prompt(true); + const auto pwd_container = default_password_prompter(true); try { @@ -507,8 +570,10 @@ bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string> const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->always_confirm_transfers(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->always_confirm_transfers(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -518,8 +583,10 @@ bool simple_wallet::set_print_ring_members(const std::vector<std::string> &args/ const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->print_ring_members(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->print_ring_members(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -535,8 +602,10 @@ bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = s const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->store_tx_info(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->store_tx_info(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -552,13 +621,13 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } uint32_t ring_size = boost::lexical_cast<uint32_t>(args[1]); - if (ring_size < 3 && ring_size != 0) + if (ring_size < MIN_RING_SIZE && ring_size != 0) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } @@ -572,7 +641,7 @@ bool simple_wallet::set_default_ring_size(const std::vector<std::string> &args/* } catch(const boost::bad_lexical_cast &) { - fail_msg_writer() << tr("ring size must be an integer >= 3"); + fail_msg_writer() << tr("ring size must be an integer >= ") << MIN_RING_SIZE; return true; } catch(...) @@ -631,14 +700,15 @@ bool simple_wallet::set_auto_refresh(const std::vector<std::string> &args/* = st const auto pwd_container = get_and_verify_password(); if (pwd_container) { - const bool auto_refresh = is_it_true(args[1]); - m_wallet->auto_refresh(auto_refresh); - m_idle_mutex.lock(); - m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed); - m_idle_cond.notify_one(); - m_idle_mutex.unlock(); + parse_bool_and_use(args[1], [&](bool auto_refresh) { + m_wallet->auto_refresh(auto_refresh); + m_idle_mutex.lock(); + m_auto_refresh_enabled.store(auto_refresh, std::memory_order_relaxed); + m_idle_cond.notify_one(); + m_idle_mutex.unlock(); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -665,8 +735,10 @@ bool simple_wallet::set_confirm_missing_payment_id(const std::vector<std::string const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->confirm_missing_payment_id(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->confirm_missing_payment_id(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -676,8 +748,10 @@ bool simple_wallet::set_ask_password(const std::vector<std::string> &args/* = st const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->ask_password(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->ask_password(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -753,8 +827,10 @@ bool simple_wallet::set_merge_destinations(const std::vector<std::string> &args/ const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->merge_destinations(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->merge_destinations(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -764,8 +840,10 @@ bool simple_wallet::set_confirm_backlog(const std::vector<std::string> &args/* = const auto pwd_container = get_and_verify_password(); if (pwd_container) { - m_wallet->confirm_backlog(is_it_true(args[1])); - m_wallet->rewrite(m_wallet_file, pwd_container->password()); + parse_bool_and_use(args[1], [&](bool r) { + m_wallet->confirm_backlog(r); + m_wallet->rewrite(m_wallet_file, pwd_container->password()); + }); } return true; } @@ -834,6 +912,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used.")); m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address")); + m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>] - Send a single output of the given key image to an address without change")); m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)")); m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file")); @@ -852,8 +931,10 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs")); m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>")); m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); - m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature to prove payment to <address> in <txid> using the transaction secret key (r) without revealing it")); + m_cmd_binder.set_handler("get_tx_proof", boost::bind(&simple_wallet::get_tx_proof, this, _1), tr("Generate a signature proving payment/receipt of money to/by <address> in <txid> using the transaction/view secret key")); m_cmd_binder.set_handler("check_tx_proof", boost::bind(&simple_wallet::check_tx_proof, this, _1), tr("Check tx proof for payment going to <address> in <txid>")); + m_cmd_binder.set_handler("get_spend_proof", boost::bind(&simple_wallet::get_spend_proof, this, _1), tr("Generate a signature proving that you generated <txid> using the spend secret key")); + m_cmd_binder.set_handler("check_spend_proof", boost::bind(&simple_wallet::check_spend_proof, this, _1), tr("Check a signature proving that the signer generated <txid>")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out|pending|failed|pool] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); m_cmd_binder.set_handler("unspent_outputs", boost::bind(&simple_wallet::unspent_outputs, this, _1), tr("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]] - Show unspent outputs of a specified address within an optional amount range")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); @@ -932,7 +1013,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("always-confirm-transfers", set_always_confirm_transfers, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("print-ring-members", set_print_ring_members, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("store-tx-info", set_store_tx_info, tr("0 or 1")); - CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= 3")); + CHECK_SIMPLE_VARIABLE("default-ring-size", set_default_ring_size, tr("integer >= ") << MIN_RING_SIZE); CHECK_SIMPLE_VARIABLE("auto-refresh", set_auto_refresh, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("refresh-type", set_refresh_type, tr("full (slowest, no assumptions); optimize-coinbase (fast, assumes the whole coinbase is paid to a single address); no-coinbase (fastest, assumes we receive no coinbase transaction), default (same as optimize-coinbase)")); CHECK_SIMPLE_VARIABLE("priority", set_default_priority, tr("0, 1, 2, 3, or 4")); @@ -975,7 +1056,7 @@ bool simple_wallet::ask_wallet_create_if_needed() do{ LOG_PRINT_L3("User asked to specify wallet file name."); - wallet_path = command_line::input_line( + wallet_path = input_line( tr(m_restoring ? "Specify a new wallet file name for your restored wallet (e.g., MyWallet).\n" "Wallet file name (or Ctrl-C to quit): " : "Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" @@ -1026,7 +1107,7 @@ bool simple_wallet::ask_wallet_create_if_needed() if (!m_restoring) { message_writer() << tr("No wallet found with that name. Confirm creation of new wallet named: ") << wallet_path; - confirm_creation = command_line::input_line(tr("(Y/Yes/N/No): ")); + confirm_creation = input_line(tr("(Y/Yes/N/No): ")); if(std::cin.eof()) { LOG_ERROR("Unexpected std::cin.eof() - Exited simple_wallet::ask_wallet_create_if_needed()"); @@ -1077,12 +1158,12 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (!handle_command_line(vm)) return false; - if((!m_generate_new.empty()) + (!m_wallet_file.empty()) + (!m_generate_from_view_key.empty()) + (!m_generate_from_keys.empty()) + (!m_generate_from_multisig_keys.empty()) + (!m_generate_from_json.empty()) > 1) + if((!m_generate_new.empty()) + (!m_wallet_file.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-keys=\"wallet_name\", --generate-from-multisig-keys=\"wallet_name\" and --generate-from-json=\"jsonfilename\""); + 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\" and --generate-from-json=\"jsonfilename\""); return false; } - else if (m_generate_new.empty() && m_wallet_file.empty() && m_generate_from_view_key.empty() && m_generate_from_keys.empty() && m_generate_from_multisig_keys.empty() && m_generate_from_json.empty()) + else if (m_generate_new.empty() && m_wallet_file.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()) { if(!ask_wallet_create_if_needed()) return false; } @@ -1110,7 +1191,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) do { const char *prompt = m_electrum_seed.empty() ? "Specify Electrum seed: " : "Electrum seed continued: "; - std::string electrum_seed = command_line::input_line(prompt); + std::string electrum_seed = input_line(prompt); if (std::cin.eof()) return false; if (electrum_seed.empty()) @@ -1139,7 +1220,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { m_wallet_file = m_generate_from_view_key; // parse address - std::string address_string = command_line::input_line("Standard address: "); + std::string address_string = input_line("Standard address: "); if (std::cin.eof()) return false; if (address_string.empty()) { @@ -1159,7 +1240,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } // parse view secret key - std::string viewkey_string = command_line::input_line("View key: "); + std::string viewkey_string = input_line("View key: "); if (std::cin.eof()) return false; if (viewkey_string.empty()) { @@ -1190,11 +1271,30 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) bool r = new_wallet(vm, info.address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); } + else if (!m_generate_from_spend_key.empty()) + { + m_wallet_file = m_generate_from_spend_key; + // parse spend secret key + std::string spendkey_string = input_line("Secret spend key: "); + if (std::cin.eof()) + return false; + if (spendkey_string.empty()) { + fail_msg_writer() << tr("No data supplied, cancelled"); + return false; + } + if (!epee::string_tools::hex_to_pod(spendkey_string, m_recovery_key)) + { + fail_msg_writer() << tr("failed to parse spend key secret key"); + return false; + } + bool r = new_wallet(vm, m_recovery_key, true, false, ""); + CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + } else if (!m_generate_from_keys.empty()) { m_wallet_file = m_generate_from_keys; // parse address - std::string address_string = command_line::input_line("Standard address: "); + std::string address_string = input_line("Standard address: "); if (std::cin.eof()) return false; if (address_string.empty()) { @@ -1214,7 +1314,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } // parse spend secret key - std::string spendkey_string = command_line::input_line("Secret spend key: "); + std::string spendkey_string = input_line("Secret spend key: "); if (std::cin.eof()) return false; if (spendkey_string.empty()) { @@ -1230,7 +1330,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) crypto::secret_key spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); // parse view secret key - std::string viewkey_string = command_line::input_line("Secret view key: "); + std::string viewkey_string = input_line("Secret view key: "); if (std::cin.eof()) return false; if (viewkey_string.empty()) { @@ -1277,7 +1377,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) unsigned int multisig_n; // parse multisig type - std::string multisig_type_string = command_line::input_line("Multisig type (input as M/N with M <= N and M > 1): "); + std::string multisig_type_string = input_line("Multisig type (input as M/N with M <= N and M > 1): "); if (std::cin.eof()) return false; if (multisig_type_string.empty()) @@ -1303,7 +1403,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) message_writer() << boost::format(tr("Generating master wallet from %u of %u multisig wallet keys")) % multisig_m % multisig_n; // parse multisig address - std::string address_string = command_line::input_line("Multisig wallet address: "); + std::string address_string = input_line("Multisig wallet address: "); if (std::cin.eof()) return false; if (address_string.empty()) { @@ -1318,7 +1418,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } // parse secret view key - std::string viewkey_string = command_line::input_line("Secret view key: "); + std::string viewkey_string = input_line("Secret view key: "); if (std::cin.eof()) return false; if (viewkey_string.empty()) @@ -1358,7 +1458,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) // get N secret spend keys from user for(unsigned int i=0; i<multisig_n; ++i) { - spendkey_string = command_line::input_line(tr((boost::format(tr("Secret spend key (%u of %u):")) % (i+i) % multisig_m).str().c_str())); + spendkey_string = input_line(tr((boost::format(tr("Secret spend key (%u of %u):")) % (i+i) % multisig_m).str().c_str())); if (std::cin.eof()) return false; if (spendkey_string.empty()) @@ -1406,7 +1506,15 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else if (!m_generate_from_json.empty()) { m_wallet_file = m_generate_from_json; - m_wallet = tools::wallet2::make_from_json(vm, m_wallet_file); + try + { + m_wallet = tools::wallet2::make_from_json(vm, m_wallet_file, password_prompter); + } + catch (const std::exception &e) + { + fail_msg_writer() << e.what(); + return false; + } if (!m_wallet) return false; } @@ -1428,9 +1536,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { std::string heightstr; if (!connected || version < MAKE_CORE_RPC_VERSION(1, 6)) - heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0): "); + heightstr = input_line("Restore from specific blockchain height (optional, default 0): "); else - heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0),\nor alternatively from specific date (YYYY-MM-DD): "); + heightstr = input_line("Restore from specific blockchain height (optional, default 0),\nor alternatively from specific date (YYYY-MM-DD): "); if (std::cin.eof()) return false; if (heightstr.empty()) @@ -1466,7 +1574,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) day = boost::lexical_cast<uint16_t>(heightstr.substr(8,2)); m_restore_height = m_wallet->get_blockchain_height_by_date(year, month, day); success_msg_writer() << tr("Restore height is: ") << m_restore_height; - std::string confirm = command_line::input_line(tr("Is this okay? (Y/Yes/N/No): ")); + std::string confirm = input_line(tr("Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) return false; if(command_line::is_yes(confirm)) @@ -1531,6 +1639,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_wallet_file = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_generate_from_view_key = command_line::get_arg(vm, arg_generate_from_view_key); + m_generate_from_spend_key = command_line::get_arg(vm, arg_generate_from_spend_key); m_generate_from_keys = command_line::get_arg(vm, arg_generate_from_keys); m_generate_from_multisig_keys = command_line::get_arg(vm, arg_generate_from_multisig_keys); m_generate_from_json = command_line::get_arg(vm, arg_generate_from_json); @@ -1543,6 +1652,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_height = command_line::get_arg(vm, arg_restore_height); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); m_restoring = !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() || @@ -1595,7 +1705,7 @@ std::string simple_wallet::get_mnemonic_language() } while (language_number < 0) { - language_choice = command_line::input_line(tr("Enter the number corresponding to the language of your choice: ")); + language_choice = input_line(tr("Enter the number corresponding to the language of your choice: ")); if (std::cin.eof()) return std::string(); try @@ -1617,7 +1727,7 @@ std::string simple_wallet::get_mnemonic_language() //---------------------------------------------------------------------------------------------------- boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const { - auto pwd_container = tools::wallet2::password_prompt(m_wallet_file.empty()); + auto pwd_container = default_password_prompter(m_wallet_file.empty()); if (!pwd_container) return boost::none; @@ -1632,7 +1742,7 @@ boost::optional<tools::password_container> simple_wallet::get_and_verify_passwor bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language) { - auto rc = tools::wallet2::make_new(vm); + auto rc = tools::wallet2::make_new(vm, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -1713,7 +1823,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey) { - auto rc = tools::wallet2::make_new(vm); + auto rc = tools::wallet2::make_new(vm, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { @@ -1755,7 +1865,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) std::string password; try { - auto rc = tools::wallet2::make_from_file(vm, m_wallet_file); + auto rc = tools::wallet2::make_from_file(vm, m_wallet_file, password_prompter); m_wallet = std::move(rc.first); password = std::move(rc.second).password(); if (!m_wallet) @@ -1904,8 +2014,16 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) bool ok = true; size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2)); size_t arg_size = args.size(); - if(arg_size >= 3) req.ignore_battery = is_it_true(args[2]); - if(arg_size >= 2) req.do_background_mining = is_it_true(args[1]); + if(arg_size >= 3) + { + if (!parse_bool_and_use(args[2], [&](bool r) { req.ignore_battery = r; })) + return true; + } + if(arg_size >= 2) + { + if (!parse_bool_and_use(args[1], [&](bool r) { req.do_background_mining = r; })) + return true; + } if(arg_size >= 1) { uint16_t num = 1; @@ -1988,7 +2106,7 @@ void simple_wallet::on_money_received(uint64_t height, const crypto::hash &txid, message_writer(console_color_green, false) << "\r" << tr("Height ") << height << ", " << tr("txid ") << txid << ", " << - print_money(amount) << tr(" XMR, ") << + print_money(amount) << tr("idx ") << subaddr_index; if (m_auto_refresh_refreshing) m_cmd_binder.print_prompt(); @@ -2478,6 +2596,101 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending return true; } //---------------------------------------------------------------------------------------------------- +static void handle_transfer_exception(const std::exception_ptr &e) +{ + try + { + std::rethrow_exception(e); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error &e) + { + fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); + } + catch (const tools::error::not_enough_unlocked_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::not_enough_money& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % + print_money(e.available()) % + print_money(e.tx_amount())); + fail_msg_writer() << tr("Not enough money in unlocked balance"); + } + catch (const tools::error::tx_not_possible& e) + { + LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee())); + fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" @@ -2510,12 +2723,23 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (fake_outs_count == 0) fake_outs_count = DEFAULT_MIX; } + else if (ring_size == 0) + { + fail_msg_writer() << tr("Ring size must not be 0"); + return true; + } else { fake_outs_count = ring_size - 1; local_args.erase(local_args.begin()); } } + uint64_t adjusted_fake_outs_count = m_wallet->adjust_mixin(fake_outs_count); + if (adjusted_fake_outs_count > fake_outs_count) + { + fail_msg_writer() << (boost::format(tr("ring size %u is too small, minimum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); + return true; + } const size_t min_args = (transfer_type == TransferLocked) ? 3 : 2; if(local_args.size() < min_args) @@ -2626,7 +2850,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri // prompt is there is no payment id and confirmation is required if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) { - std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -2710,7 +2934,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri std::string prompt_str = prompt.str(); if (!prompt_str.empty()) { - std::string accepted = command_line::input_line(prompt_str); + std::string accepted = input_line(prompt_str); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -2781,7 +3005,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } prompt << ENDL << tr("Is this okay? (Y/Yes/N/No): "); - std::string accepted = command_line::input_line(prompt.str()); + std::string accepted = input_line(prompt.str()); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -2810,92 +3034,9 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri commit_or_save(ptx_vector, m_do_not_relay); } } - catch (const tools::error::daemon_busy&) - { - fail_msg_writer() << tr("daemon is busy. Please try again later."); - } - catch (const tools::error::no_connection_to_daemon&) - { - fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); - } - catch (const tools::error::wallet_rpc_error& e) - { - LOG_ERROR("RPC error: " << e.to_string()); - fail_msg_writer() << tr("RPC error: ") << e.what(); - } - catch (const tools::error::get_random_outs_error &e) - { - fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); - } - catch (const tools::error::not_enough_unlocked_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, overall balance only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in overall balance"); - } - catch (const tools::error::tx_not_possible& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % - print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % - print_money(e.tx_amount()) % - print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); - } - catch (const tools::error::not_enough_outs_to_mix& e) - { - auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; - for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } - catch (const std::exception& e) + catch (const std::exception &e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); + handle_transfer_exception(std::current_exception()); } catch (...) { @@ -2961,7 +3102,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) print_money(total_unmixable) % print_money(total_fee)).str(); } - std::string accepted = command_line::input_line(prompt_str); + std::string accepted = input_line(prompt_str); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -2989,92 +3130,9 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) commit_or_save(ptx_vector, m_do_not_relay); } } - catch (const tools::error::daemon_busy&) - { - fail_msg_writer() << tr("daemon is busy. Please try again later."); - } - catch (const tools::error::no_connection_to_daemon&) - { - fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); - } - catch (const tools::error::wallet_rpc_error& e) - { - LOG_ERROR("RPC error: " << e.to_string()); - fail_msg_writer() << tr("RPC error: ") << e.what(); - } - catch (const tools::error::get_random_outs_error &e) - { - fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); - } - catch (const tools::error::not_enough_unlocked_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, overall balance only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in overall balance"); - } - catch (const tools::error::tx_not_possible& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % - print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % - print_money(e.tx_amount()) % - print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); - } - catch (const tools::error::not_enough_outs_to_mix& e) - { - auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; - for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } - catch (const std::exception& e) + catch (const std::exception &e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); + handle_transfer_exception(std::current_exception()); } catch (...) { @@ -3088,6 +3146,12 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &args_) { // sweep_all [index=<N1>[,<N2>,...]] [<ring_size>] <address> [<payment_id>] + if (args_.size() == 0) + { + fail_msg_writer() << tr("No address given"); + return true; + } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } if (!try_connect_to_daemon()) return true; @@ -3115,12 +3179,23 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a if (fake_outs_count == 0) fake_outs_count = DEFAULT_MIX; } + else if (ring_size == 0) + { + fail_msg_writer() << tr("Ring size must not be 0"); + return true; + } else { fake_outs_count = ring_size - 1; local_args.erase(local_args.begin()); } } + uint64_t adjusted_fake_outs_count = m_wallet->adjust_mixin(fake_outs_count); + if (adjusted_fake_outs_count > fake_outs_count) + { + fail_msg_writer() << (boost::format(tr("ring size %u is too small, minimum is %u")) % (fake_outs_count+1) % (adjusted_fake_outs_count+1)).str(); + return true; + } std::vector<uint8_t> extra; bool payment_id_seen = false; @@ -3159,12 +3234,6 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a local_args.pop_back(); } - if (local_args.size() == 0) - { - fail_msg_writer() << tr("No address given"); - return true; - } - cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[0], oa_prompter)) { @@ -3194,7 +3263,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a // prompt is there is no payment id and confirmation is required if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) { - std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -3252,7 +3321,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a print_money(total_sent) % print_money(total_fee); } - std::string accepted = command_line::input_line(prompt.str()); + std::string accepted = input_line(prompt.str()); if (std::cin.eof()) return true; if (!command_line::is_yes(accepted)) @@ -3280,6 +3349,199 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a commit_or_save(ptx_vector, m_do_not_relay); } } + catch (const std::exception& e) + { + handle_transfer_exception(std::current_exception()); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sweep_single(const std::vector<std::string> &args_) +{ + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + if (!try_connect_to_daemon()) + return true; + + std::vector<std::string> local_args = args_; + + int priority = 0; + if(local_args.size() > 0) { + auto priority_pos = std::find( + allowed_priority_strings.begin(), + allowed_priority_strings.end(), + local_args[0]); + if(priority_pos != allowed_priority_strings.end()) { + local_args.erase(local_args.begin()); + priority = std::distance(allowed_priority_strings.begin(), priority_pos); + } + } + + size_t fake_outs_count = 0; + if(local_args.size() > 0) { + size_t ring_size; + if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0])) + { + fake_outs_count = m_wallet->default_mixin(); + if (fake_outs_count == 0) + fake_outs_count = DEFAULT_MIX; + } + else + { + fake_outs_count = ring_size - 1; + local_args.erase(local_args.begin()); + } + } + + std::vector<uint8_t> extra; + bool payment_id_seen = false; + if (local_args.size() == 3) + { + crypto::hash payment_id; + crypto::hash8 payment_id8; + std::string extra_nonce; + if (tools::wallet2::parse_long_payment_id(local_args.back(), payment_id)) + { + set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + } + else if(tools::wallet2::parse_short_payment_id(local_args.back(), payment_id8)) + { + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); + } + else + { + fail_msg_writer() << tr("failed to parse Payment ID"); + return true; + } + + if (!add_extra_nonce_to_tx_extra(extra, extra_nonce)) + { + fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); + return true; + } + + local_args.pop_back(); + payment_id_seen = true; + } + + if (local_args.size() != 2) + { + fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]"); + return true; + } + + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(local_args[0], ki)) + { + fail_msg_writer() << tr("failed to parse key image"); + return true; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[1], oa_prompter)) + { + fail_msg_writer() << tr("failed to parse address"); + return true; + } + + if (info.has_payment_id) + { + if (payment_id_seen) + { + fail_msg_writer() << tr("a single transaction cannot use more than one payment id: ") << local_args[0]; + return true; + } + + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); + if (!add_extra_nonce_to_tx_extra(extra, extra_nonce)) + { + fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); + return true; + } + payment_id_seen = true; + } + + // prompt if there is no payment id and confirmation is required + if (!payment_id_seen && m_wallet->confirm_missing_payment_id()) + { + std::string accepted = input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): ")); + if (std::cin.eof()) + return true; + if (!command_line::is_yes(accepted)) + { + fail_msg_writer() << tr("transaction cancelled."); + + // would like to return false, because no tx made, but everything else returns true + // and I don't know what returning false might adversely affect. *sigh* + return true; + } + } + + try + { + // figure out what tx will be necessary + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); + + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No outputs found"); + return true; + } + if (ptx_vector.size() > 1) + { + fail_msg_writer() << tr("Multiple transactions are created, which is not supposed to happen"); + return true; + } + if (ptx_vector[0].selected_transfers.size() > 1) + { + fail_msg_writer() << tr("The transaction uses multiple inputs, which is not supposed to happen"); + return true; + } + + // give user total and fee, and prompt to confirm + uint64_t total_fee = ptx_vector[0].fee; + uint64_t total_sent = m_wallet->get_transfer_details(ptx_vector[0].selected_transfers.front()).amount(); + std::ostringstream prompt; + if (!print_ring_members(ptx_vector, prompt)) + return true; + prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + print_money(total_sent) % + print_money(total_fee); + std::string accepted = input_line(prompt.str()); + if (std::cin.eof()) + return true; + if (!command_line::is_yes(accepted)) + { + fail_msg_writer() << tr("transaction cancelled."); + return true; + } + + // actually commit the transactions + if (m_wallet->watch_only()) + { + bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx"); + if (!r) + { + fail_msg_writer() << tr("Failed to write transaction(s) to file"); + } + else + { + success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx"; + } + } + else + { + m_wallet->commit_tx(ptx_vector[0]); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx_vector[0].tx); + } + + } catch (const tools::error::daemon_busy&) { fail_msg_writer() << tr("daemon is busy. Please try again later."); @@ -3297,20 +3559,13 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a { fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } - catch (const tools::error::not_enough_unlocked_money& e) + catch (const tools::error::not_enough_money& e) { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % print_money(e.available()) % print_money(e.tx_amount())); fail_msg_writer() << tr("Not enough money in unlocked balance"); } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, overall balance only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in overall balance"); - } catch (const tools::error::tx_not_possible& e) { LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % @@ -3323,10 +3578,10 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a catch (const tools::error::not_enough_outs_to_mix& e) { auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second; } } catch (const tools::error::tx_not_constructed&) @@ -3426,7 +3681,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) local_args.push_back(amount_str); if (!payment_id_str.empty()) local_args.push_back(payment_id_str); - message_writer() << tr("Donating ") << amount_str << " XMR to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A)."; + message_writer() << tr("Donating ") << amount_str << " to The Monero Project (donate.getmonero.org/44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A)."; transfer_new(local_args); return true; } @@ -3548,7 +3803,7 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, uint64_t fee = amount - amount_to_dests; std::string prompt_str = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min ring size %lu, %s. %sIs this okay? (Y/Yes/N/No): ")) % (unsigned long)get_num_txes() % print_money(amount) % print_money(fee) % dest_string % change_string % (unsigned long)min_ring_size % payment_id_string % extra_message).str(); - return command_line::is_yes(command_line::input_line(prompt_str)); + return command_line::is_yes(input_line(prompt_str)); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) @@ -3637,92 +3892,9 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) commit_or_save(ptx_vector, false); } - catch (const tools::error::daemon_busy&) - { - fail_msg_writer() << tr("daemon is busy. Please try later"); - } - catch (const tools::error::no_connection_to_daemon&) - { - fail_msg_writer() << tr("no connection to daemon. Please, make sure daemon is running."); - } - catch (const tools::error::wallet_rpc_error& e) - { - LOG_ERROR("Unknown RPC error: " << e.to_string()); - fail_msg_writer() << tr("RPC error: ") << e.what(); - } - catch (const tools::error::get_random_outs_error &e) - { - fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); - } - catch (const tools::error::not_enough_unlocked_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in unlocked balance"); - } - catch (const tools::error::not_enough_money& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, overall balance only %s, sent amount %s") % - print_money(e.available()) % - print_money(e.tx_amount())); - fail_msg_writer() << tr("Not enough money in overall balance"); - } - catch (const tools::error::tx_not_possible& e) - { - LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") % - print_money(e.available()) % - print_money(e.tx_amount() + e.fee()) % - print_money(e.tx_amount()) % - print_money(e.fee())); - fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees"); - } - catch (const tools::error::not_enough_outs_to_mix& e) - { - auto writer = fail_msg_writer(); - writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; - for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) - { - writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; - } - } - catch (const tools::error::tx_not_constructed&) - { - fail_msg_writer() << tr("transaction was not constructed"); - } - catch (const tools::error::tx_rejected& e) - { - fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - std::string reason = e.reason(); - if (!reason.empty()) - fail_msg_writer() << tr("Reason: ") << reason; - } - catch (const tools::error::tx_sum_overflow& e) - { - fail_msg_writer() << e.what(); - } - catch (const tools::error::zero_destination&) - { - fail_msg_writer() << tr("one of destinations is zero"); - } - catch (const tools::error::tx_too_big& e) - { - fail_msg_writer() << tr("Failed to find a suitable way to split transactions"); - } - catch (const tools::error::transfer_error& e) - { - LOG_ERROR("unknown transfer error: " << e.to_string()); - fail_msg_writer() << tr("unknown transfer error: ") << e.what(); - } - catch (const tools::error::wallet_internal_error& e) - { - LOG_ERROR("internal error: " << e.to_string()); - fail_msg_writer() << tr("internal error: ") << e.what(); - } catch (const std::exception& e) { - LOG_ERROR("unexpected error: " << e.what()); - fail_msg_writer() << tr("unexpected error: ") << e.what(); + handle_transfer_exception(std::current_exception()); } catch (...) { @@ -3743,22 +3915,24 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) } if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); LOCK_IDLE_SCOPE(); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - std::vector<crypto::secret_key> amount_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - success_msg_writer() << tr("Tx key: ") << epee::string_tools::pod_to_hex(tx_key); + ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + success_msg_writer() << tr("Tx key: ") << oss.str(); return true; } else @@ -3770,19 +3944,18 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 2) { - fail_msg_writer() << tr("usage: get_tx_proof <txid> <dest_address>"); + if (args.size() != 2 && args.size() != 3) + { + fail_msg_writer() << tr("usage: get_tx_proof_out <txid> <address> [<message>]"); return true; } - if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), args[1], oa_prompter)) @@ -3791,140 +3964,21 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) return true; } - LOCK_IDLE_SCOPE(); - - crypto::secret_key tx_key; - std::vector<crypto::secret_key> additional_tx_keys; - if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) - { - fail_msg_writer() << tr("Tx secret key wasn't found in the wallet file."); - return true; - } - - // fetch tx prefix either from the daemon or from the wallet cache if it's still pending - cryptonote::transaction_prefix tx; - // first, look up the wallet cache - std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> unconfirmed_payments_out; - m_wallet->get_unconfirmed_payments_out(unconfirmed_payments_out, m_current_subaddress_account); - auto found = std::find_if(unconfirmed_payments_out.begin(), unconfirmed_payments_out.end(), [&](const std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>& p) - { - return p.first == txid; - }); - // if not found, query the daemon - if (found == unconfirmed_payments_out.end()) - { - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx_full; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx_full, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) - { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } - tx = tx_full; - } - else - { - tx = found->second.m_tx; - } - - // fetch tx pubkey - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == null_pkey) - { - fail_msg_writer() << tr("Tx pubkey was not found"); - return true; - } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - if (additional_tx_keys.size() != additional_tx_pub_keys.size()) - { - fail_msg_writer() << tr("The set of additional tx secret/public keys doesn't match"); - return true; - } - - // find the correct R: - // R = r*G for standard addresses - // R = r*B for subaddresses (where B is the recipient's spend pubkey) - crypto::public_key R; - crypto::secret_key r; - if (info.is_subaddress) - { - auto i = additional_tx_keys.begin(); - auto j = additional_tx_pub_keys.begin(); - for (; ; ++i, ++j) { - if (i == additional_tx_keys.end()) - { - r = tx_key; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == tx_pub_key) - break; - fail_msg_writer() << tr("The matching tx secret/pubkey pair wasn't found for this subaddress"); - return true; - } - else - { - r = *i; - R = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_spend_public_key), rct::sk2rct(r))); - if (R == *j) - break; - } - } - } - else - { - r = tx_key; - crypto::secret_key_to_public_key(r, R); - if (R != tx_pub_key) - { - fail_msg_writer() << tr("The destinations of this tx don't include a standard address"); - return true; - } - } + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } - crypto::public_key rA = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(info.address.m_view_public_key), rct::sk2rct(r))); - crypto::signature sig; try { - if (info.is_subaddress) - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, info.address.m_spend_public_key, rA, r, sig); + std::string sig_str = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, args.size() == 3 ? args[2] : ""); + const std::string filename = "monero_tx_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; else - crypto::generate_tx_proof(txid, R, info.address.m_view_public_key, boost::none, rA, r, sig); + fail_msg_writer() << tr("failed to save signature file"); } - catch (const std::runtime_error &e) + catch (const std::exception &e) { - fail_msg_writer() << e.what(); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - - std::string sig_str = std::string("ProofV1") + - tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))) + - tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); - - success_msg_writer() << tr("Signature: ") << sig_str; return true; } //---------------------------------------------------------------------------------------------------- @@ -3945,27 +3999,31 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) fail_msg_writer() << tr("wallet is null"); return true; } - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(local_args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - if (local_args[1].size() < 64 || local_args[1].size() % 64) + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), tx_key)) { fail_msg_writer() << tr("failed to parse tx key"); return true; } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data) || tx_key_data.size() != sizeof(crypto::secret_key)) + local_args[1] = local_args[1].substr(64); + while (!local_args[1].empty()) { - fail_msg_writer() << tr("failed to parse tx key"); - return true; + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if(!epee::string_tools::hex_to_pod(local_args[1].substr(0, 64), additional_tx_keys.back())) + { + fail_msg_writer() << tr("failed to parse tx key"); + return true; + } + local_args[1] = local_args[1].substr(64); } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); cryptonote::address_parse_info info; if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[2], oa_prompter)) @@ -3974,134 +4032,48 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - fail_msg_writer() << tr("failed to generate key derivation from supplied parameters"); - return true; - } - - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation) -{ - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - fail_msg_writer() << tr("failed to get transaction from daemon"); - return true; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - fail_msg_writer() << tr("failed to parse transaction from daemon"); - return true; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - fail_msg_writer() << tr("failed to validate transaction from daemon"); - return true; - } - if (tx_hash != txid) + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; - } + uint64_t received; + bool in_pool; + uint64_t confirmations; + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - uint64_t received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) + if (received > 0) { - if (typeid(txout_to_key) != tx.vout[n].target.type()) - continue; - const txout_to_key tx_out_to_key = boost::get<txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else { - uint64_t amount; - if (tx.version == 1) + if (confirmations != (uint64_t)-1) { - amount = tx.vout[n].amount; + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; } else { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - { - crypto::secret_key scalar1; - crypto::derivation_to_scalar(derivation, n, scalar1); - rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); - rct::key C = tx.rct_signatures.outPk[n].mask; - rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); } - received += amount; } } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - fail_msg_writer() << tr("error: ") << e.what(); - return true; - } - - std::string address_str = get_account_address_as_str(m_wallet->testnet(), is_subaddress, address); - if (received > 0) - { - success_msg_writer() << address_str << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; - } - else - { - fail_msg_writer() << address_str << " " << tr("received nothing in txid") << " " << txid; - } - if (res.txs.front().in_pool) - { - success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); - } - else - { - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (err.empty()) - { - uint64_t confirmations = bc_height - (res.txs.front().block_height + 1); - success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; - } else { - success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; } } - + catch (const std::exception &e) + { + fail_msg_writer() << tr("error: ") << e.what(); + } return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) { - if(args.size() != 3) { - fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature>"); + if(args.size() != 3 && args.size() != 4) { + fail_msg_writer() << tr("usage: check_tx_proof <txid> <address> <signature_file> [<message>]"); return true; } @@ -4109,13 +4081,12 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; // parse txid - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(args[0], txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(args[0], txid)) { fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); // parse address cryptonote::address_parse_info info; @@ -4125,117 +4096,141 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) return true; } - // parse pubkey r*A & signature - std::string sig_str = args[2]; - const size_t header_len = strlen("ProofV1"); - if (sig_str.size() < header_len || sig_str.substr(0, header_len) != "ProofV1") + // read signature file + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[2], sig_str)) { - fail_msg_writer() << tr("Signature header check error"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - crypto::public_key rA; - crypto::signature sig; - const size_t rA_len = tools::base58::encode(std::string((const char *)&rA, sizeof(crypto::public_key))).size(); - const size_t sig_len = tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))).size(); - std::string rA_decoded; - std::string sig_decoded; - if (!tools::base58::decode(sig_str.substr(header_len, rA_len), rA_decoded)) + + try { - fail_msg_writer() << tr("Signature decoding error"); - return true; + uint64_t received; + bool in_pool; + uint64_t confirmations; + if (m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, args.size() == 4 ? args[3] : "", sig_str, received, in_pool, confirmations)) + { + success_msg_writer() << tr("Good signature"); + if (received > 0) + { + success_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received") << " " << print_money(received) << " " << tr("in txid") << " " << txid; + if (in_pool) + { + success_msg_writer() << tr("WARNING: this transaction is not yet included in the blockchain!"); + } + else + { + if (confirmations != (uint64_t)-1) + { + success_msg_writer() << boost::format(tr("This transaction has %u confirmations")) % confirmations; + } + else + { + success_msg_writer() << tr("WARNING: failed to determine number of confirmations!"); + } + } + } + else + { + fail_msg_writer() << get_account_address_as_str(m_wallet->testnet(), info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid; + } + } + else + { + fail_msg_writer() << tr("Bad signature"); + } } - if (!tools::base58::decode(sig_str.substr(header_len + rA_len, sig_len), sig_decoded)) + catch (const std::exception &e) { - fail_msg_writer() << tr("Signature decoding error"); - return true; + fail_msg_writer() << tr("error: ") << e.what(); } - if (sizeof(crypto::public_key) != rA_decoded.size() || sizeof(crypto::signature) != sig_decoded.size()) - { - fail_msg_writer() << tr("Signature decoding error"); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 1 && args.size() != 2) { + fail_msg_writer() << tr("usage: get_spend_proof <txid> [<message>]"); return true; } - memcpy(&rA, rA_decoded.data(), sizeof(crypto::public_key)); - memcpy(&sig, sig_decoded.data(), sizeof(crypto::signature)); - // fetch tx pubkey from the daemon - COMMAND_RPC_GET_TRANSACTIONS::request req; - COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!net_utils::invoke_http_json("/gettransactions", req, res, m_http_client) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) + if (m_wallet->watch_only()) { - fail_msg_writer() << tr("failed to get transaction from daemon"); + fail_msg_writer() << tr("wallet is watch-only and cannot generate the proof"); return true; } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - fail_msg_writer() << tr("failed to parse transaction from daemon"); + fail_msg_writer() << tr("failed to parse txid"); return true; } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("failed to validate transaction from daemon"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - if (tx_hash != txid) + + if (m_wallet->ask_password() && !get_and_verify_password()) { return true; } + + try { - fail_msg_writer() << tr("failed to get the right transaction from daemon"); - return true; + const std::string sig_str = m_wallet->get_spend_proof(txid, args.size() == 2 ? args[1] : ""); + const std::string filename = "monero_spend_proof"; + if (epee::file_io_utils::save_string_to_file(filename, sig_str)) + success_msg_writer() << tr("signature file saved to: ") << filename; + else + fail_msg_writer() << tr("failed to save signature file"); } - crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); - if (tx_pub_key == null_pkey) + catch (const std::exception &e) { - fail_msg_writer() << tr("Tx pubkey was not found"); + fail_msg_writer() << e.what(); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) +{ + if(args.size() != 2 && args.size() != 3) { + fail_msg_writer() << tr("usage: check_spend_proof <txid> <signature_file> [<message>]"); return true; } - const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); - // check signature - bool good_signature = false; - if (info.is_subaddress) + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(args[0], txid)) { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (!good_signature) - { - for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) - { - good_signature = crypto::check_tx_proof(txid, additional_tx_pub_keys[i], info.address.m_view_public_key, info.address.m_spend_public_key, rA, sig); - if (good_signature) - break; - } - } - } - else - { - good_signature = crypto::check_tx_proof(txid, tx_pub_key, info.address.m_view_public_key, boost::none, rA, sig); - } - if (good_signature) - { - success_msg_writer() << tr("Good signature"); + fail_msg_writer() << tr("failed to parse txid"); + return true; } - else + + if (!try_connect_to_daemon()) { - fail_msg_writer() << tr("Bad signature"); + fail_msg_writer() << tr("failed to connect to the daemon"); return true; } - // obtain key derivation by multiplying scalar 1 to the pubkey r*A included in the signature - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(rA, rct::rct2sk(rct::I), derivation)) + std::string sig_str; + if (!epee::file_io_utils::load_file_to_string(args[1], sig_str)) { - fail_msg_writer() << tr("failed to generate key derivation"); + fail_msg_writer() << tr("failed to load signature file"); return true; } - return check_tx_key_helper(txid, info.address, info.is_subaddress, derivation); + try + { + if (m_wallet->check_spend_proof(txid, args.size() == 3 ? args[2] : "", sig_str)) + success_msg_writer() << tr("Good signature"); + else + fail_msg_writer() << tr("Bad signature"); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; } //---------------------------------------------------------------------------------------------------- static std::string get_human_readable_timestamp(uint64_t ts) @@ -4415,15 +4410,18 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) try { m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments; m_wallet->get_unconfirmed_payments(payments, m_current_subaddress_account, subaddr_indices); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { - const tools::wallet2::payment_details &pd = i->second; + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second.m_pd; std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); std::string note = m_wallet->get_tx_note(pd.m_tx_hash); - message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note).str(); + std::string double_spend_note; + if (i->second.m_double_spend_seen) + double_spend_note = tr("[Double spend seen on the network: this transaction may or may not end up being mined] "); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %d %s %s%s") % "pool" % "in" % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % pd.m_subaddr_index.minor % "-" % note % double_spend_note).str(); } } catch (const std::exception& e) @@ -4743,10 +4741,11 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector //---------------------------------------------------------------------------------------------------- void simple_wallet::print_accounts() { - success_msg_writer() << boost::format("%15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label"); + success_msg_writer() << boost::format(" %15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label"); for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) { - success_msg_writer() << boost::format(tr("%8u %6s %21s %21s %21s")) + success_msg_writer() << boost::format(tr(" %c%8u %6s %21s %21s %21s")) + % (m_current_subaddress_account == account_index ? '*' : ' ') % account_index % m_wallet->get_subaddress_as_str({account_index, 0}).substr(0, 6) % print_money(m_wallet->balance(account_index)) @@ -4915,7 +4914,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v fail_msg_writer() << tr("failed to parse address"); return true; } - crypto::hash payment_id = null_hash; + crypto::hash payment_id = crypto::null_hash; size_t description_start = 2; if (info.has_payment_id) { @@ -5369,7 +5368,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) const uint64_t last_block_height = m_wallet->get_blockchain_current_height(); std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; - m_wallet->get_payments(payments, 0, m_current_subaddress_account); + m_wallet->get_payments(payments, 0, (uint64_t)-1, m_current_subaddress_account); for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { const tools::wallet2::payment_details &pd = i->second; if (pd.m_tx_hash == txid) { @@ -5439,10 +5438,10 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) try { m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments; - m_wallet->get_unconfirmed_payments(pool_payments); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { - const tools::wallet2::payment_details &pd = i->second; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments; + m_wallet->get_unconfirmed_payments(pool_payments, m_current_subaddress_account); + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second.m_pd; if (pd.m_tx_hash == txid) { std::string payment_id = string_tools::pod_to_hex(i->first); @@ -5455,6 +5454,8 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) success_msg_writer() << "Payment ID: " << payment_id; success_msg_writer() << "Address index: " << pd.m_subaddr_index.minor; success_msg_writer() << "Note: " << m_wallet->get_tx_note(txid); + if (i->second.m_double_spend_seen) + success_msg_writer() << tr("Double spend seen on the network: this transaction may or may not end up being mined"); return true; } } @@ -5465,7 +5466,7 @@ bool simple_wallet::show_transfer(const std::vector<std::string> &args) } std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments; - m_wallet->get_unconfirmed_payments_out(upayments); + m_wallet->get_unconfirmed_payments_out(upayments, m_current_subaddress_account); for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { if (i->first == txid) { @@ -5546,6 +5547,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_generate_new_wallet); command_line::add_arg(desc_params, arg_generate_from_view_key); + command_line::add_arg(desc_params, arg_generate_from_spend_key); command_line::add_arg(desc_params, arg_generate_from_keys); command_line::add_arg(desc_params, arg_generate_from_multisig_keys); command_line::add_arg(desc_params, arg_generate_from_json); @@ -5568,6 +5570,7 @@ int main(int argc, char* argv[]) "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", desc_params, positional_options, + [](const std::string &s, bool emphasis){ tools::scoped_message_writer(emphasis ? epee::console_color_white : epee::console_color_default, true) << s; }, "monero-wallet-cli.log" ); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 639cee642..d6dde3ea7 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -141,6 +141,7 @@ namespace cryptonote bool sweep_main(uint64_t below, const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); bool sweep_below(const std::vector<std::string> &args); + bool sweep_single(const std::vector<std::string> &args); bool sweep_unmixable(const std::vector<std::string> &args); bool donate(const std::vector<std::string> &args); bool sign_transfer(const std::vector<std::string> &args); @@ -160,9 +161,10 @@ namespace cryptonote bool set_log(const std::vector<std::string> &args); bool get_tx_key(const std::vector<std::string> &args); bool check_tx_key(const std::vector<std::string> &args); - bool check_tx_key_helper(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const crypto::key_derivation &derivation); bool get_tx_proof(const std::vector<std::string> &args); bool check_tx_proof(const std::vector<std::string> &args); + bool get_spend_proof(const std::vector<std::string> &args); + bool check_spend_proof(const std::vector<std::string> &args); bool show_transfers(const std::vector<std::string> &args); bool unspent_outputs(const std::vector<std::string> &args); bool rescan_blockchain(const std::vector<std::string> &args); @@ -281,6 +283,7 @@ namespace cryptonote std::string m_wallet_file; std::string m_generate_new; std::string m_generate_from_view_key; + std::string m_generate_from_spend_key; std::string m_generate_from_keys; std::string m_generate_from_multisig_keys; std::string m_generate_from_json; diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 24399790c..e5c79a447 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -79,7 +79,6 @@ target_link_libraries(wallet common cryptonote_core mnemonics - p2p ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} @@ -89,42 +88,40 @@ target_link_libraries(wallet PRIVATE ${EXTRA_LIBRARIES}) -if (NOT BUILD_GUI_DEPS) - set(wallet_rpc_sources - wallet_rpc_server.cpp) +set(wallet_rpc_sources + wallet_rpc_server.cpp) - set(wallet_rpc_headers) +set(wallet_rpc_headers) - set(wallet_rpc_private_headers - wallet_rpc_server.h) +set(wallet_rpc_private_headers + wallet_rpc_server.h) - monero_private_headers(wallet_rpc_server - ${wallet_rpc_private_headers}) - monero_add_executable(wallet_rpc_server - ${wallet_rpc_sources} - ${wallet_rpc_headers} - ${wallet_rpc_private_headers}) +monero_private_headers(wallet_rpc_server + ${wallet_rpc_private_headers}) +monero_add_executable(wallet_rpc_server + ${wallet_rpc_sources} + ${wallet_rpc_headers} + ${wallet_rpc_private_headers}) - target_link_libraries(wallet_rpc_server - PRIVATE - wallet - epee - rpc - cryptonote_core - cncrypto - common - version - ${Boost_CHRONO_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${CMAKE_THREAD_LIBS_INIT} - ${EXTRA_LIBRARIES}) - set_property(TARGET wallet_rpc_server - PROPERTY - OUTPUT_NAME "monero-wallet-rpc") - install(TARGETS wallet_rpc_server DESTINATION bin) -endif() +target_link_libraries(wallet_rpc_server + PRIVATE + wallet + epee + rpc + cryptonote_core + cncrypto + common + version + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) +set_property(TARGET wallet_rpc_server + PROPERTY + OUTPUT_NAME "monero-wallet-rpc") +install(TARGETS wallet_rpc_server DESTINATION bin) # build and install libwallet_merged only if we building for GUI diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 59eca3dd7..8a8243047 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -217,10 +217,10 @@ void TransactionHistoryImpl::refresh() // unconfirmed payments (tx pool) - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> upayments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> upayments; m_wallet->m_wallet->get_unconfirmed_payments(upayments); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { - const tools::wallet2::payment_details &pd = i->second; + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { + const tools::wallet2::payment_details &pd = i->second.m_pd; std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 8e747d16b..fd0b65866 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1385,24 +1385,194 @@ std::string WalletImpl::getUserNote(const std::string &txid) const return m_wallet->get_tx_note(htxid); } -std::string WalletImpl::getTxKey(const std::string &txid) const +std::string WalletImpl::getTxKey(const std::string &txid_str) const { - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - return ""; + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; } - const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - if (m_wallet->get_tx_key(htxid, tx_key, additional_tx_keys)) + if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - return epee::string_tools::pod_to_hex(tx_key); + m_status = Status_Ok; + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + return oss.str(); } else { - return ""; + m_status = Status_Error; + m_errorString = tr("no tx keys found for this txid"); + return ""; + } +} + +bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, const std::string &address_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse tx key"); + return false; + } + tx_key_str = tx_key_str.substr(64); + while (!tx_key_str.empty()) + { + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse tx key"); + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + + try + { + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); + m_status = Status_Ok; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + cryptonote::address_parse_info info; + if (!cryptonote::get_account_address_from_str(info, m_wallet->testnet(), address_str)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse address"); + return false; + } + + try + { + good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); + m_status = Status_Ok; + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; + } +} + +std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return ""; + } + + try + { + m_status = Status_Ok; + return m_wallet->get_spend_proof(txid, message); + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return ""; + } +} + +bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string &message, const std::string &signature, bool &good) const { + good = false; + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + + try + { + m_status = Status_Ok; + good = m_wallet->check_spend_proof(txid, message, signature); + return true; + } + catch (const std::exception &e) + { + m_status = Status_Error; + m_errorString = e.what(); + return false; } } diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 051eda3ba..8ceabc843 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -137,6 +137,11 @@ public: virtual bool setUserNote(const std::string &txid, const std::string ¬e); virtual std::string getUserNote(const std::string &txid) const; virtual std::string getTxKey(const std::string &txid) const; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations); + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const; virtual std::string signMessage(const std::string &message); virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const; virtual void startRefresh(); diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a64766c84..ee69ec028 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -189,155 +189,6 @@ bool WalletManagerImpl::connected(uint32_t *version) const return true; } -bool WalletManagerImpl::checkPayment(const std::string &address_text, const std::string &txid_text, const std::string &txkey_text, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const -{ - error = ""; - cryptonote::blobdata txid_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txid_text, txid_data) || txid_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse txid"); - return false; - } - crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); - - if (txkey_text.size() < 64 || txkey_text.size() % 64) - { - error = tr("failed to parse tx key"); - return false; - } - crypto::secret_key tx_key; - cryptonote::blobdata tx_key_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(txkey_text, tx_key_data) || tx_key_data.size() != sizeof(crypto::hash)) - { - error = tr("failed to parse tx key"); - return false; - } - tx_key = *reinterpret_cast<const crypto::secret_key*>(tx_key_data.data()); - - bool testnet = address_text[0] != '4'; - cryptonote::address_parse_info info; - if(!cryptonote::get_account_address_from_str(info, testnet, address_text)) - { - error = tr("failed to parse address"); - return false; - } - - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; - cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); - if (!connect_and_invoke(m_daemonAddress, "/gettransactions", req, res) || - (res.txs.size() != 1 && res.txs_as_hex.size() != 1)) - { - error = tr("failed to get transaction from daemon"); - return false; - } - cryptonote::blobdata tx_data; - bool ok; - if (res.txs.size() == 1) - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); - else - ok = epee::string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); - if (!ok) - { - error = tr("failed to parse transaction from daemon"); - return false; - } - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - if (!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash)) - { - error = tr("failed to validate transaction from daemon"); - return false; - } - if (tx_hash != txid) - { - error = tr("failed to get the right transaction from daemon"); - return false; - } - - crypto::key_derivation derivation; - if (!crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation)) - { - error = tr("failed to generate key derivation from supplied parameters"); - return false; - } - - received = 0; - try { - for (size_t n = 0; n < tx.vout.size(); ++n) - { - if (typeid(cryptonote::txout_to_key) != tx.vout[n].target.type()) - continue; - const cryptonote::txout_to_key tx_out_to_key = boost::get<cryptonote::txout_to_key>(tx.vout[n].target); - crypto::public_key pubkey; - derive_public_key(derivation, n, info.address.m_spend_public_key, pubkey); - if (pubkey == tx_out_to_key.key) - { - uint64_t amount; - if (tx.version == 1) - { - amount = tx.vout[n].amount; - } - else - { - try - { - rct::key Ctmp; - //rct::key amount_key = rct::hash_to_scalar(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); - crypto::key_derivation derivation; - bool r = crypto::generate_key_derivation(info.address.m_view_public_key, tx_key, derivation); - if (!r) - { - LOG_ERROR("Failed to generate key derivation to decode rct output " << n); - amount = 0; - } - else - { - crypto::secret_key scalar1; - crypto::derivation_to_scalar(derivation, n, scalar1); - rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; - rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); - rct::key C = tx.rct_signatures.outPk[n].mask; - rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); - if (rct::equalKeys(C, Ctmp)) - amount = rct::h2d(ecdh_info.amount); - else - amount = 0; - } - } - catch (...) { amount = 0; } - } - received += amount; - } - } - } - catch(const std::exception &e) - { - LOG_ERROR("error: " << e.what()); - error = std::string(tr("error: ")) + e.what(); - return false; - } - - if (received > 0) - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received") << " " << cryptonote::print_money(received) << " " << tr("in txid") << " " << txid); - } - else - { - LOG_PRINT_L1(get_account_address_as_str(testnet, info.is_subaddress, info.address) << " " << tr("received nothing in txid") << " " << txid); - } - if (res.txs.front().in_pool) - { - height = 0; - } - else - { - height = res.txs.front().block_height; - } - - return true; -} - uint64_t WalletManagerImpl::blockchainHeight() const { cryptonote::COMMAND_RPC_GET_INFO::request ireq; @@ -434,12 +285,14 @@ std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool return addresses.front(); } -std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, const std::string &subdir) +std::tuple<bool, std::string, std::string, std::string, std::string> WalletManager::checkUpdates(const std::string &software, std::string subdir) { #ifdef BUILD_TAG static const char buildtag[] = BOOST_PP_STRINGIZE(BUILD_TAG); #else static const char buildtag[] = "source"; + // Override the subdir string when built from source + subdir = "source"; #endif std::string version, hash; diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 8455f0f16..5772eda05 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -55,7 +55,6 @@ public: std::string errorString() const; void setDaemonAddress(const std::string &address); bool connected(uint32_t *version = NULL) const; - bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const; uint64_t blockchainHeight() const; uint64_t blockchainTargetHeight() const; uint64_t networkDifficulty() const; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e9af93c05..c995c4333 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -59,7 +59,6 @@ using namespace epee; #include "rapidjson/stringbuffer.h" #include "common/json_util.h" #include "common/base58.h" -#include "common/scoped_message_writer.h" #include "ringct/rctSigs.h" extern "C" @@ -67,6 +66,8 @@ extern "C" #include "crypto/keccak.h" #include "crypto/crypto-ops.h" } +using namespace std; +using namespace crypto; using namespace cryptonote; #undef MONERO_DEFAULT_LOG_CATEGORY @@ -135,7 +136,7 @@ uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, ui return calculate_fee(fee_per_kb, blob.size(), fee_multiplier); } -std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts) +std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool restricted = command_line::get_arg(vm, opts.restricted); @@ -144,17 +145,16 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl auto daemon_host = command_line::get_arg(vm, opts.daemon_host); auto daemon_port = command_line::get_arg(vm, opts.daemon_port); - if (!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port) - { - tools::fail_msg_writer() << tools::wallet2::tr("can't specify daemon host or port more than once"); - return nullptr; - } + 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")); boost::optional<epee::net_utils::http::login> login{}; if (command_line::has_arg(vm, opts.daemon_login)) { auto parsed = tools::login::parse( - command_line::get_arg(vm, opts.daemon_login), false, "Daemon client password" + command_line::get_arg(vm, opts.daemon_login), false, [password_prompter](bool verify) { + return password_prompter("Daemon client password", verify); + } ); if (!parsed) return nullptr; @@ -178,12 +178,11 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl return wallet; } -boost::optional<tools::password_container> get_password(const boost::program_options::variables_map& vm, const options& opts, const bool verify) +boost::optional<tools::password_container> get_password(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char*, bool)> &password_prompter, const bool verify) { if (command_line::has_arg(vm, opts.password) && command_line::has_arg(vm, opts.password_file)) { - tools::fail_msg_writer() << tools::wallet2::tr("can't specify more than one of --password and --password-file"); - return boost::none; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("can't specify more than one of --password and --password-file")); } if (command_line::has_arg(vm, opts.password)) @@ -196,21 +195,19 @@ boost::optional<tools::password_container> get_password(const boost::program_opt std::string password; bool r = epee::file_io_utils::load_file_to_string(command_line::get_arg(vm, opts.password_file), password); - if (!r) - { - tools::fail_msg_writer() << tools::wallet2::tr("the password file specified could not be read"); - return boost::none; - } + THROW_WALLET_EXCEPTION_IF(!r, tools::error::wallet_internal_error, tools::wallet2::tr("the password file specified could not be read")); // Remove line breaks the user might have inserted boost::trim_right_if(password, boost::is_any_of("\r\n")); return {tools::password_container{std::move(password)}}; } - return tools::wallet2::password_prompt(verify); + THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password")); + + return password_prompter(verify ? tr("Enter new wallet password") : tr("Wallet password"), verify); } -std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts) +std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); @@ -221,22 +218,20 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const auto do_generate = [&]() -> bool { std::string buf; if (!epee::file_io_utils::load_file_to_string(json_file, buf)) { - tools::fail_msg_writer() << tools::wallet2::tr("Failed to load file ") << json_file; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("Failed to load file ")) + json_file); return false; } rapidjson::Document json; if (json.Parse(buf.c_str()).HasParseError()) { - tools::fail_msg_writer() << tools::wallet2::tr("Failed to parse JSON"); + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Failed to parse JSON")); return false; } GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true, 0); const int current_version = 1; - if (field_version > current_version) { - tools::fail_msg_writer() << boost::format(tools::wallet2::tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; - return false; - } + THROW_WALLET_EXCEPTION_IF(field_version > current_version, tools::error::wallet_internal_error, + ((boost::format(tools::wallet2::tr("Version %u too new, we can only grok up to %u")) % field_version % current_version)).str()); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string()); @@ -252,14 +247,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::blobdata viewkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to parse view key secret key")); } viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); crypto::public_key pkey; if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } } @@ -270,14 +263,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to parse spend key secret key")); } spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); crypto::public_key pkey; if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } } @@ -289,8 +280,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, { if (!crypto::ElectrumWords::words_to_bytes(field_seed, recovery_key, old_language)) { - tools::fail_msg_writer() << tools::wallet2::tr("Electrum-style word list failed verification"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Electrum-style word list failed verification")); } restore_deterministic_wallet = true; @@ -305,15 +295,13 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false, std::string()); // compatibility checks - if (!field_seed_found && !field_viewkey_found) + if (!field_seed_found && !field_viewkey_found && !field_spendkey_found) { - tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key must be specified"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified")); } if (field_seed_found && (field_viewkey_found || field_spendkey_found)) { - tools::fail_msg_writer() << tools::wallet2::tr("Both Electrum-style word list and private key(s) specified"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Both Electrum-style word list and private key(s) specified")); } // if an address was given, we check keys against it, and deduce the spend @@ -323,43 +311,36 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, testnet, field_address)) { - tools::fail_msg_writer() << tools::wallet2::tr("invalid address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("invalid address")); } if (field_viewkey_found) { crypto::public_key pkey; if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } if (info.address.m_view_public_key != pkey) { - tools::fail_msg_writer() << tools::wallet2::tr("view key does not match standard address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("view key does not match standard address")); } } if (field_spendkey_found) { crypto::public_key pkey; if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } if (info.address.m_spend_public_key != pkey) { - tools::fail_msg_writer() << tools::wallet2::tr("spend key does not match standard address"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("spend key does not match standard address")); } } } const bool deprecated_wallet = restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(field_seed)); - if (deprecated_wallet) { - tools::fail_msg_writer() << tools::wallet2::tr("Cannot create deprecated wallets from JSON"); - return false; - } + THROW_WALLET_EXCEPTION_IF(deprecated_wallet, tools::error::wallet_internal_error, + tools::wallet2::tr("Cannot create deprecated wallets from JSON")); - wallet.reset(make_basic(vm, opts).release()); + wallet.reset(make_basic(vm, opts, password_prompter).release()); wallet->set_refresh_from_block_height(field_scan_from_height); try @@ -368,12 +349,15 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, { wallet->generate(field_filename, field_password, recovery_key, recover, false); } + else if (field_viewkey.empty() && !field_spendkey.empty()) + { + wallet->generate(field_filename, field_password, spendkey, recover, false); + } else { cryptonote::account_public_address address; if (!crypto::secret_key_to_public_key(viewkey, address.m_view_public_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify view key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify view key secret key")); } if (field_spendkey.empty()) @@ -385,18 +369,20 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, testnet, field_address)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to parse address: ") << field_address; - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("failed to parse address: ")) + field_address); } address.m_spend_public_key = info.address.m_spend_public_key; } + else + { + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Address must be specified in order to create watch-only wallet")); + } wallet->generate(field_filename, field_password, address, viewkey); } else { if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to verify spend key secret key"); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } wallet->generate(field_filename, field_password, address, spendkey, viewkey); } @@ -404,8 +390,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, } catch (const std::exception& e) { - tools::fail_msg_writer() << tools::wallet2::tr("failed to generate new wallet: ") << e.what(); - return false; + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, std::string(tools::wallet2::tr("failed to generate new wallet: ")) + e.what()); } return true; }; @@ -444,6 +429,34 @@ std::string strjoin(const std::vector<size_t> &V, const char *sep) return ss.str(); } +static void emplace_or_replace(std::unordered_multimap<crypto::hash, tools::wallet2::pool_payment_details> &container, + const crypto::hash &key, const tools::wallet2::pool_payment_details &pd) +{ + auto range = container.equal_range(key); + for (auto i = range.first; i != range.second; ++i) + { + if (i->second.m_pd.m_tx_hash == pd.m_pd.m_tx_hash) + { + i->second = pd; + return; + } + } + container.emplace(key, pd); +} + +void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_t N) +{ + std::list<crypto::hash>::iterator right; + // drop early N off, skipping the genesis block + if (short_chain_history.size() > N) { + right = short_chain_history.end(); + std::advance(right,-1); + std::list<crypto::hash>::iterator left = right; + std::advance(left, -N); + short_chain_history.erase(left, right); + } +} + } //namespace namespace tools @@ -472,34 +485,22 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.restricted); } -boost::optional<password_container> wallet2::password_prompt(const bool new_password) -{ - auto pwd_container = tools::password_container::prompt( - new_password, (new_password ? tr("Enter new wallet password") : tr("Wallet password")) - ); - if (!pwd_container) - { - tools::fail_msg_writer() << tr("failed to read wallet password"); - } - return pwd_container; -} - -std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file) +std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return generate_from_json(json_file, vm, opts); + return generate_from_json(json_file, vm, opts, password_prompter); } std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( - const boost::program_options::variables_map& vm, const std::string& wallet_file) + const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - auto pwd = get_password(vm, opts, false); + auto pwd = get_password(vm, opts, password_prompter, false); if (!pwd) { return {nullptr, password_container{}}; } - auto wallet = make_basic(vm, opts); + auto wallet = make_basic(vm, opts, password_prompter); if (wallet) { wallet->load(wallet_file, pwd->password()); @@ -507,21 +508,21 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file( return {std::move(wallet), std::move(*pwd)}; } -std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm) +std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter) { const options opts{}; - auto pwd = get_password(vm, opts, true); + auto pwd = get_password(vm, opts, password_prompter, true); if (!pwd) { return {nullptr, password_container{}}; } - return {make_basic(vm, opts), std::move(*pwd)}; + return {make_basic(vm, opts, password_prompter), std::move(*pwd)}; } -std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm) +std::unique_ptr<wallet2> wallet2::make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; - return make_basic(vm, opts); + return make_basic(vm, opts, password_prompter); } //---------------------------------------------------------------------------------------------------- @@ -793,7 +794,7 @@ void wallet2::scan_output(const cryptonote::account_keys &keys, const cryptonote ++num_vouts_received; } //---------------------------------------------------------------------------------------------------- -void wallet2::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) +void wallet2::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) { // In this function, tx (probably) only contains the base information // (that is, the prunable stuff may or may not be included) @@ -831,7 +832,6 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote int num_vouts_received = 0; tx_pub_key = pub_key_field.pub_key; - bool r = true; tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); @@ -854,46 +854,33 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote else if (miner_tx && m_refresh_type == RefreshOptimizeCoinbase) { check_acc_out_precomp(tx.vout[0], derivation, additional_derivations, 0, tx_scan_info[0]); - if (tx_scan_info[0].error) - { - r = false; - } - else + THROW_WALLET_EXCEPTION_IF(tx_scan_info[0].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + + // this assumes that the miner tx pays a single address + if (tx_scan_info[0].received) { - // this assumes that the miner tx pays a single address - if (tx_scan_info[0].received) + // process the other outs from that tx + // the first one was already checked + for (size_t i = 1; i < tx.vout.size(); ++i) { - scan_output(keys, tx, tx_pub_key, 0, tx_scan_info[0], num_vouts_received, tx_money_got_in_outs, outs); - - // process the other outs from that tx - // the first one was already checked - for (size_t i = 1; i < tx.vout.size(); ++i) - { - tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, - std::ref(tx_scan_info[i]))); - } - waiter.wait(); + tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, + std::ref(tx_scan_info[i]))); + } + waiter.wait(); - for (size_t i = 1; i < tx.vout.size(); ++i) + // then scan all outputs from 0 + for (size_t i = 0; i < tx.vout.size(); ++i) + { + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + if (tx_scan_info[i].received) { - if (tx_scan_info[i].error) - { - r = false; - break; - } - if (tx_scan_info[i].received) - { - scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); - } + scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } } else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1) { - tools::threadpool& tpool = tools::threadpool::getInstance(); - tools::threadpool::waiter waiter; - for (size_t i = 0; i < tx.vout.size(); ++i) { tpool.submit(&waiter, boost::bind(&wallet2::check_acc_out_precomp, this, std::cref(tx.vout[i]), std::cref(derivation), std::cref(additional_derivations), i, @@ -902,11 +889,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote waiter.wait(); for (size_t i = 0; i < tx.vout.size(); ++i) { - if (tx_scan_info[i].error) - { - r = false; - break; - } + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); @@ -918,18 +901,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote for (size_t i = 0; i < tx.vout.size(); ++i) { check_acc_out_precomp(tx.vout[i], derivation, additional_derivations, i, tx_scan_info[i]); - if (tx_scan_info[i].error) - { - r = false; - break; - } + THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { scan_output(keys, tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } } - THROW_WALLET_EXCEPTION_IF(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if(!outs.empty() && num_vouts_received > 0) { @@ -1163,7 +1141,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote payment.m_timestamp = ts; payment.m_subaddr_index = i.first; if (pool) { - m_unconfirmed_payments.emplace(payment_id, payment); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, double_spend_seen}); if (0 != m_callback) m_callback->on_unconfirmed_money_received(height, txid, tx, payment.m_amount, payment.m_subaddr_index); } @@ -1241,7 +1219,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false); + process_new_transaction(get_transaction_hash(b.miner_tx), b.miner_tx, o_indices.indices[txidx++].indices, height, b.timestamp, true, false, false); TIME_MEASURE_FINISH(miner_tx_handle_time); TIME_MEASURE_START(txs_handle_time); @@ -1252,7 +1230,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry cryptonote::transaction tx; bool r = parse_and_validate_tx_base_from_blob(txblob, tx); THROW_WALLET_EXCEPTION_IF(!r, error::tx_parse_error, txblob); - process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false); + process_new_transaction(b.tx_hashes[idx], tx, o_indices.indices[txidx++].indices, height, b.timestamp, false, false, false); ++idx; } TIME_MEASURE_FINISH(txs_handle_time); @@ -1497,6 +1475,8 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei try { + drop_from_short_history(short_chain_history, 3); + // prepend the last 3 blocks, should be enough to guard against a block or two's reorg cryptonote::block bl; std::list<cryptonote::block_complete_entry>::const_reverse_iterator i = prev_blocks.rbegin(); @@ -1520,10 +1500,10 @@ void wallet2::pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_hei void wallet2::remove_obsolete_pool_txs(const std::vector<crypto::hash> &tx_hashes) { // remove pool txes to us that aren't in the pool anymore - std::unordered_multimap<crypto::hash, wallet2::payment_details>::iterator uit = m_unconfirmed_payments.begin(); + std::unordered_multimap<crypto::hash, wallet2::pool_payment_details>::iterator uit = m_unconfirmed_payments.begin(); while (uit != m_unconfirmed_payments.end()) { - const crypto::hash &txid = uit->second.m_tx_hash; + const crypto::hash &txid = uit->second.m_pd.m_tx_hash; bool found = false; for (const auto &it2: tx_hashes) { @@ -1626,23 +1606,27 @@ void wallet2::update_pool_state(bool refreshed) MDEBUG("update_pool_state done second loop"); // gather txids of new pool txes to us - std::vector<crypto::hash> txids; + std::vector<std::pair<crypto::hash, bool>> txids; for (const auto &txid: res.tx_hashes) { - if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) - { - LOG_PRINT_L2("Already seen " << txid << ", skipped"); - continue; - } bool txid_found_in_up = false; for (const auto &up: m_unconfirmed_payments) { - if (up.second.m_tx_hash == txid) + if (up.second.m_pd.m_tx_hash == txid) { txid_found_in_up = true; break; } } + if (m_scanned_pool_txs[0].find(txid) != m_scanned_pool_txs[0].end() || m_scanned_pool_txs[1].find(txid) != m_scanned_pool_txs[1].end()) + { + // if it's for us, we want to keep track of whether we saw a double spend, so don't bail out + if (!txid_found_in_up) + { + LOG_PRINT_L2("Already seen " << txid << ", and not for us, skipped"); + continue; + } + } if (!txid_found_in_up) { LOG_PRINT_L1("Found new pool tx: " << txid); @@ -1670,7 +1654,7 @@ void wallet2::update_pool_state(bool refreshed) if (!found) { // not one of those we sent ourselves - txids.push_back(txid); + txids.push_back({txid, false}); } else { @@ -1680,6 +1664,7 @@ void wallet2::update_pool_state(bool refreshed) else { LOG_PRINT_L1("Already saw that one, it's for us"); + txids.push_back({txid, true}); } } @@ -1688,8 +1673,8 @@ void wallet2::update_pool_state(bool refreshed) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; - for (const auto &txid: txids) - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + for (const auto &p: txids) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); MDEBUG("asking for " << txids.size() << " transactions"); req.decode_as_json = false; m_daemon_rpc_mutex.lock(); @@ -1711,10 +1696,11 @@ void wallet2::update_pool_state(bool refreshed) { if (cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash)) { - const std::vector<crypto::hash>::const_iterator i = std::find(txids.begin(), txids.end(), tx_hash); + const std::vector<std::pair<crypto::hash, bool>>::const_iterator i = std::find_if(txids.begin(), txids.end(), + [tx_hash](const std::pair<crypto::hash, bool> &e) { return e.first == tx_hash; }); if (i != txids.end()) { - process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true); + process_new_transaction(tx_hash, tx, std::vector<uint64_t>(), 0, time(NULL), false, true, tx_entry.double_spend_seen); m_scanned_pool_txs[0].insert(tx_hash); if (m_scanned_pool_txs[0].size() > 5000) { @@ -1759,24 +1745,29 @@ void wallet2::update_pool_state(bool refreshed) void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history) { std::list<crypto::hash> hashes; - size_t current_index = m_blockchain.size(); + const uint64_t checkpoint_height = m_checkpoints.get_max_height(); + if (stop_height > checkpoint_height && m_blockchain.size()-1 < checkpoint_height) + { + // we will drop all these, so don't bother getting them + uint64_t missing_blocks = m_checkpoints.get_max_height() - m_blockchain.size(); + while (missing_blocks-- > 0) + m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector + m_blockchain.push_back(m_checkpoints.get_points().at(checkpoint_height)); + m_local_bc_height = m_blockchain.size(); + short_chain_history.clear(); + get_short_chain_history(short_chain_history); + } + + size_t current_index = m_blockchain.size(); while(m_run.load(std::memory_order_relaxed) && current_index < stop_height) { pull_hashes(0, blocks_start_height, short_chain_history, hashes); if (hashes.size() <= 3) return; if (hashes.size() + current_index < stop_height) { - std::list<crypto::hash>::iterator right; - // drop early 3 off, skipping the genesis block - if (short_chain_history.size() > 3) { - right = short_chain_history.end(); - std::advance(right,-1); - std::list<crypto::hash>::iterator left = right; - std::advance(left, -3); - short_chain_history.erase(left, right); - } - right = hashes.end(); + drop_from_short_history(short_chain_history, 3); + std::list<crypto::hash>::iterator right = hashes.end(); // prepend 3 more for (int i = 0; i<3; i++) { right--; @@ -3073,11 +3064,11 @@ void wallet2::get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wall } } //---------------------------------------------------------------------------------------------------- -void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const +void wallet2::get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account, const std::set<uint32_t>& subaddr_indices) const { for (auto i = m_unconfirmed_payments.begin(); i != m_unconfirmed_payments.end(); ++i) { - if ((!subaddr_account || *subaddr_account == i->second.m_subaddr_index.major) && - (subaddr_indices.empty() || subaddr_indices.count(i->second.m_subaddr_index.minor) == 1)) + if ((!subaddr_account || *subaddr_account == i->second.m_pd.m_subaddr_index.major) && + (subaddr_indices.empty() || subaddr_indices.count(i->second.m_pd.m_subaddr_index.minor) == 1)) unconfirmed_payments.push_back(*i); } } @@ -3272,7 +3263,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -3280,7 +3271,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve { const transfer_details &candidate = transfers[unused_indices[n]]; float relatedness = 0.0f; - for (std::list<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) + for (std::vector<size_t>::const_iterator i = selected_transfers.begin(); i != selected_transfers.end(); ++i) { float r = get_output_relatedness(candidate, transfers[*i]); if (r > relatedness) @@ -3321,7 +3312,7 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::vector<size_t>& selected_transfers, bool smallest) const { return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest); } @@ -3330,9 +3321,10 @@ size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::l // returns: // direct return: amount of money found // modified reference: selected_transfers, a list of iterators/indices of input sources -uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon) +uint64_t wallet2::select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon) { uint64_t found_money = 0; + selected_transfers.reserve(unused_transfers_indices.size()); while (found_money < needed_money && !unused_transfers_indices.empty()) { size_t idx = pop_best_value(unused_transfers_indices, selected_transfers); @@ -3950,6 +3942,19 @@ int wallet2::get_fee_algorithm() return 1; return 0; } +//------------------------------------------------------------------------------------------------------------------------------ +uint64_t wallet2::adjust_mixin(uint64_t mixin) +{ + if (mixin < 4 && use_fork_rules(6, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); + mixin = 4; + } + else if (mixin < 2 && use_fork_rules(2, 10)) { + MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 2, using 3"); + mixin = 2; + } + return mixin; +} //---------------------------------------------------------------------------------------------------- // separated the call(s) to wallet2::transfer into their own function // @@ -4073,7 +4078,7 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out return true; } -void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) { +void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { MDEBUG("LIGHTWALLET - Getting random outs"); @@ -4177,7 +4182,7 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ } } -void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count) +void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count) { LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count); outs.clear(); @@ -4412,7 +4417,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } template<typename T> -void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::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, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) { @@ -4566,7 +4571,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent LOG_PRINT_L2("transfer_selected done"); } -void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, +void wallet2::transfer_selected_rct(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, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx) { @@ -5133,7 +5138,7 @@ void wallet2::light_wallet_get_address_txs() payments_txs.push_back(p.second.m_tx_hash); std::vector<crypto::hash> unconfirmed_payments_txs; for(const auto &up: m_unconfirmed_payments) - unconfirmed_payments_txs.push_back(up.second.m_tx_hash); + unconfirmed_payments_txs.push_back(up.second.m_pd.m_tx_hash); // for balance calculation uint64_t wallet_total_sent = 0; @@ -5199,7 +5204,11 @@ void wallet2::light_wallet_get_address_txs() if (t.mempool) { if (std::find(unconfirmed_payments_txs.begin(), unconfirmed_payments_txs.end(), tx_hash) == unconfirmed_payments_txs.end()) { pool_txs.push_back(tx_hash); - m_unconfirmed_payments.emplace(tx_hash, payment); + // assume false as we don't get that info from the light wallet server + crypto::hash payment_id; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::hex_to_pod(t.payment_id, payment_id), + error::wallet_internal_error, "Failed to parse payment id"); + emplace_or_replace(m_unconfirmed_payments, payment_id, pool_payment_details{payment, false}); if (0 != m_callback) { m_callback->on_lw_unconfirmed_money_received(t.height, payment.m_tx_hash, payment.m_amount); } @@ -5329,11 +5338,27 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, crypto::key_image calculated_key_image; cryptonote::keypair in_ephemeral; - // Subaddresses aren't supported in mymonero/openmonero yet. Using empty values. - const std::vector<crypto::public_key> additional_tx_pub_keys; - const crypto::public_key pkey = crypto::null_pkey; - - cryptonote::generate_key_image_helper(get_account().get_keys(), m_subaddresses, pkey, tx_public_key, additional_tx_pub_keys, out_index, in_ephemeral, calculated_key_image); + // Subaddresses aren't supported in mymonero/openmonero yet. Roll out the original scheme: + // compute D = a*R + // compute P = Hs(D || i)*G + B + // compute x = Hs(D || i) + b (and check if P==x*G) + // compute I = x*Hp(P) + const account_keys& ack = get_account().get_keys(); + crypto::key_derivation derivation; + bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, derivation); + CHECK_AND_ASSERT_MES(r, false, "failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + + r = crypto::derive_public_key(derivation, out_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub); + CHECK_AND_ASSERT_MES(r, false, "failed to derive_public_key (" << derivation << ", " << out_index << ", " << ack.m_account_address.m_spend_public_key << ")"); + + crypto::derive_secret_key(derivation, out_index, ack.m_spend_secret_key, in_ephemeral.sec); + crypto::public_key out_pkey_test; + r = crypto::secret_key_to_public_key(in_ephemeral.sec, out_pkey_test); + CHECK_AND_ASSERT_MES(r, false, "failed to secret_key_to_public_key(" << in_ephemeral.sec << ")"); + CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_pkey_test, false, "derived secret key doesn't match derived public key"); + + crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, calculated_key_image); + index_keyimage_map.emplace(out_index, calculated_key_image); m_key_image_cache.emplace(tx_public_key, index_keyimage_map); return key_image == calculated_key_image; @@ -5365,7 +5390,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp uint64_t needed_money; uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -5420,9 +5445,10 @@ 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> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); + std::map<uint32_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 bakance + if (subaddr_indices.empty()) // "index=<N1>[,<N2>,...]" wasn't specified -> use all the indices with non-zero unlocked balance { for (const auto& i : balance_per_subaddr) subaddr_indices.insert(i.first); @@ -5432,10 +5458,17 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we could also check for being within FEE_PER_KB, but if the fee calculation // ever changes, this might be missed, so let this go through uint64_t balance_subtotal = 0; + uint64_t unlocked_balance_subtotal = 0; for (uint32_t index_minor : subaddr_indices) + { balance_subtotal += balance_per_subaddr[index_minor]; + unlocked_balance_subtotal += unlocked_balance_per_subaddr[index_minor]; + } THROW_WALLET_EXCEPTION_IF(needed_money > balance_subtotal, error::not_enough_money, balance_subtotal, needed_money, 0); + // first check overall balance is enough, then unlocked one, so we throw distinct exceptions + THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance_subtotal, error::not_enough_unlocked_money, + unlocked_balance_subtotal, needed_money, 0); for (uint32_t i : subaddr_indices) LOG_PRINT_L2("Candidate subaddress index for spending: " << i); @@ -5479,24 +5512,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } - // early out if we know we can't make it anyway - // we could also check for being within FEE_PER_KB, but if the fee calculation - // ever changes, this might be missed, so let this go through - // first check overall balance is enough, then unlocked one, so we throw distinct exceptions - THROW_WALLET_EXCEPTION_IF(needed_money > balance(subaddr_account), error::not_enough_money, - unlocked_balance(subaddr_account), needed_money, 0); - THROW_WALLET_EXCEPTION_IF(needed_money > unlocked_balance(subaddr_account), error::not_enough_unlocked_money, - unlocked_balance(subaddr_account), needed_money, 0); - // shuffle & sort output indices { std::random_device rd; std::mt19937 g(rd()); std::shuffle(unused_transfers_indices_per_subaddr.begin(), unused_transfers_indices_per_subaddr.end(), g); std::shuffle(unused_dust_indices_per_subaddr.begin(), unused_dust_indices_per_subaddr.end(), g); - auto sort_predicate = [&balance_per_subaddr] (const std::pair<uint32_t, std::vector<size_t>>& x, const std::pair<uint32_t, std::vector<size_t>>& y) + 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 balance_per_subaddr[x.first] > balance_per_subaddr[y.first]; + return unlocked_balance_per_subaddr[x.first] > unlocked_balance_per_subaddr[y.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); @@ -5807,7 +5831,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below std::vector<size_t> unused_dust_indices; const bool use_rct = use_fork_rules(4, 0); - THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unclocked balance in the entire wallet"); + THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unlocked balance in the entire wallet"); std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); @@ -5844,11 +5868,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); } +std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) +{ + std::vector<size_t> unused_transfers_indices; + std::vector<size_t> unused_dust_indices; + const bool use_rct = use_fork_rules(4, 0); + // find output with the given key image + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) + { + if (td.is_rct() || is_valid_decomposed_amount(td.amount())) + unused_transfers_indices.push_back(i); + else + unused_dust_indices.push_back(i); + break; + } + } + return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); +} + std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) { uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<cryptonote::tx_destination_entry> dsts; cryptonote::transaction tx; pending_tx ptx; @@ -6168,6 +6213,576 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, s additional_tx_keys = j->second; return true; } +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string &message) +{ + THROW_WALLET_EXCEPTION_IF(m_watch_only, error::wallet_internal_error, + "get_spend_proof requires spend secret key and is not available for a watch-only wallet"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + std::vector<std::vector<crypto::signature>> signatures; + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // check if the key image belongs to us + const auto found = m_key_images.find(in_key->k_image); + if(found == m_key_images.end()) + { + THROW_WALLET_EXCEPTION_IF(i > 0, error::wallet_internal_error, "subset of key images belong to us, very weird!"); + THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "This tx wasn't generated by this wallet!"); + } + + // derive the real output keypair + const transfer_details& in_td = m_transfers[found->second]; + const txout_to_key* const in_tx_out_pkey = boost::get<txout_to_key>(std::addressof(in_td.m_tx.vout[in_td.m_internal_output_index].target)); + THROW_WALLET_EXCEPTION_IF(in_tx_out_pkey == nullptr, error::wallet_internal_error, "Output is not txout_to_key"); + const crypto::public_key in_tx_pub_key = get_tx_pub_key_from_extra(in_td.m_tx, in_td.m_pk_index); + const std::vector<crypto::public_key> in_additionakl_tx_pub_keys = get_additional_tx_pub_keys_from_extra(in_td.m_tx); + keypair in_ephemeral; + crypto::key_image in_img; + THROW_WALLET_EXCEPTION_IF(!generate_key_image_helper(m_account.get_keys(), m_subaddresses, in_tx_out_pkey->key, in_tx_pub_key, in_additionakl_tx_pub_keys, in_td.m_internal_output_index, in_ephemeral, in_img), + error::wallet_internal_error, "failed to generate key image"); + THROW_WALLET_EXCEPTION_IF(in_key->k_image != in_img, error::wallet_internal_error, "key image mismatch"); + + // get output pubkeys in the ring + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + const size_t ring_size = in_key->key_offsets.size(); + THROW_WALLET_EXCEPTION_IF(absolute_offsets.size() != ring_size, error::wallet_internal_error, "absolute offsets size is wrong"); + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + req.outputs.resize(ring_size); + for (size_t j = 0; j < ring_size; ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != ring_size, error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(ring_size)); + + // copy pubkey pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // figure out real output index and secret key + size_t sec_index = -1; + for (size_t j = 0; j < ring_size; ++j) + { + if (res.outs[j].key == in_ephemeral.pub) + { + sec_index = j; + break; + } + } + THROW_WALLET_EXCEPTION_IF(sec_index >= ring_size, error::wallet_internal_error, "secret index not found"); + + // generate ring sig for this input + signatures.push_back(std::vector<crypto::signature>()); + std::vector<crypto::signature>& sigs = signatures.back(); + sigs.resize(in_key->key_offsets.size()); + crypto::generate_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, in_ephemeral.sec, sec_index, sigs.data()); + } + + std::string sig_str = "SpendProofV1"; + for (const std::vector<crypto::signature>& ring_sig : signatures) + for (const crypto::signature& sig : ring_sig) + sig_str += tools::base58::encode(std::string((const char *)&sig, sizeof(crypto::signature))); + return sig_str; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str) +{ + const std::string header = "SpendProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // fetch tx from daemon + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != 1, error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected 1"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(res.txs[0].as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "txid mismatch"); + + // check signature size + size_t num_sigs = 0; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key != nullptr) + num_sigs += in_key->key_offsets.size(); + } + std::vector<std::vector<crypto::signature>> signatures = { std::vector<crypto::signature>(1) }; + const size_t sig_len = tools::base58::encode(std::string((const char *)&signatures[0][0], sizeof(crypto::signature))).size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * sig_len, + error::wallet_internal_error, "incorrect signature size"); + + // decode base58 + signatures.clear(); + size_t offset = header_len; + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + signatures.resize(signatures.size() + 1); + signatures.back().resize(in_key->key_offsets.size()); + for (size_t j = 0; j < in_key->key_offsets.size(); ++j) + { + std::string sig_decoded; + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, sig_len), sig_decoded), error::wallet_internal_error, "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, "Signature decoding error"); + memcpy(&signatures.back()[j], sig_decoded.data(), sizeof(crypto::signature)); + offset += sig_len; + } + } + + // get signature prefix hash + std::string sig_prefix_data((const char*)&txid, sizeof(crypto::hash)); + sig_prefix_data += message; + crypto::hash sig_prefix_hash; + crypto::cn_fast_hash(sig_prefix_data.data(), sig_prefix_data.size(), sig_prefix_hash); + + std::vector<std::vector<crypto::signature>>::const_iterator sig_iter = signatures.cbegin(); + for(size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_to_key* const in_key = boost::get<txin_to_key>(std::addressof(tx.vin[i])); + if (in_key == nullptr) + continue; + + // get output pubkeys in the ring + COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); + const std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key->key_offsets); + req.outputs.resize(absolute_offsets.size()); + for (size_t j = 0; j < absolute_offsets.size(); ++j) + { + req.outputs[j].amount = in_key->amount; + req.outputs[j].index = absolute_offsets[j]; + } + COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "get_outs.bin"); + THROW_WALLET_EXCEPTION_IF(res.outs.size() != req.outputs.size(), error::wallet_internal_error, + "daemon returned wrong response for get_outs.bin, wrong amounts count = " + + std::to_string(res.outs.size()) + ", expected " + std::to_string(req.outputs.size())); + + // copy pointers + std::vector<const crypto::public_key *> p_output_keys; + for (const COMMAND_RPC_GET_OUTPUTS_BIN::outkey &out : res.outs) + p_output_keys.push_back(&out.key); + + // check this ring + if (!crypto::check_ring_signature(sig_prefix_hash, in_key->k_image, p_output_keys, sig_iter->data())) + return false; + ++sig_iter; + } + THROW_WALLET_EXCEPTION_IF(sig_iter != signatures.cend(), error::wallet_internal_error, "Signature iterator didn't reach the end"); + return true; +} +//---------------------------------------------------------------------------------------------------- + +void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, tx_key, derivation), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + std::vector<crypto::key_derivation> additional_derivations; + additional_derivations.resize(additional_tx_keys.size()); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(address.m_view_public_key, additional_tx_keys[i], additional_derivations[i]), error::wallet_internal_error, + "Failed to generate key derivation from supplied parameters"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); +} + +void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + 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 || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, + "Failed to get the right transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(!additional_derivations.empty() && additional_derivations.size() != tx.vout.size(), error::wallet_internal_error, + "The size of additional derivations is wrong"); + + received = 0; + for (size_t n = 0; n < tx.vout.size(); ++n) + { + const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target)); + if (!out_key) + continue; + + crypto::public_key derived_out_key; + derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + bool found = out_key->key == derived_out_key; + crypto::key_derivation found_derivation = derivation; + if (!found && !additional_derivations.empty()) + { + derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + found = out_key->key == derived_out_key; + found_derivation = additional_derivations[n]; + } + + if (found) + { + uint64_t amount; + if (tx.version == 1 || tx.rct_signatures.type == rct::RCTTypeNull) + { + amount = tx.vout[n].amount; + } + else + { + crypto::secret_key scalar1; + crypto::derivation_to_scalar(found_derivation, n, scalar1); + rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n]; + rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1)); + const rct::key C = tx.rct_signatures.outPk[n].mask; + rct::key Ctmp; + rct::addKeys2(Ctmp, ecdh_info.mask, ecdh_info.amount, rct::H); + if (rct::equalKeys(C, Ctmp)) + amount = rct::h2d(ecdh_info.amount); + else + amount = 0; + } + received += amount; + } + } + + in_pool = res.txs.front().in_pool; + confirmations = (uint64_t)-1; + 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); + } +} + +std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) +{ + // determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound) + const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0; + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + std::vector<crypto::public_key> shared_secret; + std::vector<crypto::signature> sig; + std::string sig_str; + if (is_out) + { + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + THROW_WALLET_EXCEPTION_IF(!get_tx_key(txid, tx_key, additional_tx_keys), error::wallet_internal_error, "Tx secret key wasn't found in the wallet file."); + + const size_t num_sigs = 1 + additional_tx_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key))); + crypto::public_key tx_pub_key; + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]); + } + else + { + crypto::secret_key_to_public_key(tx_key, tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + if (is_subaddress) + { + tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]))); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + else + { + crypto::secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key); + crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); + } + } + sig_str = std::string("OutProofV1"); + } + else + { + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + const size_t num_sigs = 1 + additional_tx_pub_keys.size(); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + + const crypto::secret_key& a = m_account.get_keys().m_view_secret_key; + shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]); + } + for (size_t i = 1; i < num_sigs; ++i) + { + shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a))); + if (is_subaddress) + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]); + } + else + { + crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); + } + } + sig_str = std::string("InProofV1"); + } + const size_t num_sigs = shared_secret.size(); + + // check if this address actually received any funds + crypto::key_derivation derivation; + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + uint64_t received; + bool in_pool; + uint64_t confirmations; + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + THROW_WALLET_EXCEPTION_IF(!received, error::wallet_internal_error, tr("No funds received in this tx.")); + + // concatenate all signature strings + for (size_t i = 0; i < num_sigs; ++i) + sig_str += + tools::base58::encode(std::string((const char *)&shared_secret[i], sizeof(crypto::public_key))) + + tools::base58::encode(std::string((const char *)&sig[i], sizeof(crypto::signature))); + return sig_str; +} + +bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations) +{ + const bool is_out = sig_str.substr(0, 3) == "Out"; + const std::string header = is_out ? "OutProofV1" : "InProofV1"; + const size_t header_len = header.size(); + THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, + "Signature header check error"); + + // decode base58 + std::vector<crypto::public_key> shared_secret(1); + std::vector<crypto::signature> sig(1); + const size_t pk_len = tools::base58::encode(std::string((const char *)&shared_secret[0], sizeof(crypto::public_key))).size(); + const size_t sig_len = tools::base58::encode(std::string((const char *)&sig[0], sizeof(crypto::signature))).size(); + const size_t num_sigs = (sig_str.size() - header_len) / (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(sig_str.size() != header_len + num_sigs * (pk_len + sig_len), error::wallet_internal_error, + "Wrong signature size"); + shared_secret.resize(num_sigs); + sig.resize(num_sigs); + for (size_t i = 0; i < num_sigs; ++i) + { + std::string pk_decoded; + std::string sig_decoded; + const size_t offset = header_len + i * (pk_len + sig_len); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset, pk_len), pk_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(offset + pk_len, sig_len), sig_decoded), error::wallet_internal_error, + "Signature decoding error"); + THROW_WALLET_EXCEPTION_IF(sizeof(crypto::public_key) != pk_decoded.size() || sizeof(crypto::signature) != sig_decoded.size(), error::wallet_internal_error, + "Signature decoding error"); + memcpy(&shared_secret[i], pk_decoded.data(), sizeof(crypto::public_key)); + memcpy(&sig[i], sig_decoded.data(), sizeof(crypto::signature)); + } + + // fetch tx pubkey from the daemon + COMMAND_RPC_GET_TRANSACTIONS::request req; + COMMAND_RPC_GET_TRANSACTIONS::response res; + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + m_daemon_rpc_mutex.lock(); + bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!ok || (res.txs.size() != 1 && res.txs_as_hex.size() != 1), + error::wallet_internal_error, "Failed to get transaction from daemon"); + + cryptonote::blobdata tx_data; + if (res.txs.size() == 1) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + THROW_WALLET_EXCEPTION_IF(!ok, error::wallet_internal_error, "Failed to parse transaction from daemon"); + + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(tx_data, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, + "Failed to validate transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(tx_hash != txid, error::wallet_internal_error, "Failed to get the right transaction from daemon"); + + crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(tx_pub_key == null_pkey, error::wallet_internal_error, "Tx pubkey was not found"); + + std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); + THROW_WALLET_EXCEPTION_IF(additional_tx_pub_keys.size() + 1 != num_sigs, error::wallet_internal_error, "Signature size mismatch with additional tx pubkeys"); + + std::string prefix_data((const char*)&txid, sizeof(crypto::hash)); + prefix_data += message; + crypto::hash prefix_hash; + crypto::cn_fast_hash(prefix_data.data(), prefix_data.size(), prefix_hash); + + // check signature + std::vector<int> good_signature(num_sigs, 0); + if (is_out) + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + else + { + good_signature[0] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0]); + + for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) + { + good_signature[i + 1] = is_subaddress ? + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : + crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1]); + } + } + + if (std::any_of(good_signature.begin(), good_signature.end(), [](int i) { return i > 0; })) + { + // obtain key derivation by multiplying scalar 1 to the shared secret + crypto::key_derivation derivation; + if (good_signature[0]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[0], rct::rct2sk(rct::I), derivation), error::wallet_internal_error, "Failed to generate key derivation"); + + std::vector<crypto::key_derivation> additional_derivations(num_sigs - 1); + for (size_t i = 1; i < num_sigs; ++i) + if (good_signature[i]) + THROW_WALLET_EXCEPTION_IF(!crypto::generate_key_derivation(shared_secret[i], rct::rct2sk(rct::I), additional_derivations[i - 1]), error::wallet_internal_error, "Failed to generate key derivation"); + + check_tx_key_helper(txid, derivation, additional_derivations, address, received, in_pool, confirmations); + return true; + } + return false; +} std::string wallet2::get_wallet_file() const { @@ -6450,16 +7065,12 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent std::string data; bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) - { - fail_msg_writer() << tr("failed to read file ") << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) { - fail_msg_writer() << "Bad key image export file magic in " << filename; - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); } try @@ -6468,31 +7079,22 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent } catch (const std::exception &e) { - fail_msg_writer() << "Failed to decrypt " << filename << ": " << e.what(); - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); } const size_t headerlen = 2 * sizeof(crypto::public_key); - if (data.size() < headerlen) - { - fail_msg_writer() << "Bad data size from file " << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[0]; const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { - fail_msg_writer() << "Key images from " << filename << " are for a different account"; - return 0; + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); } const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); - if ((data.size() - headerlen) % record_size) - { - fail_msg_writer() << "Bad data size from file " << filename; - return 0; - } + THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, + error::wallet_internal_error, std::string("Bad data size from file ") + filename); size_t nki = (data.size() - headerlen) / record_size; std::vector<std::pair<crypto::key_image, crypto::signature>> ski; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f1e12a700..ce0c67fc3 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -147,7 +147,7 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false),m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex) {} public: static const char* tr(const char* str); @@ -155,25 +155,22 @@ namespace tools static bool has_testnet_option(const boost::program_options::variables_map& vm); static void init_options(boost::program_options::options_description& desc_params); - //! \return Password retrieved from prompt. Logs error on failure. - static boost::optional<password_container> password_prompt(const bool new_password); - //! Uses stdin and stdout. Returns a wallet2 if no errors. - static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file); + static std::unique_ptr<wallet2> make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for `wallet_file` if no errors. static std::pair<std::unique_ptr<wallet2>, password_container> - make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file); + make_from_file(const boost::program_options::variables_map& vm, const std::string& wallet_file, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors. - static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm); + static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); //! Just parses variables. - static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm); + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); static bool verify_password(const std::string& keys_file_name, const std::string& password, bool watch_only); - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshDefault), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), m_node_rpc_proxy(m_http_client, m_daemon_rpc_mutex), m_light_wallet(false), m_light_wallet_scanned_block_height(0), m_light_wallet_blockchain_height(0), m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0) {} struct tx_scan_info_t { @@ -244,6 +241,12 @@ namespace tools bool m_incoming; }; + struct pool_payment_details + { + payment_details m_pd; + bool m_double_spend_seen; + }; + struct unconfirmed_transfer_details { cryptonote::transaction_prefix m_tx; @@ -282,13 +285,26 @@ namespace tools std::vector<cryptonote::tx_source_entry> sources; cryptonote::tx_destination_entry change_dts; std::vector<cryptonote::tx_destination_entry> splitted_dsts; // split, includes change - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::vector<uint8_t> extra; uint64_t unlock_time; bool use_rct; std::vector<cryptonote::tx_destination_entry> dests; // original setup, does not include change uint32_t subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> subaddr_indices; // set of address indices used as inputs in this transfer + + BEGIN_SERIALIZE_OBJECT() + FIELD(sources) + FIELD(change_dts) + FIELD(splitted_dsts) + FIELD(selected_transfers) + FIELD(extra) + FIELD(unlock_time) + FIELD(use_rct) + FIELD(dests) + FIELD(subaddr_account) + FIELD(subaddr_indices) + END_SERIALIZE() }; typedef std::vector<transfer_details> transfer_container; @@ -303,13 +319,27 @@ namespace tools uint64_t dust, fee; bool dust_added_to_fee; cryptonote::tx_destination_entry change_dts; - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; std::string key_images; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<cryptonote::tx_destination_entry> dests; tx_construction_data construction_data; + + BEGIN_SERIALIZE_OBJECT() + FIELD(tx) + FIELD(dust) + FIELD(fee) + FIELD(dust_added_to_fee) + FIELD(change_dts) + FIELD(selected_transfers) + FIELD(key_images) + FIELD(tx_key) + FIELD(additional_tx_keys) + FIELD(dests) + FIELD(construction_data) + END_SERIALIZE() }; // The term "Unsigned tx" is not really a tx since it's not signed yet. @@ -501,10 +531,10 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, bool trusted_daemon); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon); template<typename T> - void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, + 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, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); - void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t>& selected_transfers, size_t fake_outputs_count, + void transfer_selected_rct(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, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); @@ -521,6 +551,7 @@ namespace tools std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); + std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); @@ -530,7 +561,7 @@ namespace tools void get_payments_out(std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>>& confirmed_payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_unconfirmed_payments_out(std::list<std::pair<crypto::hash,wallet2::unconfirmed_transfer_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; - void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; + void get_unconfirmed_payments(std::list<std::pair<crypto::hash,wallet2::pool_payment_details>>& unconfirmed_payments, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } void rescan_spent(); @@ -585,7 +616,7 @@ namespace tools std::unordered_map<crypto::hash, payment_details> m; a & m; for (std::unordered_map<crypto::hash, payment_details>::const_iterator i = m.begin(); i != m.end(); ++i) - m_unconfirmed_payments.insert(*i); + m_unconfirmed_payments.insert(std::make_pair(i->first, pool_payment_details{i->second, false})); } if(ver < 14) return; @@ -607,7 +638,14 @@ namespace tools a & m_address_book; if(ver < 17) return; - a & m_unconfirmed_payments; + if (ver < 22) + { + // we're loading an old version, where m_unconfirmed_payments payload was payment_details + std::unordered_multimap<crypto::hash, payment_details> m; + a & m; + for (const auto &i: m) + m_unconfirmed_payments.insert(std::make_pair(i.first, pool_payment_details{i.second, false})); + } if(ver < 18) return; a & m_scanned_pool_txs[0]; @@ -621,6 +659,9 @@ namespace tools if(ver < 21) return; a & m_attributes; + if(ver < 22) + return; + a & m_unconfirmed_payments; } /*! @@ -670,7 +711,13 @@ namespace tools uint32_t get_confirm_backlog_threshold() const { return m_confirm_backlog_threshold; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; + void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + void check_tx_key_helper(const crypto::hash &txid, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message); + bool check_tx_proof(const crypto::hash &txid, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received, bool &in_pool, uint64_t &confirmations); + std::string get_spend_proof(const crypto::hash &txid, const std::string &message); + bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); /*! * \brief GUI Address book get/store */ @@ -700,8 +747,8 @@ namespace tools std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::vector<size_t>& selected_transfers, bool smallest = false) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; @@ -744,6 +791,7 @@ namespace tools uint64_t get_fee_multiplier(uint32_t priority, int fee_algorithm = -1); uint64_t get_per_kb_fee(); + uint64_t adjust_mixin(uint64_t mixin); // Light wallet specific functions // fetch unspent outs from lw node and store in m_transfers @@ -757,7 +805,7 @@ namespace tools // Send an import request to lw node. returns info about import fee, address and payment_id bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response); // get random outputs from light wallet server - void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); // Parse rct string bool light_wallet_parse_rct_str(const std::string& rct_string, const crypto::public_key& tx_pub_key, uint64_t internal_output_index, rct::key& decrypted_mask, rct::key& rct_commit, bool decrypt) const; // check if key image is ours @@ -797,7 +845,7 @@ namespace tools * \param password Password of wallet file */ bool load_keys(const std::string& keys_file_name, const std::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); + 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); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices); void detach_blockchain(uint64_t height); void get_short_chain_history(std::list<crypto::hash>& ids) const; @@ -808,7 +856,7 @@ namespace tools void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history); void pull_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::list<cryptonote::block_complete_entry> &prev_blocks, std::list<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, bool &error); void process_blocks(uint64_t start_height, const std::list<cryptonote::block_complete_entry> &blocks, const std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t& blocks_added); - uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::list<size_t>& selected_transfers, bool trusted_daemon); + uint64_t select_transfers(uint64_t needed_money, std::vector<size_t> unused_transfers_indices, std::vector<size_t>& selected_transfers, bool trusted_daemon); bool prepare_file_names(const std::string& file_path); void process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height); void process_outgoing(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height, uint64_t ts, uint64_t spent, uint64_t received, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); @@ -827,7 +875,7 @@ namespace tools std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const; void set_spent(size_t idx, uint64_t height); void set_unspent(size_t idx); - void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count); + void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count); bool tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const; bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki); crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const; @@ -846,7 +894,7 @@ namespace tools std::atomic<uint64_t> m_local_bc_height; //temporary workaround std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs; - std::unordered_multimap<crypto::hash, payment_details> m_unconfirmed_payments; + std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments; std::unordered_map<crypto::hash, crypto::secret_key> m_tx_keys; cryptonote::checkpoints m_checkpoints; std::unordered_map<crypto::hash, std::vector<crypto::secret_key>> m_additional_tx_keys; @@ -908,16 +956,17 @@ namespace tools std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache; }; } -BOOST_CLASS_VERSION(tools::wallet2, 21) +BOOST_CLASS_VERSION(tools::wallet2, 22) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 2) +BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 0) -BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 1) -BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 1) +BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 2) +BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 2) namespace boost { @@ -1137,7 +1186,14 @@ namespace boost } a & x.m_subaddr_index; } - + + template <class Archive> + inline void serialize(Archive& a, tools::wallet2::pool_payment_details& x, const boost::serialization::version_type ver) + { + a & x.m_pd; + a & x.m_double_spend_seen; + } + template <class Archive> inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) { @@ -1172,7 +1228,16 @@ namespace boost a & x.sources; a & x.change_dts; a & x.splitted_dsts; - a & x.selected_transfers; + if (ver < 2) + { + // load list to vector + std::list<size_t> selected_transfers; + a & selected_transfers; + x.selected_transfers.clear(); + x.selected_transfers.reserve(selected_transfers.size()); + for (size_t t: selected_transfers) + x.selected_transfers.push_back(t); + } a & x.extra; a & x.unlock_time; a & x.use_rct; @@ -1184,6 +1249,9 @@ namespace boost } a & x.subaddr_account; a & x.subaddr_indices; + if (ver < 2) + return; + a & x.selected_transfers; } template <class Archive> @@ -1194,7 +1262,16 @@ namespace boost a & x.fee; a & x.dust_added_to_fee; a & x.change_dts; - a & x.selected_transfers; + if (ver < 2) + { + // load list to vector + std::list<size_t> selected_transfers; + a & selected_transfers; + x.selected_transfers.clear(); + x.selected_transfers.reserve(selected_transfers.size()); + for (size_t t: selected_transfers) + x.selected_transfers.push_back(t); + } a & x.key_images; a & x.tx_key; a & x.dests; @@ -1202,6 +1279,9 @@ namespace boost if (ver < 1) return; a & x.additional_tx_keys; + if (ver < 2) + return; + a & x.selected_transfers; } } } @@ -1291,7 +1371,7 @@ namespace tools // randomly select inputs for transaction // throw if requested send amount is greater than (unlocked) amount available to send - std::list<size_t> selected_transfers; + std::vector<size_t> selected_transfers; uint64_t found_money = select_transfers(needed_money, unused_transfers_indices, selected_transfers, trusted_daemon); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index a8c150ca7..b1f8369a3 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -700,6 +700,11 @@ struct Wallet */ virtual std::string getUserNote(const std::string &txid) const = 0; virtual std::string getTxKey(const std::string &txid) const = 0; + virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0; + virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; + virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; + virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; /* * \brief signMessage - sign a message with the spend private key @@ -819,19 +824,6 @@ struct WalletManager */ virtual std::vector<std::string> findWallets(const std::string &path) = 0; - /*! - * \brief checkPayment - checks a payment was made using a txkey - * \param address - the address the payment was sent to - * \param txid - the transaction id for that payment - * \param txkey - the transaction's secret key - * \param daemon_address - the address (host and port) to the daemon to request transaction data - * \param received - if succesful, will hold the amount of monero received - * \param height - if succesful, will hold the height of the transaction (0 if only in the pool) - * \param error - if unsuccesful, will hold an error string with more information about the error - * \return - true is succesful, false otherwise - */ - virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; - //! returns verbose error string regarding last error; virtual std::string errorString() const = 0; @@ -869,7 +861,7 @@ struct WalletManager virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; //! checks for an update and returns version, hash and url - static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); + static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, std::string subdir); }; diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index df01ec238..cc6bb1de2 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -30,7 +30,6 @@ #include <boost/filesystem/path.hpp> #include <boost/format.hpp> #include "common/i18n.h" -#include "common/scoped_message_writer.h" #include "common/util.h" #include "misc_log_ex.h" #include "string_tools.h" @@ -50,6 +49,20 @@ #define DEFAULT_MAX_CONCURRENCY 0 #endif +namespace +{ + class Print + { + public: + Print(const std::function<void(const std::string&, bool)> &p, bool em = false): print(p), emphasis(em) {} + ~Print() { print(ss.str(), emphasis); } + template<typename T> std::ostream &operator<<(const T &t) { ss << t; return ss; } + private: + const std::function<void(const std::string&, bool)> &print; + std::stringstream ss; + bool emphasis; + }; +} namespace wallet_args { @@ -73,6 +86,7 @@ namespace wallet_args const char* const usage, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, + const std::function<void(const std::string&, bool)> &print, const char *default_log_name, bool log_to_console) @@ -118,16 +132,16 @@ namespace wallet_args if (command_line::get_arg(vm, command_line::arg_help)) { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; - tools::msg_writer() << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << ENDL; + Print(print) << wallet_args::tr("This is the command line monero wallet. It needs to connect to a monero\n" "daemon to work correctly.") << ENDL; - tools::msg_writer() << wallet_args::tr("Usage:") << ENDL << " " << usage; - tools::msg_writer() << desc_all; + Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; + Print(print) << desc_all; return false; } else if (command_line::get_arg(vm, command_line::arg_version)) { - tools::msg_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; return false; } @@ -142,7 +156,7 @@ namespace wallet_args } else { - tools::fail_msg_writer() << wallet_args::tr("Can't find config file ") << config; + MERROR(wallet_args::tr("Can't find config file ") << config); return false; } } @@ -167,14 +181,15 @@ namespace wallet_args if(command_line::has_arg(vm, arg_max_concurrency)) tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); - tools::scoped_message_writer(epee::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; + Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; if (!command_line::is_arg_defaulted(vm, arg_log_level)) MINFO("Setting log level = " << command_line::get_arg(vm, arg_log_level)); else MINFO("Setting log levels = " << getenv("MONERO_LOGS")); MINFO(wallet_args::tr("Logging to: ") << log_path); - tools::scoped_message_writer(epee::console_color_white, true) << boost::format(wallet_args::tr("Logging to %s")) % log_path; + + Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; return {std::move(vm)}; } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index cf23ffded..8974098ad 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -50,5 +50,6 @@ namespace wallet_args const char* const usage, boost::program_options::options_description desc_params, const boost::program_options::positional_options_description& positional_options, + const std::function<void(const std::string&, bool)> &print, const char *default_log_name, bool log_to_console = false); } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 9d66f125e..41eb77451 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -765,6 +765,12 @@ namespace tools #define STRINGIZE_DETAIL(x) #x #define STRINGIZE(x) STRINGIZE_DETAIL(x) +#define THROW_WALLET_EXCEPTION(err_type, ...) \ + do { \ + LOG_ERROR("THROW EXCEPTION: " << #err_type); \ + tools::error::throw_wallet_ex<err_type>(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ## __VA_ARGS__); \ + } while(0) + #define THROW_WALLET_EXCEPTION_IF(cond, err_type, ...) \ if (cond) \ { \ diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 9e6a97bdc..fe7f51617 100755..100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -30,6 +30,7 @@ #include <boost/format.hpp> #include <boost/asio/ip/address.hpp> #include <boost/filesystem/operations.hpp> +#include <boost/algorithm/string.hpp> #include <cstdint> #include "include_base_utils.h" using namespace epee; @@ -58,8 +59,19 @@ namespace const command_line::arg_descriptor<bool> arg_disable_rpc_login = {"disable-rpc-login", "Disable HTTP authentication for RPC connections served by this process"}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", "Enable commands which rely on a trusted daemon", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; + const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; constexpr const char default_rpc_username[] = "monero"; + + boost::optional<tools::password_container> password_prompter(const char *prompt, bool verify) + { + auto pwd_container = tools::password_container::prompt(verify, prompt); + if (!pwd_container) + { + MERROR("failed to read wallet password"); + } + return pwd_container; + } } namespace tools @@ -131,7 +143,7 @@ namespace tools walvars = m_wallet; else { - tmpwal = tools::wallet2::make_dummy(*m_vm); + tmpwal = tools::wallet2::make_dummy(*m_vm, password_prompter); walvars = tmpwal.get(); } boost::optional<epee::net_utils::http::login> http_login{}; @@ -227,19 +239,6 @@ namespace tools return false; } //------------------------------------------------------------------------------------------------------------------------------ - uint64_t wallet_rpc_server::adjust_mixin(uint64_t mixin) - { - if (mixin < 4 && m_wallet->use_fork_rules(6, 10)) { - MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 6, using 5"); - mixin = 4; - } - else if (mixin < 2 && m_wallet->use_fork_rules(2, 10)) { - MWARNING("Requested ring size " << (mixin + 1) << " too low for hard fork 2, using 3"); - mixin = 2; - } - return mixin; - } - //------------------------------------------------------------------------------------------------------------------------------ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) { entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); @@ -299,8 +298,9 @@ namespace tools entry.subaddr_index = { pd.m_subaddr_account, 0 }; } //------------------------------------------------------------------------------------------------------------------------------ - void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) + void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd) { + const tools::wallet2::payment_details &pd = ppd.m_pd; entry.txid = string_tools::pod_to_hex(pd.m_tx_hash); entry.payment_id = string_tools::pod_to_hex(payment_id); if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) @@ -311,6 +311,7 @@ namespace tools entry.unlock_time = pd.m_unlock_time; entry.fee = 0; // TODO entry.note = m_wallet->get_tx_note(pd.m_tx_hash); + entry.double_spend_seen = ppd.m_double_spend_seen; entry.type = "pool"; entry.subaddr_index = pd.m_subaddr_index; } @@ -605,7 +606,7 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); // reject proposed transactions if there are more than one. see on_transfer_split below. @@ -624,6 +625,8 @@ namespace tools if (req.get_tx_key) { res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); + for (const crypto::secret_key& additional_tx_key : ptx_vector.back().additional_tx_keys) + res.tx_key += epee::string_tools::pod_to_hex(additional_tx_key); } res.fee = ptx_vector.back().fee; @@ -633,6 +636,13 @@ namespace tools tx_to_blob(ptx_vector.back().tx, blob); res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, ptx_vector.back()); + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } return true; } catch (const std::exception& e) @@ -665,7 +675,7 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); uint64_t ptx_amount; std::vector<wallet2::pending_tx> ptx_vector; LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); @@ -680,12 +690,14 @@ namespace tools } // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) { res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key); } // Compute amount leaving wallet in tx. By convention dests does not include change outputs ptx_amount = 0; @@ -701,6 +713,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -731,7 +750,7 @@ namespace tools m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) @@ -745,6 +764,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -782,14 +808,14 @@ namespace tools try { - uint64_t mixin = adjust_mixin(req.mixin); + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); if (!req.do_not_relay) m_wallet->commit_tx(ptx_vector); // populate response with tx hashes - for (auto & ptx : ptx_vector) + for (const auto & ptx : ptx_vector) { res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); if (req.get_tx_keys) @@ -802,6 +828,13 @@ namespace tools tx_to_blob(ptx.tx, blob); res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); + } } return true; @@ -813,6 +846,149 @@ namespace tools } return true; } +//------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er) + { + std::vector<cryptonote::tx_destination_entry> dsts; + std::vector<uint8_t> extra; + + if (!m_wallet) return not_open(er); + if (m_wallet->restricted()) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + // validate the transfer requested and populate dsts & extra + std::list<wallet_rpc::transfer_destination> destination; + destination.push_back(wallet_rpc::transfer_destination()); + destination.back().amount = 0; + destination.back().address = req.address; + if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + { + return false; + } + + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(req.key_image, ki)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE; + er.message = "failed to parse key image"; + return false; + } + + try + { + uint64_t mixin = m_wallet->adjust_mixin(req.mixin); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon); + + if (ptx_vector.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "No outputs found"; + return false; + } + if (ptx_vector.size() > 1) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multiple transactions are created, which is not supposed to happen"; + return false; + } + if (ptx_vector[0].selected_transfers.size() > 1) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "The transaction uses multiple inputs, which is not supposed to happen"; + return false; + } + + if (!req.do_not_relay) + m_wallet->commit_tx(ptx_vector); + + // populate response with tx hashes + const wallet2::pending_tx &ptx = ptx_vector[0]; + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + if (req.get_tx_key) + { + res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); + } + if (req.get_tx_hex) + { + cryptonote::blobdata blob; + tx_to_blob(ptx.tx, blob); + res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); + } + if (req.get_tx_metadata) + { + std::ostringstream oss; + binary_archive<true> ar(oss); + ::serialization::serialize(ar, const_cast<tools::wallet2::pending_tx&>(ptx)); + res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + + return true; + } + catch (const tools::error::daemon_busy& e) + { + er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; + er.message = e.what(); + return false; + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = e.what(); + return false; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.hex, blob)) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_HEX; + er.message = "Failed to parse hex."; + return false; + } + + std::stringstream ss; + ss << blob; + binary_archive<false> ba(ss); + + tools::wallet2::pending_tx ptx; + bool r = ::serialization::serialize(ba, ptx); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_BAD_TX_METADATA; + er.message = "Failed to parse tx metadata."; + return false; + } + + try + { + m_wallet->commit_tx(ptx); + } + catch(const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = "Failed to commit tx."; + return false; + } + + res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); + + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er) { @@ -1069,6 +1245,7 @@ namespace tools rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.tx_size = txBlob.size(); rpc_transfers.subaddr_index = td.m_subaddr_index.minor; + rpc_transfers.key_image = req.verbose && td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : ""; res.transfers.push_back(rpc_transfers); } } @@ -1301,6 +1478,208 @@ namespace tools res.value = m_wallet->get_attribute(req.key); return true; } + bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + crypto::secret_key tx_key; + std::vector<crypto::secret_key> additional_tx_keys; + if (!m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) + { + er.code = WALLET_RPC_ERROR_CODE_NO_TXKEY; + er.message = "No tx secret key is stored for this tx"; + return false; + } + + std::ostringstream oss; + oss << epee::string_tools::pod_to_hex(tx_key); + for (size_t i = 0; i < additional_tx_keys.size(); ++i) + oss << epee::string_tools::pod_to_hex(additional_tx_keys[i]); + res.tx_key = oss.str(); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + std::string tx_key_str = req.tx_key; + crypto::secret_key tx_key; + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + std::vector<crypto::secret_key> additional_tx_keys; + while (!tx_key_str.empty()) + { + additional_tx_keys.resize(additional_tx_keys.size() + 1); + if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY; + er.message = "Tx key has invalid format"; + return false; + } + tx_key_str = tx_key_str.substr(64); + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, res.received, res.in_pool, res.confirmations); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + res.signature = m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, req.message); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->testnet(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + + try + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + res.good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, req.message, req.signature, res.received, res.in_pool, res.confirmations); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.signature = m_wallet->get_spend_proof(txid, req.message); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + try + { + res.good = m_wallet->check_spend_proof(txid, req.message, req.signature); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er) { @@ -1357,9 +1736,9 @@ namespace tools { m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> payments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments; m_wallet->get_unconfirmed_payments(payments, req.account_index, req.subaddr_indices); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = payments.begin(); i != payments.end(); ++i) { res.pool.push_back(wallet_rpc::transfer_entry()); fill_transfer_entry(res.pool.back(), i->first, i->second); } @@ -1430,10 +1809,10 @@ namespace tools m_wallet->update_pool_state(); - std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> pool_payments; + std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments; m_wallet->get_unconfirmed_payments(pool_payments); - for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { - if (i->second.m_tx_hash == txid) + for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { + if (i->second.m_pd.m_tx_hash == txid) { fill_transfer_entry(res.transfer, i->first, i->second); return true; @@ -1809,7 +2188,7 @@ namespace tools command_line::add_arg(desc, arg_password); po::store(po::parse_command_line(argc, argv, desc), vm2); } - std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2).first; + std::unique_ptr<tools::wallet2> wal = tools::wallet2::make_new(vm2, nullptr).first; if (!wal) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -1883,7 +2262,7 @@ namespace tools } std::unique_ptr<tools::wallet2> wal = nullptr; try { - wal = tools::wallet2::make_from_file(vm2, wallet_file).first; + wal = tools::wallet2::make_from_file(vm2, wallet_file, nullptr).first; } catch (const std::exception& e) { @@ -1975,13 +2354,14 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_file); command_line::add_arg(desc_params, arg_from_json); command_line::add_arg(desc_params, arg_wallet_dir); - + command_line::add_arg(desc_params, arg_prompt_for_password); const auto vm = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", desc_params, po::positional_options_description(), + [](const std::string &s, bool emphasis){ epee::set_console_color(emphasis ? epee::console_color_white : epee::console_color_default, true); std::cout << s << std::endl; if (emphasis) epee::reset_console_color(); }, "monero-wallet-rpc.log", true ); @@ -1996,6 +2376,8 @@ int main(int argc, char** argv) { const auto wallet_file = command_line::get_arg(*vm, arg_wallet_file); const auto from_json = command_line::get_arg(*vm, arg_from_json); const auto wallet_dir = command_line::get_arg(*vm, arg_wallet_dir); + const auto prompt_for_password = command_line::get_arg(*vm, arg_prompt_for_password); + const auto password_prompt = prompt_for_password ? password_prompter : nullptr; if(!wallet_file.empty() && !from_json.empty()) { @@ -2018,11 +2400,19 @@ int main(int argc, char** argv) { LOG_PRINT_L0(tools::wallet_rpc_server::tr("Loading wallet...")); if(!wallet_file.empty()) { - wal = tools::wallet2::make_from_file(*vm, wallet_file).first; + wal = tools::wallet2::make_from_file(*vm, wallet_file, password_prompt).first; } else { - wal = tools::wallet2::make_from_json(*vm, from_json); + try + { + wal = tools::wallet2::make_from_json(*vm, from_json, password_prompt); + } + catch (const std::exception &e) + { + MERROR("Error creating wallet: " << e.what()); + return 1; + } } if (!wal) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index b38726cb7..9455c4769 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -79,6 +79,8 @@ namespace tools MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) + MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE) + MAP_JON_RPC_WE("relay_tx", on_relay_tx, wallet_rpc::COMMAND_RPC_RELAY_TX) MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS) MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS) @@ -92,6 +94,12 @@ namespace tools MAP_JON_RPC_WE("get_tx_notes", on_get_tx_notes, wallet_rpc::COMMAND_RPC_GET_TX_NOTES) MAP_JON_RPC_WE("set_attribute", on_set_attribute, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE) MAP_JON_RPC_WE("get_attribute", on_get_attribute, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE) + MAP_JON_RPC_WE("get_tx_key", on_get_tx_key, wallet_rpc::COMMAND_RPC_GET_TX_KEY) + MAP_JON_RPC_WE("check_tx_key", on_check_tx_key, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY) + MAP_JON_RPC_WE("get_tx_proof", on_get_tx_proof, wallet_rpc::COMMAND_RPC_GET_TX_PROOF) + MAP_JON_RPC_WE("check_tx_proof", on_check_tx_proof, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF) + MAP_JON_RPC_WE("get_spend_proof", on_get_spend_proof, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF) + MAP_JON_RPC_WE("check_spend_proof", on_check_spend_proof, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF) MAP_JON_RPC_WE("get_transfers", on_get_transfers, wallet_rpc::COMMAND_RPC_GET_TRANSFERS) MAP_JON_RPC_WE("get_transfer_by_txid", on_get_transfer_by_txid, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID) MAP_JON_RPC_WE("sign", on_sign, wallet_rpc::COMMAND_RPC_SIGN) @@ -126,6 +134,8 @@ namespace tools bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); bool on_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er); + bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er); + bool on_relay_tx(const wallet_rpc::COMMAND_RPC_RELAY_TX::request& req, wallet_rpc::COMMAND_RPC_RELAY_TX::response& res, epee::json_rpc::error& er); bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er); bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er); @@ -138,6 +148,12 @@ namespace tools bool on_get_tx_notes(const wallet_rpc::COMMAND_RPC_GET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_GET_TX_NOTES::response& res, epee::json_rpc::error& er); bool on_set_attribute(const wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_SET_ATTRIBUTE::response& res, epee::json_rpc::error& er); bool on_get_attribute(const wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::request& req, wallet_rpc::COMMAND_RPC_GET_ATTRIBUTE::response& res, epee::json_rpc::error& er); + bool on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_check_tx_key(const wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_KEY::response& res, epee::json_rpc::error& er); + bool on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_tx_proof(const wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_TX_PROOF::response& res, epee::json_rpc::error& er); + bool on_get_spend_proof(const wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_SPEND_PROOF::response& res, epee::json_rpc::error& er); + bool on_check_spend_proof(const wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::request& req, wallet_rpc::COMMAND_RPC_CHECK_SPEND_PROOF::response& res, epee::json_rpc::error& er); bool on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er); bool on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er); bool on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er); @@ -163,9 +179,8 @@ namespace tools void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd); void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd); - void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd); + void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd); bool not_open(epee::json_rpc::error& er); - uint64_t adjust_mixin(uint64_t mixin); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); wallet2 *m_wallet; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ffc2e2d49..e084d9e6d 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -291,6 +291,7 @@ namespace wallet_rpc bool get_tx_key; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -303,6 +304,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_key) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -313,6 +315,7 @@ namespace wallet_rpc std::list<std::string> amount_keys; uint64_t fee; std::string tx_blob; + std::string tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) @@ -320,6 +323,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_keys) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) END_KV_SERIALIZE_MAP() }; }; @@ -338,6 +342,7 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(destinations) @@ -350,6 +355,7 @@ namespace wallet_rpc KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -369,6 +375,7 @@ namespace wallet_rpc std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) @@ -376,6 +383,7 @@ namespace wallet_rpc KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -387,11 +395,13 @@ namespace wallet_rpc bool get_tx_keys; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -410,12 +420,14 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) END_KV_SERIALIZE_MAP() }; }; @@ -435,6 +447,7 @@ namespace wallet_rpc uint64_t below_amount; bool do_not_relay; bool get_tx_hex; + bool get_tx_metadata; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) @@ -448,6 +461,7 @@ namespace wallet_rpc KV_SERIALIZE(below_amount) KV_SERIALIZE_OPT(do_not_relay, false) KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) END_KV_SERIALIZE_MAP() }; @@ -466,12 +480,88 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; + std::list<std::string> tx_metadata_list; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) + KV_SERIALIZE(tx_metadata_list) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SWEEP_SINGLE + { + struct request + { + std::string address; + uint32_t priority; + uint64_t mixin; + uint64_t unlock_time; + std::string payment_id; + bool get_tx_key; + std::string key_image; + bool do_not_relay; + bool get_tx_hex; + bool get_tx_metadata; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + KV_SERIALIZE(priority) + KV_SERIALIZE(mixin) + KV_SERIALIZE(unlock_time) + KV_SERIALIZE(payment_id) + KV_SERIALIZE(get_tx_key) + KV_SERIALIZE(key_image) + KV_SERIALIZE_OPT(do_not_relay, false) + KV_SERIALIZE_OPT(get_tx_hex, false) + KV_SERIALIZE_OPT(get_tx_metadata, false) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + std::string tx_key; + uint64_t fee; + std::string tx_blob; + std::string tx_metadata; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) + KV_SERIALIZE(tx_metadata) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_RELAY_TX + { + struct request + { + std::string hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + std::string tx_key; + uint64_t fee; + std::string tx_blob; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(fee) + KV_SERIALIZE(tx_blob) END_KV_SERIALIZE_MAP() }; }; @@ -562,6 +652,7 @@ namespace wallet_rpc std::string tx_hash; uint64_t tx_size; uint32_t subaddr_index; + std::string key_image; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(amount) @@ -570,6 +661,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_size) KV_SERIALIZE(subaddr_index) + KV_SERIALIZE(key_image) END_KV_SERIALIZE_MAP() }; @@ -580,11 +672,13 @@ namespace wallet_rpc std::string transfer_type; uint32_t account_index; std::set<uint32_t> subaddr_indices; + bool verbose; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(transfer_type) KV_SERIALIZE(account_index) KV_SERIALIZE(subaddr_indices) + KV_SERIALIZE(verbose) END_KV_SERIALIZE_MAP() }; @@ -781,6 +875,114 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_TX_KEY + { + struct request + { + std::string txid; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_key) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_KEY + { + struct request + { + std::string txid; + std::string tx_key; + std::string address; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(tx_key) + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_TX_PROOF + { + struct request + { + std::string txid; + std::string address; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(address) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + uint64_t received; + bool in_pool; + uint64_t confirmations; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + KV_SERIALIZE(received) + KV_SERIALIZE(in_pool) + KV_SERIALIZE(confirmations) + END_KV_SERIALIZE_MAP() + }; + }; + struct transfer_entry { std::string txid; @@ -794,6 +996,7 @@ namespace wallet_rpc std::string type; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + bool double_spend_seen; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(txid); @@ -807,9 +1010,58 @@ namespace wallet_rpc KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index); + KV_SERIALIZE(double_spend_seen) END_KV_SERIALIZE_MAP() }; + struct COMMAND_RPC_GET_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_CHECK_SPEND_PROOF + { + struct request + { + std::string txid; + std::string message; + std::string signature; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txid) + KV_SERIALIZE(message) + KV_SERIALIZE(signature) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + bool good; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(good) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_TRANSFERS { struct request diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index e74e9110b..c3f3e20d1 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -54,3 +54,7 @@ #define WALLET_RPC_ERROR_CODE_WALLET_ALREADY_EXISTS -21 #define WALLET_RPC_ERROR_CODE_INVALID_PASSWORD -22 #define WALLET_RPC_ERROR_CODE_NO_WALLET_DIR -23 +#define WALLET_RPC_ERROR_CODE_NO_TXKEY -24 +#define WALLET_RPC_ERROR_CODE_WRONG_KEY -25 +#define WALLET_RPC_ERROR_CODE_BAD_HEX -26 +#define WALLET_RPC_ERROR_CODE_BAD_TX_METADATA -27 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a5f5335db..762eee776 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -111,7 +111,7 @@ add_test( COMMAND hash-target-tests) set(enabled_tests - coretests + core_tests difficulty hash performance_tests diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index a0be3db96..fd07a5d2e 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -34,12 +34,8 @@ #include "include_base_utils.h" #include "version.h" - -using namespace epee; - #include <iostream> #include <sstream> -using namespace std; #include <boost/program_options.hpp> @@ -56,6 +52,8 @@ using namespace std; #endif namespace po = boost::program_options; +using namespace std; +using namespace epee; using namespace cryptonote; using namespace crypto; @@ -71,7 +69,7 @@ int main(int argc, char* argv[]) TRY_ENTRY(); - + tools::on_startup(); string_tools::set_module_name_and_folder(argv[0]); //set up logging options @@ -81,7 +79,7 @@ int main(int argc, char* argv[]) po::options_description desc("Allowed options"); // tools::get_default_data_dir() can't be called during static initialization - command_line::add_arg(desc, command_line::arg_data_dir, tools::get_default_data_dir()); + command_line::add_arg(desc, cryptonote::arg_data_dir, tools::get_default_data_dir()); nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<tests::proxy_core> >::init_options(desc); po::variables_map vm; diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index a24bd4fce..68f2e9816 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -58,10 +58,10 @@ set(core_tests_headers v2_tests.h rct.h) -add_executable(coretests +add_executable(core_tests ${core_tests_sources} ${core_tests_headers}) -target_link_libraries(coretests +target_link_libraries(core_tests PRIVATE cryptonote_core p2p @@ -69,10 +69,10 @@ target_link_libraries(coretests epee ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -set_property(TARGET coretests +set_property(TARGET core_tests PROPERTY FOLDER "tests") add_test( - NAME coretests - COMMAND coretests --generate_and_play_test_data) + NAME core_tests + COMMAND core_tests --generate_and_play_test_data) diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp index 9f3939652..8e68554d3 100644 --- a/tests/core_tests/block_reward.cpp +++ b/tests/core_tests/block_reward.cpp @@ -29,8 +29,6 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" - #include "block_reward.h" using namespace epee; diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index db44cd279..94b636f82 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "block_validation.h" using namespace epee; using namespace cryptonote; diff --git a/tests/core_tests/chain_split_1.cpp b/tests/core_tests/chain_split_1.cpp index eaaa3e045..79aecb1c0 100644 --- a/tests/core_tests/chain_split_1.cpp +++ b/tests/core_tests/chain_split_1.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "chain_split_1.h" using namespace std; diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index b04d05219..01cf00f7a 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "chain_switch_1.h" using namespace epee; using namespace cryptonote; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 2b713cab9..4c5dc6c9a 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -47,6 +47,7 @@ using namespace std; using namespace epee; +using namespace crypto; using namespace cryptonote; diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 3e5b949c8..9eba347cd 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -47,6 +47,7 @@ namespace int main(int argc, char* argv[]) { TRY_ENTRY(); + tools::on_startup(); epee::string_tools::set_module_name_and_folder(argv[0]); //set up logging options diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp index 58114b026..d82120254 100644 --- a/tests/core_tests/double_spend.cpp +++ b/tests/core_tests/double_spend.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "double_spend.h" using namespace epee; using namespace cryptonote; diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 5a9604fc1..4abdfbff5 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -29,8 +29,6 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" - #include "integer_overflow.h" using namespace epee; diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 8a38cbc22..50f65cc67 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -30,7 +30,7 @@ #include "ringct/rctSigs.h" #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "rct.h" using namespace epee; using namespace crypto; diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp index f9ec68e45..43c63dc53 100644 --- a/tests/core_tests/ring_signature_1.cpp +++ b/tests/core_tests/ring_signature_1.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "ring_signature_1.h" using namespace epee; using namespace cryptonote; diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 0e4b2e71a..9987f80d6 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "tx_validation.h" using namespace epee; using namespace crypto; diff --git a/tests/core_tests/v2_tests.cpp b/tests/core_tests/v2_tests.cpp index 6c94ac76c..6c2f91fcf 100644 --- a/tests/core_tests/v2_tests.cpp +++ b/tests/core_tests/v2_tests.cpp @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "chaingen.h" -#include "chaingen_tests_list.h" +#include "v2_tests.h" using namespace epee; using namespace crypto; diff --git a/tests/functional_tests/main.cpp b/tests/functional_tests/main.cpp index 4c9e073d4..35a0bb9bd 100644 --- a/tests/functional_tests/main.cpp +++ b/tests/functional_tests/main.cpp @@ -34,6 +34,7 @@ using namespace epee; #include "common/command_line.h" +#include "common/util.h" #include "transactions_flow_test.h" namespace po = boost::program_options; @@ -58,6 +59,7 @@ namespace int main(int argc, char* argv[]) { TRY_ENTRY(); + tools::on_startup(); string_tools::set_module_name_and_folder(argv[0]); //set up logging options diff --git a/tests/fuzz/block.cpp b/tests/fuzz/block.cpp index 099e8e3bd..03751fc14 100644 --- a/tests/fuzz/block.cpp +++ b/tests/fuzz/block.cpp @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "include_base_utils.h" -#include "common/command_line.h" #include "file_io_utils.h" #include "cryptonote_protocol/blobdatatype.h" #include "cryptonote_basic/cryptonote_basic.h" diff --git a/tests/fuzz/cold-transaction.cpp b/tests/fuzz/cold-transaction.cpp index c35d604a8..25a80fc68 100644 --- a/tests/fuzz/cold-transaction.cpp +++ b/tests/fuzz/cold-transaction.cpp @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "include_base_utils.h" -#include "common/command_line.h" #include "file_io_utils.h" #include "cryptonote_protocol/blobdatatype.h" #include "cryptonote_basic/cryptonote_basic.h" diff --git a/tests/fuzz/fuzzer.cpp b/tests/fuzz/fuzzer.cpp index 3edf8cd19..756a8c847 100644 --- a/tests/fuzz/fuzzer.cpp +++ b/tests/fuzz/fuzzer.cpp @@ -29,6 +29,7 @@ #include <boost/program_options.hpp> #include "include_base_utils.h" #include "common/command_line.h" +#include "common/util.h" #include "fuzzer.h" #if (!defined(__clang__) || (__clang__ < 5)) @@ -48,6 +49,7 @@ using namespace boost::program_options; int run_fuzzer(int argc, const char **argv, Fuzzer &fuzzer) { TRY_ENTRY(); + tools::on_startup(); string_tools::set_module_name_and_folder(argv[0]); //set up logging options diff --git a/tests/fuzz/signature.cpp b/tests/fuzz/signature.cpp index 071297ba8..42a8fa997 100644 --- a/tests/fuzz/signature.cpp +++ b/tests/fuzz/signature.cpp @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "include_base_utils.h" -#include "common/command_line.h" #include "file_io_utils.h" #include "cryptonote_protocol/blobdatatype.h" #include "cryptonote_basic/cryptonote_basic.h" diff --git a/tests/fuzz/transaction.cpp b/tests/fuzz/transaction.cpp index 21cd01bef..9e2b9f2cb 100644 --- a/tests/fuzz/transaction.cpp +++ b/tests/fuzz/transaction.cpp @@ -27,7 +27,6 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "include_base_utils.h" -#include "common/command_line.h" #include "file_io_utils.h" #include "cryptonote_protocol/blobdatatype.h" #include "cryptonote_basic/cryptonote_basic.h" diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index 853ad7c8d..3434cd530 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -33,6 +33,7 @@ #include "wallet/wallet2_api.h" #include "wallet/wallet2.h" #include "include_base_utils.h" +#include "common/util.h" #include <boost/chrono/chrono.hpp> #include <boost/filesystem.hpp> @@ -1138,6 +1139,7 @@ TEST_F(WalletManagerMainnetTest, RecoverAndRefreshWalletMainNetAsync) int main(int argc, char** argv) { + tools::on_startup(); // we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR" const char * testnet_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS"); diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 376d7ee53..a5e5b7c0c 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -193,7 +193,7 @@ namespace { m_thread_count = (std::max)(min_thread_count, boost::thread::hardware_concurrency() / 2); - m_tcp_server.get_config_object().m_pcommands_handler = &m_commands_handler; + m_tcp_server.get_config_object().set_handler(&m_commands_handler); m_tcp_server.get_config_object().m_invoke_timeout = CONNECTION_TIMEOUT; ASSERT_TRUE(m_tcp_server.init_server(clt_port, "127.0.0.1")); @@ -238,9 +238,10 @@ namespace static void TearDownTestCase() { // Stop server - test_levin_commands_handler commands_handler; - test_tcp_server tcp_server(epee::net_utils::e_connection_type_NET); - tcp_server.get_config_object().m_pcommands_handler = &commands_handler; + test_levin_commands_handler *commands_handler_ptr = new test_levin_commands_handler(); + test_levin_commands_handler &commands_handler = *commands_handler_ptr; + test_tcp_server tcp_server(epee::net_utils::e_connection_type_RPC); + tcp_server.get_config_object().set_handler(commands_handler_ptr, [](epee::levin::levin_commands_handler<test_connection_context> *handler)->void { delete handler; }); tcp_server.get_config_object().m_invoke_timeout = CONNECTION_TIMEOUT; if (!tcp_server.init_server(clt_port, "127.0.0.1")) return; @@ -627,6 +628,7 @@ TEST_F(net_load_test_clt, permament_open_and_close_and_connections_closed_by_ser int main(int argc, char** argv) { + tools::on_startup(); epee::debug::get_set_enable_assert(true, false); //set up logging options mlog_configure(mlog_get_default_log_path("net_load_tests_clt.log"), true); diff --git a/tests/net_load_tests/net_load_tests.h b/tests/net_load_tests/net_load_tests.h index f74282683..ce9d8b6fe 100644 --- a/tests/net_load_tests/net_load_tests.h +++ b/tests/net_load_tests/net_load_tests.h @@ -151,6 +151,11 @@ namespace net_load_tests bool handle_new_connection(const boost::uuids::uuid& connection_id, bool ignore_close_fails = false) { size_t idx = m_next_opened_conn_idx.fetch_add(1, std::memory_order_relaxed); + if (idx >= m_connections.size()) + { + LOG_PRINT_L0("ERROR: connections overflow"); + exit(1); + } m_connections[idx] = connection_id; size_t prev_connection_count = m_opened_connection_count.fetch_add(1, std::memory_order_relaxed); diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index e6dee1639..a987aa4e2 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -215,6 +215,7 @@ namespace int main(int argc, char** argv) { + tools::on_startup(); //set up logging options mlog_configure(mlog_get_default_log_path("net_load_tests_srv.log"), true); @@ -224,8 +225,8 @@ int main(int argc, char** argv) if (!tcp_server.init_server(srv_port, "127.0.0.1")) return 1; - srv_levin_commands_handler commands_handler(tcp_server); - tcp_server.get_config_object().m_pcommands_handler = &commands_handler; + srv_levin_commands_handler *commands_handler = new srv_levin_commands_handler(tcp_server); + tcp_server.get_config_object().set_handler(commands_handler, [](epee::levin::levin_commands_handler<test_connection_context> *handler) { delete handler; }); tcp_server.get_config_object().m_invoke_timeout = 10000; //tcp_server.get_config_object().m_max_packet_size = max_packet_size; diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index 3c0283eca..459eecba4 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -28,6 +28,7 @@ // // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include "common/util.h" #include "performance_tests.h" #include "performance_utils.h" @@ -48,6 +49,7 @@ int main(int argc, char** argv) { + tools::on_startup(); set_process_affinity(1); set_thread_high_priority(); diff --git a/tests/performance_tests/performance_utils.h b/tests/performance_tests/performance_utils.h index 061772cbd..6bf45ff83 100644 --- a/tests/performance_tests/performance_utils.h +++ b/tests/performance_tests/performance_utils.h @@ -40,7 +40,7 @@ void set_process_affinity(int core) { -#if defined (__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#if defined (__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__sun) return; #elif defined(BOOST_WINDOWS) DWORD_PTR mask = 1; @@ -62,7 +62,7 @@ void set_process_affinity(int core) void set_thread_high_priority() { -#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__sun) return; #elif defined(BOOST_WINDOWS) ::SetPriorityClass(::GetCurrentProcess(), HIGH_PRIORITY_CLASS); diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index c7efcf074..e10648d20 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -55,6 +55,7 @@ set(unit_tests_sources serialization.cpp sha256.cpp slow_memmem.cpp + subaddress.cpp test_tx_utils.cpp test_peerlist.cpp test_protocol_pack.cpp diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp index 51e26c2bb..d9a7b8ad5 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -74,16 +74,12 @@ namespace { size_t inlen = sizeof(source); int mdlen = (int)sizeof(md); - int ret = keccak(source, inlen, md, mdlen); + keccak(source, inlen, md, mdlen); if (md[0] != 0x00) { return true; } - else if (!ret) - { - return true; - } else { return false; diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp index d2aa31555..c749c2531 100644 --- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp +++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp @@ -187,9 +187,11 @@ namespace typedef std::unique_ptr<test_connection> test_connection_ptr; - async_protocol_handler_test() + async_protocol_handler_test(): + m_pcommands_handler(new test_levin_commands_handler()), + m_commands_handler(*m_pcommands_handler) { - m_handler_config.m_pcommands_handler = &m_commands_handler; + m_handler_config.set_handler(m_pcommands_handler, [](epee::levin::levin_commands_handler<test_levin_connection_context> *handler) { delete handler; }); m_handler_config.m_invoke_timeout = invoke_timeout; m_handler_config.m_max_packet_size = max_packet_size; } @@ -212,7 +214,7 @@ namespace protected: boost::asio::io_service m_io_service; test_levin_protocol_handler_config m_handler_config; - test_levin_commands_handler m_commands_handler; + test_levin_commands_handler *m_pcommands_handler, &m_commands_handler; }; class positive_test_connection_to_levin_protocol_handler_calls : public async_protocol_handler_test diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index 2b0904224..c235f49fd 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -115,13 +115,13 @@ public: virtual void add_txpool_tx(const transaction &tx, const txpool_tx_meta_t& details) {} virtual void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& details) {} - virtual uint64_t get_txpool_tx_count() const { return 0; } + virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const { return 0; } virtual bool txpool_has_tx(const crypto::hash &txid) const { return false; } virtual void remove_txpool_tx(const crypto::hash& txid) {} virtual txpool_tx_meta_t get_txpool_tx_meta(const crypto::hash& txid) const { return txpool_tx_meta_t(); } virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const { return false; } virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const { return ""; } - virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false) const { return false; } + virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const { return false; } virtual void add_block( const block& blk , const size_t& block_size diff --git a/tests/unit_tests/main.cpp b/tests/unit_tests/main.cpp index 1706c43c9..86e7bcef3 100644 --- a/tests/unit_tests/main.cpp +++ b/tests/unit_tests/main.cpp @@ -35,6 +35,7 @@ #include "include_base_utils.h" #include "common/command_line.h" +#include "common/util.h" #include "unit_tests_utils.h" namespace po = boost::program_options; @@ -43,6 +44,7 @@ boost::filesystem::path unit_test::data_dir; int main(int argc, char** argv) { + tools::on_startup(); epee::string_tools::set_module_name_and_folder(argv[0]); mlog_configure(mlog_get_default_log_path("unit_tests.log"), true); epee::debug::get_set_enable_assert(true, false); @@ -51,7 +53,7 @@ int main(int argc, char** argv) po::options_description desc_options("Command line options"); const command_line::arg_descriptor<std::string> arg_data_dir = {"data-dir", "Data files directory", "", true}; - command_line::add_arg(desc_options, command_line::arg_data_dir, ""); + command_line::add_arg(desc_options, arg_data_dir, ""); po::variables_map vm; bool r = command_line::handle_error_helper(desc_options, [&]() @@ -64,7 +66,7 @@ int main(int argc, char** argv) return 1; if (vm["data-dir"].defaulted()) - unit_test::data_dir = boost::filesystem::path(epee::string_tools::get_current_module_folder()) + unit_test::data_dir = boost::filesystem::canonical(boost::filesystem::path(epee::string_tools::get_current_module_folder())) .parent_path().parent_path().parent_path().parent_path() .append("tests").append("data"); else diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index 6ff73b107..ccca0b799 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -76,7 +76,7 @@ TEST(select_outputs, one_out_of_N) tools::wallet2::transfer_container transfers = make_transfers_container(10); transfers[6].m_block_height = 700; std::vector<size_t> unused_indices({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); - std::list<size_t> selected; + std::vector<size_t> selected; SELECT(2); PICK(6); } @@ -93,7 +93,7 @@ TEST(select_outputs, order) transfers[3].m_block_height = 716; transfers[4].m_block_height = 701; std::vector<size_t> unused_indices({0, 1, 2, 3, 4}); - std::list<size_t> selected; + std::vector<size_t> selected; SELECT(0); PICK(3); // first the one that's far away PICK(2); // then the one that's close diff --git a/tests/unit_tests/ringct.cpp b/tests/unit_tests/ringct.cpp index af6afa636..ef6151efb 100644 --- a/tests/unit_tests/ringct.cpp +++ b/tests/unit_tests/ringct.cpp @@ -38,6 +38,7 @@ #include "ringct/rctSigs.h" #include "ringct/rctOps.h" +using namespace std; using namespace crypto; using namespace rct; diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 011082d1c..0750ab7d1 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -49,6 +49,7 @@ #include "gtest/gtest.h" #include "unit_tests_utils.h" using namespace std; +using namespace crypto; struct Struct { diff --git a/tests/unit_tests/subaddress.cpp b/tests/unit_tests/subaddress.cpp new file mode 100644 index 000000000..c304b7347 --- /dev/null +++ b/tests/unit_tests/subaddress.cpp @@ -0,0 +1,118 @@ +// Copyright (c) 2014-2017, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers +#include <boost/filesystem.hpp> +#include "gtest/gtest.h" + +#include "include_base_utils.h" +#include "wallet/wallet2.h" +#include "crypto/crypto.h" +#include "cryptonote_basic/account.h" +#include "cryptonote_basic/cryptonote_basic_impl.h" +#include "wallet/api/subaddress.h" + +class WalletSubaddress : public ::testing::Test +{ + protected: + virtual void SetUp() + { + try + { + w1.generate(wallet_name, password, recovery_key, true, false); + } + catch (const std::exception& e) + { + LOG_ERROR("failed to generate wallet: " << e.what()); + throw e; + } + + w1.add_subaddress_account(test_label); + w1.set_subaddress_label(subaddress_index, test_label); + } + + virtual void TearDown() + { + boost::filesystem::wpath wallet_file(wallet_name); + boost::filesystem::wpath wallet_address_file(wallet_name + ".address.txt"); + boost::filesystem::wpath wallet_keys_file(wallet_name + ".keys"); + + if ( boost::filesystem::exists(wallet_file) ) + boost::filesystem::remove(wallet_file); + + if ( boost::filesystem::exists(wallet_address_file) ) + boost::filesystem::remove(wallet_address_file); + + if ( boost::filesystem::exists(wallet_keys_file) ) + boost::filesystem::remove(wallet_keys_file); + } + + tools::wallet2 w1; + std::string path_working_dir = "."; + std::string path_test_wallet = "test_wallet"; + const std::string wallet_name = path_working_dir + "/" + path_test_wallet; + const std::string password = "testpass"; + crypto::secret_key recovery_key = crypto::secret_key(); + const std::string test_label = "subaddress test label"; + + uint32_t major_index = 0; + uint32_t minor_index = 0; + const cryptonote::subaddress_index subaddress_index = {major_index, minor_index}; +}; + +TEST_F(WalletSubaddress, GetSubaddressLabel) +{ + EXPECT_EQ(test_label, w1.get_subaddress_label(subaddress_index)); +} + +TEST_F(WalletSubaddress, AddSubaddress) +{ + std::string label = "test adding subaddress"; + w1.add_subaddress(0, label); + EXPECT_EQ(label, w1.get_subaddress_label({0, 1})); +} + +TEST_F(WalletSubaddress, OutOfBoundsIndexes) +{ + try + { + w1.get_subaddress_label({1,0}); + } + catch(const std::exception& e) + { + EXPECT_STREQ("index_major is out of bound", e.what()); + } + try + { + w1.get_subaddress_label({0,2}); + } + catch(const std::exception& e) + { + EXPECT_STREQ("index.minor is out of bound", e.what()); + } +} diff --git a/utils/gpg_keys/stoffu.asc b/utils/gpg_keys/stoffu.asc new file mode 100644 index 000000000..5067ccf90 --- /dev/null +++ b/utils/gpg_keys/stoffu.asc @@ -0,0 +1,62 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBFi08i4BEADYSPbRfJ4JEopmiYEzc4ipaEVu8yPJhX1sDy2Bad5FrzIFSuxU +ppcOxxsKjDBpd/OlDEAcf4RoCnWWBd7W9dEYxjHHl3rCCvQkgkD+kTXKu8piNzki +N6rCHNvpnbibicl/FYlNJsVCicLv2XS0GXEJnQeLEOvXLfl6iq3G31YcyRFKVKoE +t8+dR3B4pXIjgM4aRDcFiZcLqdzO4PIvouE6E/yqjuVDH4Y1MkEwFTw+q4+UPpzB +z6OpM+3we89me4OwEcj073N71QwneRl1I5Hltx/rJhBrHxHk0JEboj080QwGl3iw +a+weFLtWuqvwt+AsBOyiZpBOCEeiihZ6W5fWAKvVwSwQEimRF6loQdWrXriM8vfh +txZB0AqT/W/lNHVK/6pg8c3p+GREoEC0OySNymPYN6p8QEFCt1RhrhiUKTczkMFJ +g7t0rm06mPdy2Dm4OfkAeMmUPdlF5R8PSoQMmXcNu/Y0JayG5yuwROVndPiZnX6u +t/sjWpYavMuBoXwtQzP7PTNr1FDAIi/S7pwCdBSY+lqIG+u2qNimL6zXgay8WT6f +1CisFzqx0n81uhJc0buprsKKKZFaeHIs8ZJPj9Y3JI5jBHALAgJX/VIFOIN+4cWa +vaB+wB1sCenq2GvkCljdb8/XXuEtT5macaUAFkUem8U8JuotAdC5myaqKwARAQAB +tB1zdG9mZnUgPHN0b2ZmdUBwcm90b25tYWlsLmNoPokCOAQTAQIAIgUCWLTyLgIb +AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQQdq4NDqewBLY4hAAkaIiEuhi +MDJ8R186OP8rk508gfz7Lw9Usj7MibLN8WWOvaqD+fWozH+J/ThyrWvBAfzr93xC +CWYYXunmUkeUHRkoY6djnJBjFfGQl5Yhil+bzE5foZjjs7nh4oaMc77TvaZqZfAg +r/DO79Qf12M7t4vR1EhRmSfmeo1IvgwxJqhnCiYrr+M/cSk9ywnsdgGa9zdHyHyl +IthYPrqPg9kQrJPfv6NWnesYULa1GVhC/HLbeO/TIn4c+G97FF7RSm8Z/o9oAZbR +Acka56HIDqM9kM6pWeDHzZalzOiAII8+izx7KbijltWTdjns0BeWuW1/ylq+kFQ+ +NhDxe8XIBvKc3EiypLrjND8JinY9Vq5TVSYGqw6BM+0PE+38El7a81Qg2saPWqDO +rPBx31CsDC88EeCTA/FgJKLOBdMjg6YHEjU9FioRQuifNis6hvfLhQJAPB2SgQl+ +ql7jShdIpPmwWaK5t/lRtGiaIyAMd4/PK3tjEV9CwB8hQhmUykJ/OjK0sLXg7aOX +nBTBhvZtrHUCI5tFgKDQMV6lHq1WFPIYj2Ah7Zn2MuyxVk+LB8aDPqIODMVO1ehO +U23lW2Xo64nEZS2+KwRk7A+o8/FDF1j2iDbCKm8xqOrmj2lu4nPU0MIdP3Y6xYUm +Y4C57G2tFelV6k//H4ZACRfpgvvLkrzaDhGJAhwEEAECAAYFAljtzhcACgkQCF0J +Lx9D1Royvw/7BBfxl9fvq+UtNu4SK+GD8C8pnznoRL1A1t6CtboG7c1TEhpCPxIu +XHnBs74D5ZR+kDDsJegH+gYsvX9JlB+Il75DA5IYTByvijhSBXHFHKxdXnmBa2l3 +hb1yn8sxkU0jT5HpgrdY2GH93NPnfrJ4KcvINQtDEIcLwYRcQA4i4kujK55w9Q71 +s/+23wkldmSub9PDX62S3DoK37Wqn+EIUbvRrXvU9GmHLgnNiyoTy3IhrdiXRRAm +Kei999ooJe8q3LgCcRg5mUshIV8RvcfJZOWjk4TCHNxRTZ2aaOakPrj87dH53LYS +Zl3q86CvcVk8tujY+YSGb6RviKydHDZRQkvNk5WQReVa5C39qakF98lK+5DGXqNi +mdtQKGO62Dr6OS/85ATzMJ8LZK6CkGMq8lwigtnJkgRFbZqhQ1/GtlKf3Qdrf9uh +1Ds1U/hzeXLWskIXNpYcMmFISaYlEu7fyU7JAPq7otuQWMJPJHvNVHt3OTMqz3kR +LfJxW8gq175GeJagOwjeBNQOTvvfyRqO1QtmZnoovvciH7MTLfYRPhScbtfAeTmx +UR8Lqd7OssmeD95tGmF9fG9U4JsCfSE78x5mtAWdKj725T4I4QHdX7LSxeFOrCn/ +IjrM9NMW+iJpH5RguPPRyYRlgR2UvFkA3Y8b2qIA40ZXzdAc0slwDpy5Ag0EWLTy +LgEQAL8SMWnZfiNXIaGe/yi8dI2gyAtafClcleSRwk3ehaBsb6jvYlpHdn8F7kSi +jwyqu55I8fub0gtQ57GYz9rwar0E7l7USWC2GbpEs0AITCB8zDzNObW2yTmm12lS +Vj+G5YvoqIH0ktf1I6QYZSV+E36EqX09SlPdkNk08JjSzafGrs1i3ImKDsdge4ca +OelIDTm/x44sUi4AuG9fdaNzigUzTZxAZWTeH1FK55WJCacUizAFvNoDo5PcL0eB +yLuh276f3Lj6PzMN94uLJQPsf52hfr41tZI3S7+9F1hX0hBdh8N11VKwx27lMRnt +iOvkeFCGjta5VX/QSAShX1FPUqG0eyAd00RgiaBLJIoqiO6Z0VU6aMnC3Ce/MWPh +oenYSjm7vVGoxGs192tW9WDsvgYINn/6GHMF5pXwZP1JIlt13l3TqUcuduafyu+B +KseIUCnsxvW0DG1jVipc6rrEFusAfNRDHChxdnMKjcA87dLJ22tsbVE85dBXiMy5 +Setsa/mJC5w9/dUc2IOsOB+y4GVP/aTiRZRfcWHSPCQlytqKf1CT52ySctCb4LWZ ++lKbnIvQBr6CS+BKYnBsT/6QPdNfjT4HPIT8S2DOow7Ggrwaxp5kmzKU0dbqu8Hy +s1f5lIjnr1pFWUL/fQKrIz/E5o/LfNhHXxVuwdKZmC+Lt8aRABEBAAGJAh8EGAEC +AAkFAli08i4CGwwACgkQQdq4NDqewBLlqw/+N1zKL3m/UMRZOYMHGzkkQXwLIN3i +u2XE+iLb11Lh6jKLUMb9k/NG4tX/xmXSJZhA/tHR0tSRH2s4Yv02k4pFQbABZjAD +tUZly4TJ2OlBWCFE/Jr2HZFtL8xJpus0Qqv8eJFehsc1X/W6obl2sj6DLcglYOWL +0WiSi4ITQvIyg2JKxwe2wea1RWIINpMBi1JfL/wl8P61y3HouEeDVcDXvKR3FC/7 +h7YGKqvfMWwZEu/f6kjA49PGiFOYhGGW3D4fBuVUKMmsoAhrUtrMEu+7wCA/Aekx +ZO90IcPk/733KkfPpAkvre3o50JgJnXUBe0ZYXYZn/yrSE0rnF4ydfsQ7d2y+YEf +8Iwmd7zulP750mGoMDJp09jqkrrj5VL9VAQn00cSFb4TPtdgcQyHWJ50SX8uuDJx +e5ECxj+5hFntjWpFG+p4v1xedTfIQLhPtO4OXljOFzUYVzhMk/pX2oFcFoT6+AOt +BPX5Rxw0bTsUnsnkD24OEhOaxiK/aAkummNT8hAK09NV/+rXHZZUL1n93SobnGJ/ +X4DlBRYA/tbn/Ol3vYzhWFzCDLrTwVsm8cTgY4bp+8I9ysUee5Rx4rw0qiyMxz6Z +zRBUCxarimrjaoaejm6pm1yckEj2gBlf0nOaSiWgQhSTOq82Og0oVZT4ol8aDUv/ +hnbUrKukLHjFgd8= +=GKYE +-----END PGP PUBLIC KEY BLOCK----- |