diff options
89 files changed, 1603 insertions, 385 deletions
diff --git a/.gitignore b/.gitignore index a27982af1..0bd9ba3dd 100644 --- a/.gitignore +++ b/.gitignore @@ -61,7 +61,6 @@ CMakeCache.txt CMakeFiles cmake_install.cmake install_manifest.txt -*.cmake ### Linux ### *~ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2964e299c..637e9c4e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -703,8 +703,6 @@ if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND ARCH_WIDTH EQUAL "32" AND NOT IOS) list(APPEND EXTRA_LIBRARIES ${ATOMIC}) endif() -include(version.cmake) - find_path(ZMQ_INCLUDE_PATH zmq.hpp) find_library(ZMQ_LIB zmq) find_library(PGM_LIBRARY pgm) @@ -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,71 @@ 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 Linux for Android (using docker): # Build image (select android64.Dockerfile for aarch64) diff --git a/src/version.cmake b/cmake/GenVersion.cmake index 45a97cd20..45a97cd20 100644 --- a/src/version.cmake +++ b/cmake/GenVersion.cmake diff --git a/version.cmake b/cmake/Version.cmake index 75343c381..fb636c4ff 100644 --- a/version.cmake +++ b/cmake/Version.cmake @@ -41,7 +41,7 @@ elseif (GIT_FOUND OR Git_FOUND) COMMAND "${CMAKE_COMMAND}" "-D" "GIT=${GIT_EXECUTABLE}" "-D" "TO=${CMAKE_BINARY_DIR}/version.cpp" - "-P" "src/version.cmake" + "-P" "cmake/GenVersion.cmake" BYPRODUCTS "${CMAKE_BINARY_DIR}/version.cpp" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") else() 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_server_impl_base.h b/contrib/epee/include/net/http_server_impl_base.h index 0788c6a4b..8b8e31b51 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..34ec02b7e 100644 --- a/contrib/epee/include/storages/portable_storage_to_bin.h +++ b/contrib/epee/include/storages/portable_storage_to_bin.h @@ -47,6 +47,7 @@ namespace epee PRAGMA_WARNING_PUSH PRAGMA_GCC("GCC diagnostic ignored \"-Wstrict-aliasing\"") + PRAGMA_GCC("GCC diagnostic ignored \"-Wtautological-constant-out-of-range-compare\"") 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/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/unbound/CMakeLists.txt b/external/unbound/CMakeLists.txt index 3dae8b425..8ac4bbd84 100644 --- a/external/unbound/CMakeLists.txt +++ b/external/unbound/CMakeLists.txt @@ -60,6 +60,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/src/CMakeLists.txt b/src/CMakeLists.txt index 6fb08b645..0281b1df6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -83,26 +83,30 @@ function (monero_add_executable name) endfunction () function (monero_add_library name) - source_group("${name}" - FILES - ${ARGN}) + monero_add_library_with_deps(NAME "${name}" SOURCES ${ARGN}) +endfunction() + +function (monero_add_library_with_deps) + cmake_parse_arguments(MONERO_ADD_LIBRARY "" "NAME" "DEPENDS;SOURCES" ${ARGN}) + source_group("${MONERO_ADD_LIBRARY_NAME}" FILES ${MONERO_ADD_LIBRARY_SOURCES}) # Define a ("virtual") object library and an actual library that links those # objects together. The virtual libraries can be arbitrarily combined to link # any subset of objects into one library archive. This is used for releasing # libwallet, which combines multiple components. - set(objlib obj_${name}) - add_library(${objlib} OBJECT ${ARGN}) - add_library("${name}" $<TARGET_OBJECTS:${objlib}>) - set_property(TARGET "${name}" - PROPERTY - FOLDER "libs") + set(objlib obj_${MONERO_ADD_LIBRARY_NAME}) + add_library(${objlib} OBJECT ${MONERO_ADD_LIBRARY_SOURCES}) + add_library("${MONERO_ADD_LIBRARY_NAME}" $<TARGET_OBJECTS:${objlib}>) + if (MONERO_ADD_LIBRARY_DEPENDS) + add_dependencies(${objlib} ${MONERO_ADD_LIBRARY_DEPENDS}) + endif() + set_property(TARGET "${MONERO_ADD_LIBRARY_NAME}" PROPERTY FOLDER "libs") target_compile_definitions(${objlib} - PRIVATE $<TARGET_PROPERTY:${name},INTERFACE_COMPILE_DEFINITIONS>) + PRIVATE $<TARGET_PROPERTY:${MONERO_ADD_LIBRARY_NAME},INTERFACE_COMPILE_DEFINITIONS>) endfunction () -set_source_files_properties(${CMAKE_BINARY_DIR}/version.cpp PROPERTIES GENERATED ON) -monero_add_library(version ${CMAKE_BINARY_DIR}/version.cpp) +include(Version) +monero_add_library(version SOURCES ${CMAKE_BINARY_DIR}/version.cpp DEPENDS genversion) add_subdirectory(common) add_subdirectory(crypto) 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..6cf74b3b0 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" @@ -1135,6 +1136,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 +1528,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 +1667,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 +1691,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/common/command_line.cpp b/src/common/command_line.cpp index 666b3267f..d4a28fc85 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -78,6 +78,20 @@ namespace command_line return false; } + bool is_no(const std::string& str) + { + if (str == "n" || str == "N") + return true; + + boost::algorithm::is_iequal ignore_case{}; + if (boost::algorithm::equals("no", str, ignore_case)) + return true; + if (boost::algorithm::equals(command_line::tr("no"), str, ignore_case)) + return true; + + return false; + } + 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"}; diff --git a/src/common/command_line.h b/src/common/command_line.h index 04fd70be5..bfc8b19c6 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -45,6 +45,8 @@ namespace command_line //! \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; diff --git a/src/common/password.cpp b/src/common/password.cpp index 5c04023f4..b84d6fb2e 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 << '*'; } } 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..1e180d325 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -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/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 9d89c6280..4e1ab8a48 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3166,9 +3166,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 +3351,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 +3368,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 +4211,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 +4231,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 +4381,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..3f2930fb0 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -947,11 +947,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..3f56ffac7 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1182,21 +1182,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 +1210,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 diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 7340e1024..a3d47280a 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -420,11 +420,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 +435,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 +458,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 diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 96f6ee872..9409a60a5 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -160,12 +160,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 +168,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 +316,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 +345,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 +392,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 +399,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/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 9071c330c..6bfcfe529 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -187,6 +187,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 +229,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 +268,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 +357,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 +380,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 +436,7 @@ namespace cryptonote remove.insert(txid); } return true; - }); + }, false); if (!remove.empty()) { @@ -494,7 +498,7 @@ namespace cryptonote } } return true; - }); + }, false); return true; } //--------------------------------------------------------------------------------- @@ -521,14 +525,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 +546,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 +567,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 +598,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 +648,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 +671,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 +690,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 +740,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 +872,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 +903,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 +955,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 +1110,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..e41491954 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 { @@ -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/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index c85e5edb5..5307b2472 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -187,9 +187,24 @@ bool t_command_parser_executor::print_block(const std::vector<std::string>& args bool t_command_parser_executor::print_transaction(const std::vector<std::string>& args) { + bool include_hex = false; + bool include_json = false; + + // Assumes that optional flags come after mandatory argument <transaction_hash> + for (unsigned int i = 1; i < args.size(); ++i) { + if (args[i] == "+hex") + include_hex = true; + else if (args[i] == "+json") + include_json = true; + else + { + std::cout << "unexpected argument: " << args[i] << std::endl; + return true; + } + } if (args.empty()) { - std::cout << "expected: print_tx <transaction hash>" << std::endl; + std::cout << "expected: print_tx <transaction_hash> [+hex] [+json]" << std::endl; return true; } @@ -197,7 +212,7 @@ bool t_command_parser_executor::print_transaction(const std::vector<std::string> crypto::hash tx_hash; if (parse_hash256(str_hash, tx_hash)) { - m_executor.print_transaction(tx_hash); + m_executor.print_transaction(tx_hash, include_hex, include_json); } return true; diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 3f1543857..b134aee61 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -91,7 +91,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "print_tx" , std::bind(&t_command_parser_executor::print_transaction, &m_parser, p::_1) - , "Print transaction, print_tx <transaction_hash>" + , "Print transaction, print_tx <transaction_hash> [+hex] [+json]" ); m_command_lookup.set_handler( "is_key_image_spent" diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 867c523c9..83b04bff5 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -693,7 +693,9 @@ bool t_rpc_command_executor::print_block_by_height(uint64_t height) { return true; } -bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { +bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, + bool include_hex, + bool include_json) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res; @@ -728,30 +730,34 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash) { tools::success_msg_writer() << "Found in blockchain at height " << res.txs.front().block_height; } - // first as hex const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front(); - tools::success_msg_writer() << as_hex; + // Print raw hex if requested + if (include_hex) + tools::success_msg_writer() << as_hex << std::endl; - // then as json - crypto::hash tx_hash, tx_prefix_hash; - cryptonote::transaction tx; - cryptonote::blobdata blob; - if (!string_tools::parse_hexstr_to_binbuff(as_hex, blob)) - { - tools::fail_msg_writer() << "Failed to parse tx"; - } - else if (!cryptonote::parse_and_validate_tx_from_blob(blob, tx, tx_hash, tx_prefix_hash)) + // Print json if requested + if (include_json) { - tools::fail_msg_writer() << "Failed to parse tx blob"; - } - else - { - tools::success_msg_writer() << cryptonote::obj_to_json_str(tx) << std::endl; + crypto::hash tx_hash, tx_prefix_hash; + cryptonote::transaction tx; + cryptonote::blobdata blob; + if (!string_tools::parse_hexstr_to_binbuff(as_hex, blob)) + { + tools::fail_msg_writer() << "Failed to parse tx to get json format"; + } + else if (!cryptonote::parse_and_validate_tx_from_blob(blob, tx, tx_hash, tx_prefix_hash)) + { + tools::fail_msg_writer() << "Failed to parse tx blob to get json format"; + } + else + { + tools::success_msg_writer() << cryptonote::obj_to_json_str(tx) << std::endl; + } } } else { - tools::fail_msg_writer() << "transaction wasn't found: " << transaction_hash << std::endl; + tools::fail_msg_writer() << "Transaction wasn't found: " << transaction_hash << std::endl; } return true; @@ -808,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; @@ -834,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 @@ -892,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; @@ -916,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 @@ -948,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; @@ -978,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()) { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index c9d0ac671..efe1ae56a 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -95,7 +95,7 @@ public: bool print_block_by_height(uint64_t height); - bool print_transaction(crypto::hash transaction_hash); + bool print_transaction(crypto::hash transaction_hash, bool include_hex, bool include_json); bool is_key_image_spent(const crypto::key_image &ki); diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 6162d649b..ee3ff160a 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -560,7 +560,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/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index b3ce30d0c..f6932c659 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" @@ -128,6 +129,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res) { + PERF_TIMER(on_get_height); CHECK_CORE_BUSY(); res.height = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; @@ -136,6 +138,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res) { + PERF_TIMER(on_get_info); CHECK_CORE_BUSY(); crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); @@ -150,6 +153,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; @@ -179,6 +183,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) { + PERF_TIMER(on_get_blocks); CHECK_CORE_BUSY(); std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > > bs; @@ -238,6 +243,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) { + PERF_TIMER(on_get_alt_blocks_hashes); CHECK_CORE_BUSY(); std::list<block> blks; @@ -261,6 +267,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) { + PERF_TIMER(on_get_blocks_by_height); CHECK_CORE_BUSY(); res.status = "Failed"; res.blocks.clear(); @@ -291,6 +298,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) { + PERF_TIMER(on_get_hashes); CHECK_CORE_BUSY(); NOTIFY_RESPONSE_CHAIN_ENTRY::request resp; @@ -310,6 +318,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) { + PERF_TIMER(on_get_random_outs); CHECK_CORE_BUSY(); res.status = "Failed"; @@ -349,6 +358,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) { + PERF_TIMER(on_get_outs_bin); CHECK_CORE_BUSY(); res.status = "Failed"; @@ -372,6 +382,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res) { + PERF_TIMER(on_get_outs); CHECK_CORE_BUSY(); res.status = "Failed"; @@ -410,6 +421,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) { + PERF_TIMER(on_get_random_rct_outs); CHECK_CORE_BUSY(); res.status = "Failed"; if(!m_core.get_random_rct_outs(req, res)) @@ -434,6 +446,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) { + PERF_TIMER(on_get_indexes); CHECK_CORE_BUSY(); bool r = m_core.get_tx_outputs_gindexs(req.txid, res.o_indexes); if(!r) @@ -448,6 +461,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_transactions(const COMMAND_RPC_GET_TRANSACTIONS::request& req, COMMAND_RPC_GET_TRANSACTIONS::response& res) { + PERF_TIMER(on_get_transactions); CHECK_CORE_BUSY(); std::vector<crypto::hash> vh; for(const auto& tx_hex_str: req.txs_hashes) @@ -478,15 +492,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 +516,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 +561,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,8 +605,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) { + PERF_TIMER(on_is_key_image_spent); CHECK_CORE_BUSY(); std::vector<crypto::key_image> key_images; for(const auto& ki_hex_str: req.key_images) @@ -596,7 +638,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 +671,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 +735,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 +783,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,6 +796,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_mining_status(const COMMAND_RPC_MINING_STATUS::request& req, COMMAND_RPC_MINING_STATUS::response& res) { + PERF_TIMER(on_mining_status); CHECK_CORE_BUSY(); const miner& lMiner = m_core.get_miner(); @@ -770,6 +816,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res) { + PERF_TIMER(on_save_bc); CHECK_CORE_BUSY(); if( !m_core.get_blockchain_storage().store_blockchain() ) { @@ -782,6 +829,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 +859,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 +874,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 +887,43 @@ 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) { + PERF_TIMER(on_get_transaction_pool); CHECK_CORE_BUSY(); - m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images); + 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) { + PERF_TIMER(on_get_transaction_pool_hashes); CHECK_CORE_BUSY(); - m_core.get_pool_transaction_hashes(res.tx_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) { + PERF_TIMER(on_get_transaction_pool_stats); CHECK_CORE_BUSY(); - m_core.get_pool_transaction_stats(res.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,6 +933,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res) { + PERF_TIMER(on_getblockcount); CHECK_CORE_BUSY(); res.count = m_core.get_current_blockchain_height(); res.status = CORE_RPC_STATUS_OK; @@ -886,6 +942,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) { + PERF_TIMER(on_getblockhash); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -927,6 +984,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; @@ -1001,6 +1059,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 +1116,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,6 +1135,7 @@ 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) { + PERF_TIMER(on_get_last_block_header); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1104,6 +1165,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){ + PERF_TIMER(on_get_block_header_by_hash); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1146,6 +1208,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){ + PERF_TIMER(on_get_block_headers_range); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1197,6 +1260,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){ + PERF_TIMER(on_get_block_header_by_height); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1230,6 +1294,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){ + PERF_TIMER(on_get_block); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1280,6 +1345,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,6 +1358,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) { + PERF_TIMER(on_get_connections); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1308,6 +1375,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) { + PERF_TIMER(on_get_info_json); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1328,6 +1396,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; @@ -1340,6 +1409,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ 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) { + PERF_TIMER(on_hard_fork_info); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1358,6 +1428,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) { + PERF_TIMER(on_get_bans); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1387,6 +1458,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) { + PERF_TIMER(on_set_bans); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1422,6 +1494,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) { + PERF_TIMER(on_flush_txpool); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1476,6 +1549,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) { + PERF_TIMER(on_get_output_histogram); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1508,6 +1582,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 +1590,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 +1600,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 +1608,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 +1627,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 +1636,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 +1676,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 +1688,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 +1696,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 +1704,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,6 +1799,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) { + PERF_TIMER(on_relay_tx); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1761,6 +1846,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) { + PERF_TIMER(on_sync_info); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1792,6 +1878,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) { + PERF_TIMER(on_get_txpool_backlog); if(!check_core_busy()) { error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; @@ -1825,7 +1912,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..15b4b503a 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; @@ -936,6 +939,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) @@ -1217,12 +1221,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 +1363,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 +1380,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 +1490,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 +1505,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.cpp b/src/rpc/message.cpp index 086820180..d6da124d1 100644 --- a/src/rpc/message.cpp +++ b/src/rpc/message.cpp @@ -45,6 +45,15 @@ const char* Message::STATUS_FAILED = "Failed"; const char* Message::STATUS_BAD_REQUEST = "Invalid request type"; const char* Message::STATUS_BAD_JSON = "Malformed json"; +namespace +{ +constexpr const char error_field[] = "error"; +constexpr const char id_field[] = "id"; +constexpr const char method_field[] = "method"; +constexpr const char params_field[] = "params"; +constexpr const char result_field[] = "result"; +} + rapidjson::Value Message::toJson(rapidjson::Document& doc) const { rapidjson::Value val(rapidjson::kObjectType); @@ -70,8 +79,8 @@ FullMessage::FullMessage(const std::string& request, Message* message) { doc.SetObject(); - doc.AddMember("method", rapidjson::StringRef(request.c_str()), doc.GetAllocator()); - doc.AddMember("params", message->toJson(doc), doc.GetAllocator()); + doc.AddMember(method_field, rapidjson::StringRef(request.c_str()), doc.GetAllocator()); + doc.AddMember(params_field, message->toJson(doc), doc.GetAllocator()); // required by JSON-RPC 2.0 spec doc.AddMember("jsonrpc", rapidjson::Value("2.0"), doc.GetAllocator()); @@ -86,7 +95,7 @@ FullMessage::FullMessage(Message* message) if (message->status == Message::STATUS_OK) { - doc.AddMember("response", message->toJson(doc), doc.GetAllocator()); + doc.AddMember(result_field, message->toJson(doc), doc.GetAllocator()); } else { @@ -111,14 +120,14 @@ FullMessage::FullMessage(const std::string& json_string, bool request) if (request) { - OBJECT_HAS_MEMBER_OR_THROW(doc, "method") - OBJECT_HAS_MEMBER_OR_THROW(doc, "params") + OBJECT_HAS_MEMBER_OR_THROW(doc, method_field) + OBJECT_HAS_MEMBER_OR_THROW(doc, params_field) } else { - if (!doc.HasMember("response") && !doc.HasMember("error")) + if (!doc.HasMember(result_field) && !doc.HasMember(error_field)) { - throw cryptonote::json::MISSING_KEY("error/response"); + throw cryptonote::json::MISSING_KEY("error/result"); } } } @@ -126,9 +135,9 @@ FullMessage::FullMessage(const std::string& json_string, bool request) std::string FullMessage::getJson() { - if (!doc.HasMember("id")) + if (!doc.HasMember(id_field)) { - doc.AddMember("id", rapidjson::Value("unused"), doc.GetAllocator()); + doc.AddMember(id_field, rapidjson::Value("unused"), doc.GetAllocator()); } rapidjson::StringBuffer buf; @@ -142,24 +151,24 @@ std::string FullMessage::getJson() std::string FullMessage::getRequestType() const { - OBJECT_HAS_MEMBER_OR_THROW(doc, "method") - return doc["method"].GetString(); + OBJECT_HAS_MEMBER_OR_THROW(doc, method_field) + return doc[method_field].GetString(); } rapidjson::Value& FullMessage::getMessage() { - if (doc.HasMember("params")) + if (doc.HasMember(params_field)) { - return doc["params"]; + return doc[params_field]; } - else if (doc.HasMember("response")) + else if (doc.HasMember(result_field)) { - return doc["response"]; + return doc[result_field]; } //else - OBJECT_HAS_MEMBER_OR_THROW(doc, "error") - return doc["error"]; + OBJECT_HAS_MEMBER_OR_THROW(doc, error_field) + return doc[error_field]; } @@ -172,20 +181,20 @@ rapidjson::Value FullMessage::getMessageCopy() rapidjson::Value& FullMessage::getID() { - OBJECT_HAS_MEMBER_OR_THROW(doc, "id") - return doc["id"]; + OBJECT_HAS_MEMBER_OR_THROW(doc, id_field) + return doc[id_field]; } void FullMessage::setID(rapidjson::Value& id) { - auto itr = doc.FindMember("id"); + auto itr = doc.FindMember(id_field); if (itr != doc.MemberEnd()) { itr->value = id; } else { - doc.AddMember("id", id, doc.GetAllocator()); + doc.AddMember(id_field, id, doc.GetAllocator()); } } @@ -193,7 +202,7 @@ cryptonote::rpc::error FullMessage::getError() { cryptonote::rpc::error err; err.use = false; - if (doc.HasMember("error")) + if (doc.HasMember(error_field)) { GET_FROM_JSON_OBJECT(doc, err, error); err.use = true; 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/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/simplewallet/CMakeLists.txt b/src/simplewallet/CMakeLists.txt index b56085b8f..4ecda12d0 100644 --- a/src/simplewallet/CMakeLists.txt +++ b/src/simplewallet/CMakeLists.txt @@ -48,7 +48,6 @@ target_link_libraries(simplewallet cncrypto common mnemonics - p2p version ${Boost_CHRONO_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b55b39236..3d8d395db 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" @@ -107,10 +106,11 @@ enum TransferType { namespace { - const auto allowed_priority_strings = {"default", "unimportant", "normal", "elevated", "priority"}; + const std::array<const char* const, 5> allowed_priority_strings = {{"default", "unimportant", "normal", "elevated", "priority"}}; 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(); @@ -161,20 +161,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; @@ -278,6 +308,18 @@ namespace } } +bool parse_priority(const std::string& arg, uint32_t& priority) +{ + auto priority_pos = std::find( + allowed_priority_strings.begin(), + allowed_priority_strings.end(), + arg); + if(priority_pos != allowed_priority_strings.end()) { + priority = std::distance(allowed_priority_strings.begin(), priority_pos); + return true; + } + return false; +} std::string simple_wallet::get_commands_str() { @@ -495,8 +537,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; } @@ -506,8 +550,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; } @@ -523,8 +569,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; } @@ -619,14 +667,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; } @@ -653,8 +702,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; } @@ -664,8 +715,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; } @@ -741,8 +794,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; } @@ -752,8 +807,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; } @@ -820,8 +877,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer_new, this, _1), tr("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>] - Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the fee of the transaction. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); m_cmd_binder.set_handler("locked_transfer", boost::bind(&simple_wallet::locked_transfer, this, _1), tr("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <addr> <amount> <lockblocks> [<payment_id>] - Same as transfer, but with number of blocks to lock the transaction for, max 1000000")); 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>,...]] [<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>,...]] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address")); + 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("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")); @@ -847,6 +904,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); + m_cmd_binder.set_handler("set_description", boost::bind(&simple_wallet::set_description, this, _1), tr("Set an arbitrary description for the wallet")); + m_cmd_binder.set_handler("get_description", boost::bind(&simple_wallet::get_description, this, _1), tr("Get the description of the wallet ")); m_cmd_binder.set_handler("status", boost::bind(&simple_wallet::status, this, _1), tr("Show wallet status information")); m_cmd_binder.set_handler("wallet_info", boost::bind(&simple_wallet::wallet_info, this, _1), tr("Show wallet information")); m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::sign, this, _1), tr("Sign the contents of a file")); @@ -1063,12 +1122,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; } @@ -1176,6 +1235,25 @@ 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 = command_line::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; @@ -1517,6 +1595,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); @@ -1529,6 +1608,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() || @@ -1890,8 +1970,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; @@ -1974,7 +2062,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(); @@ -2483,17 +2571,9 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri local_args.erase(local_args.begin()); } - 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); - } - } + uint32_t priority = 0; + if (local_args.size() > 0 && parse_priority(local_args[0], priority)) + local_args.erase(local_args.begin()); size_t fake_outs_count = 0; if(local_args.size() > 0) { @@ -2504,12 +2584,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) @@ -2821,13 +2912,20 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri { fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } - catch (const tools::error::not_enough_money& e) + 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)") % @@ -2993,13 +3091,20 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } - catch (const tools::error::not_enough_money& e) + 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)") % @@ -3068,6 +3173,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; @@ -3082,6 +3193,10 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a local_args.erase(local_args.begin()); } + uint32_t priority = 0; + if (local_args.size() > 0 && parse_priority(local_args[0], priority)) + local_args.erase(local_args.begin()); + size_t fake_outs_count = 0; if(local_args.size() > 0) { size_t ring_size; @@ -3091,12 +3206,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; @@ -3135,12 +3261,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)) { @@ -3186,7 +3306,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); if (ptx_vector.empty()) { @@ -3273,13 +3393,20 @@ 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_money& e) + 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)") % @@ -3395,7 +3522,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; } @@ -3623,13 +3750,20 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) { fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what(); } - catch (const tools::error::not_enough_money& e) + 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)") % @@ -4377,15 +4511,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) @@ -4705,10 +4842,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)) @@ -4990,6 +5128,39 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::set_description(const std::vector<std::string> &args) +{ + // 0 arguments allowed, for setting the description to empty string + + std::string description = ""; + for (size_t n = 0; n < args.size(); ++n) + { + if (n > 0) + description += " "; + description += args[n]; + } + m_wallet->set_description(description); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_description(const std::vector<std::string> &args) +{ + if (args.size() != 0) + { + fail_msg_writer() << tr("usage: get_description"); + return true; + } + + std::string description = m_wallet->get_description(); + if (description.empty()) + success_msg_writer() << tr("no description found"); + else + success_msg_writer() << tr("description found: ") << description; + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::status(const std::vector<std::string> &args) { uint64_t local_height = m_wallet->get_blockchain_current_height(); @@ -5018,6 +5189,7 @@ bool simple_wallet::status(const std::vector<std::string> &args) bool simple_wallet::wallet_info(const std::vector<std::string> &args) { message_writer() << tr("Filename: ") << m_wallet->get_wallet_file(); + message_writer() << tr("Description: ") << m_wallet->get_description(); message_writer() << tr("Address: ") << m_wallet->get_account().get_public_address_str(m_wallet->testnet()); message_writer() << tr("Watch only: ") << (m_wallet->watch_only() ? tr("Yes") : tr("No")); message_writer() << tr("Testnet: ") << (m_wallet->testnet() ? tr("Yes") : tr("No")); @@ -5367,10 +5539,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; + 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) { - const tools::wallet2::payment_details &pd = i->second; + 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); @@ -5383,6 +5555,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; } } @@ -5474,6 +5648,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); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6edd41c32..8100fda55 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -169,6 +169,8 @@ namespace cryptonote bool refresh_main(uint64_t start_height, bool reset = false, bool is_init = false); bool set_tx_note(const std::vector<std::string> &args); bool get_tx_note(const std::vector<std::string> &args); + bool set_description(const std::vector<std::string> &args); + bool get_description(const std::vector<std::string> &args); bool status(const std::vector<std::string> &args); bool wallet_info(const std::vector<std::string> &args); bool set_default_priority(const std::vector<std::string> &args); @@ -279,6 +281,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..4c00a4d51 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} 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 8f7befc8c..8e747d16b 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -1146,7 +1146,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); m_status = Status_Error; - } catch (const tools::error::not_enough_money& e) { + } catch (const tools::error::not_enough_unlocked_money& e) { m_status = Status_Error; std::ostringstream writer; @@ -1155,6 +1155,15 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const print_money(e.tx_amount()); m_errorString = writer.str(); + } catch (const tools::error::not_enough_money& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + } catch (const tools::error::tx_not_possible& e) { m_status = Status_Error; std::ostringstream writer; @@ -1240,7 +1249,7 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() m_errorString = tr("failed to get random outputs to mix"); m_status = Status_Error; - } catch (const tools::error::not_enough_money& e) { + } catch (const tools::error::not_enough_unlocked_money& e) { m_status = Status_Error; std::ostringstream writer; @@ -1249,6 +1258,15 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() print_money(e.tx_amount()); m_errorString = writer.str(); + } catch (const tools::error::not_enough_money& e) { + m_status = Status_Error; + std::ostringstream writer; + + writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % + print_money(e.available()) % + print_money(e.tx_amount()); + m_errorString = writer.str(); + } catch (const tools::error::tx_not_possible& e) { m_status = Status_Error; std::ostringstream writer; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index a64766c84..2326f54d3 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -434,12 +434,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/wallet2.cpp b/src/wallet/wallet2.cpp index 22afac7b7..a7161ffcb 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -305,9 +305,9 @@ 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"); + tools::fail_msg_writer() << tools::wallet2::tr("At least one of Electrum-style word list and private view key and private spend key must be specified"); return false; } if (field_seed_found && (field_viewkey_found || field_spendkey_found)) @@ -368,6 +368,10 @@ 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; @@ -390,6 +394,11 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, } address.m_spend_public_key = info.address.m_spend_public_key; } + else + { + tools::fail_msg_writer() << tools::wallet2::tr("Address must be specified in order to create watch-only wallet"); + return false; + } wallet->generate(field_filename, field_password, address, viewkey); } else @@ -444,6 +453,21 @@ 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); +} + } //namespace namespace tools @@ -793,7 +817,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) @@ -1163,7 +1187,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 +1265,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 +1276,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); @@ -1520,10 +1544,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 +1650,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 +1698,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 +1708,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 +1717,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 +1740,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,8 +1789,21 @@ 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); @@ -3073,11 +3116,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 +3315,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 +3323,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 +3364,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 +3373,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 +3994,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 +4130,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 +4234,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 +4469,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) { @@ -4441,7 +4498,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) @@ -4566,7 +4623,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) { @@ -4598,7 +4655,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); - THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) @@ -5129,7 +5186,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; @@ -5195,7 +5252,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); } @@ -5325,11 +5386,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; @@ -5361,7 +5438,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; @@ -5475,6 +5552,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; @@ -5835,7 +5921,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton { 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; @@ -6248,6 +6334,29 @@ std::string wallet2::get_tx_note(const crypto::hash &txid) const return i->second; } +void wallet2::set_attribute(const std::string &key, const std::string &value) +{ + m_attributes[key] = value; +} + +std::string wallet2::get_attribute(const std::string &key) const +{ + std::unordered_map<std::string, std::string>::const_iterator i = m_attributes.find(key); + if (i == m_attributes.end()) + return std::string(); + return i->second; +} + +void wallet2::set_description(const std::string &description) +{ + set_attribute(ATTRIBUTE_DESCRIPTION, description); +} + +std::string wallet2::get_description() const +{ + return get_attribute(ATTRIBUTE_DESCRIPTION); +} + std::string wallet2::sign(const std::string &data) const { crypto::hash hash; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 746255fa9..8576227e8 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); @@ -173,7 +173,7 @@ namespace tools 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 +244,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,7 +288,7 @@ 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; @@ -303,7 +309,7 @@ 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; @@ -501,10 +507,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); @@ -530,7 +536,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 +591,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 +613,15 @@ namespace tools a & m_address_book; if(ver < 17) return; - a & m_unconfirmed_payments; + if (ver < 21) + { + // we're loading an old version, where m_unconfirmed_payments payload was payment_details + std::unordered_map<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})); + return; + } if(ver < 18) return; a & m_scanned_pool_txs[0]; @@ -618,6 +632,12 @@ namespace tools a & m_subaddresses_inv; a & m_subaddress_labels; a & m_additional_tx_keys; + if(ver < 21) + return; + a & m_attributes; + if(ver < 22) + return; + a & m_unconfirmed_payments; } /*! @@ -697,12 +717,15 @@ 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; + void set_description(const std::string &description); + std::string get_description() const; + std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; @@ -738,6 +761,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 @@ -751,12 +775,31 @@ 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 bool light_wallet_key_image_is_ours(const crypto::key_image& key_image, const crypto::public_key& tx_public_key, uint64_t out_index); + /* + * "attributes" are a mechanism to store an arbitrary number of string values + * on the level of the wallet as a whole, identified by keys. Their introduction, + * technically the unordered map m_attributes stored as part of a wallet file, + * led to a new wallet file version, but now new singular pieces of info may be added + * without the need for a new version. + * + * The first and so far only value stored as such an attribute is the description. + * It's stored under the standard key ATTRIBUTE_DESCRIPTION (see method set_description). + * + * The mechanism is open to all clients and allows them to use it for storing basically any + * single string values in a wallet. To avoid the problem that different clients possibly + * overwrite or misunderstand each other's attributes, a two-part key scheme is + * proposed: <client name>.<value name> + */ + const char* const ATTRIBUTE_DESCRIPTION = "wallet2.description"; + void set_attribute(const std::string &key, const std::string &value); + std::string get_attribute(const std::string &key) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -772,7 +815,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; @@ -783,7 +826,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); @@ -802,7 +845,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; @@ -821,7 +864,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; @@ -835,6 +878,7 @@ namespace tools std::unordered_map<cryptonote::subaddress_index, crypto::public_key> m_subaddresses_inv; std::vector<std::vector<std::string>> m_subaddress_labels; std::unordered_map<crypto::hash, std::string> m_tx_notes; + std::unordered_map<std::string, std::string> m_attributes; std::vector<tools::wallet2::address_book_row> m_address_book; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value @@ -882,16 +926,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, 20) +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 { @@ -1111,7 +1156,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) { @@ -1146,7 +1198,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; @@ -1158,6 +1219,9 @@ namespace boost } a & x.subaddr_account; a & x.subaddr_indices; + if (ver < 2) + return; + a & x.selected_transfers; } template <class Archive> @@ -1168,7 +1232,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; @@ -1176,6 +1249,9 @@ namespace boost if (ver < 1) return; a & x.additional_tx_keys; + if (ver < 2) + return; + a & x.selected_transfers; } } } @@ -1264,10 +1340,10 @@ namespace tools } // randomly select inputs for transaction - // throw if requested send amount is greater than amount available to send - std::list<size_t> selected_transfers; + // throw if requested send amount is greater than (unlocked) amount available to send + 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_money, found_money, needed_money - fee, fee); + THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_unlocked_money, found_money, needed_money - fee, fee); uint32_t subaddr_account = m_transfers[*selected_transfers.begin()].m_subaddr_index.major; for (auto i = ++selected_transfers.begin(); i != selected_transfers.end(); ++i) diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index a8c150ca7..432c820cb 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -869,7 +869,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_errors.h b/src/wallet/wallet_errors.h index d1f4a796d..9d66f125e 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -68,6 +68,7 @@ namespace tools // get_tx_pool_error // transfer_error * // get_random_outs_general_error + // not_enough_unlocked_money // not_enough_money // tx_not_possible // not_enough_outs_to_mix @@ -356,11 +357,37 @@ namespace tools //---------------------------------------------------------------------------------------------------- typedef failed_rpc_request<transfer_error, get_random_outs_error_message_index> get_random_outs_error; //---------------------------------------------------------------------------------------------------- + struct not_enough_unlocked_money : public transfer_error + { + explicit not_enough_unlocked_money(std::string&& loc, uint64_t available, uint64_t tx_amount, uint64_t fee) + : transfer_error(std::move(loc), "not enough unlocked money") + , m_available(available) + , m_tx_amount(tx_amount) + { + } + + uint64_t available() const { return m_available; } + uint64_t tx_amount() const { return m_tx_amount; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << + ", available = " << cryptonote::print_money(m_available) << + ", tx_amount = " << cryptonote::print_money(m_tx_amount); + return ss.str(); + } + + private: + uint64_t m_available; + uint64_t m_tx_amount; + }; + //---------------------------------------------------------------------------------------------------- struct not_enough_money : public transfer_error { - explicit not_enough_money(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + explicit not_enough_money(std::string&& loc, uint64_t available, uint64_t tx_amount, uint64_t fee) : transfer_error(std::move(loc), "not enough money") - , m_available(availbable) + , m_available(available) , m_tx_amount(tx_amount) { } @@ -384,9 +411,9 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct tx_not_possible : public transfer_error { - explicit tx_not_possible(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + explicit tx_not_possible(std::string&& loc, uint64_t available, uint64_t tx_amount, uint64_t fee) : transfer_error(std::move(loc), "tx not possible") - , m_available(availbable) + , m_available(available) , m_tx_amount(tx_amount) , m_fee(fee) { diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index acd5357ed..fda8f244a 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -227,19 +227,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 +286,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 +299,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 +594,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. @@ -665,7 +654,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"); @@ -782,7 +771,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_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) @@ -1273,6 +1262,35 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::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) + { + 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; + } + + m_wallet->set_attribute(req.key, req.value); + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::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) + { + 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; + } + + res.value = m_wallet->get_attribute(req.key); + 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) { if (!m_wallet) return not_open(er); @@ -1328,9 +1346,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); } @@ -1401,10 +1419,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; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index f2d98df6f..a2677ef1b 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -90,6 +90,8 @@ namespace tools MAP_JON_RPC_WE("rescan_blockchain", on_rescan_blockchain, wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN) MAP_JON_RPC_WE("set_tx_notes", on_set_tx_notes, wallet_rpc::COMMAND_RPC_SET_TX_NOTES) 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_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) @@ -134,6 +136,8 @@ namespace tools bool on_rescan_blockchain(const wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::request& req, wallet_rpc::COMMAND_RPC_RESCAN_BLOCKCHAIN::response& res, epee::json_rpc::error& er); bool on_set_tx_notes(const wallet_rpc::COMMAND_RPC_SET_TX_NOTES::request& req, wallet_rpc::COMMAND_RPC_SET_TX_NOTES::response& res, epee::json_rpc::error& er); 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_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); @@ -159,7 +163,7 @@ 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); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index f652fa7ff..06f2456c3 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -739,6 +739,48 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_SET_ATTRIBUTE + { + struct request + { + std::string key; + std::string value; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(key) + KV_SERIALIZE(value) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_GET_ATTRIBUTE + { + struct request + { + + std::string key; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(key) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string value; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(value) + END_KV_SERIALIZE_MAP() + }; + }; + struct transfer_entry { std::string txid; @@ -752,6 +794,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); @@ -765,6 +808,7 @@ namespace wallet_rpc KV_SERIALIZE(type); KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index); + KV_SERIALIZE(double_spend_seen) END_KV_SERIALIZE_MAP() }; 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/README.md b/tests/README.md new file mode 100644 index 000000000..48a6c41a7 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,117 @@ +# Running all tests + +To run all tests, run: + +``` +cd /path/to/monero +make [-jn] debug-test # where n is number of compiler processes +``` + +To test a release build, replace `debug-test` with `release-test` in the previous command. + +# Core tests + +Core tests take longer than any other Monero tests, due to the high amount of computational work involved in validating core components. + +Tests are located in `tests/core_tests/`, and follow a straightforward naming convention. Most cases cover core functionality (`block_reward.cpp`, `chaingen.cpp`, `rct.cpp`, etc.), while some cover basic security tests (`double_spend.cpp` & `integer_overflow.cpp`). + +To run only Monero's core tests (after building): + +``` +cd build/debug/tests/core +ctest +``` + +To run the same tests on a release build, replace `debug` with `release`. + + +# Crypto Tests + +Crypto tests are located under the `tests/crypto` directory. + +- `crypto-tests.h` contains test harness headers +- `main.cpp` implements the driver for the crypto tests + +Tests correspond to components under `src/crypto/`. A quick comparison reveals the pattern, and new tests should continue the naming convention. + +To run only Monero's crypto tests (after building): + +``` +cd build/debug/tests/crypto +ctest +``` + +To run the same tests on a release build, replace `debug` with `release`. + +# Daemon tests + +[TODO] + +# Functional tests + +[TODO] + +# Fuzz tests + +Fuzz tests are written using American Fuzzy Lop (AFL), and located under the `tests/fuzz` directory. + +An additional helper utility is provided `contrib/fuzz_testing/fuzz.sh`. AFL must be installed, and some additional setup may be necessary for the script to run properly. + +# Hash tests + +Hash tests exist under `tests/hash`, and include a set of target hashes in text files. + +To run only Monero's hash tests (after building): + +``` +cd build/debug/tests/hash +ctest +``` + +To run the same tests on a release build, replace `debug` with `release`. + +# Libwallet API tests + +[TODO] + +# Net Load tests + +[TODO] + +# Performance tests + +Performance tests are located in `tests/performance_tests`, and test features for performance metrics on the host machine. + +To run only Monero's performance tests (after building): + +``` +cd build/debug/tests/performance_tests +./performance_tests +``` + +If the `performance_tests` binary does not exist, try running `make` in the `build/debug/tests/performance_tests` directory. + +To run the same tests on a release build, replace `debug` with `release`. + +# Unit tests + +Unit tests are defined under the `tests/unit_tests` directory. Independent components are tested individually to ensure they work properly on their own. + +To run only Monero's unit tests (after building): + +``` +cd build/debug/tests/unit_tests +ctest +``` + +To run the same tests on a release build, replace `debug` with `release`. + +# Writing new tests + +## Test hygiene + +When writing new tests, please implement all functions in `.cpp` or `.c` files, and only put function headers in `.h` files. This will help keep the fairly complex test suites somewhat sane going forward. + +## Writing fuzz tests + +[TODO] diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index a0be3db96..0dc314b49 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -71,7 +71,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 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_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/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/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/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 3a8e787ec..51e26c2bb 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -47,6 +47,14 @@ namespace "8b655970153799af2aeadc9ff1add0ea6c7251d54154cfa92c173a0dd39c1f94" "6c7251d54154cfa92c173a0dd39c1f948b655970153799af2aeadc9ff1add0ea"; + static std::uint8_t md[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00 + }; + template<typename T> bool is_formatted() { @@ -61,6 +69,26 @@ namespace out << "BEGIN" << value << "END"; return out.str() == "BEGIN<" + std::string{expected, sizeof(T) * 2} + ">END"; } + + bool keccak_harness() + { + size_t inlen = sizeof(source); + int mdlen = (int)sizeof(md); + int ret = keccak(source, inlen, md, mdlen); + + if (md[0] != 0x00) + { + return true; + } + else if (!ret) + { + return true; + } + else + { + return false; + } + } } TEST(Crypto, Ostream) 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..76f2b2749 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); @@ -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/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----- |