diff options
104 files changed, 3557 insertions, 972 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..50297e146 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,62 @@ +name: continuous-integration/gh-actions/cli + +on: [push, pull_request] + +jobs: + build-macos: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: recursive + - name: update brew and install dependencies + run: brew update && brew install boost hidapi zmq libpgm unbound libsodium miniupnpc ldns expat libunwind-headers protobuf + - name: build + run: make -j3 + + build-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: recursive + - uses: numworks/setup-msys2@v1 + - name: update pacman + run: msys2do pacman -Syu --noconfirm + - name: install monero dependencies + run: msys2do pacman -S --noconfirm mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-zeromq mingw-w64-x86_64-libsodium mingw-w64-x86_64-hidapi mingw-w64-x86_64-protobuf-c mingw-w64-x86_64-libusb git + - name: build + run: msys2do make release-static-win64 -j3 + + build-ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: recursive + - name: remove bundled boost + run: sudo rm -rf /usr/local/share/boost + - name: update apt + run: sudo apt update + - name: install monero dependencies + run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev + - name: build + run: make -j3 + + test-ubuntu: + needs: build-ubuntu + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + with: + submodules: recursive + - name: remove bundled boost + run: sudo rm -rf /usr/local/share/boost + - name: update apt + run: sudo apt update + - name: install monero dependencies + run: sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev + - name: install requests + run: pip install requests + - name: tests + run: make release-test -j3 diff --git a/.gitignore b/.gitignore index 0ece7cb75..08c310e66 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,5 @@ nbproject .idea/ /testnet + +__pycache__/ @@ -81,11 +81,11 @@ debug-static-all: debug-static-win64: mkdir -p $(builddir)/debug - cd $(builddir)/debug && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys64 $(topdir) && $(MAKE) + cd $(builddir)/debug && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) $(topdir) && $(MAKE) debug-static-win32: mkdir -p $(builddir)/debug - cd $(builddir)/debug && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x32" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/32-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys32 $(topdir) && $(MAKE) + cd $(builddir)/debug && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Debug -D BUILD_TAG="win-x32" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/32-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) $(topdir) && $(MAKE) cmake-release: mkdir -p $(builddir)/release @@ -152,11 +152,11 @@ release-static-linux-i686: release-static-win64: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys64 $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="x86-64" -D BUILD_64=ON -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x64" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/64-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) $(topdir) && $(MAKE) release-static-win32: mkdir -p $(builddir)/release - cd $(builddir)/release && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x32" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/32-bit-toolchain.cmake -D MSYS2_FOLDER=c:/msys32 $(topdir) && $(MAKE) + cd $(builddir)/release && cmake -G "MSYS Makefiles" -D STATIC=ON -D ARCH="i686" -D BUILD_64=OFF -D CMAKE_BUILD_TYPE=Release -D BUILD_TAG="win-x32" -D CMAKE_TOOLCHAIN_FILE=$(topdir)/cmake/32-bit-toolchain.cmake -D MSYS2_FOLDER=$(shell cd ${MINGW_PREFIX}/.. && pwd -W) $(topdir) && $(MAKE) fuzz: mkdir -p $(builddir)/fuzz @@ -55,31 +55,11 @@ Our researchers are available on IRC in [#monero-research-lab on Freenode](https - You can subscribe to an [announcement listserv](https://lists.getmonero.org) to get critical announcements from the Monero core team. The announcement list can be very helpful for knowing when software updates are needed. ## Translations -The CLI wallet is available in different languages. If you want to help translate it, see our self-hosted localization platform, Weblate, on [translate.getmonero.org](https://translate.getmonero.org/projects/CLI/). Every translation *must* be uploaded on the platform, pull requests directly editing the code in this repository will be closed. If you need help with Weblate, you can find a guide with screenshots [here](https://github.com/monero-ecosystem/monero-translations/blob/master/weblate.md). +The CLI wallet is available in different languages. If you want to help translate it, see our self-hosted localization platform, Weblate, on [translate.getmonero.org]( https://translate.getmonero.org/projects/monero/cli-wallet/). Every translation *must* be uploaded on the platform, pull requests directly editing the code in this repository will be closed. If you need help with Weblate, you can find a guide with screenshots [here](https://github.com/monero-ecosystem/monero-translations/blob/master/weblate.md). If you need help/support/info about translations, contact the localization workgroup. You can find the complete list of contacts on the repository of the workgroup: [monero-translations](https://github.com/monero-ecosystem/monero-translations#contacts). -## Build Status - -### IMPORTANT - -These builds are of the master branch, which is used for active development and can be either unstable or incompatible with release software. Please compile release branches. - -| Operating System | Processor | Status | -| --------------------- | -------- |--------| -| Ubuntu 16.04 | i686 | [![Ubuntu 16.04 i686](https://build.getmonero.org/png?builder=monero-static-ubuntu-i686)](https://build.getmonero.org/builders/monero-static-ubuntu-i686) -| Ubuntu 16.04 | amd64 | [![Ubuntu 16.04 amd64](https://build.getmonero.org/png?builder=monero-static-ubuntu-amd64)](https://build.getmonero.org/builders/monero-static-ubuntu-amd64) -| Ubuntu 16.04 | armv7 | [![Ubuntu 16.04 armv7](https://build.getmonero.org/png?builder=monero-static-ubuntu-arm7)](https://build.getmonero.org/builders/monero-static-ubuntu-arm7) -| Debian Stable | armv8 | [![Debian armv8](https://build.getmonero.org/png?builder=monero-static-debian-armv8)](https://build.getmonero.org/builders/monero-static-debian-armv8) -| macOS 10.11 | amd64 | [![macOS 10.11 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.11)](https://build.getmonero.org/builders/monero-static-osx-10.11) -| macOS 10.12 | amd64 | [![macOS 10.12 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.12)](https://build.getmonero.org/builders/monero-static-osx-10.12) -| macOS 10.13 | amd64 | [![macOS 10.13 amd64](https://build.getmonero.org/png?builder=monero-static-osx-10.13)](https://build.getmonero.org/builders/monero-static-osx-10.13) -| FreeBSD 11 | amd64 | [![FreeBSD 11 amd64](https://build.getmonero.org/png?builder=monero-static-freebsd64)](https://build.getmonero.org/builders/monero-static-freebsd64) -| DragonFly BSD 4.6 | amd64 | [![DragonFly BSD amd64](https://build.getmonero.org/png?builder=monero-static-dragonflybsd-amd64)](https://build.getmonero.org/builders/monero-static-dragonflybsd-amd64) -| Windows (MSYS2/MinGW) | i686 | [![Windows (MSYS2/MinGW) i686](https://build.getmonero.org/png?builder=monero-static-win32)](https://build.getmonero.org/builders/monero-static-win32) -| Windows (MSYS2/MinGW) | amd64 | [![Windows (MSYS2/MinGW) amd64](https://build.getmonero.org/png?builder=monero-static-win64)](https://build.getmonero.org/builders/monero-static-win64) - ## Coverage | Type | Status | @@ -158,7 +138,8 @@ Dates are provided in the format YYYY-MM-DD. | XXXXXXX | XXX-XX-XX | XXX | vX.XX.X.X | vX.XX.X.X | XXX | X's indicate that these details have not been determined as of commit date. -* indicates estimate as of commit date + +\* indicates estimate as of commit date ## Release staging schedule and protocol @@ -177,31 +158,31 @@ sources are also used for statically-linked builds because distribution packages often include only shared library binaries (`.so`) but not static library archives (`.a`). -| Dep | Min. version | Vendored | Debian/Ubuntu pkg | Arch pkg | Fedora | Optional | Purpose | -| ------------ | ------------- | -------- | -------------------- | ------------ | ------------------- | -------- | --------------- | -| GCC | 4.7.3 | NO | `build-essential` | `base-devel` | `gcc` | NO | | -| CMake | 3.5 | NO | `cmake` | `cmake` | `cmake` | NO | | -| pkg-config | any | NO | `pkg-config` | `base-devel` | `pkgconf` | NO | | -| Boost | 1.58 | NO | `libboost-all-dev` | `boost` | `boost-devel` | NO | C++ libraries | -| OpenSSL | basically any | NO | `libssl-dev` | `openssl` | `openssl-devel` | NO | sha256 sum | -| libzmq | 3.0.0 | NO | `libzmq3-dev` | `zeromq` | `zeromq-devel` | NO | ZeroMQ library | -| OpenPGM | ? | NO | `libpgm-dev` | `libpgm` | `openpgm-devel` | NO | For ZeroMQ | -| libnorm[2] | ? | NO | `libnorm-dev` | | | YES | For ZeroMQ | -| libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | `unbound-devel` | NO | DNS resolver | -| libsodium | ? | NO | `libsodium-dev` | `libsodium` | `libsodium-devel` | NO | cryptography | -| libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | YES | Stack traces | -| liblzma | any | NO | `liblzma-dev` | `xz` | `xz-devel` | YES | For libunwind | -| libreadline | 6.3.0 | NO | `libreadline6-dev` | `readline` | `readline-devel` | YES | Input editing | -| ldns | 1.6.17 | NO | `libldns-dev` | `ldns` | `ldns-devel` | YES | SSL toolkit | -| expat | 1.1 | NO | `libexpat1-dev` | `expat` | `expat-devel` | YES | XML parsing | -| GTest | 1.5 | YES | `libgtest-dev`[1] | `gtest` | `gtest-devel` | YES | Test suite | -| Doxygen | any | NO | `doxygen` | `doxygen` | `doxygen` | YES | Documentation | -| Graphviz | any | NO | `graphviz` | `graphviz` | `graphviz` | YES | Documentation | -| lrelease | ? | NO | `qttools5-dev-tools` | `qt5-tools` | `qt5-linguist` | YES | Translations | -| libhidapi | ? | NO | `libhidapi-dev` | `hidapi` | `hidapi-devel` | YES | Hardware wallet | -| libusb | ? | NO | `libusb-dev` | `libusb` | `libusb-devel` | YES | Hardware wallet | -| libprotobuf | ? | NO | `libprotobuf-dev` | `protobuf` | `protobuf-devel` | YES | Hardware wallet | -| protoc | ? | NO | `protobuf-compiler` | `protobuf` | `protobuf-compiler` | YES | Hardware wallet | +| Dep | Min. version | Vendored | Debian/Ubuntu pkg | Arch pkg | Void pkg | Fedora pkg | Optional | Purpose | +| ------------ | ------------- | -------- | -------------------- | ------------ | ------------------ | ------------------- | -------- | --------------- | +| GCC | 4.7.3 | NO | `build-essential` | `base-devel` | `base-devel` | `gcc` | NO | | +| CMake | 3.5 | NO | `cmake` | `cmake` | `cmake` | `cmake` | NO | | +| pkg-config | any | NO | `pkg-config` | `base-devel` | `base-devel` | `pkgconf` | NO | | +| Boost | 1.58 | NO | `libboost-all-dev` | `boost` | `boost-devel` | `boost-devel` | NO | C++ libraries | +| OpenSSL | basically any | NO | `libssl-dev` | `openssl` | `libressl-devel` | `openssl-devel` | NO | sha256 sum | +| libzmq | 3.0.0 | NO | `libzmq3-dev` | `zeromq` | `zeromq-devel` | `zeromq-devel` | NO | ZeroMQ library | +| OpenPGM | ? | NO | `libpgm-dev` | `libpgm` | | `openpgm-devel` | NO | For ZeroMQ | +| libnorm[2] | ? | NO | `libnorm-dev` | | | | YES | For ZeroMQ | +| libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | `unbound-devel` | `unbound-devel` | NO | DNS resolver | +| libsodium | ? | NO | `libsodium-dev` | `libsodium` | `libsodium-devel` | `libsodium-devel` | NO | cryptography | +| libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | `libunwind-devel` | YES | Stack traces | +| liblzma | any | NO | `liblzma-dev` | `xz` | `liblzma-devel` | `xz-devel` | YES | For libunwind | +| libreadline | 6.3.0 | NO | `libreadline6-dev` | `readline` | `readline-devel` | `readline-devel` | YES | Input editing | +| ldns | 1.6.17 | NO | `libldns-dev` | `ldns` | `libldns-devel` | `ldns-devel` | YES | SSL toolkit | +| expat | 1.1 | NO | `libexpat1-dev` | `expat` | `expat-devel` | `expat-devel` | YES | XML parsing | +| GTest | 1.5 | YES | `libgtest-dev`[1] | `gtest` | `gtest-devel` | `gtest-devel` | YES | Test suite | +| Doxygen | any | NO | `doxygen` | `doxygen` | `doxygen` | `doxygen` | YES | Documentation | +| Graphviz | any | NO | `graphviz` | `graphviz` | `graphviz` | `graphviz` | YES | Documentation | +| lrelease | ? | NO | `qttools5-dev-tools` | `qt5-tools` | `qt5-tools` | `qt5-linguist` | YES | Translations | +| libhidapi | ? | NO | `libhidapi-dev` | `hidapi` | `hidapi-devel` | `hidapi-devel` | YES | Hardware wallet | +| libusb | ? | NO | `libusb-dev` | `libusb` | `libusb-devel` | `libusb-devel` | YES | Hardware wallet | +| libprotobuf | ? | NO | `libprotobuf-dev` | `protobuf` | `protobuf-devel` | `protobuf-devel` | YES | Hardware wallet | +| protoc | ? | NO | `protobuf-compiler` | `protobuf` | `protobuf` | `protobuf-compiler` | YES | Hardware wallet | [1] On Debian/Ubuntu `libgtest-dev` only includes sources and headers. You must diff --git a/contrib/depends/packages/sodium.mk b/contrib/depends/packages/sodium.mk index dbf86fc5a..462bd2415 100644 --- a/contrib/depends/packages/sodium.mk +++ b/contrib/depends/packages/sodium.mk @@ -1,8 +1,8 @@ package=sodium -$(package)_version=1.0.16 +$(package)_version=1.0.18 $(package)_download_path=https://download.libsodium.org/libsodium/releases/ $(package)_file_name=libsodium-$($(package)_version).tar.gz -$(package)_sha256_hash=eeadc7e1e1bcef09680fb4837d448fbdf57224978f865ac1c16745868fbd0533 +$(package)_sha256_hash=6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1 $(package)_patches=fix-whitespace.patch define $(package)_set_vars diff --git a/contrib/depends/patches/sodium/fix-whitespace.patch b/contrib/depends/patches/sodium/fix-whitespace.patch index efbfe4e83..c3d3af0b4 100644 --- a/contrib/depends/patches/sodium/fix-whitespace.patch +++ b/contrib/depends/patches/sodium/fix-whitespace.patch @@ -5,8 +5,8 @@ index b29f769..ca008ae 100755 @@ -591,7 +591,7 @@ MAKEFLAGS= PACKAGE_NAME='libsodium' PACKAGE_TARNAME='libsodium' - PACKAGE_VERSION='1.0.16' --PACKAGE_STRING='libsodium 1.0.16' + PACKAGE_VERSION='1.0.18' +-PACKAGE_STRING='libsodium 1.0.18' +PACKAGE_STRING='libsodium' PACKAGE_BUGREPORT='https://github.com/jedisct1/libsodium/issues' PACKAGE_URL='https://github.com/jedisct1/libsodium' diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 13747b0c8..1b716fca4 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -605,10 +605,17 @@ eof: std::unique_ptr<boost::thread> m_console_thread; async_console_handler m_console_handler; public: + ~console_handlers_binder() { + stop_handling(); + if (m_console_thread.get() != nullptr) + { + m_console_thread->join(); + } + } + bool start_handling(std::function<std::string(void)> prompt, const std::string& usage_string = "", std::function<void(void)> exit_handler = NULL) { m_console_thread.reset(new boost::thread(boost::bind(&console_handlers_binder::run_handling, this, prompt, usage_string, exit_handler))); - m_console_thread->detach(); return true; } bool start_handling(const std::string &prompt, const std::string& usage_string = "", std::function<void(void)> exit_handler = NULL) diff --git a/contrib/epee/include/md5_l.h b/contrib/epee/include/md5_l.h index a45d91bc8..bc7122650 100644 --- a/contrib/epee/include/md5_l.h +++ b/contrib/epee/include/md5_l.h @@ -85,7 +85,7 @@ namespace md5 MD5Update( &ctx, input, ilen ); MD5Final( output, &ctx); - memset( &ctx, 0, sizeof( MD5_CTX) ); + memwipe( &ctx, sizeof( MD5_CTX )); return true; } diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 07ed8157b..0c0653cd6 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -120,6 +120,7 @@ #define BEGIN_JSON_RPC_MAP(uri) else if(query_info.m_URI == uri) \ { \ uint64_t ticks = epee::misc_utils::get_tick_count(); \ + response_info.m_mime_tipe = "application/json"; \ epee::serialization::portable_storage ps; \ if(!ps.load_from_json(query_info.m_body)) \ { \ @@ -148,6 +149,7 @@ #define PREPARE_OBJECTS_FROM_JSON(command_type) \ handled = true; \ + response_info.m_mime_tipe = "application/json"; \ boost::value_initialized<epee::json_rpc::request<command_type::request> > req_; \ epee::json_rpc::request<command_type::request>& req = static_cast<epee::json_rpc::request<command_type::request>&>(req_);\ if(!req.load(ps)) \ diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h index 81545e502..9446e3588 100644 --- a/contrib/epee/include/net/net_helper.h +++ b/contrib/epee/include/net/net_helper.h @@ -103,8 +103,8 @@ namespace net_utils blocked_mode_client() : m_io_service(), m_ctx(boost::asio::ssl::context::tlsv12), - m_connector(direct_connect{}), m_ssl_socket(new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(m_io_service, m_ctx)), + m_connector(direct_connect{}), m_ssl_options(epee::net_utils::ssl_support_t::e_ssl_support_autodetect), m_initialized(true), m_connected(false), diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h index e100452ca..59895535f 100644 --- a/contrib/epee/include/span.h +++ b/contrib/epee/include/span.h @@ -110,7 +110,8 @@ namespace epee constexpr std::size_t size() const noexcept { return len; } constexpr std::size_t size_bytes() const noexcept { return size() * sizeof(value_type); } - const T &operator[](size_t idx) const { return ptr[idx]; } + T &operator[](size_t idx) noexcept { return ptr[idx]; } + const T &operator[](size_t idx) const noexcept { return ptr[idx]; } private: T* ptr; diff --git a/contrib/epee/include/storages/levin_abstract_invoke2.h b/contrib/epee/include/storages/levin_abstract_invoke2.h index 06eb9bdaf..b18e04a27 100644 --- a/contrib/epee/include/storages/levin_abstract_invoke2.h +++ b/contrib/epee/include/storages/levin_abstract_invoke2.h @@ -34,10 +34,28 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" +namespace +{ + template<typename context_t> + void on_levin_traffic(const context_t &context, bool initiator, bool sent, bool error, size_t bytes, const char *category) + { + MCINFO("net.p2p.traffic", context << bytes << " bytes " << (sent ? "sent" : "received") << (error ? "/corrupt" : "") + << " for category " << category << " initiated by " << (initiator ? "us" : "peer")); + } + template<typename context_t> + void on_levin_traffic(const context_t &context, bool initiator, bool sent, bool error, size_t bytes, int command) + { + char buf[32]; + snprintf(buf, sizeof(buf), "command-%u", command); + return on_levin_traffic(context, initiator, sent, error, bytes, buf); + } +} + namespace epee { namespace net_utils { +#if 0 template<class t_arg, class t_result, class t_transport> bool invoke_remote_command2(int command, const t_arg& out_struct, t_result& result_struct, t_transport& transport) { @@ -83,16 +101,18 @@ namespace epee } return true; } +#endif template<class t_arg, class t_result, class t_transport> - bool invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_result& result_struct, t_transport& transport) + bool invoke_remote_command2(const epee::net_utils::connection_context_base context, int command, const t_arg& out_struct, t_result& result_struct, t_transport& transport) { - + const boost::uuids::uuid &conn_id = context.m_connection_id; typename serialization::portable_storage stg; out_struct.store(stg); std::string buff_to_send, buff_to_recv; stg.store_to_binary(buff_to_send); + on_levin_traffic(context, true, true, false, buff_to_send.size(), command); int res = transport.invoke(command, buff_to_send, buff_to_recv, conn_id); if( res <=0 ) { @@ -102,24 +122,30 @@ namespace epee typename serialization::portable_storage stg_ret; if(!stg_ret.load_from_binary(buff_to_recv)) { + on_levin_traffic(context, true, false, true, buff_to_recv.size(), command); LOG_ERROR("Failed to load_from_binary on command " << command); return false; } + on_levin_traffic(context, true, false, false, buff_to_recv.size(), command); return result_struct.load(stg_ret); } template<class t_result, class t_arg, class callback_t, class t_transport> - bool async_invoke_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke_remote_command2(const epee::net_utils::connection_context_base &context, int command, const t_arg& out_struct, t_transport& transport, const callback_t &cb, size_t inv_timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) { + const boost::uuids::uuid &conn_id = context.m_connection_id; typename serialization::portable_storage stg; const_cast<t_arg&>(out_struct).store(stg);//TODO: add true const support to searilzation std::string buff_to_send; stg.store_to_binary(buff_to_send); + on_levin_traffic(context, true, true, false, buff_to_send.size(), command); int res = transport.invoke_async(command, epee::strspan<uint8_t>(buff_to_send), conn_id, [cb, command](int code, const epee::span<const uint8_t> buff, typename t_transport::connection_context& context)->bool { t_result result_struct = AUTO_VAL_INIT(result_struct); if( code <=0 ) { + if (!buff.empty()) + on_levin_traffic(context, true, false, true, buff.size(), command); LOG_PRINT_L1("Failed to invoke command " << command << " return code " << code); cb(code, result_struct, context); return false; @@ -127,16 +153,19 @@ namespace epee serialization::portable_storage stg_ret; if(!stg_ret.load_from_binary(buff)) { + on_levin_traffic(context, true, false, true, buff.size(), command); LOG_ERROR("Failed to load_from_binary on command " << command); cb(LEVIN_ERROR_FORMAT, result_struct, context); return false; } if (!result_struct.load(stg_ret)) { + on_levin_traffic(context, true, false, true, buff.size(), command); LOG_ERROR("Failed to load result struct on command " << command); cb(LEVIN_ERROR_FORMAT, result_struct, context); return false; } + on_levin_traffic(context, true, false, false, buff.size(), command); cb(code, result_struct, context); return true; }, inv_timeout); @@ -149,14 +178,15 @@ namespace epee } template<class t_arg, class t_transport> - bool notify_remote_command2(boost::uuids::uuid conn_id, int command, const t_arg& out_struct, t_transport& transport) + bool notify_remote_command2(const typename t_transport::connection_context &context, int command, const t_arg& out_struct, t_transport& transport) { - + const boost::uuids::uuid &conn_id = context.m_connection_id; serialization::portable_storage stg; out_struct.store(stg); std::string buff_to_send; stg.store_to_binary(buff_to_send); + on_levin_traffic(context, true, true, false, buff_to_send.size(), command); int res = transport.notify(command, epee::strspan<uint8_t>(buff_to_send), conn_id); if(res <=0 ) { @@ -173,6 +203,7 @@ namespace epee serialization::portable_storage strg; if(!strg.load_from_binary(in_buff)) { + on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load_from_binary in command " << command); return -1; } @@ -181,9 +212,11 @@ namespace epee if (!static_cast<t_in_type&>(in_struct).load(strg)) { + on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load in_struct in command " << command); return -1; } + on_levin_traffic(context, false, false, false, in_buff.size(), command); int res = cb(command, static_cast<t_in_type&>(in_struct), static_cast<t_out_type&>(out_struct), context); serialization::portable_storage strg_out; static_cast<t_out_type&>(out_struct).store(strg_out); @@ -193,6 +226,7 @@ namespace epee LOG_ERROR("Failed to store_to_binary in command" << command); return -1; } + on_levin_traffic(context, false, true, false, buff_out.size(), command); return res; } @@ -203,15 +237,18 @@ namespace epee serialization::portable_storage strg; if(!strg.load_from_binary(in_buff)) { + on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load_from_binary in notify " << command); return -1; } boost::value_initialized<t_in_type> in_struct; if (!static_cast<t_in_type&>(in_struct).load(strg)) { + on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load in_struct in notify " << command); return -1; } + on_levin_traffic(context, false, false, false, in_buff.size(), command); return cb(command, in_struct, context); } @@ -296,6 +333,7 @@ namespace epee #define END_INVOKE_MAP2() \ LOG_ERROR("Unknown command:" << command); \ + on_levin_traffic(context, false, false, true, in_buff.size(), "invalid-command"); \ return LEVIN_ERROR_CONNECTION_HANDLER_NOT_DEFINED; \ } } diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 1be5eb5e1..319c0121b 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -188,8 +188,10 @@ POP_WARNINGS return boost::lexical_cast<std::string>(val); } //---------------------------------------------------------------------------- - inline std::string to_string_hex(uint32_t val) + template<typename T> + inline std::string to_string_hex(const T &val) { + static_assert(std::is_arithmetic<T>::value, "only arithmetic types"); std::stringstream ss; ss << std::hex << val; std::string s; diff --git a/contrib/epee/src/http_auth.cpp b/contrib/epee/src/http_auth.cpp index 289069daa..5f4907cc2 100644 --- a/contrib/epee/src/http_auth.cpp +++ b/contrib/epee/src/http_auth.cpp @@ -584,8 +584,8 @@ namespace explicit server_parameters(const auth_message& request, const DigestIter& digest) : nonce(request.nonce) , opaque(request.opaque) - , stale(request.stale) , realm(request.realm) + , stale(request.stale) , value_generator() , index(boost::fusion::distance(boost::fusion::begin(digest_algorithms), digest)) { diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index 16454fce0..06997d3ba 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -43,6 +43,10 @@ // openssl req -new -key /tmp/KEY -out /tmp/REQ // openssl x509 -req -days 999999 -sha256 -in /tmp/REQ -signkey /tmp/KEY -out /tmp/CERT +#ifdef _WIN32 +static void add_windows_root_certs(SSL_CTX *ctx) noexcept; +#endif + namespace { struct openssl_bio_free @@ -324,7 +328,12 @@ boost::asio::ssl::context ssl_options_t::create_context() const switch (verification) { case ssl_verification_t::system_ca: +#ifdef _WIN32 + try { add_windows_root_certs(ssl_context.native_handle()); } + catch (const std::exception &e) { ssl_context.set_default_verify_paths(); } +#else ssl_context.set_default_verify_paths(); +#endif break; case ssl_verification_t::user_certificates: ssl_context.set_verify_depth(0); @@ -558,3 +567,36 @@ bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s) } // namespace } // namespace +#ifdef _WIN32 + +// https://stackoverflow.com/questions/40307541 +// Because Windows always has to do things wonkily +#include <wincrypt.h> +static void add_windows_root_certs(SSL_CTX *ctx) noexcept +{ + HCERTSTORE hStore = CertOpenSystemStore(0, "ROOT"); + if (hStore == NULL) { + return; + } + + X509_STORE *store = X509_STORE_new(); + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != NULL) { + // convert from DER to internal format + X509 *x509 = d2i_X509(NULL, + (const unsigned char **)&pContext->pbCertEncoded, + pContext->cbCertEncoded); + if(x509 != NULL) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + // attach X509_STORE to boost ssl context + SSL_CTX_set_cert_store(ctx, store); +} +#endif + diff --git a/contrib/gitian/gitian-build.py b/contrib/gitian/gitian-build.py index 64eb218bb..0b36fb4a1 100755 --- a/contrib/gitian/gitian-build.py +++ b/contrib/gitian/gitian-build.py @@ -66,7 +66,7 @@ def rebuild(): print('\nCompiling ' + args.version + ' ' + os_name) infile = 'inputs/monero/contrib/gitian/gitian-' + tag_name + '.yml' subprocess.check_call(['bin/gbuild', '-j', args.jobs, '-m', args.memory, '--commit', 'monero='+args.commit, '--url', 'monero='+args.url, infile]) - subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-linux', '--destination', '../sigs/', infile]) + subprocess.check_call(['bin/gsign', '-p', args.sign_prog, '--signer', args.signer, '--release', args.version+'-'+tag_name, '--destination', '../sigs/', infile]) subprocess.check_call('mv build/out/monero-*.' + suffix + ' ../out/'+args.version, shell=True) print('Moving var/install.log to var/install-' + tag_name + '.log') subprocess.check_call('mv var/install.log var/install-' + tag_name + '.log', shell=True) diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index 5c756bcdf..8439bec0b 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -1243,7 +1243,7 @@ bool OS::termSupportsColor(void) { std::string term = getEnvironmentVariable("TERM", ""); return term == "xterm" || term == "xterm-color" || term == "xterm-256color" || term == "screen" || term == "linux" || term == "cygwin" - || term == "screen-256color"; + || term == "screen-256color" || term == "screen.xterm-256color"; } // DateTime diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f332af3d3..d45363e24 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -113,12 +113,10 @@ add_subdirectory(lmdb) add_subdirectory(multisig) add_subdirectory(net) add_subdirectory(hardforks) -if(NOT IOS) - add_subdirectory(blockchain_db) -endif() +add_subdirectory(blockchain_db) add_subdirectory(mnemonics) +add_subdirectory(rpc) if(NOT IOS) - add_subdirectory(rpc) add_subdirectory(serialization) endif() add_subdirectory(wallet) diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 63ac38a88..1a6a19da5 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -44,6 +44,71 @@ using epee::string_tools::pod_to_hex; namespace cryptonote { +bool matches_category(relay_method method, relay_category category) noexcept +{ + switch (category) + { + default: + return false; + case relay_category::all: + return true; + case relay_category::relayable: + if (method == relay_method::none) + return false; + return true; + case relay_category::broadcasted: + case relay_category::legacy: + break; + } + // check for "broadcasted" or "legacy" methods: + switch (method) + { + default: + case relay_method::local: + return false; + case relay_method::block: + case relay_method::fluff: + return true; + case relay_method::none: + break; + } + return category == relay_category::legacy; +} + +void txpool_tx_meta_t::set_relay_method(relay_method method) noexcept +{ + kept_by_block = 0; + do_not_relay = 0; + is_local = 0; + + switch (method) + { + case relay_method::none: + do_not_relay = 1; + break; + case relay_method::local: + is_local = 1; + break; + default: + case relay_method::fluff: + break; + case relay_method::block: + kept_by_block = 1; + break; + } +} + +relay_method txpool_tx_meta_t::get_relay_method() const noexcept +{ + if (kept_by_block) + return relay_method::block; + if (do_not_relay) + return relay_method::none; + if (is_local) + return relay_method::local; + return relay_method::fluff; +} + const command_line::arg_descriptor<std::string> arg_db_sync_mode = { "db-sync-mode" , "Specify sync option, using format [safe|fast|fastest]:[sync|async]:[<nblocks_per_sync>[blocks]|<nbytes_per_sync>[bytes]]." @@ -924,4 +989,23 @@ void BlockchainDB::fixup() batch_stop(); } +bool BlockchainDB::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category) +{ + try + { + txpool_tx_meta_t meta{}; + if (!get_txpool_tx_meta(tx_hash, meta)) + { + MERROR("Failed to get tx meta from txpool"); + return false; + } + return meta.matches(category); + } + catch (const std::exception &e) + { + MERROR("Failed to get tx meta from txpool: " << e.what()); + } + return false; +} + } // namespace cryptonote diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index d1e4919be..e9fc85803 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -39,6 +39,7 @@ #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/difficulty.h" #include "cryptonote_basic/hardfork.h" +#include "cryptonote_protocol/enums.h" /** \file * Cryptonote Blockchain Database Interface @@ -105,6 +106,16 @@ typedef std::pair<crypto::hash, uint64_t> tx_out_index; extern const command_line::arg_descriptor<std::string> arg_db_sync_mode; extern const command_line::arg_descriptor<bool, false> arg_db_salvage; +enum class relay_category : uint8_t +{ + broadcasted = 0,//!< Public txes received via block/fluff + relayable, //!< Every tx not marked `relay_method::none` + legacy, //!< `relay_category::broadcasted` + `relay_method::none` for rpc relay requests or historical reasons + all //!< Everything in the db +}; + +bool matches_category(relay_method method, relay_category category) noexcept; + #pragma pack(push, 1) /** @@ -156,11 +167,22 @@ struct txpool_tx_meta_t uint8_t do_not_relay; uint8_t double_spend_seen: 1; uint8_t pruned: 1; - uint8_t bf_padding: 6; + uint8_t is_local: 1; + uint8_t bf_padding: 5; uint8_t padding[76]; // till 192 bytes + + void set_relay_method(relay_method method) noexcept; + relay_method get_relay_method() const noexcept; + + //! See `relay_category` description + bool matches(const relay_category category) const noexcept + { + return matches_category(get_relay_method(), category); + } }; + #define DBF_SAFE 1 #define DBF_FAST 2 #define DBF_FASTEST 4 @@ -1253,6 +1275,22 @@ public: virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0; /** + * @brief fetches a number of pruned transaction blob from the given hash, in canonical blockchain order + * + * The subclass should return the pruned transactions stored from the one with the given + * hash. + * + * If the first transaction does not exist, the subclass should return false. + * If the first transaction exists, but there are fewer transactions starting with it + * than requested, the subclass should return false. + * + * @param h the hash to look for + * + * @return true iff the transactions were found + */ + virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const = 0; + + /** * @brief fetches the prunable transaction blob with the given hash * * The subclass should return the prunable transaction stored which has the given @@ -1465,12 +1503,12 @@ public: /** * @brief get the number of transactions in the txpool */ - virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const = 0; + virtual uint64_t get_txpool_tx_count(relay_category tx_category = relay_category::broadcasted) const = 0; /** - * @brief check whether a txid is in the txpool + * @brief check whether a txid is in the txpool and meets tx_category requirements */ - virtual bool txpool_has_tx(const crypto::hash &txid) const = 0; + virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const = 0; /** * @brief remove a txpool transaction @@ -1494,10 +1532,11 @@ public: * * @param txid the transaction id of the transation to lookup * @param bd the blob to return + * @param tx_category for filtering out hidden/private txes * - * @return true if the txid was in the txpool, false otherwise + * @return True iff `txid` is in the pool and meets `tx_category` requirements */ - virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const = 0; + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const = 0; /** * @brief get a txpool transaction's blob @@ -1506,7 +1545,17 @@ public: * * @return the blob for that transaction */ - virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const = 0; + virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const = 0; + + /** + * @brief Check if `tx_hash` relay status is in `category`. + * + * @param tx_hash hash of the transaction to lookup + * @param category relay status category to test against + * + * @return True if `tx_hash` latest relay status is in `category`. + */ + bool txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category); /** * @brief prune output data for the given amount @@ -1604,7 +1653,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, bool include_unrelayed_txes = true) 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, relay_category category = relay_category::broadcasted) 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 f978ef307..5093015f2 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -163,7 +163,15 @@ int BlockchainLMDB::compare_string(const MDB_val *a, const MDB_val *b) { const char *va = (const char*) a->mv_data; const char *vb = (const char*) b->mv_data; - return strcmp(va, vb); + const size_t sz = std::min(a->mv_size, b->mv_size); + int ret = strncmp(va, vb, sz); + if (ret) + return ret; + if (a->mv_size < b->mv_size) + return -1; + if (a->mv_size > b->mv_size) + return 1; + return 0; } } @@ -1771,7 +1779,7 @@ void BlockchainLMDB::update_txpool_tx(const crypto::hash &txid, const txpool_tx_ } } -uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const +uint64_t BlockchainLMDB::get_txpool_tx_count(relay_category category) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1781,7 +1789,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const TXN_PREFIX_RDONLY(); - if (include_unrelayed_txes) + if (category == relay_category::all) { // No filtering, we can get the number of tx the "fast" way MDB_stat db_stats; @@ -1807,7 +1815,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const 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) + if (meta.matches(category)) ++num_entries; } } @@ -1816,7 +1824,7 @@ uint64_t BlockchainLMDB::get_txpool_tx_count(bool include_unrelayed_txes) const return num_entries; } -bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const +bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid, relay_category tx_category) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1825,11 +1833,21 @@ bool BlockchainLMDB::txpool_has_tx(const crypto::hash& txid) const RCURSOR(txpool_meta) MDB_val k = {sizeof(txid), (void *)&txid}; - auto result = mdb_cursor_get(m_cur_txpool_meta, &k, NULL, MDB_SET); + MDB_val v; + auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); if (result != 0 && result != MDB_NOTFOUND) throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); + if (result == MDB_NOTFOUND) + return false; + + bool found = true; + if (tx_category != relay_category::all) + { + const txpool_tx_meta_t &meta = *(const txpool_tx_meta_t*)v.mv_data; + found = meta.matches(tx_category); + } TXN_POSTFIX_RDONLY(); - return result != MDB_NOTFOUND; + return found; } void BlockchainLMDB::remove_txpool_tx(const crypto::hash& txid) @@ -1883,7 +1901,7 @@ bool BlockchainLMDB::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta return true; } -bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const +bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -1893,6 +1911,21 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::bl MDB_val k = {sizeof(txid), (void *)&txid}; MDB_val v; + + // if filtering, make sure those requirements are met before copying blob + if (tx_category != relay_category::all) + { + auto result = mdb_cursor_get(m_cur_txpool_meta, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) + return false; + if (result != 0) + throw1(DB_ERROR(lmdb_error("Error finding txpool tx meta: ", result).c_str())); + + const txpool_tx_meta_t& meta = *(const txpool_tx_meta_t*)v.mv_data; + if (!meta.matches(tx_category)) + return false; + } + auto result = mdb_cursor_get(m_cur_txpool_blob, &k, &v, MDB_SET); if (result == MDB_NOTFOUND) return false; @@ -1904,10 +1937,10 @@ bool BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::bl return true; } -cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid) const +cryptonote::blobdata BlockchainLMDB::get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const { cryptonote::blobdata bd; - if (!get_txpool_tx_blob(txid, bd)) + if (!get_txpool_tx_blob(txid, bd, tx_category)) throw1(DB_ERROR("Tx not found in txpool: ")); return bd; } @@ -2245,7 +2278,7 @@ bool BlockchainLMDB::check_pruning() return prune_worker(prune_mode_check, 0); } -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 +bool BlockchainLMDB::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, relay_category category) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -2269,8 +2302,7 @@ 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 + if (!meta.matches(category)) continue; const cryptonote::blobdata *passed_bd = NULL; cryptonote::blobdata bd; @@ -3033,6 +3065,48 @@ bool BlockchainLMDB::get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobd return true; } +bool BlockchainLMDB::get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + if (!count) + return true; + + TXN_PREFIX_RDONLY(); + RCURSOR(tx_indices); + RCURSOR(txs_pruned); + + bd.reserve(bd.size() + count); + + MDB_val_set(v, h); + MDB_val result; + int res = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + if (res == MDB_NOTFOUND) + return false; + if (res) + throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", res).c_str())); + + const txindex *tip = (const txindex *)v.mv_data; + const uint64_t id = tip->data.tx_id; + MDB_val_set(val_tx_id, id); + MDB_cursor_op op = MDB_SET; + while (count--) + { + res = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &result, op); + op = MDB_NEXT; + if (res == MDB_NOTFOUND) + return false; + if (res) + throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx blob", res).c_str())); + bd.emplace_back(reinterpret_cast<char*>(result.mv_data), result.mv_size); + } + + TXN_POSTFIX_RDONLY(); + + return true; +} + bool BlockchainLMDB::get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 61a551476..7c0b4c72c 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -254,6 +254,7 @@ public: virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; + virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const; virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const; @@ -281,12 +282,12 @@ public: virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, 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(bool include_unrelayed_txes = true) const; - virtual bool txpool_has_tx(const crypto::hash &txid) const; + virtual uint64_t get_txpool_tx_count(relay_category category = relay_category::broadcasted) const; + virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const; virtual void remove_txpool_tx(const crypto::hash& txid); virtual bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) 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 get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata& bd, relay_category tx_category) const; + virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const; virtual uint32_t get_blockchain_pruning_seed() const; virtual bool prune_blockchain(uint32_t pruning_seed = 0); virtual bool update_pruning(); @@ -298,7 +299,7 @@ public: virtual uint64_t get_alt_block_count(); virtual void drop_alt_blocks(); - 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_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob = false, relay_category category = relay_category::broadcasted) const; virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index ac19fae25..46de38c7e 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -69,6 +69,7 @@ public: virtual cryptonote::blobdata get_block_blob(const crypto::hash& h) const override { return cryptonote::blobdata(); } virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } + virtual bool get_pruned_tx_blobs_from(const crypto::hash& h, size_t count, std::vector<cryptonote::blobdata> &bd) const { return false; } virtual bool get_prunable_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const override { return false; } virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const override { return false; } virtual uint64_t get_block_height(const crypto::hash& h) const override { return 0; } @@ -126,14 +127,14 @@ public: virtual void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const cryptonote::txpool_tx_meta_t& details) override {} virtual void update_txpool_tx(const crypto::hash &txid, const cryptonote::txpool_tx_meta_t& details) override {} - virtual uint64_t get_txpool_tx_count(bool include_unrelayed_txes = true) const override { return 0; } - virtual bool txpool_has_tx(const crypto::hash &txid) const override { return false; } + virtual uint64_t get_txpool_tx_count(relay_category tx_relay = relay_category::broadcasted) const override { return 0; } + virtual bool txpool_has_tx(const crypto::hash &txid, relay_category tx_category) const override { return false; } virtual void remove_txpool_tx(const crypto::hash& txid) override {} virtual bool get_txpool_tx_meta(const crypto::hash& txid, cryptonote::txpool_tx_meta_t &meta) const override { return false; } - virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const override { return false; } + virtual bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const override { return false; } virtual uint64_t get_database_size() const override { return 0; } - virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid) const override { return ""; } - virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, bool include_unrelayed_txes = false) const override { return false; } + virtual cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const override { return ""; } + virtual bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const cryptonote::txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, relay_category category = relay_category::broadcasted) const override { return false; } virtual void add_block( const cryptonote::block& blk , size_t block_weight diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 5d039d7f4..852e9cf4f 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -174,7 +174,7 @@ int check_flush(cryptonote::core &core, std::vector<block_complete_entry> &block for(auto& tx_blob: block_entry.txs) { tx_verification_context tvc = AUTO_VAL_INIT(tvc); - core.handle_incoming_tx(tx_blob, tvc, true, true, false); + core.handle_incoming_tx(tx_blob, tvc, relay_method::block, true); if(tvc.m_verifivation_failed) { MERROR("transaction verification failed, tx_id = " diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 2f66d54aa..0d18b8819 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -264,12 +264,12 @@ skip: { throw std::runtime_error("Aborting: tx == null_hash"); } - if (!db->get_tx_blob(tx_id, bd)) + if (!db->get_pruned_tx_blob(tx_id, bd)) { throw std::runtime_error("Aborting: tx not found"); } transaction tx; - if (!parse_and_validate_tx_from_blob(bd, tx)) + if (!parse_and_validate_tx_base_from_blob(bd, tx)) { LOG_PRINT_L0("Bad txn from db"); return 1; diff --git a/src/common/download.cpp b/src/common/download.cpp index f07d6798d..2b6a3f9d3 100644 --- a/src/common/download.cpp +++ b/src/common/download.cpp @@ -107,13 +107,17 @@ namespace tools MINFO("Content-Length: " << length); content_length = length; boost::filesystem::path path(control->path); - boost::filesystem::space_info si = boost::filesystem::space(path); - if (si.available < (size_t)content_length) + try { - const uint64_t avail = (si.available + 1023) / 1024, needed = (content_length + 1023) / 1024; - MERROR("Not enough space to download " << needed << " kB to " << path << " (" << avail << " kB available)"); - return false; + boost::filesystem::space_info si = boost::filesystem::space(path); + if (si.available < (size_t)content_length) + { + const uint64_t avail = (si.available + 1023) / 1024, needed = (content_length + 1023) / 1024; + MERROR("Not enough space to download " << needed << " kB to " << path << " (" << avail << " kB available)"); + return false; + } } + catch (const std::exception &e) { MWARNING("Failed to check for free space: " << e.what()); } } if (offset > 0) { diff --git a/src/common/perf_timer.h b/src/common/perf_timer.h index ea2237348..29a37e655 100644 --- a/src/common/perf_timer.h +++ b/src/common/perf_timer.h @@ -84,7 +84,7 @@ void set_performance_timer_log_level(el::Level level); #define PERF_TIMER_START_UNIT(name, unit) std::unique_ptr<tools::LoggingPerformanceTimer> PERF_TIMER_NAME(name)(new tools::LoggingPerformanceTimer(#name, "perf." MONERO_DEFAULT_LOG_CATEGORY, unit, el::Level::Info)) #define PERF_TIMER_START(name) PERF_TIMER_START_UNIT(name, 1000000) #define PERF_TIMER_STOP(name) do { PERF_TIMER_NAME(name).reset(NULL); } while(0) -#define PERF_TIMER_PAUSE(name) PERF_TIMER_NAME(name)->pause() -#define PERF_TIMER_RESUME(name) PERF_TIMER_NAME(name)->resume() +#define PERF_TIMER_PAUSE(name) PERF_TIMER_NAME(name).pause() +#define PERF_TIMER_RESUME(name) PERF_TIMER_NAME(name).resume() } diff --git a/src/common/threadpool.cpp b/src/common/threadpool.cpp index 2748c798c..18204eeee 100644 --- a/src/common/threadpool.cpp +++ b/src/common/threadpool.cpp @@ -37,16 +37,14 @@ static __thread bool is_leaf = false; namespace tools { threadpool::threadpool(unsigned int max_threads) : running(true), active(0) { - boost::thread::attributes attrs; - attrs.set_stack_size(THREAD_STACK_SIZE); - max = max_threads ? max_threads : tools::get_max_concurrency(); - size_t i = max ? max - 1 : 0; - while(i--) { - threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this, false))); - } + create(max_threads); } threadpool::~threadpool() { + destroy(); +} + +void threadpool::destroy() { try { const boost::unique_lock<boost::mutex> lock(mutex); @@ -64,6 +62,23 @@ threadpool::~threadpool() { try { threads[i].join(); } catch (...) { /* ignore */ } } + threads.clear(); +} + +void threadpool::recycle() { + destroy(); + create(max); +} + +void threadpool::create(unsigned int max_threads) { + boost::thread::attributes attrs; + attrs.set_stack_size(THREAD_STACK_SIZE); + max = max_threads ? max_threads : tools::get_max_concurrency(); + size_t i = max ? max - 1 : 0; + running = true; + while(i--) { + threads.push_back(boost::thread(attrs, boost::bind(&threadpool::run, this, false))); + } } void threadpool::submit(waiter *obj, std::function<void()> f, bool leaf) { @@ -145,7 +160,7 @@ void threadpool::run(bool flush) { if (!running) break; active++; - e = queue.front(); + e = std::move(queue.front()); queue.pop_front(); lock.unlock(); ++depth; diff --git a/src/common/threadpool.h b/src/common/threadpool.h index 5e490ee7d..a49d0e14f 100644 --- a/src/common/threadpool.h +++ b/src/common/threadpool.h @@ -69,12 +69,17 @@ public: // task to finish. void submit(waiter *waiter, std::function<void()> f, bool leaf = false); + // destroy and recreate threads + void recycle(); + unsigned int get_max_concurrency() const; ~threadpool(); private: threadpool(unsigned int max_threads = 0); + void destroy(); + void create(unsigned int max_threads); typedef struct entry { waiter *wo; std::function<void()> f; diff --git a/src/common/updates.cpp b/src/common/updates.cpp index 0bc6ff63c..f620bb53a 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -101,7 +101,7 @@ namespace tools { const char *base = user ? "https://downloads.getmonero.org/" : "https://updates.getmonero.org/"; #ifdef _WIN32 - static const char *extension = strncmp(buildtag.c_str(), "install-", 8) ? ".zip" : ".exe"; + static const char *extension = strncmp(buildtag.c_str(), "source", 6) ? (strncmp(buildtag.c_str(), "install-", 8) ? ".zip" : ".exe") : ".tar.bz2"; #else static const char extension[] = ".tar.bz2"; #endif diff --git a/src/crypto/blake256.c b/src/crypto/blake256.c index 1e305b3a6..bb2c5fb40 100644 --- a/src/crypto/blake256.c +++ b/src/crypto/blake256.c @@ -40,6 +40,7 @@ #include <string.h> #include <stdio.h> #include <stdint.h> +#include <memwipe.h> #include "blake256.h" #define U8TO32(p) \ @@ -277,7 +278,7 @@ void hmac_blake256_init(hmac_state *S, const uint8_t *_key, uint64_t keylen) { } blake256_update(&S->outer, pad, 512); - memset(keyhash, 0, 32); + memwipe(keyhash, sizeof(keyhash)); } // keylen = number of bytes @@ -307,7 +308,7 @@ void hmac_blake224_init(hmac_state *S, const uint8_t *_key, uint64_t keylen) { } blake224_update(&S->outer, pad, 512); - memset(keyhash, 0, 32); + memwipe(keyhash, sizeof(keyhash)); } // datalen = number of bits @@ -327,7 +328,7 @@ void hmac_blake256_final(hmac_state *S, uint8_t *digest) { blake256_final(&S->inner, ihash); blake256_update(&S->outer, ihash, 256); blake256_final(&S->outer, digest); - memset(ihash, 0, 32); + memwipe(ihash, sizeof(ihash)); } void hmac_blake224_final(hmac_state *S, uint8_t *digest) { @@ -335,7 +336,7 @@ void hmac_blake224_final(hmac_state *S, uint8_t *digest) { blake224_final(&S->inner, ihash); blake224_update(&S->outer, ihash, 224); blake224_final(&S->outer, digest); - memset(ihash, 0, 32); + memwipe(ihash, sizeof(ihash)); } // keylen = number of bytes; inlen = number of bytes diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 688aeaea3..c1e8365ac 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -476,7 +476,7 @@ namespace cryptonote for(; bl.nonce != std::numeric_limits<uint32_t>::max(); bl.nonce++) { crypto::hash h; - gbh(bl, height, tools::get_max_concurrency(), h); + gbh(bl, height, diffic <= 100 ? 0 : tools::get_max_concurrency(), h); if(check_hash(h, diffic)) { diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index ca127c3ee..134b630f7 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -101,6 +101,9 @@ #define CRYPTONOTE_MEMPOOL_TX_LIVETIME (86400*3) //seconds, three days #define CRYPTONOTE_MEMPOOL_TX_FROM_ALT_BLOCK_LIVETIME 604800 //seconds, one week + +#define CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE 5 // seconds + // see src/cryptonote_protocol/levin_notify.cpp #define CRYPTONOTE_NOISE_MIN_EPOCH 5 // minutes #define CRYPTONOTE_NOISE_EPOCH_RANGE 30 // seconds diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index d22158dfc..78893fdf2 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -616,7 +616,7 @@ block Blockchain::pop_block_from_blockchain() // that might not be always true. Unlikely though, and always relaying // these again might cause a spike of traffic as many nodes re-relay // all the transactions in a popped block when a reorg happens. - bool r = m_tx_pool.add_tx(tx, tvc, true, true, false, version); + bool r = m_tx_pool.add_tx(tx, tvc, relay_method::block, true, version); if (!r) { LOG_ERROR("Error returning transaction to tx_pool"); @@ -1765,7 +1765,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id { cryptonote::tx_memory_pool::tx_details td; cryptonote::blobdata blob; - if (m_tx_pool.have_tx(txid)) + if (m_tx_pool.have_tx(txid, relay_category::legacy)) { if (m_tx_pool.get_transaction_info(txid, td)) { @@ -2498,10 +2498,17 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons block b; CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first.first, b), false, "internal error, invalid block"); blocks.back().first.second = get_miner_tx_hash ? cryptonote::get_transaction_hash(b.miner_tx) : crypto::null_hash; - std::vector<crypto::hash> mis; std::vector<cryptonote::blobdata> txs; - get_transactions_blobs(b.tx_hashes, txs, mis, pruned); - CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); + if (pruned) + { + CHECK_AND_ASSERT_MES(m_db->get_pruned_tx_blobs_from(b.tx_hashes.front(), b.tx_hashes.size(), txs), false, "Failed to retrieve all transactions needed"); + } + else + { + std::vector<crypto::hash> mis; + get_transactions_blobs(b.tx_hashes, txs, mis, pruned); + CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); + } size += blocks.back().first.first.size(); for (const auto &t: txs) size += t.size(); @@ -3641,7 +3648,7 @@ void Blockchain::return_tx_to_pool(std::vector<std::pair<transaction, blobdata>> // all the transactions in a popped block when a reorg happens. const size_t weight = get_transaction_weight(tx.first, tx.second.size()); const crypto::hash tx_hash = get_transaction_hash(tx.first); - if (!m_tx_pool.add_tx(tx.first, tx_hash, tx.second, weight, tvc, true, true, false, version)) + if (!m_tx_pool.add_tx(tx.first, tx_hash, tx.second, weight, tvc, relay_method::block, true, version)) { MERROR("Failed to return taken transaction with hash: " << get_transaction_hash(tx.first) << " to tx_pool"); } @@ -3661,7 +3668,7 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids) uint64_t fee; bool relayed, do_not_relay, double_spend_seen, pruned; MINFO("Removing txid " << txid << " from the pool"); - if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) + if(m_tx_pool.have_tx(txid, relay_category::all) && !m_tx_pool.take_tx(txid, tx, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned)) { MERROR("Failed to remove txid " << txid << " from the pool"); res = false; @@ -4895,9 +4902,9 @@ void Blockchain::remove_txpool_tx(const crypto::hash &txid) m_db->remove_txpool_tx(txid); } -uint64_t Blockchain::get_txpool_tx_count(bool include_unrelayed_txes) const +uint64_t Blockchain::get_txpool_tx_count(bool include_sensitive) const { - return m_db->get_txpool_tx_count(include_unrelayed_txes); + return m_db->get_txpool_tx_count(include_sensitive ? relay_category::all : relay_category::broadcasted); } bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) const @@ -4905,19 +4912,24 @@ bool Blockchain::get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t & return m_db->get_txpool_tx_meta(txid, meta); } -bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd) const +bool Blockchain::get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const { - return m_db->get_txpool_tx_blob(txid, bd); + return m_db->get_txpool_tx_blob(txid, bd, tx_category); } -cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid) const +cryptonote::blobdata Blockchain::get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const { - return m_db->get_txpool_tx_blob(txid); + return m_db->get_txpool_tx_blob(txid, tx_category); } -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 +bool Blockchain::for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)> f, bool include_blob, relay_category tx_category) const { - return m_db->for_all_txpool_txes(f, include_blob, include_unrelayed_txes); + return m_db->for_all_txpool_txes(f, include_blob, tx_category); +} + +bool Blockchain::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category) +{ + return m_db->txpool_tx_matches_category(tx_hash, category); } void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint64_t sync_threshold, blockchain_db_sync_mode sync_mode, bool fast_sync) @@ -5074,7 +5086,12 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get return; } const size_t size_needed = 4 + nblocks * (sizeof(crypto::hash) * 2); - if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP && checkpoints.size() >= size_needed) + if(checkpoints.size() != size_needed) + { + MERROR("Failed to load hashes - unexpected data size"); + return; + } + else if(nblocks > 0 && nblocks > (m_db->height() + HASH_OF_HASHES_STEP - 1) / HASH_OF_HASHES_STEP) { p += sizeof(uint32_t); m_blocks_hash_of_hashes.reserve(nblocks); @@ -5098,7 +5115,7 @@ void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get CRITICAL_REGION_LOCAL(m_tx_pool); std::vector<transaction> txs; - m_tx_pool.get_transactions(txs); + m_tx_pool.get_transactions(txs, true); size_t tx_weight; uint64_t fee; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 6467031c2..0aecdcb57 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -964,11 +964,12 @@ namespace cryptonote void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, 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(bool include_unrelayed_txes = true) const; + uint64_t get_txpool_tx_count(bool include_sensitive = false) const; bool get_txpool_tx_meta(const crypto::hash& txid, txpool_tx_meta_t &meta) 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, bool include_unrelayed_txes = true) const; + bool get_txpool_tx_blob(const crypto::hash& txid, cryptonote::blobdata &bd, relay_category tx_category) const; + cryptonote::blobdata get_txpool_tx_blob(const crypto::hash& txid, relay_category tx_category) const; + bool for_all_txpool_txes(std::function<bool(const crypto::hash&, const txpool_tx_meta_t&, const cryptonote::blobdata*)>, bool include_blob = false, relay_category tx_category = relay_category::broadcasted) const; + bool txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category); bool is_within_compiled_block_hash_area() const { return is_within_compiled_block_hash_area(m_db->height()); } uint64_t prevalidate_block_hashes(uint64_t height, const std::vector<crypto::hash> &hashes, const std::vector<uint64_t> &weights); @@ -1042,7 +1043,7 @@ namespace cryptonote std::unordered_map<crypto::hash, std::unordered_map<crypto::key_image, std::vector<output_data_t>>> m_scan_table; std::unordered_map<crypto::hash, crypto::hash> m_blocks_longhash_table; - // SHA-3 hashes for each block and for fast pow checking + // Keccak hashes for each block and for fast pow checking std::vector<std::pair<crypto::hash, crypto::hash>> m_blocks_hash_of_hashes; std::vector<std::pair<crypto::hash, uint64_t>> m_blocks_hash_check; std::vector<crypto::hash> m_blocks_txs_check; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 02620996e..5c8f59291 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include <boost/algorithm/string.hpp> +#include <boost/uuid/nil_generator.hpp> #include "string_tools.h" using namespace epee; @@ -83,6 +84,11 @@ namespace cryptonote , "Run in a regression testing mode." , false }; + const command_line::arg_descriptor<bool> arg_keep_fakechain = { + "keep-fakechain" + , "Don't delete any existing database when in fakechain mode." + , false + }; const command_line::arg_descriptor<difficulty_type> arg_fixed_difficulty = { "fixed-difficulty" , "Fixed difficulty used for testing." @@ -173,11 +179,6 @@ namespace cryptonote , "Relay blocks as normal blocks" , false }; - static const command_line::arg_descriptor<bool> arg_pad_transactions = { - "pad-transactions" - , "Pad relayed transactions to help defend against traffic volume analysis" - , false - }; static const command_line::arg_descriptor<size_t> arg_max_txpool_weight = { "max-txpool-weight" , "Set maximum txpool weight in bytes." @@ -234,8 +235,7 @@ namespace cryptonote m_disable_dns_checkpoints(false), m_update_download(0), m_nettype(UNDEFINED), - m_update_available(false), - m_pad_transactions(false) + m_update_available(false) { m_checkpoints_updating.clear(); set_cryptonote_protocol(pprotocol); @@ -317,6 +317,7 @@ namespace cryptonote command_line::add_arg(desc, arg_testnet_on); command_line::add_arg(desc, arg_stagenet_on); command_line::add_arg(desc, arg_regtest_on); + command_line::add_arg(desc, arg_keep_fakechain); command_line::add_arg(desc, arg_fixed_difficulty); command_line::add_arg(desc, arg_dns_checkpoints); command_line::add_arg(desc, arg_prep_blocks_threads); @@ -332,7 +333,6 @@ namespace cryptonote command_line::add_arg(desc, arg_block_download_max_size); command_line::add_arg(desc, arg_sync_pruned_blocks); command_line::add_arg(desc, arg_max_txpool_weight); - command_line::add_arg(desc, arg_pad_transactions); command_line::add_arg(desc, arg_block_notify); command_line::add_arg(desc, arg_prune_blockchain); command_line::add_arg(desc, arg_reorg_notify); @@ -375,7 +375,6 @@ namespace cryptonote set_enforce_dns_checkpoints(command_line::get_arg(vm, arg_dns_checkpoints)); test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height)); m_fluffy_blocks_enabled = !get_arg(vm, arg_no_fluffy_blocks); - m_pad_transactions = get_arg(vm, arg_pad_transactions); m_offline = get_arg(vm, arg_offline); m_disable_dns_checkpoints = get_arg(vm, arg_disable_dns_checkpoints); if (!command_line::is_arg_defaulted(vm, arg_fluffy_blocks)) @@ -470,6 +469,7 @@ namespace cryptonote size_t max_txpool_weight = command_line::get_arg(vm, arg_max_txpool_weight); bool prune_blockchain = command_line::get_arg(vm, arg_prune_blockchain); bool keep_alt_blocks = command_line::get_arg(vm, arg_keep_alt_blocks); + bool keep_fakechain = command_line::get_arg(vm, arg_keep_fakechain); boost::filesystem::path folder(m_config_folder); if (m_nettype == FAKECHAIN) @@ -511,7 +511,7 @@ namespace cryptonote bool sync_on_blocks = true; uint64_t sync_threshold = 1; - if (m_nettype == FAKECHAIN) + if (m_nettype == FAKECHAIN && !keep_fakechain) { // reset the db by removing the database file before opening it if (!db->remove_data_file(filename)) @@ -753,7 +753,7 @@ namespace cryptonote return false; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash) { tvc = {}; @@ -817,7 +817,7 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash) { if(!check_tx_syntax(tx)) { @@ -946,23 +946,29 @@ namespace cryptonote return ret; } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_txs(const epee::span<const tx_blob_entry> tx_blobs, epee::span<tx_verification_context> tvc, relay_method tx_relay, bool relayed) { TRY_ENTRY(); - CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + + if (tx_blobs.size() != tvc.size()) + { + MERROR("tx_blobs and tx_verification_context spans must have equal size"); + return false; + } struct result { bool res; cryptonote::transaction tx; crypto::hash hash; }; std::vector<result> results(tx_blobs.size()); - tvc.resize(tx_blobs.size()); + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; - std::vector<tx_blob_entry>::const_iterator it = tx_blobs.begin(); + epee::span<tx_blob_entry>::const_iterator it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { tpool.submit(&waiter, [&, i, it] { try { - results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash, keeped_by_block, relayed, do_not_relay); + results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash); } catch (const std::exception &e) { @@ -978,7 +984,7 @@ namespace cryptonote for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { if (!results[i].res) continue; - if(m_mempool.have_tx(results[i].hash)) + if(m_mempool.have_tx(results[i].hash, relay_category::legacy)) { LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool"); already_have[i] = true; @@ -993,7 +999,7 @@ namespace cryptonote tpool.submit(&waiter, [&, i, it] { try { - results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash, keeped_by_block, relayed, do_not_relay); + results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash); } catch (const std::exception &e) { @@ -1014,7 +1020,7 @@ namespace cryptonote tx_info.push_back({&results[i].tx, results[i].hash, tvc[i], results[i].res}); } if (!tx_info.empty()) - handle_incoming_tx_accumulated_batch(tx_info, keeped_by_block); + handle_incoming_tx_accumulated_batch(tx_info, tx_relay == relay_method::block); bool ok = true; it = tx_blobs.begin(); @@ -1024,13 +1030,14 @@ namespace cryptonote ok = false; continue; } - if (keeped_by_block) + if (tx_relay == relay_method::block) get_blockchain_storage().on_new_tx_from_block(results[i].tx); if (already_have[i]) continue; const uint64_t weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size()); - ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, weight, tvc[i], keeped_by_block, relayed, do_not_relay); + ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, weight, tvc[i], tx_relay, relayed); + if(tvc[i].m_verifivation_failed) {MERROR_VER("Transaction verification failed: " << results[i].hash);} else if(tvc[i].m_verifivation_impossible) @@ -1044,19 +1051,9 @@ namespace cryptonote CATCH_ENTRY_L0("core::handle_incoming_txs()", false); } //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed) { - std::vector<tx_blob_entry> tx_blobs; - tx_blobs.push_back(tx_blob); - std::vector<tx_verification_context> tvcv(1); - bool r = handle_incoming_txs(tx_blobs, tvcv, keeped_by_block, relayed, do_not_relay); - tvc = tvcv[0]; - return r; - } - //----------------------------------------------------------------------------------------------- - bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) - { - return handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, keeped_by_block, relayed, do_not_relay); + return handle_incoming_txs({std::addressof(tx_blob), 1}, {std::addressof(tvc), 1}, tx_relay, relayed); } //----------------------------------------------------------------------------------------------- bool core::get_stat_info(core_stat_info& st_inf) const @@ -1246,13 +1243,13 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::add_new_tx(transaction& tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed) { crypto::hash tx_hash = get_transaction_hash(tx); blobdata bl; t_serializable_object_to_blob(tx, bl); size_t tx_weight = get_transaction_weight(tx, bl.size()); - return add_new_tx(tx, tx_hash, bl, tx_weight, tvc, keeped_by_block, relayed, do_not_relay); + return add_new_tx(tx, tx_hash, bl, tx_weight, tvc, tx_relay, relayed); } //----------------------------------------------------------------------------------------------- size_t core::get_blockchain_total_transactions() const @@ -1260,9 +1257,9 @@ namespace cryptonote return m_blockchain_storage.get_total_transactions(); } //----------------------------------------------------------------------------------------------- - bool core::add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) + bool core::add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed) { - if(m_mempool.have_tx(tx_hash)) + if(m_mempool.have_tx(tx_hash, relay_category::legacy)) { LOG_PRINT_L2("tx " << tx_hash << "already have transaction in tx_pool"); return true; @@ -1275,40 +1272,62 @@ namespace cryptonote } uint8_t version = m_blockchain_storage.get_current_hard_fork_version(); - return m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, keeped_by_block, relayed, do_not_relay, version); + return m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_relay, relayed, version); } //----------------------------------------------------------------------------------------------- bool core::relay_txpool_transactions() { // we attempt to relay txes that should be relayed, but were not - std::vector<std::pair<crypto::hash, cryptonote::blobdata>> txs; + std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>> txs; if (m_mempool.get_relayable_transactions(txs) && !txs.empty()) { - cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); - tx_verification_context tvc = AUTO_VAL_INIT(tvc); - NOTIFY_NEW_TRANSACTIONS::request r; - for (auto it = txs.begin(); it != txs.end(); ++it) + NOTIFY_NEW_TRANSACTIONS::request public_req{}; + NOTIFY_NEW_TRANSACTIONS::request private_req{}; + for (auto& tx : txs) { - r.txs.push_back(it->second); + switch (std::get<2>(tx)) + { + default: + case relay_method::none: + break; + case relay_method::local: + private_req.txs.push_back(std::move(std::get<1>(tx))); + break; + case relay_method::block: + case relay_method::fluff: + public_req.txs.push_back(std::move(std::get<1>(tx))); + break; + } } - get_protocol()->relay_transactions(r, fake_context); - m_mempool.set_relayed(txs); + + /* All txes are sent on randomized timers per connection in + `src/cryptonote_protocol/levin_notify.cpp.` They are either sent with + "white noise" delays or via diffusion (Dandelion++ fluff). So + re-relaying public and private _should_ be acceptable here. */ + const boost::uuids::uuid source = boost::uuids::nil_uuid(); + if (!public_req.txs.empty()) + get_protocol()->relay_transactions(public_req, source, epee::net_utils::zone::public_); + if (!private_req.txs.empty()) + get_protocol()->relay_transactions(private_req, source, epee::net_utils::zone::invalid); } return true; } //----------------------------------------------------------------------------------------------- - void core::on_transaction_relayed(const cryptonote::blobdata& tx_blob) + void core::on_transactions_relayed(const epee::span<const cryptonote::blobdata> tx_blobs, const relay_method tx_relay) { - std::vector<std::pair<crypto::hash, cryptonote::blobdata>> txs; - cryptonote::transaction tx; - crypto::hash tx_hash; - if (!parse_and_validate_tx_from_blob(tx_blob, tx, tx_hash)) + std::vector<crypto::hash> tx_hashes{}; + tx_hashes.resize(tx_blobs.size()); + + cryptonote::transaction tx{}; + for (std::size_t i = 0; i < tx_blobs.size(); ++i) { - LOG_ERROR("Failed to parse relayed transaction"); - return; + if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i])) + { + LOG_ERROR("Failed to parse relayed transaction"); + return; + } } - txs.push_back(std::make_pair(tx_hash, std::move(tx_blob))); - m_mempool.set_relayed(txs); + m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay); } //----------------------------------------------------------------------------------------------- bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce) @@ -1369,7 +1388,7 @@ namespace cryptonote for (const auto &tx_hash: b.tx_hashes) { cryptonote::blobdata txblob; - CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob), "Transaction not found in pool"); + CHECK_AND_ASSERT_THROW_MES(pool.get_transaction(tx_hash, txblob, relay_category::all), "Transaction not found in pool"); bce.txs.push_back({txblob, crypto::null_hash}); } return bce; @@ -1573,14 +1592,14 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- - bool core::get_pool_transaction(const crypto::hash &id, cryptonote::blobdata& tx) const + bool core::get_pool_transaction(const crypto::hash &id, cryptonote::blobdata& tx, relay_category tx_category) const { - return m_mempool.get_transaction(id, tx); + return m_mempool.get_transaction(id, tx, tx_category); } //----------------------------------------------------------------------------------------------- bool core::pool_has_tx(const crypto::hash &id) const { - return m_mempool.have_tx(id); + return m_mempool.have_tx(id, relay_category::legacy); } //----------------------------------------------------------------------------------------------- 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 @@ -1639,8 +1658,9 @@ namespace cryptonote << "You can set the level of process detailization through \"set_log <level|categories>\" command," << ENDL << "where <level> is between 0 (no details) and 4 (very verbose), or custom category based levels (eg, *:WARNING)." << ENDL << ENDL - << "Use the \"help\" command to see the list of available commands." << ENDL - << "Use \"help <command>\" to see a command's documentation." << ENDL + << "Use the \"help\" command to see a simplified list of available commands." << ENDL + << "Use the \"help_advanced\" command to see an advanced list of available commands." << ENDL + << "Use \"help_advanced <command>\" to see a command's documentation." << ENDL << "**********************************************************************" << ENDL); m_starter_message_showed = true; } @@ -1883,9 +1903,10 @@ namespace cryptonote } static constexpr double threshold = 1. / (864000 / DIFFICULTY_TARGET_V2); // one false positive every 10 days + static constexpr unsigned int max_blocks_checked = 150; const time_t now = time(NULL); - const std::vector<time_t> timestamps = m_blockchain_storage.get_last_block_timestamps(60); + const std::vector<time_t> timestamps = m_blockchain_storage.get_last_block_timestamps(max_blocks_checked); static const unsigned int seconds[] = { 5400, 3600, 1800, 1200, 600 }; for (size_t n = 0; n < sizeof(seconds)/sizeof(seconds[0]); ++n) @@ -1897,7 +1918,7 @@ namespace cryptonote MDEBUG("blocks in the last " << seconds[n] / 60 << " minutes: " << b << " (probability " << p << ")"); if (p < threshold) { - MWARNING("There were " << b << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck."); + MWARNING("There were " << b << (b == max_blocks_checked ? " or more" : "") << " blocks in the last " << seconds[n] / 60 << " minutes, there might be large hash rate changes, or we might be partitioned, cut off from the Monero network or under attack. Or it could be just sheer bad luck."); std::shared_ptr<tools::Notify> block_rate_notify = m_block_rate_notify; if (block_rate_notify) diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index f69ac3509..55efb566c 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -35,7 +35,9 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> +#include "cryptonote_core/i_core_events.h" #include "cryptonote_protocol/cryptonote_protocol_handler_common.h" +#include "cryptonote_protocol/enums.h" #include "storages/portable_storage_template_helper.h" #include "common/download.h" #include "common/command_line.h" @@ -46,6 +48,7 @@ #include "cryptonote_basic/cryptonote_stat_info.h" #include "warnings.h" #include "crypto/hash.h" +#include "span.h" PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) @@ -77,7 +80,7 @@ namespace cryptonote * limited to, communication among the Blockchain, the transaction pool, * any miners, and the network. */ - class core: public i_miner_handler + class core final: public i_miner_handler, public i_core_events { public: @@ -115,14 +118,12 @@ namespace cryptonote * * @param tx_blob the tx to handle * @param tvc metadata about the transaction's validity - * @param keeped_by_block if the transaction has been in a block + * @param tx_relay how the transaction was received * @param relayed whether or not the transaction was relayed to us - * @param do_not_relay whether to prevent the transaction from being relayed * * @return true if the transaction was accepted, false otherwise */ - bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); - bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed); /** * @brief handles a list of incoming transactions @@ -130,15 +131,35 @@ namespace cryptonote * Parses incoming transactions and, if nothing is obviously wrong, * passes them along to the transaction pool * + * @pre `tx_blobs.size() == tvc.size()` + * * @param tx_blobs the txs to handle * @param tvc metadata about the transactions' validity - * @param keeped_by_block if the transactions have been in a block + * @param tx_relay how the transaction was received. * @param relayed whether or not the transactions were relayed to us - * @param do_not_relay whether to prevent the transactions from being relayed * * @return true if the transactions were accepted, false otherwise */ - bool handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_txs(epee::span<const tx_blob_entry> tx_blobs, epee::span<tx_verification_context> tvc, relay_method tx_relay, bool relayed); + + /** + * @brief handles a list of incoming transactions + * + * Parses incoming transactions and, if nothing is obviously wrong, + * passes them along to the transaction pool + * + * @param tx_blobs the txs to handle + * @param tvc metadata about the transactions' validity + * @param tx_relay how the transaction was received. + * @param relayed whether or not the transactions were relayed to us + * + * @return true if the transactions were accepted, false otherwise + */ + bool handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, relay_method tx_relay, bool relayed) + { + tvc.resize(tx_blobs.size()); + return handle_incoming_txs(epee::to_span(tx_blobs), epee::to_mut_span(tvc), tx_relay, relayed); + } /** * @brief handles an incoming block @@ -212,9 +233,10 @@ namespace cryptonote virtual bool get_block_template(block& b, const crypto::hash *prev_block, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce); /** - * @brief called when a transaction is relayed + * @brief called when a transaction is relayed. + * @note Should only be invoked from `levin_notify`. */ - virtual void on_transaction_relayed(const cryptonote::blobdata& tx); + virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, relay_method tx_relay) final; /** @@ -440,11 +462,11 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions - * @param include_unrelayed_txes include unrelayed txes in result + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes = true) const; + bool get_pool_transactions(std::vector<transaction>& txs, bool include_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_txpool_backlog @@ -455,34 +477,34 @@ namespace cryptonote /** * @copydoc tx_memory_pool::get_transactions - * @param include_unrelayed_txes include unrelayed txes in result + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const; + bool get_pool_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_transactions - * @param include_unrelayed_txes include unrelayed txes in result + * @param include_sensitive_txes include private transactions * * @note see tx_memory_pool::get_transactions */ - bool get_pool_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const; + bool get_pool_transaction_stats(struct txpool_stats& stats, bool include_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_transaction * * @note see tx_memory_pool::get_transaction */ - bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx) const; + bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx, relay_category tx_category) const; /** * @copydoc tx_memory_pool::get_pool_transactions_and_spent_keys_info - * @param include_unrelayed_txes include unrelayed txes in result + * @param include_sensitive_txes include private transactions * * @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, bool include_unrelayed_txes = true) 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_sensitive_txes = false) const; /** * @copydoc tx_memory_pool::get_pool_for_rpc @@ -770,13 +792,6 @@ namespace cryptonote bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; } /** - * @brief get whether transaction relay should be padded - * - * @return whether transaction relay should be padded - */ - bool pad_transactions() const { return m_pad_transactions; } - - /** * @brief check a set of hashes against the precompiled hash set * * @return number of usable blocks @@ -852,11 +867,11 @@ namespace cryptonote * @param tx_hash the transaction's hash * @param blob the transaction as a blob * @param tx_weight the weight of the transaction + * @param tx_relay how the transaction was received * @param relayed whether or not the transaction was relayed to us - * @param do_not_relay whether to prevent the transaction from being relayed * */ - bool add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool add_new_tx(transaction& tx, const crypto::hash& tx_hash, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed); /** * @brief add a new transaction to the transaction pool @@ -865,15 +880,14 @@ namespace cryptonote * * @param tx the transaction to add * @param tvc return-by-reference metadata about the transaction's verification process - * @param keeped_by_block whether or not the transaction has been in a block + * @param tx_relay how the transaction was received * @param relayed whether or not the transaction was relayed to us - * @param do_not_relay whether to prevent the transaction from being relayed * * @return true if the transaction is already in the transaction pool, * is already in a block on the Blockchain, or is successfully added * to the transaction pool */ - bool add_new_tx(transaction& tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool add_new_tx(transaction& tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed); /** * @copydoc Blockchain::add_new_block @@ -929,8 +943,8 @@ namespace cryptonote bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const; void set_semantics_failed(const crypto::hash &tx_hash); - bool handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); - bool handle_incoming_tx_post(const tx_blob_entry &tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash); + bool handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash); struct tx_verification_batch_info { const cryptonote::transaction *tx; crypto::hash tx_hash; tx_verification_context &tvc; bool &result; }; bool handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block); @@ -1081,7 +1095,6 @@ namespace cryptonote bool m_fluffy_blocks_enabled; bool m_offline; - bool m_pad_transactions; std::shared_ptr<tools::Notify> m_block_rate_notify; }; diff --git a/src/cryptonote_core/i_core_events.h b/src/cryptonote_core/i_core_events.h new file mode 100644 index 000000000..f49fbdf07 --- /dev/null +++ b/src/cryptonote_core/i_core_events.h @@ -0,0 +1,44 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_protocol/enums.h" +#include "span.h" + +namespace cryptonote +{ + struct i_core_events + { + virtual ~i_core_events() noexcept + {} + + virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, relay_method tx_relay) = 0; + }; +} diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 392e611e9..1bc475879 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -115,8 +115,10 @@ namespace cryptonote } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version) + bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version) { + const bool kept_by_block = (tx_relay == relay_method::block); + // this should already be called with that lock, but let's make it explicit for clarity CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -227,7 +229,7 @@ namespace cryptonote crypto::hash max_used_block_id = null_hash; uint64_t max_used_block_height = 0; - cryptonote::txpool_tx_meta_t meta; + cryptonote::txpool_tx_meta_t meta{}; bool ch_inp_res = check_tx_inputs([&tx]()->cryptonote::transaction&{ return tx; }, id, max_used_block_height, max_used_block_id, tvc, kept_by_block); if(!ch_inp_res) { @@ -241,11 +243,10 @@ namespace cryptonote meta.max_used_block_height = 0; meta.last_failed_height = 0; meta.last_failed_id = null_hash; - meta.kept_by_block = kept_by_block; meta.receive_time = receive_time; meta.last_relayed_time = time(NULL); meta.relayed = relayed; - meta.do_not_relay = do_not_relay; + meta.set_relay_method(tx_relay); meta.double_spend_seen = have_tx_keyimges_as_spent(tx); meta.pruned = tx.pruned; meta.bf_padding = 0; @@ -256,15 +257,16 @@ namespace cryptonote m_parsed_tx_cache.insert(std::make_pair(id, tx)); CRITICAL_REGION_LOCAL1(m_blockchain); LockedTXN lock(m_blockchain); - m_blockchain.add_txpool_tx(id, blob, meta); - if (!insert_key_images(tx, id, kept_by_block)) + if (!insert_key_images(tx, id, tx_relay)) return false; + + m_blockchain.add_txpool_tx(id, blob, meta); m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); lock.commit(); } catch (const std::exception &e) { - MERROR("transaction already exists at inserting in memory pool: " << e.what()); + MERROR("Error adding transaction to txpool: " << e.what()); return false; } tvc.m_verifivation_impossible = true; @@ -280,7 +282,6 @@ namespace cryptonote { //update transactions container meta.weight = tx_weight; - meta.kept_by_block = kept_by_block; meta.fee = fee; meta.max_used_block_id = max_used_block_id; meta.max_used_block_height = max_used_block_height; @@ -289,7 +290,7 @@ namespace cryptonote meta.receive_time = receive_time; meta.last_relayed_time = time(NULL); meta.relayed = relayed; - meta.do_not_relay = do_not_relay; + meta.set_relay_method(tx_relay); meta.double_spend_seen = false; meta.pruned = tx.pruned; meta.bf_padding = 0; @@ -302,20 +303,21 @@ namespace cryptonote CRITICAL_REGION_LOCAL1(m_blockchain); LockedTXN lock(m_blockchain); m_blockchain.remove_txpool_tx(id); - m_blockchain.add_txpool_tx(id, blob, meta); - if (!insert_key_images(tx, id, kept_by_block)) + if (!insert_key_images(tx, id, tx_relay)) return false; + + m_blockchain.add_txpool_tx(id, blob, meta); m_txs_by_fee_and_receive_time.emplace(std::pair<double, std::time_t>(fee / (double)(tx_weight ? tx_weight : 1), receive_time), id); lock.commit(); } catch (const std::exception &e) { - MERROR("internal error: transaction already exists at inserting in memory pool: " << e.what()); + MERROR("internal error: error adding transaction to txpool: " << e.what()); return false; } tvc.m_added_to_pool = true; - if(meta.fee > 0 && !do_not_relay) + if(meta.fee > 0 && tx_relay != relay_method::none) tvc.m_should_be_relayed = true; } @@ -331,7 +333,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay, uint8_t version) + bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version) { crypto::hash h = null_hash; size_t blob_size = 0; @@ -339,7 +341,7 @@ namespace cryptonote t_serializable_object_to_blob(tx, bl); if (bl.size() == 0 || !get_transaction_hash(tx, h)) return false; - return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, keeped_by_block, relayed, do_not_relay, version); + return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, tx_relay, relayed, version); } //--------------------------------------------------------------------------------- size_t tx_memory_pool::get_txpool_weight() const @@ -375,7 +377,7 @@ namespace cryptonote txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(txid, meta)) { - MERROR("Failed to find tx in txpool"); + MERROR("Failed to find tx_meta in txpool"); return; } // don't prune the kept_by_block ones, they're likely added because we're adding a block with those @@ -384,7 +386,7 @@ namespace cryptonote --it; continue; } - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid); + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); cryptonote::transaction_prefix tx; if (!parse_and_validate_tx_prefix_from_blob(txblob, tx)) { @@ -413,17 +415,38 @@ namespace cryptonote MINFO("Pool weight after pruning is larger than limit: " << m_txpool_weight << "/" << bytes); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::insert_key_images(const transaction_prefix &tx, const crypto::hash &id, bool kept_by_block) + bool tx_memory_pool::insert_key_images(const transaction_prefix &tx, const crypto::hash &id, relay_method tx_relay) { for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, txin, false); std::unordered_set<crypto::hash>& kei_image_set = m_spent_key_images[txin.k_image]; - CHECK_AND_ASSERT_MES(kept_by_block || kei_image_set.size() == 0, false, "internal error: kept_by_block=" << kept_by_block - << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL - << "tx_id=" << id ); - auto ins_res = kei_image_set.insert(id); - CHECK_AND_ASSERT_MES(ins_res.second, false, "internal error: try to insert duplicate iterator in key_image set"); + + /* If any existing key-image in the set is publicly visible AND this is + not forcibly "kept_by_block", then fail (duplicate key image). If all + existing key images are supposed to be hidden, we silently allow so + that the node doesn't leak knowledge of a local/stem tx. */ + bool visible = false; + if (tx_relay != relay_method::block) + { + for (const crypto::hash& other_id : kei_image_set) + visible |= m_blockchain.txpool_tx_matches_category(other_id, relay_category::legacy); + } + + CHECK_AND_ASSERT_MES(!visible, false, "internal error: tx_relay=" << unsigned(tx_relay) + << ", kei_image_set.size()=" << kei_image_set.size() << ENDL << "txin.k_image=" << txin.k_image << ENDL + << "tx_id=" << id); + + /* If adding a tx (hash) that already exists, fail only if the tx has + been publicly "broadcast" previously. This way, when a private tx is + received for the first time from a remote node, "this" node will + respond as-if it were seen for the first time. LMDB does the + "hard-check" on key-images, so the effect is overwriting the existing + tx_pool metadata and "first seen" time. */ + const bool new_or_previously_private = + kei_image_set.insert(id).second || + !m_blockchain.txpool_tx_matches_category(id, relay_category::legacy); + CHECK_AND_ASSERT_MES(new_or_previously_private, false, "internal error: try to insert duplicate iterator in key_image set"); } ++m_cookie; return true; @@ -475,10 +498,10 @@ namespace cryptonote txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(id, meta)) { - MERROR("Failed to find tx in txpool"); + MERROR("Failed to find tx_meta in txpool"); return false; } - txblob = m_blockchain.get_txpool_tx_blob(id); + txblob = m_blockchain.get_txpool_tx_blob(id, relay_category::all); auto ci = m_parsed_tx_cache.find(id); if (ci != m_parsed_tx_cache.end()) { @@ -533,7 +556,7 @@ namespace cryptonote MERROR("Failed to find tx in txpool"); return false; } - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid); + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); auto ci = m_parsed_tx_cache.find(txid); if (ci != m_parsed_tx_cache.end()) { @@ -611,7 +634,7 @@ namespace cryptonote remove.push_back(std::make_pair(txid, meta.weight)); } return true; - }, false); + }, false, relay_category::all); if (!remove.empty()) { @@ -621,7 +644,7 @@ namespace cryptonote const crypto::hash &txid = entry.first; try { - cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid); + cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); cryptonote::transaction_prefix tx; if (!parse_and_validate_tx_prefix_from_blob(bd, tx)) { @@ -649,7 +672,7 @@ namespace cryptonote } //--------------------------------------------------------------------------------- //TODO: investigate whether boolean return is appropriate - bool tx_memory_pool::get_relayable_transactions(std::vector<std::pair<crypto::hash, cryptonote::blobdata>> &txs) const + bool tx_memory_pool::get_relayable_transactions(std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>> &txs) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); @@ -667,8 +690,7 @@ namespace cryptonote { try { - cryptonote::blobdata bd = m_blockchain.get_txpool_tx_blob(txid); - txs.push_back(std::make_pair(txid, bd)); + txs.emplace_back(txid, m_blockchain.get_txpool_tx_blob(txid, relay_category::all), meta.get_relay_method()); } catch (const std::exception &e) { @@ -678,26 +700,27 @@ namespace cryptonote } } return true; - }, false); + }, false, relay_category::relayable); return true; } //--------------------------------------------------------------------------------- - void tx_memory_pool::set_relayed(const std::vector<std::pair<crypto::hash, cryptonote::blobdata>> &txs) + void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method) { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); const time_t now = time(NULL); LockedTXN lock(m_blockchain); - for (auto it = txs.begin(); it != txs.end(); ++it) + for (const auto& hash : hashes) { try { txpool_tx_meta_t meta; - if (m_blockchain.get_txpool_tx_meta(it->first, meta)) + if (m_blockchain.get_txpool_tx_meta(hash, meta)) { meta.relayed = true; meta.last_relayed_time = now; - m_blockchain.update_txpool_tx(it->first, meta); + meta.set_relay_method(method); + m_blockchain.update_txpool_tx(hash, meta); } } catch (const std::exception &e) @@ -709,18 +732,19 @@ namespace cryptonote lock.commit(); } //--------------------------------------------------------------------------------- - size_t tx_memory_pool::get_transactions_count(bool include_unrelayed_txes) const + size_t tx_memory_pool::get_transactions_count(bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - return m_blockchain.get_txpool_tx_count(include_unrelayed_txes); + return m_blockchain.get_txpool_tx_count(include_sensitive); } //--------------------------------------------------------------------------------- - void tx_memory_pool::get_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes) const + void tx_memory_pool::get_transactions(std::vector<transaction>& txs, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - txs.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes)); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + txs.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); m_blockchain.for_all_txpool_txes([&txs](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ transaction tx; if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) @@ -732,39 +756,42 @@ namespace cryptonote tx.set_hash(txid); txs.push_back(std::move(tx)); return true; - }, true, include_unrelayed_txes); + }, true, category); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes) const + void tx_memory_pool::get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - txs.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes)); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + txs.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); 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); + }, false, category); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes) const + void tx_memory_pool::get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); const uint64_t now = time(NULL); - backlog.reserve(m_blockchain.get_txpool_tx_count(include_unrelayed_txes)); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; + backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); 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.weight, meta.fee, meta.receive_time - now}); return true; - }, false, include_unrelayed_txes); + }, false, category); } //------------------------------------------------------------------ - void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes) const + void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); const uint64_t now = time(NULL); + const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; std::map<uint64_t, txpool_histo> agebytes; - stats.txs_total = m_blockchain.get_txpool_tx_count(include_unrelayed_txes); + stats.txs_total = m_blockchain.get_txpool_tx_count(include_sensitive); std::vector<uint32_t> weights; weights.reserve(stats.txs_total); m_blockchain.for_all_txpool_txes([&stats, &weights, now, &agebytes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ @@ -789,7 +816,8 @@ namespace cryptonote if (meta.double_spend_seen) ++stats.num_double_spends; return true; - }, false, include_unrelayed_txes); + }, false, category); + stats.bytes_med = epee::misc_utils::median(weights); if (stats.txs_total > 1) { @@ -847,8 +875,10 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - tx_infos.reserve(m_blockchain.get_txpool_tx_count()); - key_image_infos.reserve(m_blockchain.get_txpool_tx_count()); + const relay_category category = include_sensitive_data ? relay_category::all : relay_category::broadcasted; + const size_t count = m_blockchain.get_txpool_tx_count(include_sensitive_data); + tx_infos.reserve(count); + key_image_infos.reserve(count); 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); @@ -879,7 +909,7 @@ namespace cryptonote txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(std::move(txi)); return true; - }, true, include_sensitive_data); + }, true, category); txpool_tx_meta_t meta; for (const key_images_container::value_type& kee : m_spent_key_images) { @@ -889,30 +919,13 @@ 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 - { - if (!m_blockchain.get_txpool_tx_meta(tx_id_hash, meta)) - { - MERROR("Failed to get tx meta from txpool"); - return false; - } - 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)); + if (m_blockchain.txpool_tx_matches_category(tx_id_hash, category)) + ki.txs_hashes.push_back(epee::string_tools::pod_to_hex(tx_id_hash)); } + // 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); + key_image_infos.push_back(std::move(ki)); } return true; } @@ -948,18 +961,19 @@ namespace cryptonote txi.double_spend_seen = meta.double_spend_seen; tx_infos.push_back(txi); return true; - }, true, false); + }, true, relay_category::broadcasted); for (const key_images_container::value_type& kee : m_spent_key_images) { std::vector<crypto::hash> tx_hashes; const std::unordered_set<crypto::hash>& kei_image_set = kee.second; for (const crypto::hash& tx_id_hash : kei_image_set) { - tx_hashes.push_back(tx_id_hash); + if (m_blockchain.txpool_tx_matches_category(tx_id_hash, relay_category::broadcasted)) + tx_hashes.push_back(tx_id_hash); } - const crypto::key_image& k_image = kee.first; - key_image_infos[k_image] = std::move(tx_hashes); + if (!tx_hashes.empty()) + key_image_infos[kee.first] = std::move(tx_hashes); } return true; } @@ -973,19 +987,26 @@ namespace cryptonote for (const auto& image : key_images) { - spent.push_back(m_spent_key_images.find(image) == m_spent_key_images.end() ? false : true); + bool is_spent = false; + const auto found = m_spent_key_images.find(image); + if (found != m_spent_key_images.end()) + { + for (const crypto::hash& tx_hash : found->second) + is_spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted); + } + spent.push_back(is_spent); } return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob) const + bool tx_memory_pool::get_transaction(const crypto::hash& id, cryptonote::blobdata& txblob, relay_category tx_category) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); try { - return m_blockchain.get_txpool_tx_blob(id, txblob); + return m_blockchain.get_txpool_tx_blob(id, txblob, tx_category); } catch (const std::exception &e) { @@ -1009,11 +1030,11 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - bool tx_memory_pool::have_tx(const crypto::hash &id) const + bool tx_memory_pool::have_tx(const crypto::hash &id, relay_category tx_category) const { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - return m_blockchain.get_db().txpool_has_tx(id); + return m_blockchain.get_db().txpool_has_tx(id, tx_category); } //--------------------------------------------------------------------------------- bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const @@ -1032,7 +1053,14 @@ namespace cryptonote bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const { CRITICAL_REGION_LOCAL(m_transactions_lock); - return m_spent_key_images.end() != m_spent_key_images.find(key_im); + bool spent = false; + const auto found = m_spent_key_images.find(key_im); + if (found != m_spent_key_images.end()) + { + for (const crypto::hash& tx_hash : found->second) + spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted); + } + return spent; } //--------------------------------------------------------------------------------- void tx_memory_pool::lock() const @@ -1217,13 +1245,14 @@ namespace cryptonote << "weight: " << meta.weight << std::endl << "fee: " << print_money(meta.fee) << std::endl << "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl + << "is_local" << (meta.is_local ? '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 << "last_failed_id: " << meta.last_failed_id << std::endl; return true; - }, !short_format); + }, !short_format, relay_category::all); return ss.str(); } @@ -1255,7 +1284,7 @@ namespace cryptonote for (; sorted_it != m_txs_by_fee_and_receive_time.end(); ++sorted_it) { txpool_tx_meta_t meta; - if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta)) + if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta) && !meta.matches(relay_category::legacy)) { MERROR(" failed to find tx meta"); continue; @@ -1304,7 +1333,9 @@ namespace cryptonote } } - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(sorted_it->second); + // "local" and "stem" txes are filtered above + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(sorted_it->second, relay_category::all); + cryptonote::transaction tx; // Skip transactions that are not ready to be @@ -1379,7 +1410,7 @@ namespace cryptonote remove.insert(txid); } return true; - }, false); + }, false, relay_category::all); size_t n_removed = 0; if (!remove.empty()) @@ -1389,7 +1420,7 @@ namespace cryptonote { try { - cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid); + cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); cryptonote::transaction tx; if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary { @@ -1450,7 +1481,7 @@ namespace cryptonote remove.push_back(txid); return true; } - if (!insert_key_images(tx, txid, meta.kept_by_block)) + if (!insert_key_images(tx, txid, meta.get_relay_method())) { MFATAL("Failed to insert key images from txpool tx"); return false; @@ -1458,7 +1489,7 @@ namespace cryptonote m_txs_by_fee_and_receive_time.emplace(std::pair<double, time_t>(meta.fee / (double)meta.weight, meta.receive_time), txid); m_txpool_weight += meta.weight; return true; - }, true); + }, true, relay_category::all); if (!r) return false; } diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index dec7e3cd9..f716440ad 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -32,17 +32,20 @@ #include "include_base_utils.h" #include <set> +#include <tuple> #include <unordered_map> #include <unordered_set> #include <queue> #include <boost/serialization/version.hpp> #include <boost/utility.hpp> +#include "span.h" #include "string_tools.h" #include "syncobj.h" #include "math_helper.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/verification_context.h" +#include "cryptonote_protocol/enums.h" #include "blockchain_db/blockchain_db.h" #include "crypto/hash.h" #include "rpc/core_rpc_server_commands_defs.h" @@ -105,9 +108,10 @@ namespace cryptonote * @copydoc add_tx(transaction&, tx_verification_context&, bool, bool, uint8_t) * * @param id the transaction's hash + * @tx_relay how the transaction was received * @param tx_weight the transaction's weight */ - bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version); + bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version); /** * @brief add a transaction to the transaction pool @@ -119,14 +123,13 @@ namespace cryptonote * * @param tx the transaction to be added * @param tvc return-by-reference status about the transaction verification - * @param kept_by_block has this transaction been in a block? + * @tx_relay how the transaction was received * @param relayed was this transaction from the network or a local client? - * @param do_not_relay to avoid relaying the transaction to the network * @param version the version used to create the transaction * * @return true if the transaction passes validations, otherwise false */ - bool add_tx(transaction &tx, tx_verification_context& tvc, bool kept_by_block, bool relayed, bool do_not_relay, uint8_t version); + bool add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version); /** * @brief takes a transaction with the given hash from the pool @@ -149,10 +152,11 @@ namespace cryptonote * @brief checks if the pool has a transaction with the given hash * * @param id the hash to look for + * @param tx_category a filter for txes * - * @return true if the transaction is in the pool, otherwise false + * @return true if the transaction is in the pool and meets tx_category requirements */ - bool have_tx(const crypto::hash &id) const; + bool have_tx(const crypto::hash &id, relay_category tx_category) const; /** * @brief action to take when notified of a block added to the blockchain @@ -236,37 +240,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 + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes * */ - void get_transactions(std::vector<transaction>& txs, bool include_unrelayed_txes = true) const; + void get_transactions(std::vector<transaction>& txs, bool include_sensitive = false) 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 + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes * */ - void get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_unrelayed_txes = true) const; + void get_transaction_hashes(std::vector<crypto::hash>& txs, bool include_sensitive = false) const; /** * @brief get (weight, 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 + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes * */ - void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_unrelayed_txes = true) const; + void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) 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 + * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes * */ - void get_transaction_stats(struct txpool_stats& stats, bool include_unrelayed_txes = true) const; + void get_transaction_stats(struct txpool_stats& stats, bool include_sensitive = false) const; /** * @brief get information about all transactions and key images in the pool @@ -275,11 +279,12 @@ 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 + * @param include_sensitive_data return stempool, anonymity-pool, and 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, bool include_sensitive_data = true) 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 = false) const; /** * @brief get information about all transactions and key images in the pool @@ -308,10 +313,11 @@ namespace cryptonote * * @param h the hash of the transaction to get * @param tx return-by-reference the transaction blob requested + * @param tx_relay last relay method us * * @return true if the transaction is found, otherwise false */ - bool get_transaction(const crypto::hash& h, cryptonote::blobdata& txblob) const; + bool get_transaction(const crypto::hash& h, cryptonote::blobdata& txblob, relay_category tx_category) const; /** * @brief get a list of all relayable transactions and their hashes @@ -326,21 +332,22 @@ namespace cryptonote * * @return true */ - bool get_relayable_transactions(std::vector<std::pair<crypto::hash, cryptonote::blobdata>>& txs) const; + bool get_relayable_transactions(std::vector<std::tuple<crypto::hash, cryptonote::blobdata, relay_method>>& txs) const; /** * @brief tell the pool that certain transactions were just relayed * - * @param txs the list of transactions (and their hashes) + * @param hashes list of tx hashes that are about to be relayed + * @param tx_relay update how the tx left this node */ - void set_relayed(const std::vector<std::pair<crypto::hash, cryptonote::blobdata>>& txs); + void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay); /** * @brief get the total number of transactions in the pool * * @return the number of transactions in the pool */ - size_t get_transactions_count(bool include_unrelayed_txes = true) const; + size_t get_transactions_count(bool include_sensitive = false) const; /** * @brief get a string containing human-readable pool information @@ -441,7 +448,7 @@ namespace cryptonote * * @return true on success, false on error */ - bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, bool kept_by_block); + bool insert_key_images(const transaction_prefix &tx, const crypto::hash &txid, relay_method tx_relay); /** * @brief remove old transactions from the pool @@ -544,7 +551,7 @@ namespace cryptonote * transaction on the assumption that the original will not be in a * block again. */ - typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash> > key_images_container; + typedef std::unordered_map<crypto::key_image, std::unordered_set<crypto::hash>> key_images_container; #if defined(DEBUG_CREATE_BLOCK_TEMPLATE) public: diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 3b456e324..ddbd45a61 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -130,7 +130,7 @@ namespace cryptonote //----------------- i_bc_protocol_layout --------------------------------------- virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context); - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context); + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone); //---------------------------------------------------------------------------------- //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, cryptonote_connection_context& context); bool should_drop_connection(cryptonote_connection_context& context, uint32_t next_stripe); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 74ceeb41d..a7bf0c283 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -188,7 +188,7 @@ namespace cryptonote auto connection_time = time(NULL) - cntxt.m_started; ss << std::setw(30) << std::left << std::string(cntxt.m_is_income ? " [INC]":"[OUT]") + cntxt.m_remote_address.str() - << std::setw(20) << std::hex << peer_id + << std::setw(20) << nodetool::peerid_to_string(peer_id) << std::setw(20) << std::hex << support_flags << std::setw(30) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) @@ -248,9 +248,7 @@ namespace cryptonote cnx.rpc_port = cntxt.m_rpc_port; cnx.rpc_credits_per_hash = cntxt.m_rpc_credits_per_hash; - std::stringstream peer_id_str; - peer_id_str << std::hex << std::setw(16) << peer_id; - peer_id_str >> cnx.peer_id; + cnx.peer_id = nodetool::peerid_to_string(peer_id); cnx.support_flags = support_flags; @@ -455,7 +453,7 @@ namespace cryptonote for(auto tx_blob_it = arg.b.txs.begin(); tx_blob_it!=arg.b.txs.end();tx_blob_it++) { cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx(*tx_blob_it, tvc, true, true, false); + m_core.handle_incoming_tx(*tx_blob_it, tvc, relay_method::block, true); if(tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection"); @@ -619,7 +617,7 @@ namespace cryptonote { MDEBUG("Incoming tx " << tx_hash << " not in pool, adding"); cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx(tx_blob, tvc, true, true, false) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx(tx_blob, tvc, relay_method::block, true) || tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Block verification failed: transaction verification failed, dropping connection"); drop_connection(context, false, false); @@ -667,13 +665,13 @@ namespace cryptonote drop_connection(context, false, false); m_core.resume_mine(); return 1; - } - + } + size_t tx_idx = 0; for(auto& tx_hash: new_block.tx_hashes) { cryptonote::blobdata txblob; - if(m_core.get_pool_transaction(tx_hash, txblob)) + if(m_core.get_pool_transaction(tx_hash, txblob, relay_category::broadcasted)) { have_tx.push_back({txblob, crypto::null_hash}); } @@ -702,7 +700,7 @@ namespace cryptonote need_tx_indices.push_back(tx_idx); } } - + ++tx_idx; } @@ -909,8 +907,8 @@ namespace cryptonote newtxs.reserve(arg.txs.size()); for (size_t i = 0; i < arg.txs.size(); ++i) { - cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, false, true, false); + cryptonote::tx_verification_context tvc{}; + m_core.handle_incoming_tx({arg.txs[i], crypto::null_hash}, tvc, relay_method::fluff, true); if(tvc.m_verifivation_failed) { LOG_PRINT_CCONTEXT_L1("Tx verification failed, dropping connection"); @@ -925,7 +923,7 @@ namespace cryptonote if(arg.txs.size()) { //TODO: add announce usage here - relay_transactions(arg, context); + relay_transactions(arg, context.m_connection_id, context.m_remote_address.get_zone()); } return 1; @@ -1316,7 +1314,7 @@ namespace cryptonote TIME_MEASURE_START(transactions_process_time); num_txs += block_entry.txs.size(); std::vector<tx_verification_context> tvc; - m_core.handle_incoming_txs(block_entry.txs, tvc, true, true, false); + m_core.handle_incoming_txs(block_entry.txs, tvc, relay_method::block, true); if (tvc.size() != block_entry.txs.size()) { LOG_ERROR_CCONTEXT("Internal error: tvc.size() != block_entry.txs.size()"); @@ -2181,7 +2179,8 @@ skip: MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << "You are now synchronized with the network. You may now start monero-wallet-cli." << ENDL << ENDL - << "Use the \"help\" command to see the list of available commands." << ENDL + << "Use the \"help\" command to see a simplified list of available commands." << ENDL + << "Use the \"help_advanced\" command to see an advanced list of available commands." << ENDL << "**********************************************************************"); m_sync_timer.pause(); if (ELPP->vRegistry()->allowed(el::Level::Info, "sync-info")) @@ -2344,14 +2343,14 @@ skip: } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> - bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context) + bool t_cryptonote_protocol_handler<t_core>::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone) { - for(auto& tx_blob : arg.txs) - m_core.on_transaction_relayed(tx_blob); - - // no check for success, so tell core they're relayed unconditionally - m_p2p->send_txs(std::move(arg.txs), exclude_context.m_remote_address.get_zone(), exclude_context.m_connection_id, m_core.pad_transactions()); - return true; + /* Push all outgoing transactions to this function. The behavior needs to + identify how the transaction is going to be relayed, and then update the + local mempool before doing the relay. The code was already updating the + DB twice on received transactions - it is difficult to workaround this + due to the internal design. */ + return m_p2p->send_txs(std::move(arg.txs), zone, source, m_core) != epee::net_utils::zone::invalid; } //------------------------------------------------------------------------------------------------------------------------ template<class t_core> diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index a67178c52..978a9ebf3 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -41,7 +41,7 @@ namespace cryptonote struct i_cryptonote_protocol { virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, cryptonote_connection_context& exclude_context)=0; - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context)=0; + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone)=0; //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, cryptonote_connection_context& context)=0; }; @@ -54,7 +54,7 @@ namespace cryptonote { return false; } - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, cryptonote_connection_context& exclude_context) + virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, const boost::uuids::uuid& source, epee::net_utils::zone zone) { return false; } diff --git a/src/cryptonote_protocol/enums.h b/src/cryptonote_protocol/enums.h new file mode 100644 index 000000000..2ec622d94 --- /dev/null +++ b/src/cryptonote_protocol/enums.h @@ -0,0 +1,43 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <cstdint> + +namespace cryptonote +{ + //! Methods tracking how a tx was received and relayed + enum class relay_method : std::uint8_t + { + none = 0, //!< Received via RPC with `do_not_relay` set + local, //!< Received via RPC; trying to send over i2p/tor, etc. + block, //!< Received in block, takes precedence over others + fluff //!< Received/sent over public networks + }; +} diff --git a/src/cryptonote_protocol/fwd.h b/src/cryptonote_protocol/fwd.h new file mode 100644 index 000000000..616b48be3 --- /dev/null +++ b/src/cryptonote_protocol/fwd.h @@ -0,0 +1,37 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +namespace cryptonote +{ + class core; + struct cryptonote_connection_context; + struct i_core_events; +} + diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 4b41b5bfc..e45c34e02 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -33,6 +33,7 @@ #include <chrono> #include <deque> #include <stdexcept> +#include <utility> #include "common/expect.h" #include "common/varint.h" @@ -43,6 +44,14 @@ #include "net/dandelionpp.h" #include "p2p/net_node.h" +namespace +{ + int get_command_from_message(const cryptonote::blobdata &msg) + { + return msg.size() >= sizeof(epee::levin::bucket_head2) ? SWAP32LE(((epee::levin::bucket_head2*)msg.data())->m_command) : 0; + } +} + namespace cryptonote { namespace levin @@ -57,6 +66,37 @@ namespace levin constexpr const std::chrono::seconds noise_min_delay{CRYPTONOTE_NOISE_MIN_DELAY}; constexpr const std::chrono::seconds noise_delay_range{CRYPTONOTE_NOISE_DELAY_RANGE}; + /* A custom duration is used for the poisson distribution because of the + variance. If 5 seconds is given to `std::poisson_distribution`, 95% of + the values fall between 1-9s in 1s increments (not granular enough). If + 5000 milliseconds is given, 95% of the values fall between 4859ms-5141ms + in 1ms increments (not enough time variance). Providing 20 quarter + seconds yields 95% of the values between 3s-7.25s in 1/4s increments. */ + using fluff_stepsize = std::chrono::duration<std::chrono::milliseconds::rep, std::ratio<1, 4>>; + constexpr const std::chrono::seconds fluff_average_in{CRYPTONOTE_DANDELIONPP_FLUSH_AVERAGE}; + + /*! Bitcoin Core is using 1/2 average seconds for outgoing connections + compared to incoming. The thinking is that the user controls outgoing + connections (Dandelion++ makes similar assumptions in its stem + algorithm). The randomization yields 95% values between 1s-4s in + 1/4s increments. */ + constexpr const fluff_stepsize fluff_average_out{fluff_stepsize{fluff_average_in} / 2}; + + class random_poisson + { + std::poisson_distribution<fluff_stepsize::rep> dist; + public: + explicit random_poisson(fluff_stepsize average) + : dist(average.count() < 0 ? 0 : average.count()) + {} + + fluff_stepsize operator()() + { + crypto::random_device rand{}; + return fluff_stepsize{dist(rand)}; + } + }; + /*! Select a randomized duration from 0 to `range`. The precision will be to the systems `steady_clock`. As an example, supplying 3 seconds to this function will select a duration from [0, 3] seconds, and the increments @@ -129,6 +169,16 @@ namespace levin return fullBlob; } + bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad) + { + const cryptonote::blobdata blob = make_tx_payload(std::move(txs), pad); + p2p.for_connection(destination, [&blob](detail::p2p_context& context) { + on_levin_traffic(context, true, true, false, blob.size(), get_command_from_message(blob)); + return true; + }); + return p2p.notify(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(blob), destination); + } + /* The current design uses `asio::strand`s. The documentation isn't as clear as it should be - a `strand` has an internal `mutex` and `bool`. The `mutex` synchronizes thread access and the `bool` is set when a thread is @@ -187,15 +237,18 @@ namespace levin { struct zone { - explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in, bool is_public) + explicit zone(boost::asio::io_service& io_service, std::shared_ptr<connections> p2p, epee::byte_slice noise_in, bool is_public, bool pad_txs) : p2p(std::move(p2p)), noise(std::move(noise_in)), next_epoch(io_service), + flush_txs(io_service), strand(io_service), map(), channels(), + flush_time(std::chrono::steady_clock::time_point::max()), connection_count(0), - is_public(is_public) + is_public(is_public), + pad_txs(pad_txs) { for (std::size_t count = 0; !noise.empty() && count < CRYPTONOTE_NOISE_CHANNELS; ++count) channels.emplace_back(io_service); @@ -204,11 +257,14 @@ namespace levin const std::shared_ptr<connections> p2p; const epee::byte_slice noise; //!< `!empty()` means zone is using noise channels boost::asio::steady_timer next_epoch; + boost::asio::steady_timer flush_txs; boost::asio::io_service::strand strand; net::dandelionpp::connection_map map;//!< Tracks outgoing uuid's for noise channels or Dandelion++ stems std::deque<noise_channel> channels; //!< Never touch after init; only update elements on `noise_channel.strand` + std::chrono::steady_clock::time_point flush_time; //!< Next expected Dandelion++ fluff flush std::atomic<std::size_t> connection_count; //!< Only update in strand, can be read at any time const bool is_public; //!< Zone is public ipv4/ipv6 connections + const bool pad_txs; //!< Pad txs to the next boundary for privacy }; } // detail @@ -245,49 +301,112 @@ namespace levin } }; - //! Sends a message to every active connection - class flood_notify + //! Sends txs on connections with expired timers, and queues callback for next timer expiration (if any). + struct fluff_flush { std::shared_ptr<detail::zone> zone_; - epee::byte_slice message_; // Requires manual copy - boost::uuids::uuid source_; + std::chrono::steady_clock::time_point flush_time_; - public: - explicit flood_notify(std::shared_ptr<detail::zone> zone, epee::byte_slice message, const boost::uuids::uuid& source) - : zone_(std::move(zone)), message_(message.clone()), source_(source) - {} + static void queue(std::shared_ptr<detail::zone> zone, const std::chrono::steady_clock::time_point flush_time) + { + assert(zone != nullptr); + assert(zone->strand.running_in_this_thread()); - flood_notify(flood_notify&&) = default; - flood_notify(const flood_notify& source) - : zone_(source.zone_), message_(source.message_.clone()), source_(source.source_) - {} + detail::zone& this_zone = *zone; + this_zone.flush_time = flush_time; + this_zone.flush_txs.expires_at(flush_time); + this_zone.flush_txs.async_wait(this_zone.strand.wrap(fluff_flush{std::move(zone), flush_time})); + } - void operator()() const + void operator()(const boost::system::error_code error) { if (!zone_ || !zone_->p2p) return; assert(zone_->strand.running_in_this_thread()); - /* The foreach should be quick, but then it iterates and acquires the - same lock for every connection. So do in a strand because two threads - will ping-pong each other with cacheline invalidations. Revisit if - algorithm changes or the locking strategy within the levin config - class changes. */ - - std::vector<boost::uuids::uuid> connections; - connections.reserve(connection_id_reserve_size); - zone_->p2p->foreach_connection([this, &connections] (detail::p2p_context& context) { - /* Only send to outgoing connections when "flooding" over i2p/tor. - Otherwise this makes the tx linkable to a hidden service address, - making things linkable across connections. */ + const bool timer_error = bool(error); + if (timer_error) + { + if (error != boost::system::errc::operation_canceled) + throw boost::system::system_error{error, "fluff_flush timer failed"}; + + // new timer canceled this one set in future + if (zone_->flush_time < flush_time_) + return; + } + + const auto now = std::chrono::steady_clock::now(); + auto next_flush = std::chrono::steady_clock::time_point::max(); + std::vector<std::pair<std::vector<blobdata>, boost::uuids::uuid>> connections{}; + zone_->p2p->foreach_connection([timer_error, now, &next_flush, &connections] (detail::p2p_context& context) + { + if (!context.fluff_txs.empty()) + { + if (context.flush_time <= now || timer_error) // flush on canceled timer + { + context.flush_time = std::chrono::steady_clock::time_point::max(); + connections.emplace_back(std::move(context.fluff_txs), context.m_connection_id); + context.fluff_txs.clear(); + } + else // not flushing yet + next_flush = std::min(next_flush, context.flush_time); + } + else // nothing to flush + context.flush_time = std::chrono::steady_clock::time_point::max(); + return true; + }); + + for (auto& connection : connections) + make_payload_send_txs(*zone_->p2p, std::move(connection.first), connection.second, zone_->pad_txs); + + if (next_flush != std::chrono::steady_clock::time_point::max()) + fluff_flush::queue(std::move(zone_), next_flush); + else + zone_->flush_time = next_flush; // signal that no timer is set + } + }; + + /*! The "fluff" portion of the Dandelion++ algorithm. Every tx is queued + per-connection and flushed with a randomized poisson timer. This + implementation only has one system timer per-zone, and instead tracks + the lowest flush time. */ + struct fluff_notify + { + std::shared_ptr<detail::zone> zone_; + std::vector<blobdata> txs_; + boost::uuids::uuid source_; + + void operator()() + { + if (!zone_ || !zone_->p2p || txs_.empty()) + return; + + assert(zone_->strand.running_in_this_thread()); + + const auto now = std::chrono::steady_clock::now(); + auto next_flush = std::chrono::steady_clock::time_point::max(); + + random_poisson in_duration(fluff_average_in); + random_poisson out_duration(fluff_average_out); + + zone_->p2p->foreach_connection([this, now, &in_duration, &out_duration, &next_flush] (detail::p2p_context& context) + { if (this->source_ != context.m_connection_id && (this->zone_->is_public || !context.m_is_income)) - connections.emplace_back(context.m_connection_id); + { + if (context.fluff_txs.empty()) + context.flush_time = now + (context.m_is_income ? in_duration() : out_duration()); + + next_flush = std::min(next_flush, context.flush_time); + context.fluff_txs.reserve(context.fluff_txs.size() + this->txs_.size()); + for (const blobdata& tx : this->txs_) + context.fluff_txs.push_back(tx); // must copy instead of move (multiple conns) + } return true; }); - for (const boost::uuids::uuid& connection : connections) - zone_->p2p->send(message_.clone(), connection); + if (next_flush < zone_->flush_time) + fluff_flush::queue(std::move(zone_), next_flush); } }; @@ -432,6 +551,10 @@ namespace levin else message = zone_->noise.clone(); + zone_->p2p->for_connection(channel.connection, [&](detail::p2p_context& context) { + on_levin_traffic(context, true, true, false, message.size(), "noise"); + return true; + }); if (zone_->p2p->send(std::move(message), channel.connection)) { if (!channel.queue.empty() && channel.active.empty()) @@ -451,7 +574,7 @@ namespace levin } }; - //! Prepares connections for new channel epoch and sets timer for next epoch + //! Prepares connections for new channel/dandelionpp epoch and sets timer for next epoch struct start_epoch { // Variables allow for Dandelion++ extension @@ -481,8 +604,8 @@ namespace levin }; } // anonymous - notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, bool is_public) - : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise), is_public)) + notify::notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, const bool is_public, const bool pad_txs) + : zone_(std::make_shared<detail::zone>(service, std::move(p2p), std::move(noise), is_public, pad_txs)) { if (!zone_->p2p) throw std::logic_error{"cryptonote::levin::notify cannot have nullptr p2p argument"}; @@ -533,9 +656,19 @@ namespace levin channel.next_noise.cancel(); } - bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, const bool pad_txs) + void notify::run_fluff() { if (!zone_) + return; + zone_->flush_txs.cancel(); + } + + bool notify::send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source) + { + if (txs.empty()) + return true; + + if (!zone_) return false; if (!zone_->noise.empty() && !zone_->channels.empty()) @@ -565,12 +698,7 @@ namespace levin } else { - const std::string payload = make_tx_payload(std::move(txs), pad_txs); - epee::byte_slice message = - epee::levin::make_notify(NOTIFY_NEW_TRANSACTIONS::ID, epee::strspan<std::uint8_t>(payload)); - - // traditional monero send technique - zone_->strand.dispatch(flood_notify{zone_, std::move(message), source}); + zone_->strand.dispatch(fluff_notify{zone_, std::move(txs), source}); } return true; diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 484243af5..ce652d933 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -35,6 +35,7 @@ #include "byte_slice.h" #include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_protocol/fwd.h" #include "net/enums.h" #include "span.h" @@ -53,11 +54,6 @@ namespace nodetool namespace cryptonote { - struct cryptonote_connection_context; -} - -namespace cryptonote -{ namespace levin { namespace detail @@ -86,7 +82,7 @@ namespace levin {} //! Construct an instance with available notification `zones`. - explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, bool is_public); + explicit notify(boost::asio::io_service& service, std::shared_ptr<connections> p2p, epee::byte_slice noise, bool is_public, bool pad_txs); notify(const notify&) = delete; notify(notify&&) = default; @@ -108,11 +104,14 @@ namespace levin //! Run the logic for the next stem timeout imemdiately. Only use in testing. void run_stems(); + //! Run the logic for flushing all Dandelion++ fluff queued txs. Only use in testing. + void run_fluff(); + /*! Send txs using `cryptonote_protocol_defs.h` payload format wrapped in a levin header. The message will be sent in a "discreet" manner if enabled - if `!noise.empty()` then the `command`/`payload` will be queued to send at the next available noise interval. Otherwise, a - standard Monero flood notification will be used. + Dandelion++ fluff algorithm will be used. \note Eventually Dandelion++ stem sending will be used here when enabled. @@ -121,12 +120,9 @@ namespace levin \param source The source of the notification. `is_nil()` indicates this node is the source. Dandelion++ will use this to map a source to a particular stem. - \param pad_txs A request to pad txs to help conceal origin via - statistical analysis. Ignored if noise was enabled during - construction. \return True iff the notification is queued for sending. */ - bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source, bool pad_txs); + bool send_txs(std::vector<blobdata> txs, const boost::uuids::uuid& source); }; } // levin } // net diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index b827221f6..ed6d3af01 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -28,7 +28,6 @@ #include "common/dns_utils.h" #include "common/command_line.h" -#include "version.h" #include "daemon/command_parser_executor.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -154,6 +153,16 @@ bool t_command_parser_executor::print_blockchain_info(const std::vector<std::str } uint64_t start_index = 0; uint64_t end_index = 0; + if (args[0][0] == '-') + { + int64_t nblocks; + if(!epee::string_tools::get_xtype_from_string(nblocks, args[0])) + { + std::cout << "wrong number of blocks" << std::endl; + return false; + } + return m_executor.print_blockchain_info(nblocks, (uint64_t)-nblocks); + } if(!epee::string_tools::get_xtype_from_string(start_index, args[0])) { std::cout << "wrong starter block index parameter" << std::endl; @@ -244,12 +253,15 @@ 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_metadata = false; 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") + if (args[i] == "+meta") + include_metadata = true; + else if (args[i] == "+hex") include_hex = true; else if (args[i] == "+json") include_json = true; @@ -261,7 +273,7 @@ bool t_command_parser_executor::print_transaction(const std::vector<std::string> } if (args.empty()) { - std::cout << "expected: print_tx <transaction_hash> [+hex] [+json]" << std::endl; + std::cout << "expected: print_tx <transaction_hash> [+meta] [+hex] [+json]" << std::endl; return true; } @@ -269,7 +281,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, include_hex, include_json); + m_executor.print_transaction(tx_hash, include_metadata, include_hex, include_json); } return true; @@ -803,8 +815,7 @@ bool t_command_parser_executor::rpc_payments(const std::vector<std::string>& arg bool t_command_parser_executor::version(const std::vector<std::string>& args) { - std::cout << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")" << std::endl; - return true; + return m_executor.version(); } bool t_command_parser_executor::prune_blockchain(const std::vector<std::string>& args) diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index ed614a89b..1ca728e39 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -38,6 +38,7 @@ #include "cryptonote_basic/difficulty.h" #include "cryptonote_basic/hardfork.h" #include "rpc/rpc_payment_signature.h" +#include "rpc/rpc_version_str.h" #include <boost/format.hpp> #include <ctime> #include <string> @@ -380,7 +381,7 @@ static void get_metric_prefix(cryptonote::difficulty_type hr, double& hr_d, char prefix = 0; return; } - static const char metric_prefixes[4] = { 'k', 'M', 'G', 'T' }; + static const char metric_prefixes[] = { 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; for (size_t i = 0; i < sizeof(metric_prefixes); ++i) { if (hr < 1000000) @@ -664,7 +665,7 @@ bool t_rpc_command_executor::print_connections() { << std::setw(30) << std::left << address << std::setw(8) << (get_address_type_name((epee::net_utils::address_type)info.address_type)) << std::setw(6) << (info.ssl ? "yes" : "no") - << std::setw(20) << epee::string_tools::pad_string(info.peer_id, 16, '0', true) + << std::setw(20) << info.peer_id << std::setw(20) << info.support_flags << std::setw(30) << std::to_string(info.recv_count) + "(" + std::to_string(info.recv_idle_time) + ")/" + std::to_string(info.send_count) + "(" + std::to_string(info.send_idle_time) + ")" << std::setw(25) << info.state @@ -743,17 +744,46 @@ bool t_rpc_command_executor::print_net_stats() return true; } -bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index) { +bool t_rpc_command_executor::print_blockchain_info(int64_t start_block_index, uint64_t end_block_index) { cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req; cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res; epee::json_rpc::error error_resp; + std::string fail_message = "Problem fetching info"; + + // negative: relative to the end + if (start_block_index < 0) + { + cryptonote::COMMAND_RPC_GET_INFO::request ireq; + cryptonote::COMMAND_RPC_GET_INFO::response ires; + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(ireq, ires, "/getinfo", fail_message.c_str())) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_info(ireq, ires) || ires.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, ires.status); + return true; + } + } + if (start_block_index < 0 && (uint64_t)-start_block_index >= ires.height) + { + tools::fail_msg_writer() << "start offset is larger than blockchain height"; + return true; + } + start_block_index = ires.height + start_block_index; + end_block_index = start_block_index + end_block_index - 1; + } req.start_height = start_block_index; req.end_height = end_block_index; req.fill_pow_hash = false; - std::string fail_message = "Unsuccessful"; - + fail_message = "Failed calling getblockheadersrange"; if (m_is_rpc) { if (!m_rpc_client->json_rpc_request(req, res, "getblockheadersrange", fail_message.c_str())) @@ -939,6 +969,7 @@ bool t_rpc_command_executor::print_block_by_height(uint64_t height, bool include } bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, + bool include_metadata, bool include_hex, bool include_json) { cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req; @@ -981,6 +1012,29 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, const std::string &as_hex = (1 == res.txs.size()) ? res.txs.front().as_hex : res.txs_as_hex.front(); const std::string &pruned_as_hex = (1 == res.txs.size()) ? res.txs.front().pruned_as_hex : ""; const std::string &prunable_as_hex = (1 == res.txs.size()) ? res.txs.front().prunable_as_hex : ""; + // Print metadata if requested + if (include_metadata) + { + if (!res.txs.front().in_pool) + { + tools::msg_writer() << "Block timestamp: " << res.txs.front().block_timestamp << " (" << tools::get_human_readable_timestamp(res.txs.front().block_timestamp) << ")"; + } + cryptonote::blobdata blob; + if (epee::string_tools::parse_hexstr_to_binbuff(pruned_as_hex + prunable_as_hex, blob)) + { + cryptonote::transaction tx; + if (cryptonote::parse_and_validate_tx_from_blob(blob, tx)) + { + tools::msg_writer() << "Size: " << blob.size(); + tools::msg_writer() << "Weight: " << cryptonote::get_transaction_weight(tx); + } + else + tools::fail_msg_writer() << "Error parsing transaction blob"; + } + else + tools::fail_msg_writer() << "Error parsing transaction from hex"; + } + // Print raw hex if requested if (include_hex) { @@ -2217,7 +2271,7 @@ bool t_rpc_command_executor::sync_info() for (const auto &s: res.spans) if (s.connection_id == p.info.connection_id) nblocks += s.nblocks, size += s.size; - tools::success_msg_writer() << address << " " << epee::string_tools::pad_string(p.info.peer_id, 16, '0', true) << " " << + tools::success_msg_writer() << address << " " << p.info.peer_id << " " << epee::string_tools::pad_string(p.info.state, 16) << " " << epee::string_tools::pad_string(epee::string_tools::to_string_hex(p.info.pruning_seed), 8) << " " << p.info.height << " " << p.info.current_download << " kB/s, " << nblocks << " blocks / " << size/1e6 << " MB queued"; @@ -2442,4 +2496,39 @@ bool t_rpc_command_executor::rpc_payments() return true; } +bool t_rpc_command_executor::version() +{ + cryptonote::COMMAND_RPC_GET_INFO::request req; + cryptonote::COMMAND_RPC_GET_INFO::response res; + + const char *fail_message = "Problem fetching info"; + + if (m_is_rpc) + { + if (!m_rpc_client->rpc_request(req, res, "/getinfo", fail_message)) + { + return true; + } + } + else + { + if (!m_rpc_server->on_get_info(req, res) || res.status != CORE_RPC_STATUS_OK) + { + tools::fail_msg_writer() << make_error(fail_message, res.status); + return true; + } + } + + if (res.version.empty() || !cryptonote::rpc::is_version_string_valid(res.version)) + { + tools::fail_msg_writer() << "The daemon software version is not available."; + } + else + { + tools::success_msg_writer() << res.version; + } + + return true; +} + }// namespace daemonize diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index e8b12cb9b..1754ce32e 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -85,7 +85,7 @@ public: bool print_connections(); - bool print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index); + bool print_blockchain_info(int64_t start_block_index, uint64_t end_block_index); bool set_log_level(int8_t level); @@ -97,7 +97,7 @@ public: bool print_block_by_height(uint64_t height, bool include_hex); - bool print_transaction(crypto::hash transaction_hash, bool include_hex, bool include_json); + bool print_transaction(crypto::hash transaction_hash, bool include_metadata, bool include_hex, bool include_json); bool is_key_image_spent(const crypto::key_image &ki); @@ -163,6 +163,8 @@ public: bool print_net_stats(); + bool version(); + bool set_bootstrap_daemon( const std::string &address, const std::string &username, diff --git a/src/lmdb/table.h b/src/lmdb/table.h index 41a3de296..4ded4ba54 100644 --- a/src/lmdb/table.h +++ b/src/lmdb/table.h @@ -15,8 +15,8 @@ namespace lmdb { char const* const name; const unsigned flags; - MDB_cmp_func const* const key_cmp; - MDB_cmp_func const* const value_cmp; + MDB_cmp_func* const key_cmp; + MDB_cmp_func* const value_cmp; //! \pre `name != nullptr` \return Open table. expect<MDB_dbi> open(MDB_txn& write_txn) const noexcept; diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index 58c1717e0..58c5706de 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -161,6 +161,10 @@ namespace nodetool const command_line::arg_descriptor<int64_t> arg_limit_rate_down = {"limit-rate-down", "set limit-rate-down [kB/s]", P2P_DEFAULT_LIMIT_RATE_DOWN}; const command_line::arg_descriptor<int64_t> arg_limit_rate = {"limit-rate", "set limit-rate [kB/s]", -1}; + const command_line::arg_descriptor<bool> arg_pad_transactions = { + "pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis", false + }; + boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm) { namespace ip = boost::asio::ip; diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 8d861ce29..0e9c1c942 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -38,11 +38,13 @@ #include <boost/program_options/options_description.hpp> #include <boost/program_options/variables_map.hpp> #include <boost/uuid/uuid.hpp> +#include <chrono> #include <functional> #include <utility> #include <vector> #include "cryptonote_config.h" +#include "cryptonote_protocol/fwd.h" #include "cryptonote_protocol/levin_notify.h" #include "warnings.h" #include "net/abstract_tcp_server2.h" @@ -108,8 +110,16 @@ namespace nodetool template<class base_type> struct p2p_connection_context_t: base_type //t_payload_net_handler::connection_context //public net_utils::connection_context_base { - p2p_connection_context_t(): peer_id(0), support_flags(0), m_in_timedsync(false) {} + p2p_connection_context_t() + : fluff_txs(), + flush_time(std::chrono::steady_clock::time_point::max()), + peer_id(0), + support_flags(0), + m_in_timedsync(false) + {} + std::vector<cryptonote::blobdata> fluff_txs; + std::chrono::steady_clock::time_point flush_time; peerid_type peer_id; uint32_t support_flags; bool m_in_timedsync; @@ -336,7 +346,7 @@ namespace nodetool virtual void callback(p2p_connection_context& context); //----------------- i_p2p_endpoint ------------------------------------------------------------- virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections); - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs); + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core); virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context); virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context); virtual bool drop_connection(const epee::net_utils::connection_context_base& context); @@ -539,6 +549,7 @@ namespace nodetool extern const command_line::arg_descriptor<int64_t> arg_limit_rate_up; extern const command_line::arg_descriptor<int64_t> arg_limit_rate_down; extern const command_line::arg_descriptor<int64_t> arg_limit_rate; + extern const command_line::arg_descriptor<bool> arg_pad_transactions; } POP_WARNINGS diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index f8094bfa8..08bc76d26 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -116,6 +116,7 @@ namespace nodetool command_line::add_arg(desc, arg_limit_rate_up); command_line::add_arg(desc, arg_limit_rate_down); command_line::add_arg(desc, arg_limit_rate); + command_line::add_arg(desc, arg_pad_transactions); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> @@ -340,6 +341,7 @@ namespace nodetool { bool testnet = command_line::get_arg(vm, cryptonote::arg_testnet_on); bool stagenet = command_line::get_arg(vm, cryptonote::arg_stagenet_on); + const bool pad_txs = command_line::get_arg(vm, arg_pad_transactions); m_nettype = testnet ? cryptonote::TESTNET : stagenet ? cryptonote::STAGENET : cryptonote::MAINNET; network_zone& public_zone = m_network_zones[epee::net_utils::zone::public_]; @@ -384,7 +386,7 @@ namespace nodetool m_use_ipv6 = command_line::get_arg(vm, arg_p2p_use_ipv6); m_require_ipv4 = !command_line::get_arg(vm, arg_p2p_ignore_ipv4); public_zone.m_notifier = cryptonote::levin::notify{ - public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr, true + public_zone.m_net_server.get_io_service(), public_zone.m_net_server.get_config_shared(), nullptr, true, pad_txs }; if (command_line::has_arg(vm, arg_p2p_add_peer)) @@ -495,7 +497,7 @@ namespace nodetool } zone.m_notifier = cryptonote::levin::notify{ - zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise), false + zone.m_net_server.get_io_service(), zone.m_net_server.get_config_shared(), std::move(this_noise), false, pad_txs }; } @@ -1022,7 +1024,7 @@ namespace nodetool epee::simple_event ev; std::atomic<bool> hsh_result(false); - bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_.m_connection_id, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(), + bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_HANDSHAKE::response>(context_, COMMAND_HANDSHAKE::ID, arg, zone.m_net_server.get_config_object(), [this, &pi, &ev, &hsh_result, &just_take_peerlist, &context_](int code, const typename COMMAND_HANDSHAKE::response& rsp, p2p_connection_context& context) { epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ev.raise();}); @@ -1089,7 +1091,7 @@ namespace nodetool LOG_WARNING_CC(context_, "COMMAND_HANDSHAKE Failed"); m_network_zones.at(context_.m_remote_address.get_zone()).m_net_server.get_config_object().close(context_.m_connection_id); } - else + else if (!just_take_peerlist) { try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) { @@ -1107,7 +1109,7 @@ namespace nodetool m_payload_handler.get_payload_sync_data(arg.payload_data); network_zone& zone = m_network_zones.at(context_.m_remote_address.get_zone()); - bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_TIMED_SYNC::response>(context_.m_connection_id, COMMAND_TIMED_SYNC::ID, arg, zone.m_net_server.get_config_object(), + bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_TIMED_SYNC::response>(context_, COMMAND_TIMED_SYNC::ID, arg, zone.m_net_server.get_config_object(), [this](int code, const typename COMMAND_TIMED_SYNC::response& rsp, p2p_connection_context& context) { context.m_in_timedsync = false; @@ -1368,7 +1370,7 @@ namespace nodetool bool node_server<t_payload_net_handler>::make_new_connection_from_anchor_peerlist(const std::vector<anchor_peerlist_entry>& anchor_peerlist) { for (const auto& pe: anchor_peerlist) { - _note("Considering connecting (out) to anchor peer: " << peerid_type(pe.id) << " " << pe.adr.str()); + _note("Considering connecting (out) to anchor peer: " << peerid_to_string(pe.id) << " " << pe.adr.str()); if(is_peer_used(pe)) { _note("Peer is used"); @@ -1882,6 +1884,7 @@ namespace nodetool --i; continue; } + local_peerlist[i].last_seen = 0; #ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED be.pruning_seed = tools::make_pruning_seed(1 + (be.adr.as<epee::net_utils::ipv4_network_address>().ip()) % (1ul << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES); @@ -1949,7 +1952,7 @@ namespace nodetool const network_zone& zone = m_network_zones.at(zone_type); if(zone.m_config.m_peer_id != tr.peer_id) { - MWARNING("check_trust failed: peer_id mismatch (passed " << tr.peer_id << ", expected " << zone.m_config.m_peer_id<< ")"); + MWARNING("check_trust failed: peer_id mismatch (passed " << tr.peer_id << ", expected " << peerid_to_string(zone.m_config.m_peer_id) << ")"); return false; } crypto::public_key pk = AUTO_VAL_INIT(pk); @@ -2053,13 +2056,18 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs) + epee::net_utils::zone node_server<t_payload_net_handler>::send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core) { namespace enet = epee::net_utils; - const auto send = [&txs, &source, pad_txs] (std::pair<const enet::zone, network_zone>& network) + const auto send = [&txs, &source, &core] (std::pair<const enet::zone, network_zone>& network) { - if (network.second.m_notifier.send_txs(std::move(txs), source, (pad_txs || network.first != enet::zone::public_))) + const bool is_public = (network.first == enet::zone::public_); + const cryptonote::relay_method tx_relay = is_public ? + cryptonote::relay_method::fluff : cryptonote::relay_method::local; + + core.on_transactions_relayed(epee::to_span(txs), tx_relay); + if (network.second.m_notifier.send_txs(std::move(txs), source)) return network.first; return enet::zone::invalid; }; @@ -2208,7 +2216,7 @@ namespace nodetool network_zone& zone = m_network_zones.at(address.get_zone()); - bool inv_call_res = epee::net_utils::async_invoke_remote_command2<COMMAND_PING::response>(ping_context.m_connection_id, COMMAND_PING::ID, req, zone.m_net_server.get_config_object(), + bool inv_call_res = epee::net_utils::async_invoke_remote_command2<COMMAND_PING::response>(ping_context, COMMAND_PING::ID, req, zone.m_net_server.get_config_object(), [=](int code, const COMMAND_PING::response& rsp, p2p_connection_context& context) { if(code <= 0) @@ -2220,7 +2228,7 @@ namespace nodetool network_zone& zone = m_network_zones.at(address.get_zone()); if(rsp.status != PING_OK_RESPONSE_STATUS_TEXT || pr != rsp.peer_id) { - LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << rsp.peer_id); + LOG_WARNING_CC(ping_context, "back ping invoke wrong response \"" << rsp.status << "\" from" << address.str() << ", hsh_peer_id=" << pr_ << ", rsp.peer_id=" << peerid_to_string(rsp.peer_id)); zone.m_net_server.get_config_object().close(ping_context.m_connection_id); return; } @@ -2252,7 +2260,7 @@ namespace nodetool COMMAND_REQUEST_SUPPORT_FLAGS::request support_flags_request; bool r = epee::net_utils::async_invoke_remote_command2<typename COMMAND_REQUEST_SUPPORT_FLAGS::response> ( - context.m_connection_id, + context, COMMAND_REQUEST_SUPPORT_FLAGS::ID, support_flags_request, m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object(), @@ -2453,7 +2461,7 @@ namespace nodetool zone.second.m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { ss << cntxt.m_remote_address.str() - << " \t\tpeer_id " << cntxt.peer_id + << " \t\tpeer_id " << peerid_to_string(cntxt.peer_id) << " \t\tconn_id " << cntxt.m_connection_id << (cntxt.m_is_income ? " INC":" OUT") << std::endl; return true; @@ -2711,12 +2719,12 @@ namespace nodetool if (!check_connection_and_handshake_with_peer(pe.adr, pe.last_seen)) { zone.second.m_peerlist.remove_from_peer_gray(pe); - LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); + LOG_PRINT_L2("PEER EVICTED FROM GRAY PEER LIST: address: " << pe.adr.host_str() << " Peer ID: " << peerid_to_string(pe.id)); } else { zone.second.m_peerlist.set_peer_just_seen(pe.id, pe.adr, pe.pruning_seed, pe.rpc_port, pe.rpc_credits_per_hash); - LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_type(pe.id)); + LOG_PRINT_L2("PEER PROMOTED TO WHITE PEER LIST IP address: " << pe.adr.host_str() << " Peer ID: " << peerid_to_string(pe.id)); } } return true; diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 752873666..ed88aa28c 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -34,6 +34,8 @@ #include <utility> #include <vector> #include "cryptonote_basic/blobdatatype.h" +#include "cryptonote_protocol/enums.h" +#include "cryptonote_protocol/fwd.h" #include "net/enums.h" #include "net/net_utils_base.h" #include "p2p_protocol_defs.h" @@ -48,7 +50,7 @@ namespace nodetool struct i_p2p_endpoint { virtual bool relay_notify_to_list(int command, const epee::span<const uint8_t> data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections)=0; - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs)=0; + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core)=0; virtual bool invoke_command_to_peer(int command, const epee::span<const uint8_t> req_buff, std::string& resp_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context)=0; virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; @@ -73,7 +75,7 @@ namespace nodetool { return false; } - virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, const bool pad_txs) + virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::i_core_events& core) { return epee::net_utils::zone::invalid; } diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index 44b278589..393bddd05 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -132,7 +132,7 @@ namespace nodetool ss << std::setfill ('0') << std::setw (8) << std::hex << std::noshowbase; for(const peerlist_entry& pe: pl) { - ss << pe.id << "\t" << pe.adr.str() + ss << peerid_to_string(pe.id) << "\t" << pe.adr.str() << " \trpc port " << (pe.rpc_port > 0 ? std::to_string(pe.rpc_port) : "-") << " \trpc credits per hash " << (pe.rpc_credits_per_hash > 0 ? std::to_string(pe.rpc_credits_per_hash) : "-") << " \tpruning seed " << pe.pruning_seed diff --git a/src/ringct/bulletproofs.cc b/src/ringct/bulletproofs.cc index ff6fee95c..80ecc5593 100644 --- a/src/ringct/bulletproofs.cc +++ b/src/ringct/bulletproofs.cc @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // Adapted from Java code by Sarang Noether +// Paper references are to https://eprint.iacr.org/2017/1066 (revision 1 July 2018) #include <stdlib.h> #include <boost/thread/mutex.hpp> @@ -521,6 +522,7 @@ Bulletproof bulletproof_PROVE(const rct::keyV &sv, const rct::keyV &gamma) } PERF_TIMER_STOP_BP(PROVE_v); + // PAPER LINES 41-42 PERF_TIMER_START_BP(PROVE_aLaR); for (size_t j = 0; j < M; ++j) { @@ -566,14 +568,14 @@ try_again: rct::key hash_cache = rct::hash_to_scalar(V); PERF_TIMER_START_BP(PROVE_step1); - // PAPER LINES 38-39 + // PAPER LINES 43-44 rct::key alpha = rct::skGen(); rct::key ve = vector_exponent(aL8, aR8); rct::key A; sc_mul(tmp.bytes, alpha.bytes, INV_EIGHT.bytes); rct::addKeys(A, ve, rct::scalarmultBase(tmp)); - // PAPER LINES 40-42 + // PAPER LINES 45-47 rct::keyV sL = rct::skvGen(MN), sR = rct::skvGen(MN); rct::key rho = rct::skGen(); ve = vector_exponent(sL, sR); @@ -581,7 +583,7 @@ try_again: rct::addKeys(S, ve, rct::scalarmultBase(rho)); S = rct::scalarmultKey(S, INV_EIGHT); - // PAPER LINES 43-45 + // PAPER LINES 48-50 rct::key y = hash_cache_mash(hash_cache, A, S); if (y == rct::zero()) { @@ -598,24 +600,20 @@ try_again: } // Polynomial construction by coefficients + // PAPER LINES 70-71 rct::keyV l0 = vector_subtract(aL, z); const rct::keyV &l1 = sL; - // This computes the ugly sum/concatenation from PAPER LINE 65 rct::keyV zero_twos(MN); const rct::keyV zpow = vector_powers(z, M+2); - for (size_t i = 0; i < MN; ++i) + for (size_t j = 0; j < M; ++j) { - zero_twos[i] = rct::zero(); - for (size_t j = 1; j <= M; ++j) - { - if (i >= (j-1)*N && i < j*N) + for (size_t i = 0; i < N; ++i) { - CHECK_AND_ASSERT_THROW_MES(1+j < zpow.size(), "invalid zpow index"); - CHECK_AND_ASSERT_THROW_MES(i-(j-1)*N < twoN.size(), "invalid twoN index"); - sc_muladd(zero_twos[i].bytes, zpow[1+j].bytes, twoN[i-(j-1)*N].bytes, zero_twos[i].bytes); + CHECK_AND_ASSERT_THROW_MES(j+2 < zpow.size(), "invalid zpow index"); + CHECK_AND_ASSERT_THROW_MES(i < twoN.size(), "invalid twoN index"); + sc_mul(zero_twos[j*N+i].bytes,zpow[j+2].bytes,twoN[i].bytes); } - } } rct::keyV r0 = vector_add(aR, z); @@ -624,7 +622,7 @@ try_again: r0 = vector_add(r0, zero_twos); rct::keyV r1 = hadamard(yMN, sR); - // Polynomial construction before PAPER LINE 46 + // Polynomial construction before PAPER LINE 51 rct::key t1_1 = inner_product(l0, r1); rct::key t1_2 = inner_product(l1, r0); rct::key t1; @@ -634,7 +632,7 @@ try_again: PERF_TIMER_STOP_BP(PROVE_step1); PERF_TIMER_START_BP(PROVE_step2); - // PAPER LINES 47-48 + // PAPER LINES 52-53 rct::key tau1 = rct::skGen(), tau2 = rct::skGen(); rct::key T1, T2; @@ -648,7 +646,7 @@ try_again: ge_double_scalarmult_base_vartime_p3(&p3, tmp.bytes, &ge_p3_H, tmp2.bytes); ge_p3_tobytes(T2.bytes, &p3); - // PAPER LINES 49-51 + // PAPER LINES 54-56 rct::key x = hash_cache_mash(hash_cache, z, T1, T2); if (x == rct::zero()) { @@ -657,7 +655,7 @@ try_again: goto try_again; } - // PAPER LINES 52-53 + // PAPER LINES 61-63 rct::key taux; sc_mul(taux.bytes, tau1.bytes, x.bytes); rct::key xsq; @@ -671,7 +669,7 @@ try_again: rct::key mu; sc_muladd(mu.bytes, x.bytes, rho.bytes, alpha.bytes); - // PAPER LINES 54-57 + // PAPER LINES 58-60 rct::keyV l = l0; l = vector_add(l, vector_scalar(l1, x)); rct::keyV r = r0; @@ -690,7 +688,7 @@ try_again: CHECK_AND_ASSERT_THROW_MES(test_t == t, "test_t check failed"); #endif - // PAPER LINES 32-33 + // PAPER LINE 6 rct::key x_ip = hash_cache_mash(hash_cache, x, taux, mu, t); if (x_ip == rct::zero()) { @@ -725,20 +723,19 @@ try_again: PERF_TIMER_STOP_BP(PROVE_step3); PERF_TIMER_START_BP(PROVE_step4); - // PAPER LINE 13 const rct::keyV *scale = &yinvpow; while (nprime > 1) { - // PAPER LINE 15 + // PAPER LINE 20 nprime /= 2; - // PAPER LINES 16-17 + // PAPER LINES 21-22 PERF_TIMER_START_BP(PROVE_inner_product); rct::key cL = inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size())); rct::key cR = inner_product(slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime)); PERF_TIMER_STOP_BP(PROVE_inner_product); - // PAPER LINES 18-19 + // PAPER LINES 23-24 PERF_TIMER_START_BP(PROVE_LR); sc_mul(tmp.bytes, cL.bytes, x_ip.bytes); L[round] = cross_vector_exponent8(nprime, Gprime, nprime, Hprime, 0, aprime, 0, bprime, nprime, scale, &ge_p3_H, &tmp); @@ -746,7 +743,7 @@ try_again: R[round] = cross_vector_exponent8(nprime, Gprime, 0, Hprime, nprime, aprime, nprime, bprime, 0, scale, &ge_p3_H, &tmp); PERF_TIMER_STOP_BP(PROVE_LR); - // PAPER LINES 21-22 + // PAPER LINES 25-27 w[round] = hash_cache_mash(hash_cache, L[round], R[round]); if (w[round] == rct::zero()) { @@ -755,7 +752,7 @@ try_again: goto try_again; } - // PAPER LINES 24-25 + // PAPER LINES 29-30 const rct::key winv = invert(w[round]); if (nprime > 1) { @@ -765,7 +762,7 @@ try_again: PERF_TIMER_STOP_BP(PROVE_hadamard2); } - // PAPER LINES 28-29 + // PAPER LINES 33-34 PERF_TIMER_START_BP(PROVE_prime); aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), w[round]), vector_scalar(slice(aprime, nprime, aprime.size()), winv)); bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), winv), vector_scalar(slice(bprime, nprime, bprime.size()), w[round])); @@ -776,7 +773,6 @@ try_again: } PERF_TIMER_STOP_BP(PROVE_step4); - // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20) return Bulletproof(std::move(V), A, S, T1, T2, taux, mu, std::move(L), std::move(R), aprime[0], bprime[0], t); } @@ -810,7 +806,10 @@ struct proof_data_t size_t logM, inv_offset; }; -/* Given a range proof, determine if it is valid */ +/* Given a range proof, determine if it is valid + * This uses the method in PAPER LINES 95-105, + * weighted across multiple proofs in a batch + */ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) { init_exponents(); @@ -871,7 +870,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); PERF_TIMER_START_BP(VERIFY_line_21_22); - // PAPER LINES 21-22 // The inner product challenges are computed per round pd.w.resize(rounds); for (size_t i = 0; i < rounds; ++i) @@ -929,7 +927,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) rct::key proof8_A = rct::scalarmult8(proof.A); PERF_TIMER_START_BP(VERIFY_line_61); - // PAPER LINE 61 sc_mulsub(m_y0.bytes, proof.taux.bytes, weight_y.bytes, m_y0.bytes); const rct::keyV zpow = vector_powers(pd.z, M+3); @@ -962,7 +959,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) PERF_TIMER_STOP_BP(VERIFY_line_61rl_new); PERF_TIMER_START_BP(VERIFY_line_62); - // PAPER LINE 62 multiexp_data.emplace_back(weight_z, proof8_A); sc_mul(tmp.bytes, pd.x.bytes, weight_z.bytes); multiexp_data.emplace_back(tmp, proof8_S); @@ -973,7 +969,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds"); PERF_TIMER_START_BP(VERIFY_line_24_25); - // Basically PAPER LINES 24-25 // Compute the curvepoints from G[i] and H[i] rct::key yinvpow = rct::identity(); rct::key ypow = rct::identity(); @@ -1010,7 +1005,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) sc_mul(g_scalar.bytes, g_scalar.bytes, w_cache[i].bytes); sc_mul(h_scalar.bytes, h_scalar.bytes, w_cache[(~i) & (MN-1)].bytes); - // Adjust the scalars using the exponents from PAPER LINE 62 sc_add(g_scalar.bytes, g_scalar.bytes, pd.z.bytes); CHECK_AND_ASSERT_MES(2+i/N < zpow.size(), false, "invalid zpow index"); CHECK_AND_ASSERT_MES(i%N < twoN.size(), false, "invalid twoN index"); @@ -1043,7 +1037,6 @@ bool bulletproof_VERIFY(const std::vector<const Bulletproof*> &proofs) PERF_TIMER_STOP_BP(VERIFY_line_24_25); - // PAPER LINE 26 PERF_TIMER_START_BP(VERIFY_line_26_new); sc_muladd(z1.bytes, proof.mu.bytes, weight_z.bytes, z1.bytes); for (size_t i = 0; i < rounds; ++i) diff --git a/src/rpc/CMakeLists.txt b/src/rpc/CMakeLists.txt index ebb1e767f..65d88b57e 100644 --- a/src/rpc/CMakeLists.txt +++ b/src/rpc/CMakeLists.txt @@ -37,6 +37,7 @@ set(rpc_sources bootstrap_daemon.cpp core_rpc_server.cpp rpc_payment.cpp + rpc_version_str.cpp instanciations) set(daemon_messages_sources @@ -54,6 +55,7 @@ set(rpc_base_headers rpc_handler.h) set(rpc_headers + rpc_version_str.h rpc_handler.h) set(daemon_rpc_server_headers) diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9117b5b3a..e92ae7c08 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include <boost/preprocessor/stringize.hpp> +#include <boost/uuid/nil_generator.hpp> #include "include_base_utils.h" #include "string_tools.h" using namespace epee; @@ -86,10 +87,14 @@ namespace RPCTracker(const char *rpc, tools::LoggingPerformanceTimer &timer): rpc(rpc), timer(timer) { } ~RPCTracker() { - boost::unique_lock<boost::mutex> lock(mutex); - auto &e = tracker[rpc]; - ++e.count; - e.time += timer.value(); + try + { + boost::unique_lock<boost::mutex> lock(mutex); + auto &e = tracker[rpc]; + ++e.count; + e.time += timer.value(); + } + catch (...) { /* ignore */ } } void pay(uint64_t amount) { boost::unique_lock<boost::mutex> lock(mutex); @@ -953,18 +958,21 @@ namespace cryptonote { e.double_spend_seen = it->second.double_spend_seen; e.relayed = it->second.relayed; + e.received_timestamp = it->second.receive_time; } else { MERROR("Failed to determine pool info for " << tx_hash); e.double_spend_seen = false; e.relayed = false; + e.received_timestamp = 0; } } 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.received_timestamp = 0; e.double_spend_seen = false; e.relayed = false; } @@ -1095,9 +1103,8 @@ namespace cryptonote } res.sanity_check_failed = false; - cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); - tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, req.do_not_relay) || tvc.m_verifivation_failed) + tx_verification_context tvc{}; + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (req.do_not_relay ? relay_method::none : relay_method::local), false) || tvc.m_verifivation_failed) { res.status = "Failed"; std::string reason = ""; @@ -1142,7 +1149,7 @@ namespace cryptonote NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = CORE_RPC_STATUS_OK; return true; @@ -1958,7 +1965,11 @@ namespace cryptonote m_was_bootstrap_ever_used = true; } - r = r && res.status == CORE_RPC_STATUS_OK; + if (r && res.status != CORE_RPC_STATUS_PAYMENT_REQUIRED && res.status != CORE_RPC_STATUS_OK) + { + MINFO("Failing RPC " << command_name << " due to peer return status " << res.status); + r = false; + } res.untrusted = true; return true; } @@ -2222,8 +2233,7 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_info_json(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { - on_get_info(req, res, ctx); - if (res.status != CORE_RPC_STATUS_OK) + if (!on_get_info(req, res, ctx) || res.status != CORE_RPC_STATUS_OK) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = res.status; @@ -2370,7 +2380,7 @@ namespace cryptonote if (req.txids.empty()) { std::vector<transaction> pool_txs; - bool r = m_core.get_pool_transactions(pool_txs); + bool r = m_core.get_pool_transactions(pool_txs, true); if (!r) { res.status = "Failed to get txpool contents"; @@ -2617,6 +2627,7 @@ namespace cryptonote { RPC_TRACKER(update); + res.update = false; if (m_core.offline()) { res.status = "Daemon is running offline"; @@ -2747,13 +2758,11 @@ namespace cryptonote crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); cryptonote::blobdata txblob; - bool r = m_core.get_pool_transaction(txid, txblob); - if (r) + if (!m_core.get_pool_transaction(txid, txblob, relay_category::legacy)) { - cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(txblob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes } else @@ -2940,7 +2949,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_info); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON, "rpc_access_info", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_INFO>(invoke_http_mode::JON_RPC, "rpc_access_info", req, res, r)) return r; // if RPC payment is not enabled @@ -3012,7 +3021,7 @@ namespace cryptonote { RPC_TRACKER(rpc_access_submit_nonce); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON, "rpc_access_submit_nonce", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_SUBMIT_NONCE>(invoke_http_mode::JON_RPC, "rpc_access_submit_nonce", req, res, r)) return r; // if RPC payment is not enabled @@ -3071,7 +3080,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_pay); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON, "rpc_access_pay", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_PAY>(invoke_http_mode::JON_RPC, "rpc_access_pay", req, res, r)) return r; // if RPC payment is not enabled @@ -3130,7 +3139,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_data); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON, "rpc_access_data", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_DATA>(invoke_http_mode::JON_RPC, "rpc_access_data", req, res, r)) return r; if (!m_rpc_payment) @@ -3158,7 +3167,7 @@ namespace cryptonote RPC_TRACKER(rpc_access_account); bool r; - if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON, "rpc_access_account", req, res, r)) + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ACCESS_ACCOUNT>(invoke_http_mode::JON_RPC, "rpc_access_account", req, res, r)) return r; if (!m_rpc_payment) diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 855ea854c..12b042c7e 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -88,7 +88,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 3 -#define CORE_RPC_VERSION_MINOR 0 +#define CORE_RPC_VERSION_MINOR 1 #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) @@ -351,6 +351,7 @@ namespace cryptonote bool double_spend_seen; uint64_t block_height; uint64_t block_timestamp; + uint64_t received_timestamp; std::vector<uint64_t> output_indices; bool relayed; @@ -372,6 +373,7 @@ namespace cryptonote else { KV_SERIALIZE(relayed) + KV_SERIALIZE(received_timestamp) } END_KV_SERIALIZE_MAP() }; @@ -2556,22 +2558,21 @@ namespace cryptonote struct COMMAND_RPC_FLUSH_CACHE { - struct request_t + struct request_t: public rpc_request_base { bool bad_txs; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) KV_SERIALIZE_OPT(bad_txs, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; - struct response_t + struct response_t: public rpc_response_base { - std::string status; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) + KV_SERIALIZE_PARENT(rpc_response_base) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index d7e081af3..24800ff20 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -28,6 +28,7 @@ #include "daemon_handler.h" +#include <boost/uuid/nil_generator.hpp> // likely included by daemon_handler.h's includes, // but including here for clarity #include "cryptonote_core/cryptonote_core.h" @@ -288,10 +289,9 @@ namespace rpc return; } - cryptonote_connection_context fake_context = AUTO_VAL_INIT(fake_context); tx_verification_context tvc = AUTO_VAL_INIT(tvc); - if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, false, false, !relay) || tvc.m_verifivation_failed) + if(!m_core.handle_incoming_tx({tx_blob, crypto::null_hash}, tvc, (relay ? relay_method::local : relay_method::none), false) || tvc.m_verifivation_failed) { if (tvc.m_verifivation_failed) { @@ -311,42 +311,42 @@ namespace rpc if (tvc.m_double_spend) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "double spend"; + res.error_details += "double spend"; } if (tvc.m_invalid_input) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "invalid input"; + res.error_details += "invalid input"; } if (tvc.m_invalid_output) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "invalid output"; + res.error_details += "invalid output"; } if (tvc.m_too_big) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "too big"; + res.error_details += "too big"; } if (tvc.m_overspend) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "overspend"; + res.error_details += "overspend"; } if (tvc.m_fee_too_low) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "fee too low"; + res.error_details += "fee too low"; } if (tvc.m_not_rct) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "tx is not ringct"; + res.error_details += "tx is not ringct"; } if (tvc.m_too_few_outputs) { if (!res.error_details.empty()) res.error_details += " and "; - res.error_details = "too few outputs"; + res.error_details += "too few outputs"; } if (res.error_details.empty()) { @@ -368,7 +368,7 @@ namespace rpc NOTIFY_NEW_TRANSACTIONS::request r; r.txs.push_back(tx_blob); - m_core.get_protocol()->relay_transactions(r, fake_context); + m_core.get_protocol()->relay_transactions(r, boost::uuids::nil_uuid(), epee::net_utils::zone::invalid); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes res.status = Message::STATUS_OK; diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index 0637db728..b363c27b2 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -59,6 +59,10 @@ namespace cryptonote { rpc_payment::client_info::client_info(): + previous_seed_height(0), + seed_height(0), + previous_seed_hash(crypto::null_hash), + seed_hash(crypto::null_hash), cookie(0), top(crypto::null_hash), previous_top(crypto::null_hash), diff --git a/src/rpc/rpc_version_str.cpp b/src/rpc/rpc_version_str.cpp new file mode 100644 index 000000000..c60cf4891 --- /dev/null +++ b/src/rpc/rpc_version_str.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "rpc_version_str.h" +#include "version.h" +#include <regex> + +namespace cryptonote +{ + +namespace rpc +{ + +// Expected format of Monero software version string: +// 1) Four numbers, one to two digits each, separated by periods +// 2) Optionally, one of the following suffixes: +// a) -release +// b) -<hash> where <hash> is exactly nine lowercase hex digits + +bool is_version_string_valid(const std::string& str) +{ + return std::regex_match(str, std::regex( + "^\\d{1,2}(\\.\\d{1,2}){3}(-(release|[0-9a-f]{9}))?$", + std::regex_constants::nosubs + )); +} + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/rpc/rpc_version_str.h b/src/rpc/rpc_version_str.h new file mode 100644 index 000000000..930c807d2 --- /dev/null +++ b/src/rpc/rpc_version_str.h @@ -0,0 +1,43 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +#pragma once + +#include <string> + +namespace cryptonote +{ + +namespace rpc +{ + +bool is_version_string_valid(const std::string& str); + +} // namespace rpc + +} // namespace cryptonote diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index ea8f6f2f5..74e793669 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -121,7 +121,7 @@ typedef cryptonote::simple_wallet sw; #define SCOPED_WALLET_UNLOCK() SCOPED_WALLET_UNLOCK_ON_BAD_PASSWORD(return true;) -#define PRINT_USAGE(usage_help) fail_msg_writer() << boost::format(tr("usage: %s")) % usage_help; +#define PRINT_USAGE(usage_help_advanced) fail_msg_writer() << boost::format(tr("usage: %s")) % usage_help_advanced; #define LONG_PAYMENT_ID_SUPPORT_CHECK() \ do { \ @@ -196,7 +196,7 @@ namespace " account tag_description <tag_name> <description>"); const char* USAGE_ADDRESS("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed> | device [<index>]]"); const char* USAGE_INTEGRATED_ADDRESS("integrated_address [device] [<payment_id> | <address>]"); - const char* USAGE_ADDRESS_BOOK("address_book [(add ((<address> [pid <id>])|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"); + const char* USAGE_ADDRESS_BOOK("address_book [(add (<address>|<integrated address>) [<description possibly with whitespaces>])|(delete <index>)]"); const char* USAGE_SET_VARIABLE("set <option> [<value>]"); const char* USAGE_GET_TX_KEY("get_tx_key <txid>"); const char* USAGE_SET_TX_KEY("set_tx_key <txid> <tx_key>"); @@ -214,7 +214,7 @@ namespace const char* USAGE_GET_TX_NOTE("get_tx_note <txid>"); const char* USAGE_GET_DESCRIPTION("get_description"); const char* USAGE_SET_DESCRIPTION("set_description [free text note]"); - const char* USAGE_SIGN("sign <filename>"); + const char* USAGE_SIGN("sign [<account_index>,<address_index>] <filename>"); const char* USAGE_VERIFY("verify <filename> <address> <signature>"); const char* USAGE_EXPORT_KEY_IMAGES("export_key_images [all] <filename>"); const char* USAGE_IMPORT_KEY_IMAGES("import_key_images <filename>"); @@ -268,7 +268,8 @@ namespace const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_VERSION("version"); - const char* USAGE_HELP("help [<command>]"); + const char* USAGE_HELP_ADVANCED("help_advanced [<command>]"); + const char* USAGE_HELP("help"); std::string input_line(const std::string& prompt, bool yesno = false) { @@ -912,16 +913,6 @@ bool simple_wallet::change_password(const std::vector<std::string> &args) bool simple_wallet::payment_id(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { LONG_PAYMENT_ID_SUPPORT_CHECK(); - - crypto::hash payment_id; - if (args.size() > 0) - { - PRINT_USAGE(USAGE_PAYMENT_ID); - return true; - } - payment_id = crypto::rand<crypto::hash>(); - success_msg_writer() << tr("Random payment ID: ") << payment_id; - return true; } bool simple_wallet::print_fee_info(const std::vector<std::string> &args/* = std::vector<std::string>()*/) @@ -2315,7 +2306,7 @@ bool simple_wallet::on_unknown_command(const std::vector<std::string> &args) { if (args[0] == "exit" || args[0] == "q") // backward compat return false; - fail_msg_writer() << boost::format(tr("Unknown command '%s', try 'help'")) % args.front(); + fail_msg_writer() << boost::format(tr("Unknown command '%s', try 'help_advanced'")) % args.front(); return true; } @@ -3044,13 +3035,37 @@ bool simple_wallet::set_export_format(const std::vector<std::string> &args/* = s bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + message_writer() << ""; + message_writer() << tr("Commands:"); + message_writer() << ""; + message_writer() << tr("\"welcome\" - Read welcome message."); + message_writer() << tr("\"donate <amount>\" - Donate XMR to the development team."); + message_writer() << tr("\"balance\" - Show balance."); + message_writer() << tr("\"address new\" - Create new subaddress."); + message_writer() << tr("\"address all\" - Show all addresses."); + message_writer() << tr("\"transfer <address> <amount>\" - Send XMR to an address."); + message_writer() << tr("\"show_transfers [in|out|pending|failed|pool]\" - Show transactions."); + message_writer() << tr("\"sweep_all <address>\" - Send whole balance to another wallet."); + message_writer() << tr("\"seed\" - Show secret 25 words that can be used to recover this wallet."); + message_writer() << tr("\"refresh\" - Synchronize wallet with the Monero network."); + message_writer() << tr("\"status\" - Check current status of wallet."); + message_writer() << tr("\"version\" - Check software version."); + message_writer() << tr("\"help_advanced\" - Show list with more available commands."); + message_writer() << tr("\"save\" - Save wallet."); + message_writer() << tr("\"exit\" - Exit wallet."); + message_writer() << ""; + return true; +} + +bool simple_wallet::help_advanced(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ if(args.empty()) { success_msg_writer() << get_commands_str(); } else if ((args.size() == 2) && (args.front() == "mms")) { - // Little hack to be able to do "help mms <subcommand>" + // Little hack to be able to do "help_advanced mms <subcommand>" std::vector<std::string> mms_args(1, args.front() + " " + args.back()); success_msg_writer() << get_command_usage(mms_args); } @@ -3262,7 +3277,9 @@ simple_wallet::simple_wallet() "auto-mine-for-rpc-payment-threshold <float>\n " " Whether to automatically start mining for RPC payment if the daemon requires it.\n" "credits-target <unsigned int>\n" - " The RPC payment credits balance to target (0 for default).")); + " The RPC payment credits balance to target (0 for default).\n " + "inactivity-lock-timeout <unsigned int>\n " + " How many seconds to wait before locking the wallet (0 to disable).")); m_cmd_binder.set_handler("encrypted_seed", boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1), tr("Display the encrypted Electrum-style mnemonic seed.")); @@ -3356,7 +3373,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("sign", boost::bind(&simple_wallet::on_command, this, &simple_wallet::sign, _1), tr(USAGE_SIGN), - tr("Sign the contents of a file.")); + tr("Sign the contents of a file with the given subaddress (or the main address if not specified)")); m_cmd_binder.set_handler("verify", boost::bind(&simple_wallet::on_command, this, &simple_wallet::verify, _1), tr(USAGE_VERIFY), @@ -3439,7 +3456,7 @@ simple_wallet::simple_wallet() "<subcommand> is one of:\n" " init, info, signer, list, next, sync, transfer, delete, send, receive, export, note, show, set, help\n" " send_signer_config, start_auto_config, stop_auto_config, auto_config\n" - "Get help about a subcommand with: help mms <subcommand>, or mms help <subcommand>")); + "Get help about a subcommand with: help_advanced mms <subcommand>")); m_cmd_binder.set_handler("mms init", boost::bind(&simple_wallet::on_command, this, &simple_wallet::mms, _1), tr(USAGE_MMS_INIT), @@ -3589,10 +3606,14 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::stop_mining_for_rpc, this, _1), tr(USAGE_STOP_MINING_FOR_RPC), tr("Stop mining to pay for RPC access")); + m_cmd_binder.set_handler("help_advanced", + boost::bind(&simple_wallet::on_command, this, &simple_wallet::help_advanced, _1), + tr(USAGE_HELP_ADVANCED), + tr("Show the help section or the documentation about a <command>.")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::on_command, this, &simple_wallet::help, _1), tr(USAGE_HELP), - tr("Show the help section or the documentation about a <command>.")); + tr("Show simplified list of available commands.")); m_cmd_binder.set_unknown_command_handler(boost::bind(&simple_wallet::on_command, this, &simple_wallet::on_unknown_command, _1)); m_cmd_binder.set_empty_command_handler(boost::bind(&simple_wallet::on_empty_command, this)); m_cmd_binder.set_cancel_handler(boost::bind(&simple_wallet::on_cancelled_command, this)); @@ -4356,7 +4377,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (std::cin.eof() || !command_line::is_yes(confirm)) CHECK_AND_ASSERT_MES(false, false, tr("account creation aborted")); - m_wallet->set_refresh_from_block_height(m_wallet->estimate_blockchain_height()); + m_wallet->set_refresh_from_block_height(m_wallet->estimate_blockchain_height() > 0 ? m_wallet->estimate_blockchain_height() - 1 : 0); m_wallet->explicit_refresh_from_block_height(true); m_restore_height = m_wallet->get_refresh_from_block_height(); } @@ -4744,8 +4765,9 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr "**********************************************************************\n" << tr("Your wallet has been generated!\n" "To start synchronizing with the daemon, use the \"refresh\" command.\n" - "Use the \"help\" command to see the list of available commands.\n" - "Use \"help <command>\" to see a command's documentation.\n" + "Use the \"help\" command to see a simplified list of available commands.\n" + "Use the \"help_advanced\" command to see an advanced list of available commands.\n" + "Use \"help_advanced <command>\" to see a command's documentation.\n" "Always use the \"exit\" command when closing monero-wallet-cli to save \n" "your current session's state. Otherwise, you might need to synchronize \n" "your wallet again (your wallet keys are NOT at risk in any case).\n") @@ -5004,8 +5026,9 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p } success_msg_writer() << "**********************************************************************\n" << - tr("Use the \"help\" command to see the list of available commands.\n") << - tr("Use \"help <command>\" to see a command's documentation.\n") << + tr("Use the \"help\" command to see a simplified list of available commands.\n") << + tr("Use the \"help_advanced\" command to see an advanced list of available commands.\n") << + tr("Use \"help_advanced <command>\" to see a command's documentation.\n") << "**********************************************************************"; return password; } @@ -6195,7 +6218,8 @@ void simple_wallet::check_for_inactivity_lock(bool user) } while (1) { - tools::msg_writer() << tr("Locked due to inactivity. The wallet password is required to unlock the console."); + const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); + tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); try { if (get_and_verify_password()) @@ -6293,13 +6317,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (tools::wallet2::parse_long_payment_id(payment_id_str, payment_id)) { LONG_PAYMENT_ID_SUPPORT_CHECK(); - - std::string extra_nonce; - set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - r = add_extra_nonce_to_tx_extra(extra, extra_nonce); - local_args.pop_back(); - payment_id_seen = true; - message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead"); } if(!r) { @@ -6408,8 +6425,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri else if (tools::wallet2::parse_payment_id(payment_id_uri, payment_id)) { LONG_PAYMENT_ID_SUPPORT_CHECK(); - set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead"); } else { @@ -6945,11 +6960,6 @@ bool simple_wallet::sweep_main(uint64_t below, bool locked, const std::vector<st if(r) { LONG_PAYMENT_ID_SUPPORT_CHECK(); - - std::string extra_nonce; - set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); - r = add_extra_nonce_to_tx_extra(extra, extra_nonce); - payment_id_seen = true; } if(!r && local_args.size() == 3) @@ -7191,7 +7201,6 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) if (tools::wallet2::parse_long_payment_id(local_args.back(), payment_id)) { LONG_PAYMENT_ID_SUPPORT_CHECK(); - set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); } else { @@ -9410,30 +9419,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v fail_msg_writer() << tr("failed to parse address"); return true; } - crypto::hash payment_id = crypto::null_hash; size_t description_start = 2; - if (info.has_payment_id) - { - memcpy(payment_id.data, info.payment_id.data, 8); - } - else if (!info.has_payment_id && args.size() >= 4 && args[2] == "pid") - { - if (tools::wallet2::parse_long_payment_id(args[3], payment_id)) - { - LONG_PAYMENT_ID_SUPPORT_CHECK(); - description_start += 2; - } - else if (tools::wallet2::parse_short_payment_id(args[3], info.payment_id)) - { - fail_msg_writer() << tr("Short payment IDs are to be used within an integrated address only"); - return true; - } - else - { - fail_msg_writer() << tr("failed to parse payment ID"); - return true; - } - } std::string description; for (size_t i = description_start; i < args.size(); ++i) { @@ -9441,7 +9427,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v description += " "; description += args[i]; } - m_wallet->add_address_book_row(info.address, payment_id, description, info.is_subaddress); + m_wallet->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL, description, info.is_subaddress); } else { @@ -9463,8 +9449,12 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v for (size_t i = 0; i < address_book.size(); ++i) { auto& row = address_book[i]; success_msg_writer() << tr("Index: ") << i; - success_msg_writer() << tr("Address: ") << get_account_address_as_str(m_wallet->nettype(), row.m_is_subaddress, row.m_address); - success_msg_writer() << tr("Payment ID: ") << row.m_payment_id << " (OBSOLETE)"; + std::string address; + if (row.m_has_payment_id) + address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), row.m_address, row.m_payment_id); + else + address = get_account_address_as_str(m_wallet->nettype(), row.m_is_subaddress, row.m_address); + success_msg_writer() << tr("Address: ") << address; success_msg_writer() << tr("Description: ") << row.m_description << "\n"; } } @@ -9616,7 +9606,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (args.size() != 1) + if (args.size() != 1 && args.size() != 2) { PRINT_USAGE(USAGE_SIGN); return true; @@ -9632,7 +9622,20 @@ bool simple_wallet::sign(const std::vector<std::string> &args) return true; } - std::string filename = args[0]; + subaddress_index index{0, 0}; + if (args.size() == 2) + { + unsigned int a, b; + if (sscanf(args[0].c_str(), "%u,%u", &a, &b) != 2) + { + fail_msg_writer() << tr("Invalid subaddress index format"); + return true; + } + index.major = a; + index.minor = b; + } + + const std::string &filename = args.back(); std::string data; bool r = m_wallet->load_from_file(filename, data); if (!r) @@ -9643,7 +9646,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) SCOPED_WALLET_UNLOCK(); - std::string signature = m_wallet->sign(data); + std::string signature = m_wallet->sign(data, index); success_msg_writer() << signature; return true; } @@ -11016,7 +11019,7 @@ void simple_wallet::mms_help(const std::vector<std::string> &args) { if (args.size() > 1) { - fail_msg_writer() << tr("Usage: mms help [<subcommand>]"); + fail_msg_writer() << tr("Usage: help_advanced mms [<subcommand>]"); return; } std::vector<std::string> help_args; diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 75bd893d5..e401f5fda 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -154,6 +154,7 @@ namespace cryptonote bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>()); bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>()); bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); + bool help_advanced(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index 3be1a6f6b..a0a166a93 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -77,42 +77,43 @@ target_link_libraries(wallet PRIVATE ${EXTRA_LIBRARIES}) -set(wallet_rpc_sources - wallet_rpc_server.cpp) +if(NOT IOS) + set(wallet_rpc_sources + wallet_rpc_server.cpp) -set(wallet_rpc_headers) + set(wallet_rpc_headers) -set(wallet_rpc_private_headers - wallet_rpc_server.h) + set(wallet_rpc_private_headers + wallet_rpc_server.h) -monero_private_headers(wallet_rpc_server - ${wallet_rpc_private_headers}) -monero_add_executable(wallet_rpc_server - ${wallet_rpc_sources} - ${wallet_rpc_headers} - ${wallet_rpc_private_headers}) - -target_link_libraries(wallet_rpc_server - PRIVATE - wallet - rpc_base - cryptonote_core - cncrypto - common - version - daemonizer - ${EPEE_READLINE} - ${Boost_CHRONO_LIBRARY} - ${Boost_PROGRAM_OPTIONS_LIBRARY} - ${Boost_FILESYSTEM_LIBRARY} - ${Boost_THREAD_LIBRARY} - ${CMAKE_THREAD_LIBS_INIT} - ${EXTRA_LIBRARIES}) -set_property(TARGET wallet_rpc_server - PROPERTY - OUTPUT_NAME "monero-wallet-rpc") -install(TARGETS wallet_rpc_server DESTINATION bin) + monero_private_headers(wallet_rpc_server + ${wallet_rpc_private_headers}) + monero_add_executable(wallet_rpc_server + ${wallet_rpc_sources} + ${wallet_rpc_headers} + ${wallet_rpc_private_headers}) + target_link_libraries(wallet_rpc_server + PRIVATE + wallet + rpc_base + cryptonote_core + cncrypto + common + version + daemonizer + ${EPEE_READLINE} + ${Boost_CHRONO_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${Boost_THREAD_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES}) + set_property(TARGET wallet_rpc_server + PROPERTY + OUTPUT_NAME "monero-wallet-rpc") + install(TARGETS wallet_rpc_server DESTINATION bin) +endif() # build and install libwallet_merged only if we building for GUI if (BUILD_GUI_DEPS) diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index 7be78bba7..005ddf7ee 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -55,37 +55,14 @@ bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &pa return false; } - crypto::hash payment_id = crypto::null_hash; - bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); - - // Short payment id provided - if(payment_id_str.length() == 16) { - m_errorString = tr("Invalid payment ID. Short payment ID should only be used in an integrated address"); - m_errorCode = Invalid_Payment_Id; - return false; - } - - // long payment id provided but not valid - if(!payment_id_str.empty() && !has_long_pid) { - m_errorString = tr("Invalid payment ID"); - m_errorCode = Invalid_Payment_Id; - return false; - } - - // integrated + long payment id provided - if(has_long_pid && info.has_payment_id) { - m_errorString = tr("Integrated address and long payment ID can't be used at the same time"); + if (!payment_id_str.empty()) + { + m_errorString = tr("Payment ID supplied: this is obsolete"); m_errorCode = Invalid_Payment_Id; return false; } - // Pad short pid with zeros - if (info.has_payment_id) - { - memcpy(payment_id.data, info.payment_id.data, 8); - } - - bool r = m_wallet->m_wallet->add_address_book_row(info.address,payment_id,description,info.is_subaddress); + bool r = m_wallet->m_wallet->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL,description,info.is_subaddress); if (r) refresh(); else @@ -104,19 +81,12 @@ void AddressBookImpl::refresh() for (size_t i = 0; i < rows.size(); ++i) { tools::wallet2::address_book_row * row = &rows.at(i); - std::string payment_id = (row->m_payment_id == crypto::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id); - std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->nettype(), row->m_is_subaddress, row->m_address); - // convert the zero padded short payment id to integrated address - if (!row->m_is_subaddress && payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) { - payment_id = payment_id.substr(0,16); - crypto::hash8 payment_id_short; - if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) { - address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->nettype(), row->m_address, payment_id_short); - // Don't show payment id when integrated address is used - payment_id = ""; - } - } - AddressBookRow * abr = new AddressBookRow(i, address, payment_id, row->m_description); + std::string address; + if (row->m_has_payment_id) + address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->nettype(), row->m_address, row->m_payment_id); + else + address = get_account_address_as_str(m_wallet->m_wallet->nettype(), row->m_is_subaddress, row->m_address); + AddressBookRow * abr = new AddressBookRow(i, address, "", row->m_description); m_rows.push_back(abr); } diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 005b0bafa..f3698b599 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -78,6 +78,10 @@ void NodeRPCProxy::invalidate() m_rpc_payment_seed_hash = crypto::null_hash; m_rpc_payment_next_seed_hash = crypto::null_hash; m_height_time = 0; + m_rpc_payment_diff = 0; + m_rpc_payment_credits_per_hash_found = 0; + m_rpc_payment_height = 0; + m_rpc_payment_cookie = 0; } boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e60c6b7e1..870aa65ac 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1038,10 +1038,15 @@ uint64_t gamma_picker::pick() return first_rct + crypto::rand_idx(n_rct); }; +boost::mutex wallet_keys_unlocker::lockers_lock; +unsigned int wallet_keys_unlocker::lockers = 0; wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<tools::password_container> &password): w(w), locked(password != boost::none) { + boost::lock_guard<boost::mutex> lock(lockers_lock); + if (lockers++ > 0) + locked = false; if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only()) { locked = false; @@ -1056,6 +1061,9 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee:: w(w), locked(locked) { + boost::lock_guard<boost::mutex> lock(lockers_lock); + if (lockers++ > 0) + locked = false; if (!locked) return; w.generate_chacha_key_from_password(password, key); @@ -1064,9 +1072,19 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, bool locked, const epee:: wallet_keys_unlocker::~wallet_keys_unlocker() { - if (!locked) - return; - try { w.encrypt_keys(key); } + try + { + boost::lock_guard<boost::mutex> lock(lockers_lock); + if (lockers == 0) + { + MERROR("There are no lockers in wallet_keys_unlocker dtor"); + return; + } + --lockers; + if (!locked) + return; + w.encrypt_keys(key); + } catch (...) { MERROR("Failed to re-encrypt wallet keys"); @@ -3133,11 +3151,12 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, } -bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress) +bool wallet2::add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress) { wallet2::address_book_row a; a.m_address = address; - a.m_payment_id = payment_id; + a.m_has_payment_id = !!payment_id; + a.m_payment_id = payment_id ? *payment_id : crypto::null_hash8; a.m_description = description; a.m_is_subaddress = is_subaddress; @@ -3148,11 +3167,12 @@ bool wallet2::add_address_book_row(const cryptonote::account_public_address &add return false; } -bool wallet2::set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress) +bool wallet2::set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress) { wallet2::address_book_row a; a.m_address = address; - a.m_payment_id = payment_id; + a.m_has_payment_id = !!payment_id; + a.m_payment_id = payment_id ? *payment_id : crypto::null_hash8; a.m_description = description; a.m_is_subaddress = is_subaddress; @@ -5394,6 +5414,7 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) void wallet2::set_offline(bool offline) { m_offline = offline; + m_node_rpc_proxy.set_offline(offline); m_http_client.set_auto_connect(!offline); if (offline) { @@ -7555,6 +7576,8 @@ bool wallet2::is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) bool wallet2::lock_keys_file() { + if (m_wallet_file.empty()) + return true; if (m_keys_file_locker) { MDEBUG(m_keys_file << " is already locked."); @@ -7566,6 +7589,8 @@ bool wallet2::lock_keys_file() bool wallet2::unlock_keys_file() { + if (m_wallet_file.empty()) + return true; if (!m_keys_file_locker) { MDEBUG(m_keys_file << " is already unlocked."); @@ -7577,6 +7602,8 @@ bool wallet2::unlock_keys_file() bool wallet2::is_keys_file_locked() const { + if (m_wallet_file.empty()) + return false; return m_keys_file_locker->locked(); } @@ -11802,13 +11829,27 @@ void wallet2::set_account_tag_description(const std::string& tag, const std::str m_account_tags.first[tag] = description; } -std::string wallet2::sign(const std::string &data) const +std::string wallet2::sign(const std::string &data, cryptonote::subaddress_index index) const { crypto::hash hash; crypto::cn_fast_hash(data.data(), data.size(), hash); const cryptonote::account_keys &keys = m_account.get_keys(); crypto::signature signature; - crypto::generate_signature(hash, keys.m_account_address.m_spend_public_key, keys.m_spend_secret_key, signature); + crypto::secret_key skey; + crypto::public_key pkey; + if (index.is_zero()) + { + skey = keys.m_spend_secret_key; + pkey = keys.m_account_address.m_spend_public_key; + } + else + { + skey = keys.m_spend_secret_key; + crypto::secret_key m = m_account.get_device().get_subaddress_secret_key(keys.m_view_secret_key, index); + sc_add((unsigned char*)&skey, (unsigned char*)&m, (unsigned char*)&skey); + secret_key_to_public_key(skey, pkey); + } + crypto::generate_signature(hash, pkey, skey, signature); return std::string("SigV1") + tools::base58::encode(std::string((const char *)&signature, sizeof(signature))); } @@ -13601,4 +13642,22 @@ std::vector<cryptonote::public_node> wallet2::get_public_nodes(bool white_only) std::copy(res.gray.begin(), res.gray.end(), std::back_inserter(nodes)); return nodes; } +//---------------------------------------------------------------------------------------------------- +std::pair<size_t, uint64_t> wallet2::estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size) +{ + THROW_WALLET_EXCEPTION_IF(n_inputs <= 0, tools::error::wallet_internal_error, "Invalid n_inputs"); + THROW_WALLET_EXCEPTION_IF(n_outputs < 0, tools::error::wallet_internal_error, "Invalid n_outputs"); + THROW_WALLET_EXCEPTION_IF(ring_size < 0, tools::error::wallet_internal_error, "Invalid ring size"); + + if (ring_size == 0) + ring_size = get_min_ring_size(); + if (n_outputs == 1) + n_outputs = 2; // extra dummy output + + const bool bulletproof = use_fork_rules(get_bulletproof_fork(), 0); + size_t size = estimate_tx_size(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); + uint64_t weight = estimate_tx_weight(use_rct, n_inputs, ring_size - 1, n_outputs, extra_size, bulletproof); + return std::make_pair(size, weight); +} +//---------------------------------------------------------------------------------------------------- } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c86315f7c..b17fe6f3a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -122,6 +122,8 @@ private: wallet2 &w; bool locked; crypto::chacha_key key; + static boost::mutex lockers_lock; + static unsigned int lockers; }; class i_wallet2_callback @@ -544,9 +546,10 @@ private: struct address_book_row { cryptonote::account_public_address m_address; - crypto::hash m_payment_id; + crypto::hash8 m_payment_id; std::string m_description; bool m_is_subaddress; + bool m_has_payment_id; }; struct reserve_proof_entry @@ -1123,8 +1126,8 @@ private: * \brief GUI Address book get/store */ std::vector<address_book_row> get_address_book() const { return m_address_book; } - bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress); - bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash &payment_id, const std::string &description, bool is_subaddress); + bool add_address_book_row(const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress); + bool set_address_book_row(size_t row_id, const cryptonote::account_public_address &address, const crypto::hash8 *payment_id, const std::string &description, bool is_subaddress); bool delete_address_book_row(std::size_t row_id); uint64_t get_num_rct_outputs(); @@ -1182,7 +1185,7 @@ private: */ void set_account_tag_description(const std::string& tag, const std::string& description); - std::string sign(const std::string &data) const; + std::string sign(const std::string &data, cryptonote::subaddress_index index = {0, 0}) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; /*! @@ -1252,6 +1255,8 @@ private: bool is_unattended() const { return m_unattended; } + std::pair<size_t, uint64_t> estimate_tx_size_and_weight(bool use_rct, int n_inputs, int ring_size, int n_outputs, size_t extra_size); + bool get_rpc_payment_info(bool mining, bool &payment_required, uint64_t &credits, uint64_t &diff, uint64_t &credits_per_hash_found, cryptonote::blobdata &hashing_blob, uint64_t &height, uint64_t &seed_height, crypto::hash &seed_hash, crypto::hash &next_seed_hash, uint32_t &cookie); bool daemon_requires_payment(); bool make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credits, uint64_t &balance); @@ -1630,7 +1635,7 @@ BOOST_CLASS_VERSION(tools::wallet2::payment_details, 4) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) -BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) +BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) @@ -1950,7 +1955,26 @@ namespace boost inline void serialize(Archive& a, tools::wallet2::address_book_row& x, const boost::serialization::version_type ver) { a & x.m_address; - a & x.m_payment_id; + if (ver < 18) + { + crypto::hash payment_id; + a & payment_id; + x.m_has_payment_id = !(payment_id == crypto::null_hash); + if (x.m_has_payment_id) + { + bool is_long = false; + for (int i = 8; i < 32; ++i) + is_long |= payment_id.data[i]; + if (is_long) + { + MWARNING("Long payment ID ignored on address book load"); + x.m_payment_id = crypto::null_hash8; + x.m_has_payment_id = false; + } + else + memcpy(x.m_payment_id.data, payment_id.data, 8); + } + } a & x.m_description; if (ver < 17) { @@ -1958,6 +1982,11 @@ namespace boost return; } a & x.m_is_subaddress; + if (ver < 18) + return; + a & x.m_has_payment_id; + if (x.m_has_payment_id) + a & x.m_payment_id; } template <class Archive> diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index de501f056..d282d7cb2 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -550,9 +550,29 @@ namespace tools if (!m_wallet) return not_open(er); try { - m_wallet->add_subaddress(req.account_index, req.label); - res.address_index = m_wallet->get_num_subaddresses(req.account_index) - 1; - res.address = m_wallet->get_subaddress_as_str({req.account_index, res.address_index}); + if (req.count < 1 || req.count > 64) { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Count must be between 1 and 64."; + return false; + } + + std::vector<std::string> addresses; + std::vector<uint32_t> address_indices; + + addresses.reserve(req.count); + address_indices.reserve(req.count); + + for (uint32_t i = 0; i < req.count; i++) { + m_wallet->add_subaddress(req.account_index, req.label); + uint32_t new_address_index = m_wallet->get_num_subaddresses(req.account_index) - 1; + address_indices.push_back(new_address_index); + addresses.push_back(m_wallet->get_subaddress_as_str({req.account_index, new_address_index})); + } + + res.address = addresses[0]; + res.address_index = address_indices[0]; + res.addresses = addresses; + res.address_indices = address_indices; } catch (const std::exception& e) { @@ -843,7 +863,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ template<typename Ts, typename Tu> bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er) { for (const auto & ptx : ptx_vector) @@ -858,6 +878,7 @@ namespace tools // Compute amount leaving wallet in tx. By convention dests does not include change outputs fill(amount, total_amount(ptx)); fill(fee, ptx.fee); + fill(weight, cryptonote::get_transaction_weight(ptx.tx)); } if (m_wallet->multisig()) @@ -943,7 +964,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -989,7 +1010,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -1353,7 +1374,7 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -1400,7 +1421,7 @@ namespace tools uint32_t priority = m_wallet->adjust_priority(req.priority); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); - return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) @@ -1475,7 +1496,7 @@ namespace tools return false; } - return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, res.unsigned_txset, req.do_not_relay, + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.weight, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) @@ -1943,7 +1964,7 @@ namespace tools return false; } - res.signature = m_wallet->sign(req.data); + res.signature = m_wallet->sign(req.data, {req.account_index, req.address_index}); return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -2725,7 +2746,14 @@ namespace tools { uint64_t idx = 0; for (const auto &entry: ab) - res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + { + std::string address; + if (entry.m_has_payment_id) + address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.m_address, entry.m_payment_id); + else + address = get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address); + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx++, address, entry.m_description}); + } } else { @@ -2738,7 +2766,12 @@ namespace tools return false; } const auto &entry = ab[idx]; - res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address), epee::string_tools::pod_to_hex(entry.m_payment_id), entry.m_description}); + std::string address; + if (entry.m_has_payment_id) + address = cryptonote::get_account_integrated_address_as_str(m_wallet->nettype(), entry.m_address, entry.m_payment_id); + else + address = get_account_address_as_str(m_wallet->nettype(), entry.m_is_subaddress, entry.m_address); + res.entries.push_back(wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::entry{idx, address, entry.m_description}); } } return true; @@ -2755,7 +2788,6 @@ namespace tools } cryptonote::address_parse_info info; - crypto::hash payment_id = crypto::null_hash; er.message = ""; if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address, [&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string { @@ -2777,39 +2809,7 @@ namespace tools er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + req.address; return false; } - if (info.has_payment_id) - { - memcpy(payment_id.data, info.payment_id.data, 8); - memset(payment_id.data + 8, 0, 24); - } - if (!req.payment_id.empty()) - { - if (info.has_payment_id) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Separate payment ID given with integrated address"; - return false; - } - - crypto::hash long_payment_id; - - if (!wallet2::parse_long_payment_id(req.payment_id, payment_id)) - { - if (!wallet2::parse_short_payment_id(req.payment_id, info.payment_id)) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string"; - return false; - } - else - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address"; - return false; - } - } - } - if (!m_wallet->add_address_book_row(info.address, payment_id, req.description, info.is_subaddress)) + if (!m_wallet->add_address_book_row(info.address, info.has_payment_id ? &info.payment_id : NULL, req.description, info.is_subaddress)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Failed to add address book entry"; @@ -2840,7 +2840,6 @@ namespace tools tools::wallet2::address_book_row entry = ab[req.index]; cryptonote::address_parse_info info; - crypto::hash payment_id = crypto::null_hash; if (req.set_address) { er.message = ""; @@ -2867,52 +2866,13 @@ namespace tools entry.m_address = info.address; entry.m_is_subaddress = info.is_subaddress; if (info.has_payment_id) - { - memcpy(entry.m_payment_id.data, info.payment_id.data, 8); - memset(entry.m_payment_id.data + 8, 0, 24); - } - } - - if (req.set_payment_id) - { - if (req.payment_id.empty()) - { - payment_id = crypto::null_hash; - } - else - { - if (req.set_address && info.has_payment_id) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Separate payment ID given with integrated address"; - return false; - } - - if (!wallet2::parse_long_payment_id(req.payment_id, payment_id)) - { - crypto::hash8 spid; - if (!wallet2::parse_short_payment_id(req.payment_id, spid)) - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment id has invalid format: \"" + req.payment_id + "\", expected 64 character string"; - return false; - } - else - { - er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; - er.message = "Payment id has invalid format: standalone short payment IDs are forbidden, they must be part of an integrated address"; - return false; - } - } - } - - entry.m_payment_id = payment_id; + entry.m_payment_id = info.payment_id; } if (req.set_description) entry.m_description = req.description; - if (!m_wallet->set_address_book_row(req.index, entry.m_address, entry.m_payment_id, entry.m_description, entry.m_is_subaddress)) + if (!m_wallet->set_address_book_row(req.index, entry.m_address, req.set_address && entry.m_has_payment_id ? &entry.m_payment_id : NULL, entry.m_description, entry.m_is_subaddress)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Failed to edit address book entry"; @@ -4291,6 +4251,25 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); + try + { + size_t extra_size = 34 /* pubkey */ + 10 /* encrypted payment id */; // typical makeup + const std::pair<size_t, uint64_t> sw = m_wallet->estimate_tx_size_and_weight(req.rct, req.n_inputs, req.ring_size, req.n_outputs, extra_size); + res.size = sw.first; + res.weight = sw.second; + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to determine size and weight"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx) { res.version = WALLET_RPC_VERSION; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index b2b5e7116..89bf3a924 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -154,6 +154,7 @@ namespace tools MAP_JON_RPC_WE("set_daemon", on_set_daemon, wallet_rpc::COMMAND_RPC_SET_DAEMON) MAP_JON_RPC_WE("set_log_level", on_set_log_level, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL) MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES) + MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT) MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -240,6 +241,7 @@ namespace tools bool on_set_daemon(const wallet_rpc::COMMAND_RPC_SET_DAEMON::request& req, wallet_rpc::COMMAND_RPC_SET_DAEMON::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_set_log_level(const wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_LEVEL::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); //json rpc v2 @@ -255,7 +257,7 @@ namespace tools template<typename Ts, typename Tu> bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, - bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, Tu &weight, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er); bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 0c86f404d..d6735e117 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 16 +#define WALLET_RPC_VERSION_MINOR 17 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -182,11 +182,13 @@ namespace wallet_rpc { struct request_t { - uint32_t account_index; + uint32_t account_index; + uint32_t count; std::string label; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) + KV_SERIALIZE_OPT(count, 1U) KV_SERIALIZE(label) END_KV_SERIALIZE_MAP() }; @@ -194,12 +196,16 @@ namespace wallet_rpc struct response_t { - std::string address; - uint32_t address_index; + std::string address; + uint32_t address_index; + std::vector<std::string> addresses; + std::vector<uint32_t> address_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) KV_SERIALIZE(address_index) + KV_SERIALIZE(addresses) + KV_SERIALIZE(address_indices) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; @@ -484,6 +490,7 @@ namespace wallet_rpc std::string tx_key; uint64_t amount; uint64_t fee; + uint64_t weight; std::string tx_blob; std::string tx_metadata; std::string multisig_txset; @@ -494,6 +501,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) KV_SERIALIZE(fee) + KV_SERIALIZE(weight) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) @@ -550,6 +558,7 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; + std::list<uint64_t> weight_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; @@ -560,6 +569,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(weight_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) @@ -723,6 +733,7 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; + std::list<uint64_t> weight_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; @@ -733,6 +744,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(weight_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) @@ -793,6 +805,7 @@ namespace wallet_rpc std::list<std::string> tx_key_list; std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; + std::list<uint64_t> weight_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; std::string multisig_txset; @@ -803,6 +816,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_key_list) KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) + KV_SERIALIZE(weight_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) KV_SERIALIZE(multisig_txset) @@ -850,6 +864,7 @@ namespace wallet_rpc std::string tx_key; uint64_t amount; uint64_t fee; + uint64_t weight; std::string tx_blob; std::string tx_metadata; std::string multisig_txset; @@ -860,6 +875,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_key) KV_SERIALIZE(amount) KV_SERIALIZE(fee) + KV_SERIALIZE(weight) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) KV_SERIALIZE(multisig_txset) @@ -1591,9 +1607,13 @@ namespace wallet_rpc struct request_t { std::string data; + uint32_t account_index; + uint32_t address_index; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(data); + KV_SERIALIZE(data) + KV_SERIALIZE_OPT(account_index, 0u) + KV_SERIALIZE_OPT(address_index, 0u) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -1823,12 +1843,10 @@ namespace wallet_rpc struct request_t { std::string address; - std::string payment_id; std::string description; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) - KV_SERIALIZE(payment_id) KV_SERIALIZE(description) END_KV_SERIALIZE_MAP() }; @@ -1852,8 +1870,6 @@ namespace wallet_rpc uint64_t index; bool set_address; std::string address; - bool set_payment_id; - std::string payment_id; bool set_description; std::string description; @@ -1861,8 +1877,6 @@ namespace wallet_rpc KV_SERIALIZE(index) KV_SERIALIZE(set_address) KV_SERIALIZE(address) - KV_SERIALIZE(set_payment_id) - KV_SERIALIZE(payment_id) KV_SERIALIZE(set_description) KV_SERIALIZE(description) END_KV_SERIALIZE_MAP() @@ -1893,13 +1907,11 @@ namespace wallet_rpc { uint64_t index; std::string address; - std::string payment_id; std::string description; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(index) KV_SERIALIZE(address) - KV_SERIALIZE(payment_id) KV_SERIALIZE(description) END_KV_SERIALIZE_MAP() }; @@ -2580,5 +2592,36 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT + { + struct request_t + { + uint32_t n_inputs; + uint32_t n_outputs; + uint32_t ring_size; + bool rct; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(n_inputs) + KV_SERIALIZE(n_outputs) + KV_SERIALIZE_OPT(ring_size, 0u) + KV_SERIALIZE_OPT(rct, true) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + uint64_t size; + uint64_t weight; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(size) + KV_SERIALIZE(weight) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + } } diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index db5b7797a..9463d14ce 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -160,8 +160,8 @@ string tx2str(const cryptonote::transaction& tx, const cryptonote::hash256& tx_h return ss.str(); }*/ -bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { - if (!keeped_by_block) +bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed) { + if (tx_relay != cryptonote::relay_method::block) return true; crypto::hash tx_hash = null_hash; @@ -190,13 +190,13 @@ bool tests::proxy_core::handle_incoming_tx(const cryptonote::tx_blob_entry& tx_b return true; } -bool tests::proxy_core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) +bool tests::proxy_core::handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed) { tvc.resize(tx_blobs.size()); size_t i = 0; for (const auto &tx_blob: tx_blobs) { - if (!handle_incoming_tx(tx_blob, tvc[i], keeped_by_block, relayed, do_not_relay)) + if (!handle_incoming_tx(tx_blob, tvc[i], tx_relay, relayed)) return false; ++i; } diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index 0e41a2be0..8732c85cc 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -34,6 +34,7 @@ #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/verification_context.h" +#include "cryptonote_core/i_core_events.h" #include <unordered_map> namespace tests @@ -51,7 +52,7 @@ namespace tests : height(_height), id(_id), longhash(_longhash), blk(_blk), blob(_blob), txes(_txes) { } }; - class proxy_core + class proxy_core : public cryptonote::i_core_events { cryptonote::block m_genesis; std::list<crypto::hash> m_known_block_list; @@ -75,8 +76,8 @@ namespace tests bool get_stat_info(cryptonote::core_stat_info& st_inf){return true;} bool have_block(const crypto::hash& id); void get_blockchain_top(uint64_t& height, crypto::hash& top_id); - bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); - bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay); + bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed); + bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blobs, std::vector<cryptonote::tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed); bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true); void pause_mine(){} void resume_mine(){} @@ -90,9 +91,9 @@ namespace tests bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } - virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {} + virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {} cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; } - bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; } + bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob, cryptonote::relay_category tx_category) const { return false; } bool pool_has_tx(const crypto::hash &txid) const { return false; } bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; } bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; } diff --git a/tests/core_tests/CMakeLists.txt b/tests/core_tests/CMakeLists.txt index f93cbf3ad..42e76e613 100644 --- a/tests/core_tests/CMakeLists.txt +++ b/tests/core_tests/CMakeLists.txt @@ -40,6 +40,7 @@ set(core_tests_sources ring_signature_1.cpp transaction_tests.cpp tx_validation.cpp + tx_pool.cpp v2_tests.cpp rct.cpp bulletproofs.cpp @@ -57,6 +58,7 @@ set(core_tests_headers integer_overflow.h multisig.h ring_signature_1.h + tx_pool.h transaction_tests.h tx_validation.h v2_tests.h diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 35e449e10..f935d4f64 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -47,6 +47,12 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/miner.h" +#include "blockchain_db/blockchain_db.h" +#include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_core/tx_pool.h" +#include "cryptonote_core/blockchain.h" +#include "blockchain_db/testdb.h" + #include "chaingen.h" #include "device/device.hpp" using namespace std; @@ -55,6 +61,126 @@ using namespace epee; using namespace crypto; using namespace cryptonote; +namespace +{ + /** + * Dummy TestDB to store height -> (block, hash) information + * for the use only in the test_generator::fill_nonce() function, + * which requires blockchain object to correctly compute PoW on HF12+ blocks + * as the mining function requires it to obtain a valid seedhash. + */ + class TestDB: public cryptonote::BaseTestDB + { + private: + struct block_t + { + cryptonote::block bl; + crypto::hash hash; + }; + + public: + TestDB() { m_open = true; } + + virtual void add_block( const cryptonote::block& blk + , size_t block_weight + , uint64_t long_term_block_weight + , const cryptonote::difficulty_type& cumulative_difficulty + , const uint64_t& coins_generated + , uint64_t num_rct_outs + , const crypto::hash& blk_hash + ) override + { + blocks.push_back({blk, blk_hash}); + } + + virtual uint64_t height() const override { return blocks.empty() ? 0 : blocks.size() - 1; } + + // Required for randomx + virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override + { + if (height < blocks.size()) + { + MDEBUG("Get hash for block height: " << height << " hash: " << blocks[height].hash); + return blocks[height].hash; + } + + MDEBUG("Get hash for block height: " << height << " zero-hash"); + crypto::hash hash = crypto::null_hash; + *(uint64_t*)&hash = height; + return hash; + } + + virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override + { + const uint64_t h = height(); + if (block_height != nullptr) + { + *block_height = h; + } + + return get_block_hash_from_height(h); + } + + virtual cryptonote::block get_top_block() const override + { + if (blocks.empty()) + { + cryptonote::block b; + return b; + } + + return blocks[blocks.size()-1].bl; + } + + virtual void pop_block(cryptonote::block &blk, std::vector<cryptonote::transaction> &txs) override { if (!blocks.empty()) blocks.pop_back(); } + virtual void set_hard_fork_version(uint64_t height, uint8_t version) override { if (height >= hf.size()) hf.resize(height + 1); hf[height] = version; } + virtual uint8_t get_hard_fork_version(uint64_t height) const override { if (height >= hf.size()) return 255; return hf[height]; } + + private: + std::vector<block_t> blocks; + std::vector<uint8_t> hf; + }; + +} + +static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) +{ + std::unique_ptr<cryptonote::Blockchain> bc; + v_hardforks_t hardforks; + cryptonote::test_options test_options_tmp{nullptr, 0}; + const cryptonote::test_options * test_options = &test_options_tmp; + if (!extract_hard_forks(events, hardforks)) + { + MDEBUG("Extracting hard-forks from blocks"); + extract_hard_forks_from_blocks(events, hardforks); + } + + hardforks.push_back(std::make_pair((uint8_t)0, (uint64_t)0)); // terminator + test_options_tmp.hard_forks = hardforks.data(); + test_options = &test_options_tmp; + + cryptonote::tx_memory_pool txpool(*bc); + bc.reset(new cryptonote::Blockchain(txpool)); + + cryptonote::Blockchain *blockchain = bc.get(); + auto bdb = new TestDB(); + + BOOST_FOREACH(const test_event_entry &ev, events) + { + if (typeid(block) != ev.type()) + { + continue; + } + + const block *blk = &boost::get<block>(ev); + auto blk_hash = get_block_hash(*blk); + bdb->add_block(*blk, 1, 1, 1, 0, 0, blk_hash); + } + + bool r = blockchain->init(bdb, nettype, true, test_options, 2, nullptr); + CHECK_AND_ASSERT_THROW_MES(r, "could not init blockchain from events"); + return bc; +} void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const { @@ -184,13 +310,7 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co //blk.tree_root_hash = get_tx_tree_hash(blk); - // Nonce search... - blk.nonce = 0; - while (!miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ - return cryptonote::get_block_longhash(NULL, b, hash, height, threads); - }, blk, get_test_difficulty(hf_ver), height)) - blk.timestamp++; - + fill_nonce(blk, get_test_difficulty(hf_ver), height); add_block(blk, txs_weight, block_weights, already_generated_coins, hf_ver ? hf_ver.get() : 1); return true; @@ -268,6 +388,32 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c return construct_block_manually(blk, prev_block, miner_acc, bf_tx_hashes, 0, 0, 0, crypto::hash(), 0, transaction(), tx_hashes, txs_weight); } +void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) +{ + const cryptonote::Blockchain *blockchain = nullptr; + std::unique_ptr<cryptonote::Blockchain> bc; + + if (blk.major_version >= RX_BLOCK_VERSION && diffic > 1) + { + if (m_events == nullptr) + { + MDEBUG("events not set, RandomX PoW can fail due to zero seed hash"); + } + else + { + bc = init_blockchain(*m_events, m_nettype); + blockchain = bc.get(); + } + } + + blk.nonce = 0; + while (!miner::find_nonce_for_given_block([blockchain](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ + return cryptonote::get_block_longhash(blockchain, b, hash, height, threads); + }, blk, diffic, height)) { + blk.timestamp++; + } +} + namespace { uint64_t get_inputs_amount(const vector<tx_source_entry> &s) @@ -796,15 +942,6 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event fill_tx_sources_and_destinations(events, blk_head, from, to.get_keys().m_account_address, amount, fee, nmix, sources, destinations); } -void fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) -{ - blk.nonce = 0; - while (!miner::find_nonce_for_given_block([](const cryptonote::block &b, uint64_t height, unsigned int threads, crypto::hash &hash){ - return cryptonote::get_block_longhash(NULL, b, hash, height, threads); - }, blk, diffic, height)) - blk.timestamp++; -} - cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr, uint64_t amount) { tx_destination_entry de; @@ -983,6 +1120,31 @@ bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks return !hard_forks.empty(); } +bool extract_hard_forks_from_blocks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks) +{ + int hf = -1; + int64_t height = 0; + + for(auto & ev : events) + { + if (typeid(block) != ev.type()) + { + continue; + } + + const block *blk = &boost::get<block>(ev); + if (blk->major_version != hf) + { + hf = blk->major_version; + hard_forks.push_back(std::make_pair(blk->major_version, (uint64_t)height)); + } + + height += 1; + } + + return !hard_forks.empty(); +} + void get_confirmed_txs(const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs) { std::unordered_set<crypto::hash> confirmed_hashes; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index b78640dc9..80ce7404b 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -47,12 +47,14 @@ #include "include_base_utils.h" #include "common/boost_serialization_helper.h" #include "common/command_line.h" +#include "common/threadpool.h" #include "cryptonote_basic/account_boost_serialization.h" #include "cryptonote_basic/cryptonote_basic.h" #include "cryptonote_basic/cryptonote_basic_impl.h" #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_core/cryptonote_core.h" +#include "cryptonote_protocol/enums.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" #include "misc_language.h" @@ -108,17 +110,17 @@ typedef serialized_object<cryptonote::transaction> serialized_transaction; struct event_visitor_settings { - int valid_mask; - bool txs_keeped_by_block; + int mask; enum settings { - set_txs_keeped_by_block = 1 << 0 + set_txs_keeped_by_block = 1 << 0, + set_txs_do_not_relay = 1 << 1, + set_local_relay = 1 << 2 }; - event_visitor_settings(int a_valid_mask = 0, bool a_txs_keeped_by_block = false) - : valid_mask(a_valid_mask) - , txs_keeped_by_block(a_txs_keeped_by_block) + event_visitor_settings(int a_mask = 0) + : mask(a_mask) { } @@ -128,8 +130,7 @@ private: template<class Archive> void serialize(Archive & ar, const unsigned int /*version*/) { - ar & valid_mask; - ar & txs_keeped_by_block; + ar & mask; } }; @@ -227,8 +228,8 @@ public: bf_hf_version= 1 << 8 }; - test_generator() {} - test_generator(const test_generator &other): m_blocks_info(other.m_blocks_info) {} + test_generator(): m_events(nullptr) {} + test_generator(const test_generator &other): m_blocks_info(other.m_blocks_info), m_events(other.m_events), m_nettype(other.m_nettype) {} void get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const; void get_last_n_block_weights(std::vector<size_t>& block_weights, const crypto::hash& head, size_t n) const; uint64_t get_already_generated_coins(const crypto::hash& blk_id) const; @@ -253,9 +254,14 @@ public: uint8_t hf_version = 1); bool construct_block_manually_tx(cryptonote::block& blk, const cryptonote::block& prev_block, const cryptonote::account_base& miner_acc, const std::vector<crypto::hash>& tx_hashes, size_t txs_size); + void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); + void set_events(const std::vector<test_event_entry> * events) { m_events = events; } + void set_network_type(const cryptonote::network_type nettype) { m_nettype = nettype; } private: std::unordered_map<crypto::hash, block_info> m_blocks_info; + const std::vector<test_event_entry> * m_events; + cryptonote::network_type m_nettype; friend class boost::serialization::access; @@ -407,7 +413,6 @@ cryptonote::account_public_address get_address(const cryptonote::tx_destination_ inline cryptonote::difficulty_type get_test_difficulty(const boost::optional<uint8_t>& hf_ver=boost::none) {return !hf_ver || hf_ver.get() <= 1 ? 1 : 2;} inline uint64_t current_difficulty_window(const boost::optional<uint8_t>& hf_ver=boost::none){ return !hf_ver || hf_ver.get() <= 1 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; } -void fill_nonce(cryptonote::block& blk, const cryptonote::difficulty_type& diffic, uint64_t height); cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr=false, uint64_t amount=0); std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1=false, uint64_t am1=0); @@ -490,6 +495,7 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx); bool extract_hard_forks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks); +bool extract_hard_forks_from_blocks(const std::vector<test_event_entry>& events, v_hardforks_t& hard_forks); /************************************************************************/ /* */ @@ -503,7 +509,7 @@ private: t_test_class& m_validator; size_t m_ev_index; - bool m_txs_keeped_by_block; + cryptonote::relay_method m_tx_relay; public: push_core_event_visitor(cryptonote::core& c, const std::vector<test_event_entry>& events, t_test_class& validator) @@ -511,7 +517,7 @@ public: , m_events(events) , m_validator(validator) , m_ev_index(0) - , m_txs_keeped_by_block(false) + , m_tx_relay(cryptonote::relay_method::fluff) { } @@ -530,9 +536,21 @@ public: { log_event("event_visitor_settings"); - if (settings.valid_mask & event_visitor_settings::set_txs_keeped_by_block) + if (settings.mask & event_visitor_settings::set_txs_keeped_by_block) { - m_txs_keeped_by_block = settings.txs_keeped_by_block; + m_tx_relay = cryptonote::relay_method::block; + } + else if (settings.mask & event_visitor_settings::set_local_relay) + { + m_tx_relay = cryptonote::relay_method::local; + } + else if (settings.mask & event_visitor_settings::set_txs_do_not_relay) + { + m_tx_relay = cryptonote::relay_method::none; + } + else + { + m_tx_relay = cryptonote::relay_method::fluff; } return true; @@ -544,7 +562,7 @@ public: cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); size_t pool_size = m_c.get_pool_transactions_count(); - m_c.handle_incoming_tx({t_serializable_object_to_blob(tx), crypto::null_hash}, tvc, m_txs_keeped_by_block, false, false); + m_c.handle_incoming_tx({t_serializable_object_to_blob(tx), crypto::null_hash}, tvc, m_tx_relay, false); bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); bool r = m_validator.check_tx_verification_context(tvc, tx_added, m_ev_index, tx); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed"); @@ -564,7 +582,7 @@ public: tvcs.push_back(tvc0); } size_t pool_size = m_c.get_pool_transactions_count(); - m_c.handle_incoming_txs(tx_blobs, tvcs, m_txs_keeped_by_block, false, false); + m_c.handle_incoming_txs(tx_blobs, tvcs, m_tx_relay, false); size_t tx_added = m_c.get_pool_transactions_count() - pool_size; bool r = m_validator.check_tx_verification_context_array(tvcs, tx_added, m_ev_index, txs); CHECK_AND_NO_ASSERT_MES(r, false, "tx verification context check failed"); @@ -644,7 +662,7 @@ public: cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc); size_t pool_size = m_c.get_pool_transactions_count(); - m_c.handle_incoming_tx(sr_tx.data, tvc, m_txs_keeped_by_block, false, false); + m_c.handle_incoming_tx(sr_tx.data, tvc, m_tx_relay, false); bool tx_added = pool_size + 1 == m_c.get_pool_transactions_count(); cryptonote::transaction tx; @@ -758,6 +776,7 @@ inline bool do_replay_events_get_core(std::vector<test_event_entry>& events, cry t_test_class validator; bool ret = replay_events_through_core<t_test_class>(c, events, validator); + tools::threadpool::getInstance().recycle(); // c.deinit(); return ret; } @@ -955,7 +974,7 @@ inline bool do_replay_file(const std::string& filename) #define MAKE_MINER_TX_MANUALLY(TX, BLK) MAKE_MINER_TX_AND_KEY_MANUALLY(TX, BLK, 0) -#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT, VAL) VEC_EVENTS.push_back(event_visitor_settings(SETT, VAL)); +#define SET_EVENT_VISITOR_SETT(VEC_EVENTS, SETT) VEC_EVENTS.push_back(event_visitor_settings(SETT)); #define GENERATE(filename, genclass) \ { \ diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 8406f416f..23f3170b8 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -32,6 +32,7 @@ #include "chaingen_tests_list.h" #include "common/util.h" #include "common/command_line.h" +#include "tx_pool.h" #include "transaction_tests.h" namespace po = boost::program_options; @@ -155,6 +156,12 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_tx_output_is_not_txout_to_key); GENERATE_AND_PLAY(gen_tx_signatures_are_invalid); + // Mempool + GENERATE_AND_PLAY(txpool_spend_key_public); + GENERATE_AND_PLAY(txpool_spend_key_all); + GENERATE_AND_PLAY(txpool_double_spend_norelay); + GENERATE_AND_PLAY(txpool_double_spend_local); + // Double spend GENERATE_AND_PLAY(gen_double_spend_in_tx<false>); GENERATE_AND_PLAY(gen_double_spend_in_tx<true>); diff --git a/tests/core_tests/double_spend.cpp b/tests/core_tests/double_spend.cpp index afd212b27..4aa12c8e0 100644 --- a/tests/core_tests/double_spend.cpp +++ b/tests/core_tests/double_spend.cpp @@ -46,7 +46,7 @@ bool gen_double_spend_in_different_chains::generate(std::vector<test_event_entry { INIT_DOUBLE_SPEND_TEST(); - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, true); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block); MAKE_TX(events, tx_1, bob_account, alice_account, send_amount / 2 - TESTS_DEFAULT_FEE, blk_1); events.pop_back(); MAKE_TX(events, tx_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); @@ -96,3 +96,4 @@ bool gen_double_spend_in_different_chains::check_double_spend(cryptonote::core& return true; } + diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index 600899b6f..684c9c4de 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -147,7 +147,7 @@ bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_even if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1, 0)) return false; - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); DO_CALLBACK(events, "mark_invalid_tx"); events.push_back(tx_1); DO_CALLBACK(events, "mark_invalid_block"); @@ -163,7 +163,7 @@ bool gen_double_spend_in_the_same_block<txs_keeped_by_block>::generate(std::vect INIT_DOUBLE_SPEND_TEST(); DO_CALLBACK(events, "mark_last_valid_block"); - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); MAKE_TX_LIST_START(events, txs_1, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); cryptonote::transaction tx_1 = txs_1.front(); @@ -190,7 +190,7 @@ bool gen_double_spend_in_different_blocks<txs_keeped_by_block>::generate(std::ve INIT_DOUBLE_SPEND_TEST(); DO_CALLBACK(events, "mark_last_valid_block"); - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); // Create two identical transactions, but don't push it to events list MAKE_TX(events, tx_blk_2, bob_account, alice_account, send_amount - TESTS_DEFAULT_FEE, blk_1); @@ -220,7 +220,7 @@ bool gen_double_spend_in_alt_chain_in_the_same_block<txs_keeped_by_block>::gener { INIT_DOUBLE_SPEND_TEST(); - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); // Main chain MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account); @@ -255,7 +255,7 @@ bool gen_double_spend_in_alt_chain_in_different_blocks<txs_keeped_by_block>::gen { INIT_DOUBLE_SPEND_TEST(); - SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block, txs_keeped_by_block); + SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); // Main chain MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_account); diff --git a/tests/core_tests/tx_pool.cpp b/tests/core_tests/tx_pool.cpp new file mode 100644 index 000000000..537015dca --- /dev/null +++ b/tests/core_tests/tx_pool.cpp @@ -0,0 +1,561 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "tx_pool.h" + +#include <boost/chrono/chrono.hpp> +#include <boost/thread/thread_only.hpp> +#include <limits> +#include "string_tools.h" + +#define INIT_MEMPOOL_TEST() \ + uint64_t send_amount = 1000; \ + uint64_t ts_start = 1338224400; \ + GENERATE_ACCOUNT(miner_account); \ + GENERATE_ACCOUNT(bob_account); \ + MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); \ + REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); \ + + +txpool_base::txpool_base() + : test_chain_unit_base() + , m_broadcasted_tx_count(0) + , m_all_tx_count(0) +{ + REGISTER_CALLBACK_METHOD(txpool_spend_key_public, increase_broadcasted_tx_count); + REGISTER_CALLBACK_METHOD(txpool_spend_key_public, increase_all_tx_count); + REGISTER_CALLBACK_METHOD(txpool_spend_key_public, check_txpool_spent_keys); +} + +bool txpool_base::increase_broadcasted_tx_count(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/) +{ + ++m_broadcasted_tx_count; + return true; +} + +bool txpool_base::increase_all_tx_count(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/) +{ + ++m_all_tx_count; + return true; +} + +bool txpool_base::check_txpool_spent_keys(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events) +{ + std::vector<cryptonote::tx_info> infos{}; + std::vector<cryptonote::spent_key_image_info> key_images{}; + if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images) || infos.size() != m_broadcasted_tx_count || key_images.size() != m_broadcasted_tx_count) + { + MERROR("Failed broadcasted spent keys retrieval - Expected Broadcasted Count: " << m_broadcasted_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size()); + return false; + } + + infos.clear(); + key_images.clear(); + if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, false) || infos.size() != m_broadcasted_tx_count || key_images.size() != m_broadcasted_tx_count) + { + MERROR("Failed broadcasted spent keys retrieval - Expected Broadcasted Count: " << m_broadcasted_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size()); + return false; + } + + infos.clear(); + key_images.clear(); + if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, true) || infos.size() != m_all_tx_count || key_images.size() != m_all_tx_count) + { + MERROR("Failed all spent keys retrieval - Expected All Count: " << m_all_tx_count << " Actual Info Count: " << infos.size() << " Actual Key Image Count: " << key_images.size()); + return false; + } + + return true; +} + +bool txpool_spend_key_public::generate(std::vector<test_event_entry>& events) const +{ + INIT_MEMPOOL_TEST(); + + DO_CALLBACK(events, "check_txpool_spent_keys"); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + DO_CALLBACK(events, "increase_broadcasted_tx_count"); + DO_CALLBACK(events, "increase_all_tx_count"); + DO_CALLBACK(events, "check_txpool_spent_keys"); + + return true; +} + +bool txpool_spend_key_all::generate(std::vector<test_event_entry>& events) +{ + INIT_MEMPOOL_TEST(); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay); + + DO_CALLBACK(events, "check_txpool_spent_keys"); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + DO_CALLBACK(events, "increase_all_tx_count"); + DO_CALLBACK(events, "check_txpool_spent_keys"); + + return true; +} + +txpool_double_spend_base::txpool_double_spend_base() + : txpool_base() + , m_broadcasted_hashes() + , m_no_relay_hashes() + , m_all_hashes() + , m_no_new_index(0) + , m_new_timestamp_index(0) + , m_last_tx(crypto::hash{}) +{ + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, mark_no_new); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, mark_timestamp_change); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, timestamp_change_pause); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_unchanged); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_broadcasted); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_hidden); + REGISTER_CALLBACK_METHOD(txpool_double_spend_base, check_new_no_relay); +} + +bool txpool_double_spend_base::mark_no_new(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_no_new_index = ev_index + 1; + return true; +} + +bool txpool_double_spend_base::mark_timestamp_change(cryptonote::core& /*c*/, size_t ev_index, const std::vector<test_event_entry>& /*events*/) +{ + m_new_timestamp_index = ev_index + 1; + return true; +} + +bool txpool_double_spend_base::timestamp_change_pause(cryptonote::core& /*c*/, size_t /*ev_index*/, const std::vector<test_event_entry>& /*events*/) +{ + boost::this_thread::sleep_for(boost::chrono::seconds{1} + boost::chrono::milliseconds{100}); + return true; +} + +bool txpool_double_spend_base::check_changed(cryptonote::core& c, const size_t ev_index, relay_test condition) +{ + const std::size_t public_hash_count = m_broadcasted_hashes.size(); + const std::size_t all_hash_count = m_all_hashes.size(); + + const std::size_t new_broadcasted_hash_count = m_broadcasted_hashes.size() + unsigned(condition == relay_test::broadcasted); + const std::size_t new_all_hash_count = m_all_hashes.size() + unsigned(condition == relay_test::hidden) + unsigned(condition == relay_test::no_relay); + + std::vector<crypto::hash> hashes{}; + if (!c.get_pool_transaction_hashes(hashes)) + { + MERROR("Failed to get broadcasted transaction pool hashes"); + return false; + } + + for (const crypto::hash& hash : hashes) + m_broadcasted_hashes.insert(hash); + + if (new_broadcasted_hash_count != m_broadcasted_hashes.size()) + { + MERROR("Expected " << new_broadcasted_hash_count << " broadcasted hashes but got " << m_broadcasted_hashes.size()); + return false; + } + + if (m_broadcasted_hashes.size() != c.get_pool_transactions_count()) + { + MERROR("Expected " << m_broadcasted_hashes.size() << " broadcasted hashes but got " << c.get_pool_transactions_count()); + return false; + } + + hashes.clear(); + if (!c.get_pool_transaction_hashes(hashes, false)) + { + MERROR("Failed to get broadcasted transaction pool hashes"); + return false; + } + + for (const crypto::hash& hash : hashes) + m_all_hashes.insert(std::make_pair(hash, 0)); + + if (new_broadcasted_hash_count != m_broadcasted_hashes.size()) + { + MERROR("Expected " << new_broadcasted_hash_count << " broadcasted hashes but got " << m_broadcasted_hashes.size()); + return false; + } + + hashes.clear(); + if (!c.get_pool_transaction_hashes(hashes, true)) + { + + MERROR("Failed to get all transaction pool hashes"); + return false; + } + + for (const crypto::hash& hash : hashes) + m_all_hashes.insert(std::make_pair(hash, 0)); + + if (new_all_hash_count != m_all_hashes.size()) + { + MERROR("Expected " << new_all_hash_count << " all hashes but got " << m_all_hashes.size()); + return false; + } + + if (condition == relay_test::no_relay) + { + if (!m_no_relay_hashes.insert(m_last_tx).second) + { + MERROR("Expected new no_relay tx but got a duplicate legacy tx"); + return false; + } + + for (const crypto::hash& hash : m_no_relay_hashes) + { + if (!c.pool_has_tx(hash)) + { + MERROR("Expected public tx " << hash << " to be listed in pool"); + return false; + } + } + } + + // check receive time changes + { + std::vector<cryptonote::tx_info> infos{}; + std::vector<cryptonote::spent_key_image_info> key_images{}; + if (!c.get_pool_transactions_and_spent_keys_info(infos, key_images, true) || infos.size() != m_all_hashes.size()) + { + MERROR("Unable to retrieve all txpool metadata"); + return false; + } + + for (const cryptonote::tx_info& info : infos) + { + crypto::hash tx_hash; + if (!epee::string_tools::hex_to_pod(info.id_hash, tx_hash)) + { + MERROR("Unable to convert tx_hash hex to binary"); + return false; + } + + const auto entry = m_all_hashes.find(tx_hash); + if (entry == m_all_hashes.end()) + { + MERROR("Unable to find tx_hash in set of tracked hashes"); + return false; + } + + if (m_new_timestamp_index == ev_index && m_last_tx == tx_hash) + { + if (entry->second >= info.receive_time) + { + MERROR("Last relay time did not change as expected - last at " << entry->second << " and current at " << info.receive_time); + return false; + } + entry->second = info.receive_time; + } + else if (entry->second != info.receive_time) + { + MERROR("Last relayed time changed unexpectedly from " << entry->second << " to " << info.receive_time); + return false; + } + } + } + + { + std::vector<cryptonote::transaction> txes{}; + if (!c.get_pool_transactions(txes)) + { + MERROR("Failed to get broadcasted transactions from pool"); + return false; + } + + hashes.clear(); + for (const cryptonote::transaction& tx : txes) + hashes.push_back(cryptonote::get_transaction_hash(tx)); + + std::unordered_set<crypto::hash> public_hashes = m_broadcasted_hashes; + for (const crypto::hash& hash : hashes) + { + if (!c.pool_has_tx(hash)) + { + MERROR("Expected broadcasted tx " << hash << " to be listed in pool"); + return false; + } + + if (!public_hashes.erase(hash)) + { + MERROR("An unexected transaction was returned from the public pool"); + return false; + } + } + if (!public_hashes.empty()) + { + MERROR(public_hashes.size() << " transaction(s) were missing from the public pool"); + return false; + } + } + + { + std::vector<cryptonote::transaction> txes{}; + if (!c.get_pool_transactions(txes, false)) + { + MERROR("Failed to get broadcasted transactions from pool"); + return false; + } + + hashes.clear(); + for (const cryptonote::transaction& tx : txes) + hashes.push_back(cryptonote::get_transaction_hash(tx)); + + std::unordered_set<crypto::hash> public_hashes = m_broadcasted_hashes; + for (const crypto::hash& hash : hashes) + { + + if (!public_hashes.erase(hash)) + { + MERROR("An unexected transaction was returned from the public pool"); + return false; + } + } + if (!public_hashes.empty()) + { + MERROR(public_hashes.size() << " transaction(s) were missing from the public pool"); + return false; + } + } + + { + std::vector<cryptonote::transaction> txes{}; + if (!c.get_pool_transactions(txes, true)) + { + MERROR("Failed to get all transactions from pool"); + return false; + } + + hashes.clear(); + for (const cryptonote::transaction& tx : txes) + hashes.push_back(cryptonote::get_transaction_hash(tx)); + + std::unordered_map<crypto::hash, uint64_t> all_hashes = m_all_hashes; + for (const crypto::hash& hash : hashes) + { + if (!all_hashes.erase(hash)) + { + MERROR("An unexected transaction was returned from the all pool"); + return false; + } + } + if (!all_hashes.empty()) + { + MERROR(m_broadcasted_hashes.size() << " transaction(s) were missing from the all pool"); + return false; + } + } + + { + std::vector<cryptonote::tx_backlog_entry> entries{}; + if (!c.get_txpool_backlog(entries)) + { + MERROR("Failed to get broadcasted txpool backlog"); + return false; + } + + if (m_broadcasted_hashes.size() != entries.size()) + { + MERROR("Expected " << m_broadcasted_hashes.size() << " in the broadcasted txpool backlog but got " << entries.size()); + return false; + } + } + + for (const std::pair<crypto::hash, uint64_t>& hash : m_all_hashes) + { + cryptonote::blobdata tx_blob{}; + if (!c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::all)) + { + MERROR("Failed to retrieve tx expected to be in pool: " << hash.first); + return false; + } + } + + { + std::unordered_map<crypto::hash, uint64_t> difference = m_all_hashes; + for (const crypto::hash& hash : m_broadcasted_hashes) + difference.erase(hash); + + for (const crypto::hash& hash : m_no_relay_hashes) + difference.erase(hash); + + for (const std::pair<crypto::hash, uint64_t>& hash : difference) + { + if (c.pool_has_tx(hash.first)) + { + MERROR("Did not expect private/hidden tx " << hash.first << " to be listed in pool"); + return false; + } + + cryptonote::blobdata tx_blob{}; + if (c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::broadcasted)) + { + MERROR("Tx " << hash.first << " is not supposed to be in broadcasted pool"); + return false; + } + + if (!c.get_pool_transaction(hash.first, tx_blob, cryptonote::relay_category::all)) + { + MERROR("Tx " << hash.first << " blob could not be retrieved from pool"); + return false; + } + } + } + + { + cryptonote::txpool_stats stats{}; + if (!c.get_pool_transaction_stats(stats) || stats.txs_total != m_broadcasted_hashes.size()) + { + MERROR("Expected broadcasted stats to list " << m_broadcasted_hashes.size() << " txes but got " << stats.txs_total); + return false; + } + + if (!c.get_pool_transaction_stats(stats, false) || stats.txs_total != m_broadcasted_hashes.size()) + { + MERROR("Expected broadcasted stats to list " << m_broadcasted_hashes.size() << " txes but got " << stats.txs_total); + return false; + } + + if (!c.get_pool_transaction_stats(stats, true) || stats.txs_total != m_all_hashes.size()) + { + MERROR("Expected all stats to list " << m_all_hashes.size() << " txes but got " << stats.txs_total); + return false; + } + } + + { + std::vector<cryptonote::rpc::tx_in_pool> infos{}; + cryptonote::rpc::key_images_with_tx_hashes key_images{}; + if (!c.get_pool_for_rpc(infos, key_images) || infos.size() != m_broadcasted_hashes.size() || key_images.size() != m_broadcasted_hashes.size()) + { + MERROR("Expected broadcasted rpc data to return " << m_broadcasted_hashes.size() << " but got " << infos.size() << " infos and " << key_images.size() << "key images"); + return false; + } + } + return true; +} + +bool txpool_double_spend_base::check_unchanged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */) +{ + return check_changed(c, ev_index, relay_test::no_change); +} + +bool txpool_double_spend_base::check_new_broadcasted(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */) +{ + return check_changed(c, ev_index, relay_test::broadcasted); +} + +bool txpool_double_spend_base::check_new_hidden(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */) +{ + return check_changed(c, ev_index, relay_test::hidden); +} +bool txpool_double_spend_base::check_new_no_relay(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& /*events */) +{ + return check_changed(c, ev_index, relay_test::no_relay); +} + +bool txpool_double_spend_base::check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& tx) +{ + m_last_tx = cryptonote::get_transaction_hash(tx); + if (m_no_new_index == event_idx) + return !tvc.m_verifivation_failed && !tx_added; + else + return !tvc.m_verifivation_failed && tx_added; +} + +bool txpool_double_spend_norelay::generate(std::vector<test_event_entry>& events) const +{ + INIT_MEMPOOL_TEST(); + + DO_CALLBACK(events, "check_txpool_spent_keys"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay); + DO_CALLBACK(events, "mark_no_new"); + + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + + DO_CALLBACK(events, "increase_all_tx_count"); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "mark_timestamp_change"); + DO_CALLBACK(events, "check_new_no_relay"); + DO_CALLBACK(events, "timestamp_change_pause"); + DO_CALLBACK(events, "mark_no_new"); + events.push_back(tx_0); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "check_unchanged"); + SET_EVENT_VISITOR_SETT(events, 0); + DO_CALLBACK(events, "timestamp_change_pause"); + DO_CALLBACK(events, "mark_no_new"); + events.push_back(tx_0); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "check_unchanged"); + + // kepped by block currently does not change txpool status + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block); + DO_CALLBACK(events, "timestamp_change_pause"); + DO_CALLBACK(events, "mark_no_new"); + events.push_back(tx_0); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "check_unchanged"); + + return true; +} + +bool txpool_double_spend_local::generate(std::vector<test_event_entry>& events) const +{ + INIT_MEMPOOL_TEST(); + + DO_CALLBACK(events, "check_txpool_spent_keys"); + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_local_relay); + DO_CALLBACK(events, "mark_no_new"); + + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + + DO_CALLBACK(events, "increase_all_tx_count"); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "mark_timestamp_change"); + DO_CALLBACK(events, "check_new_hidden"); + DO_CALLBACK(events, "timestamp_change_pause"); + DO_CALLBACK(events, "mark_no_new"); + events.push_back(tx_0); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "mark_timestamp_change"); + DO_CALLBACK(events, "check_unchanged"); + SET_EVENT_VISITOR_SETT(events, 0); + DO_CALLBACK(events, "timestamp_change_pause"); + events.push_back(tx_0); + DO_CALLBACK(events, "increase_broadcasted_tx_count"); + DO_CALLBACK(events, "check_txpool_spent_keys"); + DO_CALLBACK(events, "mark_timestamp_change"); + DO_CALLBACK(events, "check_new_broadcasted"); + DO_CALLBACK(events, "timestamp_change_pause"); + DO_CALLBACK(events, "mark_no_new"); + events.push_back(tx_0); + DO_CALLBACK(events, "check_unchanged"); + + return true; +} + diff --git a/tests/core_tests/tx_pool.h b/tests/core_tests/tx_pool.h new file mode 100644 index 000000000..996c76698 --- /dev/null +++ b/tests/core_tests/tx_pool.h @@ -0,0 +1,118 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <unordered_map> +#include <unordered_set> + +#include "chaingen.h" +#include "crypto/crypto.h" + +enum class relay_test +{ + no_change = 0, //!< No expected changes to the txpool + broadcasted, //!< A new block or fluff/flood tx is expected in txpool + hidden, //!< A new stem or local tx is expected in txpool + no_relay //!< A new no relay is expected in txpool +}; + +class txpool_base : public test_chain_unit_base +{ + size_t m_broadcasted_tx_count; + size_t m_all_tx_count; + +public: + txpool_base(); + + bool increase_broadcasted_tx_count(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events); + bool increase_all_tx_count(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events); + bool check_txpool_spent_keys(cryptonote::core& c, size_t /*ev_index*/, const std::vector<test_event_entry>& events); +}; + +struct txpool_spend_key_public : txpool_base +{ + txpool_spend_key_public() : txpool_base() + {} + + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct txpool_spend_key_all : txpool_base +{ + txpool_spend_key_all() : txpool_base() + {} + + bool generate(std::vector<test_event_entry>& events); +}; + +class txpool_double_spend_base : public txpool_base +{ + std::unordered_set<crypto::hash> m_broadcasted_hashes; + std::unordered_set<crypto::hash> m_no_relay_hashes; + std::unordered_map<crypto::hash, uint64_t> m_all_hashes; + size_t m_no_new_index; + size_t m_new_timestamp_index; + crypto::hash m_last_tx; + + bool check_changed(cryptonote::core& c, size_t ev_index, relay_test condition); + +public: + txpool_double_spend_base(); + + bool mark_no_new(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool mark_timestamp_change(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + + //! Pause for 1 second, so that `receive_time` for tx meta changes (tx hidden from public rpc being updated) + bool timestamp_change_pause(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + + bool check_unchanged(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_new_broadcasted(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_new_hidden(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + bool check_new_no_relay(cryptonote::core& c, size_t ev_index, const std::vector<test_event_entry>& events); + + bool check_tx_verification_context(const cryptonote::tx_verification_context& tvc, bool tx_added, size_t event_idx, const cryptonote::transaction& /*tx*/); +}; + +struct txpool_double_spend_norelay : txpool_double_spend_base +{ + txpool_double_spend_norelay() + : txpool_double_spend_base() + {} + + bool generate(std::vector<test_event_entry>& events) const; +}; + +struct txpool_double_spend_local : txpool_double_spend_base +{ + txpool_double_spend_local() + : txpool_double_spend_base() + {} + + bool generate(std::vector<test_event_entry>& events) const; +}; diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index 21a9455c0..fdc4753f9 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -10,9 +10,6 @@ using namespace epee; using namespace crypto; using namespace cryptonote; -// Shared random generator -static std::default_random_engine RND(crypto::rand<unsigned>()); - void wallet_accessor_test::set_account(tools::wallet2 * wallet, cryptonote::account_base& account) { wallet->clear(); diff --git a/tests/functional_tests/address_book.py b/tests/functional_tests/address_book.py index 8d8711ffc..f9ec217af 100755 --- a/tests/functional_tests/address_book.py +++ b/tests/functional_tests/address_book.py @@ -73,14 +73,13 @@ class AddressBookTest(): # add one res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', description = 'self') - assert res.index == 0 + assert res.index == 0, res for get_all in [True, False]: res = wallet.get_address_book() if get_all else wallet.get_address_book([0]) assert len(res.entries) == 1 e = res.entries[0] assert e.index == 0 - assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert e.payment_id == '' or e.payment_id == '0' * 16 or e.payment_id == '0' * 64 + assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', e assert e.description == 'self' # add a duplicate @@ -91,7 +90,6 @@ class AddressBookTest(): assert res.entries[0].index == 0 assert res.entries[1].index == 1 assert res.entries[0].address == res.entries[1].address - assert res.entries[0].payment_id == res.entries[1].payment_id assert res.entries[0].description == res.entries[1].description e = res.entries[1] res = wallet.get_address_book([1]) @@ -118,7 +116,6 @@ class AddressBookTest(): assert len(res.entries) == 1 assert res.entries[0].index == 0 assert res.entries[0].address == e.address - assert res.entries[0].payment_id == e.payment_id assert res.entries[0].description == e.description # delete (new) first @@ -165,38 +162,13 @@ class AddressBookTest(): assert res.entries[0] == e assert res.entries[1] == e - # payment IDs - res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', payment_id = '0' * 64) - assert res.index == 2 - ok = False - try: res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', payment_id = 'x' * 64) - except: ok = True - assert ok - ok = False - try: res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', payment_id = '0' * 65) - except: ok = True - assert ok - ok = False - try: res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', payment_id = '0' * 63) - except: ok = True - assert ok - ok = False - try: res = wallet.add_address_book('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', payment_id = '0' * 16) - except: ok = True - assert ok - # various address types res = wallet.make_integrated_address() integrated_address = res.integrated_address - integrated_address_payment_id = res.payment_id - ok = False - try: res = wallet.add_address_book(integrated_address, payment_id = '0' * 64) - except: ok = True - assert ok res = wallet.add_address_book(integrated_address) - assert res.index == 3 + assert res.index == 2 res = wallet.add_address_book('87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB') - assert res.index == 4 + assert res.index == 3 # get them back res = wallet.get_address_book([0]) @@ -209,16 +181,9 @@ class AddressBookTest(): assert res.entries[0].description == u'あまやかす' res = wallet.get_address_book([2]) assert len(res.entries) == 1 - assert res.entries[0].address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res.entries[0].address == integrated_address res = wallet.get_address_book([3]) assert len(res.entries) == 1 - if False: # for now, the address book splits integrated addresses - assert res.entries[0].address == integrated_address - else: - assert res.entries[0].address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert res.entries[0].payment_id == integrated_address_payment_id + '0' * 48 - res = wallet.get_address_book([4]) - assert len(res.entries) == 1 assert res.entries[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB' # edit @@ -227,15 +192,12 @@ class AddressBookTest(): e = res.entries[0] assert e.index == 1 assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert e.payment_id == '0' * 64 assert e.description == u'あまやかす' - res = wallet.edit_address_book(1, payment_id = '1' * 64) res = wallet.get_address_book([1]) assert len(res.entries) == 1 e = res.entries[0] assert e.index == 1 assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert e.payment_id == '1' * 64 assert e.description == u'あまやかす' res = wallet.edit_address_book(1, description = '') res = wallet.get_address_book([1]) @@ -243,7 +205,6 @@ class AddressBookTest(): e = res.entries[0] assert e.index == 1 assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert e.payment_id == '1' * 64 assert e.description == '' res = wallet.edit_address_book(1, description = 'えんしゅう') res = wallet.get_address_book([1]) @@ -251,7 +212,6 @@ class AddressBookTest(): e = res.entries[0] assert e.index == 1 assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' - assert e.payment_id == '1' * 64 assert e.description == u'えんしゅう' res = wallet.edit_address_book(1, address = '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A') res = wallet.get_address_book([1]) @@ -259,25 +219,12 @@ class AddressBookTest(): e = res.entries[0] assert e.index == 1 assert e.address == '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A' - assert e.payment_id == '1' * 64 - assert e.description == u'えんしゅう' - res = wallet.edit_address_book(1, payment_id = '') - res = wallet.get_address_book([1]) - assert len(res.entries) == 1 - e = res.entries[0] - assert e.index == 1 - assert e.address == '44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A' - assert e.payment_id == '0' * 64 assert e.description == u'えんしゅう' ok = False try: res = wallet.edit_address_book(1, address = '') except: ok = True assert ok ok = False - try: res = wallet.edit_address_book(1, payment_id = 'asdnd') - except: ok = True - assert ok - ok = False try: res = wallet.edit_address_book(1, address = 'address') except: ok = True assert ok @@ -287,7 +234,6 @@ class AddressBookTest(): assert e == res.entries[0] # empty - wallet.delete_address_book(4) wallet.delete_address_book(0) res = wallet.get_address_book([0]) # entries above the deleted one collapse one slot up assert len(res.entries) == 1 diff --git a/tests/functional_tests/make_test_signature.cc b/tests/functional_tests/make_test_signature.cc index 8c0333233..789523de5 100644 --- a/tests/functional_tests/make_test_signature.cc +++ b/tests/functional_tests/make_test_signature.cc @@ -32,6 +32,7 @@ int main(int argc, const char **argv) { + TRY_ENTRY(); if (argc > 2) { fprintf(stderr, "usage: %s <secret_key>\n", argv[0]); @@ -57,4 +58,5 @@ int main(int argc, const char **argv) std::string signature = cryptonote::make_rpc_payment_signature(skey); printf("%s\n", signature.c_str()); return 0; + CATCH_ENTRY_L0("main()", 1); } diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index ad646417e..d067c25e1 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -170,5 +170,15 @@ class MiningTest(): assert res.hash == block_hash +class Guard: + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_value, traceback): + daemon = Daemon() + try: daemon.stop_mining() + except: pass + if __name__ == '__main__': - MiningTest().run_test() + with Guard() as guard: + MiningTest().run_test() diff --git a/tests/functional_tests/sign_message.py b/tests/functional_tests/sign_message.py index de8f0cee2..9dd70f8bc 100755 --- a/tests/functional_tests/sign_message.py +++ b/tests/functional_tests/sign_message.py @@ -43,7 +43,8 @@ from framework.wallet import Wallet class MessageSigningTest(): def run_test(self): self.create() - self.check_signing() + self.check_signing(False) + self.check_signing(True) def create(self): print('Creating wallets') @@ -65,20 +66,34 @@ class MessageSigningTest(): assert res.address == self.address[i] assert res.seed == seeds[i] - def check_signing(self): - print('Signing/verifing messages') + def check_signing(self, subaddress): + print('Signing/verifing messages with ' + ('subaddress' if subaddress else 'standard address')) messages = ['foo', ''] + if subaddress: + address = [] + for i in range(2): + res = self.wallet[i].create_account() + if i == 0: + account_index = res.account_index + res = self.wallet[i].create_address(account_index = account_index) + if i == 0: + address_index = res.address_index + address.append(res.address) + else: + address = [self.address[0], self.address[1]] + account_index = 0 + address_index = 0 for message in messages: - res = self.wallet[0].sign(message) + res = self.wallet[0].sign(message, account_index = account_index, address_index = address_index) signature = res.signature for i in range(2): - res = self.wallet[i].verify(message, self.address[0], signature) + res = self.wallet[i].verify(message, address[0], signature) assert res.good - res = self.wallet[i].verify('different', self.address[0], signature) + res = self.wallet[i].verify('different', address[0], signature) assert not res.good - res = self.wallet[i].verify(message, self.address[1], signature) + res = self.wallet[i].verify(message, address[1], signature) assert not res.good - res = self.wallet[i].verify(message, self.address[0], signature + 'x') + res = self.wallet[i].verify(message, address[0], signature + 'x') assert not res.good if __name__ == '__main__': diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index fc2280f23..e154363e7 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -202,11 +202,11 @@ namespace // Connect to server std::atomic<int> conn_status(0); - m_cmd_conn_id = boost::uuids::nil_uuid(); + m_context = {}; ASSERT_TRUE(m_tcp_server.connect_async("127.0.0.1", srv_port, CONNECTION_TIMEOUT, [&](const test_connection_context& context, const boost::system::error_code& ec) { if (!ec) { - m_cmd_conn_id = context.m_connection_id; + m_context = context; } else { @@ -217,11 +217,11 @@ namespace EXPECT_TRUE(busy_wait_for(DEFAULT_OPERATION_TIMEOUT, [&]{ return 0 != conn_status.load(std::memory_order_seq_cst); })) << "connect_async timed out"; ASSERT_EQ(1, conn_status.load(std::memory_order_seq_cst)); - ASSERT_FALSE(m_cmd_conn_id.is_nil()); + ASSERT_FALSE(m_context.m_connection_id.is_nil()); conn_status.store(0, std::memory_order_seq_cst); CMD_RESET_STATISTICS::request req; - ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_RESET_STATISTICS::response>(m_cmd_conn_id, CMD_RESET_STATISTICS::ID, req, + ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_RESET_STATISTICS::response>(m_context, CMD_RESET_STATISTICS::ID, req, m_tcp_server.get_config_object(), [&](int code, const CMD_RESET_STATISTICS::response& rsp, const test_connection_context&) { conn_status.store(code, std::memory_order_seq_cst); })); @@ -250,16 +250,16 @@ namespace // Connect to server and invoke shutdown command std::atomic<int> conn_status(0); - boost::uuids::uuid cmd_conn_id = boost::uuids::nil_uuid(); + test_connection_context cmd_context; tcp_server.connect_async("127.0.0.1", srv_port, CONNECTION_TIMEOUT, [&](const test_connection_context& context, const boost::system::error_code& ec) { - cmd_conn_id = context.m_connection_id; + cmd_context = context; conn_status.store(!ec ? 1 : -1, std::memory_order_seq_cst); }); if (!busy_wait_for(DEFAULT_OPERATION_TIMEOUT, [&]{ return 0 != conn_status.load(std::memory_order_seq_cst); })) return; if (1 != conn_status.load(std::memory_order_seq_cst)) return; - epee::net_utils::notify_remote_command2(cmd_conn_id, CMD_SHUTDOWN::ID, CMD_SHUTDOWN::request(), tcp_server.get_config_object()); + epee::net_utils::notify_remote_command2(cmd_context, CMD_SHUTDOWN::ID, CMD_SHUTDOWN::request(), tcp_server.get_config_object()); busy_wait_for(DEFAULT_OPERATION_TIMEOUT, [&]{ return 0 != commands_handler.close_connection_counter(); }); } @@ -299,7 +299,7 @@ namespace { std::atomic<int> req_status(0); CMD_GET_STATISTICS::request req; - ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_GET_STATISTICS::response>(m_cmd_conn_id, CMD_GET_STATISTICS::ID, req, + ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_GET_STATISTICS::response>(m_context, CMD_GET_STATISTICS::ID, req, m_tcp_server.get_config_object(), [&](int code, const CMD_GET_STATISTICS::response& rsp, const test_connection_context&) { if (0 < code) { @@ -338,14 +338,14 @@ namespace { CMD_SEND_DATA_REQUESTS::request req; req.request_size = request_size; - epee::net_utils::notify_remote_command2(m_cmd_conn_id, CMD_SEND_DATA_REQUESTS::ID, req, m_tcp_server.get_config_object()); + epee::net_utils::notify_remote_command2(m_context, CMD_SEND_DATA_REQUESTS::ID, req, m_tcp_server.get_config_object()); } protected: test_tcp_server m_tcp_server; test_levin_commands_handler m_commands_handler; size_t m_thread_count; - boost::uuids::uuid m_cmd_conn_id; + test_connection_context m_context; }; } @@ -434,7 +434,7 @@ TEST_F(net_load_test_clt, a_lot_of_client_connections_and_connections_closed_by_ // Close connections CMD_CLOSE_ALL_CONNECTIONS::request req; - ASSERT_TRUE(epee::net_utils::notify_remote_command2(m_cmd_conn_id, CMD_CLOSE_ALL_CONNECTIONS::ID, req, m_tcp_server.get_config_object())); + ASSERT_TRUE(epee::net_utils::notify_remote_command2(m_context, CMD_CLOSE_ALL_CONNECTIONS::ID, req, m_tcp_server.get_config_object())); // Wait for all opened connections to close busy_wait_for(DEFAULT_OPERATION_TIMEOUT, [&](){ return m_commands_handler.new_connection_counter() - RESERVED_CONN_CNT <= m_commands_handler.close_connection_counter(); }); @@ -455,10 +455,10 @@ TEST_F(net_load_test_clt, a_lot_of_client_connections_and_connections_closed_by_ // Close rest connections m_tcp_server.get_config_object().foreach_connection([&](test_connection_context& ctx) { - if (ctx.m_connection_id != m_cmd_conn_id) + if (ctx.m_connection_id != m_context.m_connection_id) { CMD_DATA_REQUEST::request req; - bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx.m_connection_id, CMD_DATA_REQUEST::ID, req, + bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx, CMD_DATA_REQUEST::ID, req, m_tcp_server.get_config_object(), [=](int code, const CMD_DATA_REQUEST::response& rsp, const test_connection_context&) { if (code <= 0) { @@ -548,7 +548,7 @@ TEST_F(net_load_test_clt, permament_open_and_close_and_connections_closed_by_ser CMD_START_OPEN_CLOSE_TEST::request req_start; req_start.open_request_target = CONNECTION_COUNT; req_start.max_opened_conn_count = MAX_OPENED_CONN_COUNT; - ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_START_OPEN_CLOSE_TEST::response>(m_cmd_conn_id, CMD_START_OPEN_CLOSE_TEST::ID, req_start, + ASSERT_TRUE(epee::net_utils::async_invoke_remote_command2<CMD_START_OPEN_CLOSE_TEST::response>(m_context, CMD_START_OPEN_CLOSE_TEST::ID, req_start, m_tcp_server.get_config_object(), [&](int code, const CMD_START_OPEN_CLOSE_TEST::response&, const test_connection_context&) { test_state.store(0 < code ? 1 : -1, std::memory_order_seq_cst); })); @@ -582,7 +582,7 @@ TEST_F(net_load_test_clt, permament_open_and_close_and_connections_closed_by_ser // Ask server to close rest connections CMD_CLOSE_ALL_CONNECTIONS::request req; - ASSERT_TRUE(epee::net_utils::notify_remote_command2(m_cmd_conn_id, CMD_CLOSE_ALL_CONNECTIONS::ID, req, m_tcp_server.get_config_object())); + ASSERT_TRUE(epee::net_utils::notify_remote_command2(m_context, CMD_CLOSE_ALL_CONNECTIONS::ID, req, m_tcp_server.get_config_object())); // Wait for almost all connections to be closed by server busy_wait_for(DEFAULT_OPERATION_TIMEOUT, [&](){ return m_commands_handler.new_connection_counter() <= m_commands_handler.close_connection_counter() + RESERVED_CONN_CNT; }); @@ -601,10 +601,10 @@ TEST_F(net_load_test_clt, permament_open_and_close_and_connections_closed_by_ser // Close rest connections m_tcp_server.get_config_object().foreach_connection([&](test_connection_context& ctx) { - if (ctx.m_connection_id != m_cmd_conn_id) + if (ctx.m_connection_id != m_context.m_connection_id) { CMD_DATA_REQUEST::request req; - bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx.m_connection_id, CMD_DATA_REQUEST::ID, req, + bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx, CMD_DATA_REQUEST::ID, req, m_tcp_server.get_config_object(), [=](int code, const CMD_DATA_REQUEST::response& rsp, const test_connection_context&) { if (code <= 0) { diff --git a/tests/net_load_tests/net_load_tests.h b/tests/net_load_tests/net_load_tests.h index cdc2d267a..4a76f2ec6 100644 --- a/tests/net_load_tests/net_load_tests.h +++ b/tests/net_load_tests/net_load_tests.h @@ -47,6 +47,7 @@ namespace net_load_tests { struct test_connection_context : epee::net_utils::connection_context_base { + test_connection_context(): epee::net_utils::connection_context_base(boost::uuids::nil_uuid(), {}, false, false), m_closed(false) {} volatile bool m_closed; }; diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index fe32ec5cb..b42b1e1b0 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -147,7 +147,7 @@ namespace CMD_DATA_REQUEST::request req2; req2.data.resize(req.request_size); - bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx.m_connection_id, CMD_DATA_REQUEST::ID, req2, + bool r = epee::net_utils::async_invoke_remote_command2<CMD_DATA_REQUEST::response>(ctx, CMD_DATA_REQUEST::ID, req2, m_tcp_server.get_config_object(), [=](int code, const CMD_DATA_REQUEST::response& rsp, const test_connection_context&) { if (code <= 0) { diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 96825f54f..cda25dfc9 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -92,7 +92,8 @@ set(unit_tests_sources ringdb.cpp wipeable_string.cpp is_hdd.cpp - aligned.cpp) + aligned.cpp + rpc_version_str.cpp) set(unit_tests_headers unit_tests_utils.h) diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index e5ca4e41e..38707f075 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -271,12 +271,12 @@ namespace EXPECT_EQ(connection_ids_.size(), connections_->get_connections_count()); } - cryptonote::levin::notify make_notifier(const std::size_t noise_size, bool is_public) + cryptonote::levin::notify make_notifier(const std::size_t noise_size, bool is_public, bool pad_txs) { epee::byte_slice noise = nullptr; if (noise_size) noise = epee::levin::make_noise_notify(noise_size); - return cryptonote::levin::notify{io_service_, connections_, std::move(noise), is_public}; + return cryptonote::levin::notify{io_service_, connections_, std::move(noise), is_public, pad_txs}; } boost::uuids::random_generator random_generator_; @@ -434,12 +434,16 @@ TEST_F(levin_notify, defaulted) EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); } - EXPECT_FALSE(notifier.send_txs({}, random_generator_(), false)); + EXPECT_TRUE(notifier.send_txs({}, random_generator_())); + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + EXPECT_FALSE(notifier.send_txs(std::move(txs), random_generator_())); } -TEST_F(levin_notify, flood) +TEST_F(levin_notify, fluff_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, true); + cryptonote::levin::notify notifier = make_notifier(0, true, false); for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -464,10 +468,13 @@ TEST_F(levin_notify, flood) ASSERT_EQ(10u, contexts_.size()); { auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), false)); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id())); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) EXPECT_EQ(1u, context->process_send_queue()); @@ -480,14 +487,42 @@ TEST_F(levin_notify, flood) EXPECT_TRUE(notification._.empty()); } } +} + +TEST_F(levin_notify, fluff_with_padding) +{ + cryptonote::levin::notify notifier = make_notifier(0, true, true); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); // not tracked + } + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + txs[1].resize(200, 'f'); ASSERT_EQ(10u, contexts_.size()); { auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), true)); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id())); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) EXPECT_EQ(1u, context->process_send_queue()); @@ -502,9 +537,9 @@ TEST_F(levin_notify, flood) } } -TEST_F(levin_notify, private_flood) +TEST_F(levin_notify, private_fluff_without_padding) { - cryptonote::levin::notify notifier = make_notifier(0, false); + cryptonote::levin::notify notifier = make_notifier(0, false, false); for (unsigned count = 0; count < 10; ++count) add_connection(count % 2 == 0); @@ -529,10 +564,14 @@ TEST_F(levin_notify, private_flood) ASSERT_EQ(10u, contexts_.size()); { auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), false)); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id())); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) { @@ -548,14 +587,43 @@ TEST_F(levin_notify, private_flood) EXPECT_TRUE(notification._.empty()); } } +} + +TEST_F(levin_notify, private_fluff_with_padding) +{ + cryptonote::levin::notify notifier = make_notifier(0, false, true); + + for (unsigned count = 0; count < 10; ++count) + add_connection(count % 2 == 0); + + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); + } + notifier.new_out_connection(); + io_service_.poll(); + { + const auto status = notifier.get_status(); + EXPECT_FALSE(status.has_noise); + EXPECT_FALSE(status.connections_filled); // not tracked + } + + std::vector<cryptonote::blobdata> txs(2); + txs[0].resize(100, 'e'); + txs[1].resize(200, 'f'); ASSERT_EQ(10u, contexts_.size()); { auto context = contexts_.begin(); - EXPECT_TRUE(notifier.send_txs(txs, context->get_id(), true)); + EXPECT_TRUE(notifier.send_txs(txs, context->get_id())); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); + notifier.run_fluff(); + io_service_.reset(); + ASSERT_LT(0u, io_service_.poll()); + EXPECT_EQ(0u, context->process_send_queue()); for (++context; context != contexts_.end(); ++context) { @@ -582,7 +650,7 @@ TEST_F(levin_notify, noise) txs[0].resize(1900, 'h'); const boost::uuids::uuid incoming_id = random_generator_(); - cryptonote::levin::notify notifier = make_notifier(2048, false); + cryptonote::levin::notify notifier = make_notifier(2048, false, true); { const auto status = notifier.get_status(); @@ -608,7 +676,7 @@ TEST_F(levin_notify, noise) EXPECT_EQ(0u, receiver_.notified_size()); } - EXPECT_TRUE(notifier.send_txs(txs, incoming_id, false)); + EXPECT_TRUE(notifier.send_txs(txs, incoming_id)); notifier.run_stems(); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); @@ -627,7 +695,7 @@ TEST_F(levin_notify, noise) } txs[0].resize(3000, 'r'); - EXPECT_TRUE(notifier.send_txs(txs, incoming_id, true)); + EXPECT_TRUE(notifier.send_txs(txs, incoming_id)); notifier.run_stems(); io_service_.reset(); ASSERT_LT(0u, io_service_.poll()); diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index 5f91fc6d4..c92f70b97 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -32,6 +32,7 @@ #include "cryptonote_core/cryptonote_core.h" #include "p2p/net_node.h" #include "p2p/net_node.inl" +#include "cryptonote_core/i_core_events.h" #include "cryptonote_protocol/cryptonote_protocol_handler.h" #include "cryptonote_protocol/cryptonote_protocol_handler.inl" @@ -43,7 +44,7 @@ namespace cryptonote { class blockchain_storage; } -class test_core +class test_core : public cryptonote::i_core_events { public: void on_synchronized(){} @@ -56,8 +57,8 @@ public: bool get_stat_info(cryptonote::core_stat_info& st_inf) const {return true;} bool have_block(const crypto::hash& id) const {return true;} void get_blockchain_top(uint64_t& height, crypto::hash& top_id)const{height=0;top_id=crypto::null_hash;} - bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } - bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, bool keeped_by_block, bool relayed, bool do_not_relay) { return true; } + bool handle_incoming_tx(const cryptonote::tx_blob_entry& tx_blob, cryptonote::tx_verification_context& tvc, cryptonote::relay_method tx_relay, bool relayed) { return true; } + bool handle_incoming_txs(const std::vector<cryptonote::tx_blob_entry>& tx_blob, std::vector<cryptonote::tx_verification_context>& tvc, cryptonote::relay_method tx_relay, bool relayed) { return true; } bool handle_incoming_block(const cryptonote::blobdata& block_blob, const cryptonote::block *block, cryptonote::block_verification_context& bvc, bool update_miner_blocktemplate = true) { return true; } void pause_mine(){} void resume_mine(){} @@ -71,9 +72,9 @@ public: bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } - virtual void on_transaction_relayed(const cryptonote::blobdata& tx) {} + virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {} cryptonote::network_type get_nettype() const { return cryptonote::MAINNET; } - bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob) const { return false; } + bool get_pool_transaction(const crypto::hash& id, cryptonote::blobdata& tx_blob, cryptonote::relay_category tx_category) const { return false; } bool pool_has_tx(const crypto::hash &txid) const { return false; } bool get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata, cryptonote::block>>& blocks, std::vector<cryptonote::blobdata>& txs) const { return false; } bool get_transactions(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::transaction>& txs, std::vector<crypto::hash>& missed_txs) const { return false; } diff --git a/tests/unit_tests/rpc_version_str.cpp b/tests/unit_tests/rpc_version_str.cpp new file mode 100644 index 000000000..5dce60465 --- /dev/null +++ b/tests/unit_tests/rpc_version_str.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2019, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "gtest/gtest.h" + +#include "rpc/rpc_version_str.h" +#include "version.h" + +TEST(rpc, is_version_string_valid) +{ + using namespace cryptonote::rpc; + ASSERT_TRUE(is_version_string_valid(MONERO_VERSION)); + ASSERT_TRUE(is_version_string_valid("0.14.1.2")); + ASSERT_TRUE(is_version_string_valid("0.15.0.0-release")); + ASSERT_TRUE(is_version_string_valid("0.15.0.0-fe3f6a3e6")); + + ASSERT_FALSE(is_version_string_valid("")); + ASSERT_FALSE(is_version_string_valid("invalid")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-invalid")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-release0")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-release ")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-fe3f6a3e60")); + ASSERT_FALSE(is_version_string_valid("0.15.0.0-fe3f6a3e6 ")); +} diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index 23f028464..b711526e6 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -735,7 +735,6 @@ TEST(Serialization, portability_wallet) auto address_book_row = w.m_address_book.begin(); ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_spend_public_key) == "9bc53a6ff7b0831c9470f71b6b972dbe5ad1e8606f72682868b1dda64e119fb3"); ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_address.m_view_public_key) == "49fece1ef97dc0c0f7a5e2106e75e96edd910f7e86b56e1e308cd0cf734df191"); - ASSERT_TRUE(epee::string_tools::pod_to_hex(address_book_row->m_payment_id) == "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); ASSERT_TRUE(address_book_row->m_description == "testnet wallet 9y52S6"); } } diff --git a/utils/gpg_keys/binaryfate.asc b/utils/gpg_keys/binaryfate.asc new file mode 100644 index 000000000..55c5b161c --- /dev/null +++ b/utils/gpg_keys/binaryfate.asc @@ -0,0 +1,87 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +lmE0kDaYQqoBKXNXcybElBID8Q== +=tkJZ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/gpg_keys/snipa.asc b/utils/gpg_keys/snipa.asc new file mode 100644 index 000000000..caee18fda --- /dev/null +++ b/utils/gpg_keys/snipa.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +nDKy8l5Mnmigd0+k1Mx+Lw== +=ACfD +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/gpg_keys/thecharlatan.asc b/utils/gpg_keys/thecharlatan.asc new file mode 100644 index 000000000..a10a1fb54 --- /dev/null +++ b/utils/gpg_keys/thecharlatan.asc @@ -0,0 +1,316 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=abJr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/gpg_keys/xiphon.asc b/utils/gpg_keys/xiphon.asc new file mode 100644 index 000000000..01ad7fb77 --- /dev/null +++ b/utils/gpg_keys/xiphon.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +JezqaExCLJDmDVnOj/t8V7dUv4o84A== +=y+nZ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/logs/levin-traffic.awk b/utils/logs/levin-traffic.awk new file mode 100755 index 000000000..25c1c3ed1 --- /dev/null +++ b/utils/logs/levin-traffic.awk @@ -0,0 +1,73 @@ +#!/bin/awk -f + +function max(a, b) { if (a < b) return b; return a; } +function bytes_str(b) { if (b < 1024) return b " bytes"; if (b < 1024 * 1024) return b/1024 " kB"; return b/1024/1024 " MB"; } +function time_str(b) { if (b < 120) return b " sec"; if (b < 3600) return b/60 " min"; if (b < 86400) return b/3600 " hours"; return b/86400 " days"} + +BEGIN { +commands["command-1001"] = "HANDSHAKE" +commands["command-1002"] = "TIMED_SYNC" +commands["command-1003"] = "PING" +commands["command-1004"] = "REQUEST_STAT_INFO" +commands["command-1005"] = "REQUEST_NETWORK_STATE" +commands["command-1006"] = "REQUEST_PEER_ID" +commands["command-1007"] = "REQUEST_SUPPORT_FLAGS" +commands["command-2001"] = "NOTIFY_NEW_BLOCK" +commands["command-2002"] = "NOTIFY_NEW_TRANSACTIONS" +commands["command-2003"] = "REQUEST_GET_OBJECTS" +commands["command-2004"] = "RESPONSE_GET_OBJECTS" +commands["command-2006"] = "NOTIFY_REQUEST_CHAIN" +commands["command-2007"] = "RESPONSE_CHAIN_ENTRY" +commands["command-2008"] = "NOTIFY_NEW_FLUFFY_BLOCK" +commands["command-2009"] = "NOTIFY_REQUEST_FLUFFY_MISSING_TX" +} + +/ net.p2p.traffic / { + date=gensub(/-/, " ", "g", $1) + time=gensub(/\..*/, "", "g", gensub(/:/, " ", "g", $2)) + ip=gensub(/\[/, "", "g", $7) + outin=gensub(/]/, "", "g", $8) + timestamp=date " " time + timestamp=mktime(timestamp) + if (!t0) + t0 = timestamp + if (!t0ip[ip]) + t0ip[ip] = timestamp + t1 = timestamp + t1ip[ip] = timestamp + bytes=$9 + dir=$11 + command=$14 + initiator=$17 + + bytes_by_command[command] += bytes + bytes_by_ip[ip] += bytes + if (dir == "sent") + bytes_sent_by_ip[ip] += bytes + else + bytes_received_by_ip[ip] += bytes + bytes_by_outin[outin] += bytes + bytes_by_direction[dir] += bytes + bytes_by_initiator[initiator] += bytes +} + +END { + dt = t1 - t0 + print "Running time:", time_str(dt) + for (command in bytes_by_command) { + category = command + if (commands[command]) + category = commands[command]; + print "Category", category ":", bytes_str(bytes_by_command[command]) + } + for (direction in bytes_by_direction) print direction ":", bytes_str(bytes_by_direction[direction]) + for (initiator in bytes_by_initiator) print "Initiating from", initiator ":", bytes_str(bytes_by_initiator[initiator]) + for (outin in bytes_by_outin) print "With", outin, "peers:", bytes_str(bytes_by_outin[outin]) + for (ip in bytes_by_ip) print "IP", ip ":", bytes_str(bytes_by_ip[ip]) + print "Download rate:", bytes_str(bytes_by_direction["received"] / max(dt, 1)) "/s" + for (ip in bytes_received_by_ip) + print " ", ip ":", bytes_str(bytes_received_by_ip[ip] / max(t1ip[ip] - t0ip[ip], 1)) "/s over", time_str(t1ip[ip] - t0ip[ip]) + print "Upload rate:", bytes_str(bytes_by_direction["sent"] / max(dt, 1)) "/s" + for (ip in bytes_sent_by_ip) + print " ", ip ":", bytes_str(bytes_sent_by_ip[ip] / max(t1ip[ip] - t0ip[ip], 1)) "/s over", time_str(t1ip[ip] - t0ip[ip]) +} diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 6a3fabdc9..ac9ba2d3a 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -237,14 +237,15 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(create_account) - def create_address(self, account_index = 0, label = ""): + def create_address(self, account_index = 0, label = "", count = 1): create_address = { 'method': 'create_address', 'params' : { 'account_index': account_index, - 'label': label + 'label': label, + 'count': count }, - 'jsonrpc': '2.0', + 'jsonrpc': '2.0', 'id': '0' } return self.rpc.send_json_rpc_request(create_address) @@ -705,11 +706,13 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(check_reserve_proof) - def sign(self, data): + def sign(self, data, account_index = 0, address_index = 0): sign = { 'method': 'sign', 'params' : { 'data': data, + 'account_index': account_index, + 'address_index': address_index, }, 'jsonrpc': '2.0', 'id': '0' @@ -838,11 +841,12 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(validate_address) - def get_accounts(self, tag): + def get_accounts(self, tag, strict_balances = False): get_accounts = { 'method': 'get_accounts', 'params': { 'tag': tag, + 'strict_balances': strict_balances, }, 'jsonrpc': '2.0', 'id': '0' @@ -1062,6 +1066,20 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(stop_mining) + def estimate_tx_size_and_weight(self, n_inputs, n_outputs, ring_size = 0, rct = True): + estimate_tx_size_and_weight = { + 'method': 'estimate_tx_size_and_weight', + 'jsonrpc': '2.0', + 'params': { + 'n_inputs': n_inputs, + 'n_outputs': n_outputs, + 'ring_size': ring_size, + 'rct': rct, + }, + 'id': '0' + } + return self.rpc.send_json_rpc_request(estimate_tx_size_and_weight) + def get_version(self): get_version = { 'method': 'get_version', diff --git a/utils/translations/build-translations.sh b/utils/translations/build-translations.sh index 1217dca0a..c868a691f 100755 --- a/utils/translations/build-translations.sh +++ b/utils/translations/build-translations.sh @@ -7,6 +7,10 @@ then fi if test -z "$lrelease" then + lrelease=`which lrelease-qt5 2> /dev/null` +fi +if test -z "$lrelease" +then echo "lrelease not found" exit 1 fi @@ -17,7 +21,7 @@ then languages="" for language in $(cat translations/ready) do - languages="$languages translations/$language.ts" + languages="$languages translations/monero_$language.ts" done else languages="translations/*.ts" |