diff options
179 files changed, 4804 insertions, 1320 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6ed13e62..aa6afbfbc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: submodules: recursive - uses: actions/cache@v2 with: - path: ~/.ccache + path: /Users/runner/Library/Caches/ccache key: ccache-macos-build-${{ github.sha }} restore-keys: ccache-macos-build- - name: install dependencies @@ -26,6 +26,10 @@ jobs: build-windows: runs-on: windows-latest + env: + CCACHE_COMPRESS: 1 + CCACHE_TEMPDIR: C:\Users\runneradmin\.ccache-temp + CCACHE_DIR: C:\Users\runneradmin\.ccache defaults: run: shell: msys2 {0} @@ -33,12 +37,19 @@ jobs: - uses: actions/checkout@v1 with: submodules: recursive + - uses: actions/cache@v2 + with: + path: C:\Users\runneradmin\.ccache + key: ccache-windows-build-${{ github.sha }} + restore-keys: ccache-windows-build- - uses: eine/setup-msys2@v2 with: update: true - install: 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 + install: mingw-w64-x86_64-toolchain make mingw-w64-x86_64-cmake mingw-w64-x86_64-ccache 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: make release-static-win64 -j2 + run: | + ccache --max-size=150M + make release-static-win64 -j2 build-ubuntu: runs-on: ubuntu-latest @@ -104,10 +115,19 @@ jobs: test-ubuntu: needs: build-ubuntu runs-on: ubuntu-latest + env: + CCACHE_COMPRESS: 1 + CCACHE_TEMPDIR: /tmp/.ccache-temp steps: - uses: actions/checkout@v1 with: submodules: recursive + - name: ccache + uses: actions/cache@v2 + with: + path: ~/.ccache + key: test-ubuntu-ccache-${{ github.sha }} + restore-keys: test-ubuntu-ccache- - name: remove bundled boost run: sudo rm -rf /usr/local/share/boost - name: set apt conf @@ -118,10 +138,19 @@ jobs: - 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 libprotobuf-dev protobuf-compiler - - name: install requests - run: pip install requests + 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 libprotobuf-dev protobuf-compiler ccache + - name: install Python dependencies + run: pip install requests psutil monotonic - name: tests env: CTEST_OUTPUT_ON_FAILURE: ON - run: make release-test -j3 + run: | + ccache --max-size=150M + DIR_BUILD="build/ci/release" + DIR_SRC="`pwd`" + mkdir -p "${DIR_BUILD}" && cd "${DIR_BUILD}" + cmake -S "${DIR_SRC}" -D ARCH="default" -D BUILD_SHARED_LIBS=ON -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=release && make -j3 && make test + +# ARCH="default" (not "native") ensures, that a different execution host can execute binaries compiled elsewhere. +# BUILD_SHARED_LIBS=ON speeds up the linkage part a bit, reduces size, and is the only place where the dynamic linkage is tested. + diff --git a/.gitmodules b/.gitmodules index 9dacf534f..3cf831bcd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,8 +4,7 @@ branch = monero [submodule "external/miniupnp"] path = external/miniupnp - url = https://github.com/monero-project/miniupnp - branch = monero + url = https://github.com/miniupnp/miniupnp [submodule "external/rapidjson"] path = external/rapidjson url = https://github.com/Tencent/rapidjson diff --git a/CMakeLists.txt b/CMakeLists.txt index c6d102ba7..953707657 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -484,7 +484,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "(SunOS|Solaris)") endif () if (APPLE AND NOT IOS) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=x86-64 -fvisibility=default -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=default -std=c++11") if (NOT OPENSSL_ROOT_DIR) EXECUTE_PROCESS(COMMAND brew --prefix openssl OUTPUT_VARIABLE OPENSSL_ROOT_DIR @@ -522,7 +522,7 @@ ExternalProject_Add(generate_translations_header BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/translations" STAMP_DIR ${LRELEASE_PATH} CMAKE_ARGS -DLRELEASE_PATH=${LRELEASE_PATH} - INSTALL_COMMAND cmake -E echo "") + INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "") include_directories("${CMAKE_CURRENT_BINARY_DIR}/translations") add_subdirectory(external) @@ -554,6 +554,17 @@ endif() # Trezor support check include(CheckTrezor) +# As of OpenBSD 6.8, -march=<anything> breaks the build +function(set_default_arch) + if (OPENBSD) + set(ARCH default) + else() + set(ARCH native) + endif() + + set(ARCH ${ARCH} CACHE STRING "CPU to build for: -march value or 'default' to not pass -march at all") +endfunction() + if(MSVC) add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__") # set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Dinline=__inline") @@ -567,7 +578,7 @@ if(MSVC) else() include(TestCXXAcceptsFlag) if (NOT ARCH) - set(ARCH native CACHE STRING "CPU to build for: -march value or 'default' to not pass -march at all") + set_default_arch() endif() message(STATUS "Building on ${CMAKE_SYSTEM_PROCESSOR} for ${ARCH}") if(ARCH STREQUAL "default") @@ -629,7 +640,7 @@ else() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ARCH_FLAG}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ARCH_FLAG}") - set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wundef -Wvla -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-unused-variable -Wno-error=unused-variable -Wno-error=undef -Wno-error=uninitialized") + set(WARNINGS "-Wall -Wextra -Wpointer-arith -Wundef -Wvla -Wwrite-strings -Wno-error=extra -Wno-error=deprecated-declarations -Wno-unused-parameter -Wno-error=unused-variable -Wno-error=undef -Wno-error=uninitialized") if(CMAKE_C_COMPILER_ID STREQUAL "Clang") if(ARM) set(WARNINGS "${WARNINGS} -Wno-error=inline-asm") @@ -714,9 +725,10 @@ else() endif() # linker - if (NOT SANITIZE AND NOT OSSFUZZ AND NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1))) + if (NOT SANITIZE AND NOT OSSFUZZ AND NOT (WIN32 AND (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND (CMAKE_C_COMPILER_VERSION VERSION_LESS 9.1 OR NOT STATIC)))) # PIE executables randomly crash at startup with ASAN # Windows binaries die on startup with PIE when compiled with GCC <9.x + # Windows dynamically-linked binaries die on startup with PIE regardless of GCC version add_linker_flag_if_supported(-pie LD_SECURITY_FLAGS) endif() add_linker_flag_if_supported(-Wl,-z,relro LD_SECURITY_FLAGS) diff --git a/Dockerfile b/Dockerfile index 61bbd76f2..51fd51e1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,8 +55,8 @@ RUN set -ex \ ENV BOOST_ROOT /usr/local/boost_${BOOST_VERSION} # OpenSSL -ARG OPENSSL_VERSION=1.1.1g -ARG OPENSSL_HASH=ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46 +ARG OPENSSL_VERSION=1.1.1i +ARG OPENSSL_HASH=e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242 RUN set -ex \ && curl -s -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \ && echo "${OPENSSL_HASH} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c \ @@ -68,7 +68,7 @@ OUTPUT_DIRECTORY = doc # performance problems for the file system. # The default value is: NO. -CREATE_SUBDIRS = NO +CREATE_SUBDIRS = YES # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII @@ -754,7 +754,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = src +INPUT = . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1552,7 +1552,7 @@ EXTRA_SEARCH_MAPPINGS = # If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output. # The default value is: YES. -GENERATE_LATEX = YES +GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of @@ -1,4 +1,4 @@ -Copyright (c) 2014-2020, The Monero Project +Copyright (c) 2014-2021, The Monero Project All rights reserved. @@ -1,6 +1,6 @@ # Monero -Copyright (c) 2014-2020 The Monero Project. +Copyright (c) 2014-2021 The Monero Project. Portions Copyright (c) 2012-2013 The Cryptonote developers. ## Table of Contents @@ -165,7 +165,7 @@ library archives (`.a`). | 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 | +| libzmq | 4.2.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 | @@ -176,6 +176,7 @@ library archives (`.a`). | 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 | +| ccache | any | NO | `ccache` | `ccache` | `ccache` | `ccache` | YES | Compil. cache | | 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 | @@ -183,7 +184,7 @@ library archives (`.a`). | libusb | ? | NO | `libusb-1.0-0-dev` | `libusb` | `libusb-devel` | `libusbx-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 | -| libudev | ? | No | `libudev-dev` | `systemd` | `eudev-libudev-devel` | `systemd-devel` | YES | Hardware wallet | +| libudev | ? | No | `libudev-dev` | `systemd` | `eudev-libudev-devel` | `systemd-devel` | YES | Hardware wallet | [1] On Debian/Ubuntu `libgtest-dev` only includes sources and headers. You must build the library binary manually. This can be done with the following command ```sudo apt-get install libgtest-dev && cd /usr/src/gtest && sudo cmake . && sudo make && sudo mv libg* /usr/lib/ ``` @@ -191,7 +192,11 @@ build the library binary manually. This can be done with the following command ` Install all dependencies at once on Debian/Ubuntu: -``` sudo apt update && sudo apt install build-essential cmake pkg-config libboost-all-dev libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev doxygen graphviz libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev``` +``` sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev ccache doxygen graphviz ``` + +Install all dependencies at once on openSUSE: + +``` sudo zypper ref && sudo zypper in cppzmq-devel ldns-devel libboost_chrono-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_program_options-devel libboost_regex-devel libboost_serialization-devel libboost_system-devel libboost_thread-devel libexpat-devel libminiupnpc-devel libsodium-devel libunwind-devel unbound-devel cmake doxygen ccache fdupes gcc-c++ libevent-devel libopenssl-devel pkgconf-pkg-config readline-devel xz-devel libqt5-qttools-devel patterns-devel-C-C++-devel_C_C++``` Install all dependencies at once on macOS with the provided Brewfile: ``` brew update && brew bundle --file=contrib/brew/Brewfile ``` @@ -209,6 +214,10 @@ If you already have a repo cloned, initialize and update: `$ cd monero && git submodule init && git submodule update` +*Note*: If there are submodule differences between branches, you may need +to use ```git submodule sync && git submodule update``` after changing branches +to build successfully. + ### Build instructions Monero uses the CMake build system and a top-level [Makefile](Makefile) that @@ -270,6 +279,12 @@ Dependencies need to be built with -fPIC. Static libraries usually aren't, so yo HAVE_DOT=YES doxygen Doxyfile ``` +* **Optional**: use ccache not to rebuild translation units, that haven't really changed. Monero's CMakeLists.txt file automatically handles it + + ```bash + sudo apt install ccache + ``` + #### On the Raspberry Pi Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch (2017-09-07 or later) from https://www.raspberrypi.org/downloads/raspbian/. If you are using Raspian Jessie, [please see note in the following section](#note-for-raspbian-jessie-users). @@ -300,14 +315,16 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch ( * Build: ```bash - make release + USE_SINGLE_BUILDDIR=1 make release ``` * Wait 4-6 hours * The resulting executables can be found in `build/release/bin` -* Add `PATH="$PATH:$HOME/monero/build/release/bin"` to `.profile` +* Add `export PATH="$PATH:$HOME/monero/build/release/bin"` to `$HOME/.profile` + +* Run `source $HOME/.profile` * Run Monero with `monerod --detach` @@ -704,14 +721,16 @@ DNS_PUBLIC=tcp torsocks ./monerod --p2p-bind-ip 127.0.0.1 --no-igd --rpc-bind-ip ## Pruning -As of May 2020, the full Monero blockchain file is about 80 GB. One can store a pruned blockchain, which is about 28 GB. +As of May 2020, the full Monero blockchain file is about 100 GB. One can store a pruned blockchain, which is about 30 GB. A pruned blockchain can only serve part of the historical chain data to other peers, but is otherwise identical in functionality to the full blockchain. -To use a pruned blockchain, it is best to start the initial sync with --prune-blockchain. However, it is also possible -to prune an existing blockchain using the monero-blockchain-prune tool or using the --prune-blockchain monerod option +To use a pruned blockchain, it is best to start the initial sync with `--prune-blockchain`. However, it is also possible +to prune an existing blockchain using the `monero-blockchain-prune` tool or using the `--prune-blockchain` `monerod` option with an existing chain. If an existing chain exists, pruning will temporarily require disk space to store both the full and pruned blockchains. +For more detailed information see the ['Pruning' entry in the Moneropedia](https://www.getmonero.org/resources/moneropedia/pruning.html) + ## Debugging This section contains general instructions for debugging failed installs or problems encountered with Monero. First, ensure you are running the latest version built from the Github repo. diff --git a/cmake/FindCcache.cmake b/cmake/FindCcache.cmake index a0734a312..f7b1b8425 100644 --- a/cmake/FindCcache.cmake +++ b/cmake/FindCcache.cmake @@ -42,12 +42,19 @@ find_program(CCACHE_FOUND ccache) if (CCACHE_FOUND) # Try to compile a test program with ccache, in order to verify if it really works. (needed on exotic setups) - # Create a temporary file with a simple program. - set(TEMP_CPP_FILE "${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CMakeTmp/test-program.cpp") - file(WRITE "${TEMP_CPP_FILE}" "int main() { return 0; }") - # And run the found ccache on it. - execute_process(COMMAND "${CCACHE_FOUND}" "${CMAKE_CXX_COMPILER}" "${TEMP_CPP_FILE}" RESULT_VARIABLE RET) - if (${RET} EQUAL 0) + set(TEST_PROJECT "${CMAKE_BINARY_DIR}/${CMAKE_FILES_DIRECTORY}/CMakeTmp") + file(WRITE "${TEST_PROJECT}/CMakeLists.txt" [=[ +cmake_minimum_required(VERSION 3.1) +project(test) +option (CCACHE "") +file(WRITE "${CMAKE_SOURCE_DIR}/test.cpp" "int main() { return 0; }") +set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE}") +set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CCACHE}") +add_executable(test test.cpp) +]=]) + try_compile(RET "${TEST_PROJECT}/build" "${TEST_PROJECT}" "test" CMAKE_FLAGS -DCCACHE="${CCACHE_FOUND}") + unset(TEST_PROJECT) + if (${RET}) # Success message(STATUS "Found usable ccache: ${CCACHE_FOUND}") set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_FOUND}") diff --git a/contrib/brew/Brewfile b/contrib/brew/Brewfile index 1fdf45cfe..36b073b25 100644 --- a/contrib/brew/Brewfile +++ b/contrib/brew/Brewfile @@ -27,6 +27,7 @@ brew "miniupnpc" brew "readline" brew "ldns" brew "expat" +brew "ccache" brew "doxygen" brew "graphviz" brew "libunwind-headers" diff --git a/contrib/depends/packages/openssl.mk b/contrib/depends/packages/openssl.mk index 62e975e50..d80775b22 100644 --- a/contrib/depends/packages/openssl.mk +++ b/contrib/depends/packages/openssl.mk @@ -1,8 +1,8 @@ package=openssl -$(package)_version=1.1.1i +$(package)_version=1.1.1k $(package)_download_path=https://www.openssl.org/source $(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242 +$(package)_sha256_hash=892a0875b9872acd04a9fde79b1f943075d5ea162415de3047c327df33fbaee5 define $(package)_set_vars $(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" diff --git a/contrib/epee/include/byte_slice.h b/contrib/epee/include/byte_slice.h index 6b79f6d92..18d60e088 100644 --- a/contrib/epee/include/byte_slice.h +++ b/contrib/epee/include/byte_slice.h @@ -112,7 +112,7 @@ namespace epee explicit byte_slice(std::string&& buffer); //! Convert `stream` into a slice with zero allocations. - explicit byte_slice(byte_stream&& stream) noexcept; + explicit byte_slice(byte_stream&& stream, bool shrink = true); byte_slice(byte_slice&& source) noexcept; ~byte_slice() noexcept = default; diff --git a/contrib/epee/include/file_io_utils.h b/contrib/epee/include/file_io_utils.h index 25f8c648b..84dc79266 100644 --- a/contrib/epee/include/file_io_utils.h +++ b/contrib/epee/include/file_io_utils.h @@ -24,211 +24,23 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - #ifndef _FILE_IO_UTILS_H_ #define _FILE_IO_UTILS_H_ -#include <fstream> -#include <boost/filesystem/path.hpp> -#include <boost/filesystem/operations.hpp> -#ifdef WIN32 -#include <windows.h> -#include "string_tools.h" -#endif - -// On Windows there is a problem with non-ASCII characters in path and file names -// as far as support by the standard components used is concerned: - -// The various file stream classes, e.g. std::ifstream and std::ofstream, are -// part of the GNU C++ Library / libstdc++. On the most basic level they use the -// fopen() call as defined / made accessible to programs compiled within MSYS2 -// by the stdio.h header file maintained by the MinGW project. - -// The critical point: The implementation of fopen() is part of MSVCRT, the -// Microsoft Visual C/C++ Runtime Library, and this method does NOT offer any -// Unicode support. - -// Monero code that would want to continue to use the normal file stream classes -// but WITH Unicode support could therefore not solve this problem on its own, -// but 2 different projects from 2 different maintaining groups would need changes -// in this particular direction - something probably difficult to achieve and -// with a long time to wait until all new versions / releases arrive. - -// Implemented solution approach: Circumvent the problem by stopping to use std -// file stream classes on Windows and directly use Unicode-capable WIN32 API -// calls. Most of the code doing so is concentrated in this header file here. +#include <string> +#include <ctime> namespace epee { namespace file_io_utils { - inline - bool is_file_exist(const std::string& path) - { - boost::filesystem::path p(path); - return boost::filesystem::exists(p); - } - - inline - bool save_string_to_file(const std::string& path_to_file, const std::string& str) - { -#ifdef WIN32 - std::wstring wide_path; - try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } - HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (file_handle == INVALID_HANDLE_VALUE) - return false; - DWORD bytes_written; - DWORD bytes_to_write = (DWORD)str.size(); - BOOL result = WriteFile(file_handle, str.data(), bytes_to_write, &bytes_written, NULL); - CloseHandle(file_handle); - if (bytes_written != bytes_to_write) - result = FALSE; - return result; -#else - try - { - std::ofstream fstream; - fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - fstream.open(path_to_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); - fstream << str; - fstream.close(); - return true; - } - - catch(...) - { - return false; - } -#endif - } - - inline - bool get_file_time(const std::string& path_to_file, time_t& ft) - { - boost::system::error_code ec; - ft = boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ec); - if(!ec) - return true; - else - return false; - } - - inline - bool set_file_time(const std::string& path_to_file, const time_t& ft) - { - boost::system::error_code ec; - boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ft, ec); - if(!ec) - return true; - else - return false; - } - - - inline - bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size = 1000000000) - { -#ifdef WIN32 - std::wstring wide_path; - try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } - HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (file_handle == INVALID_HANDLE_VALUE) - return false; - DWORD file_size = GetFileSize(file_handle, NULL); - if ((file_size == INVALID_FILE_SIZE) || (uint64_t)file_size > (uint64_t)max_size) { - CloseHandle(file_handle); - return false; - } - target_str.resize(file_size); - DWORD bytes_read; - BOOL result = ReadFile(file_handle, &target_str[0], file_size, &bytes_read, NULL); - CloseHandle(file_handle); - if (bytes_read != file_size) - result = FALSE; - return result; -#else - try - { - std::ifstream fstream; - fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); - - std::ifstream::pos_type file_size = fstream.tellg(); - - if((uint64_t)file_size > (uint64_t)max_size) // ensure a large domain for comparison, and negative -> too large - return false;//don't go crazy - size_t file_size_t = static_cast<size_t>(file_size); - - target_str.resize(file_size_t); - - fstream.seekg (0, std::ios::beg); - fstream.read((char*)target_str.data(), target_str.size()); - fstream.close(); - return true; - } - - catch(...) - { - return false; - } -#endif - } - - inline - bool append_string_to_file(const std::string& path_to_file, const std::string& str) - { - // No special Windows implementation because so far not used in Monero code - try - { - std::ofstream fstream; - fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - fstream.open(path_to_file.c_str(), std::ios_base::binary | std::ios_base::out | std::ios_base::app); - fstream << str; - fstream.close(); - return true; - } - - catch(...) - { - return false; - } - } - - inline - bool get_file_size(const std::string& path_to_file, uint64_t &size) - { -#ifdef WIN32 - std::wstring wide_path; - try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } - HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (file_handle == INVALID_HANDLE_VALUE) - return false; - LARGE_INTEGER file_size; - BOOL result = GetFileSizeEx(file_handle, &file_size); - CloseHandle(file_handle); - if (result) { - size = file_size.QuadPart; - } - return size; -#else - try - { - std::ifstream fstream; - fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); - size = fstream.tellg(); - fstream.close(); - return true; - } - - catch(...) - { - return false; - } -#endif - } - + bool is_file_exist(const std::string& path); + bool save_string_to_file(const std::string& path_to_file, const std::string& str); + bool get_file_time(const std::string& path_to_file, time_t& ft); + bool set_file_time(const std::string& path_to_file, const time_t& ft); + bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size = 1000000000); + bool append_string_to_file(const std::string& path_to_file, const std::string& str); + bool get_file_size(const std::string& path_to_file, uint64_t &size); } } diff --git a/contrib/epee/include/misc_language.h b/contrib/epee/include/misc_language.h index a04c63231..5ccfe6fcc 100644 --- a/contrib/epee/include/misc_language.h +++ b/contrib/epee/include/misc_language.h @@ -28,9 +28,11 @@ #pragma once -#include <limits> -#include <boost/thread.hpp> #include <boost/utility/value_init.hpp> +#include <boost/shared_ptr.hpp> +#include <limits> +#include <functional> +#include <vector> namespace epee { #define STD_TRY_BEGIN() try { @@ -95,16 +97,7 @@ namespace misc_utils return memcmp(&_Left, &_Right, sizeof(_Left)) < 0; } - - inline - bool sleep_no_w(long ms ) - { - boost::this_thread::sleep( - boost::get_system_time() + - boost::posix_time::milliseconds( std::max<long>(ms,0) ) ); - - return true; - } + bool sleep_no_w(long ms ); template <typename T> T get_mid(const T &a, const T &b) diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index 3be335e85..392ec11eb 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -41,7 +41,7 @@ #define MAX_LOG_FILES 50 #define MCLOG_TYPE(level, cat, color, type, x) do { \ - if (ELPP->vRegistry()->allowed(level, cat)) { \ + if (el::Loggers::allowed(level, cat)) { \ el::base::Writer(level, color, __FILE__, __LINE__, ELPP_FUNC, type).construct(cat) << x; \ } \ } while (0) @@ -89,7 +89,7 @@ #define IFLOG(level, cat, color, type, init, x) \ do { \ - if (ELPP->vRegistry()->allowed(level, cat)) { \ + if (el::Loggers::allowed(level, cat)) { \ init; \ el::base::Writer(level, color, __FILE__, __LINE__, ELPP_FUNC, type).construct(cat) << x; \ } \ diff --git a/contrib/epee/include/net/abstract_tcp_server2.h b/contrib/epee/include/net/abstract_tcp_server2.h index 3c31cf22b..f40cd108a 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.h +++ b/contrib/epee/include/net/abstract_tcp_server2.h @@ -265,6 +265,12 @@ namespace net_utils template<class t_callback> bool connect_async(const std::string& adr, const std::string& port, uint32_t conn_timeot, const t_callback &cb, const std::string& bind_ip = "0.0.0.0", epee::net_utils::ssl_support_t ssl_support = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); + boost::asio::ssl::context& get_ssl_context() noexcept + { + assert(m_state != nullptr); + return m_state->ssl_context; + } + typename t_protocol_handler::config_type& get_config_object() { assert(m_state != nullptr); // always set in constructor diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index cb1388f3b..61e2b30fe 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -40,6 +40,7 @@ #include <boost/date_time/posix_time/posix_time.hpp> // TODO #include <boost/thread/condition_variable.hpp> // TODO #include <boost/make_shared.hpp> +#include <boost/thread.hpp> #include "warnings.h" #include "string_tools.h" #include "misc_language.h" @@ -269,8 +270,6 @@ PRAGMA_WARNING_DISABLE_VS(4355) //_dbg3("[sock " << socket().native_handle() << "] add_ref, m_peer_number=" << mI->m_peer_number); CRITICAL_REGION_LOCAL(self->m_self_refs_lock); //_dbg3("[sock " << socket().native_handle() << "] add_ref 2, m_peer_number=" << mI->m_peer_number); - if(m_was_shutdown) - return false; ++m_reference_count; m_self_ref = std::move(self); return true; @@ -562,7 +561,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) { // LOCK: chunking epee::critical_region_t<decltype(m_chunking_lock)> send_guard(m_chunking_lock); // *** critical *** - MDEBUG("do_send() will SPLIT into small chunks, from packet="<<message_size<<" B for ptr="<<message_data); + MDEBUG("do_send() will SPLIT into small chunks, from packet="<<message_size<<" B for ptr="<<(const void*)message_data); // 01234567890 // ^^^^ (pos=0, len=4) ; pos:=pos+len, pos=4 // ^^^^ (pos=4, len=4) ; pos:=pos+len, pos=8 @@ -575,14 +574,14 @@ PRAGMA_WARNING_DISABLE_VS(4355) while (!message.empty()) { byte_slice chunk = message.take_slice(chunksize_good); - MDEBUG("chunk_start="<<(void*)chunk.data()<<" ptr="<<message_data<<" pos="<<(chunk.data() - message_data)); + MDEBUG("chunk_start="<<(void*)chunk.data()<<" ptr="<<(const void*)message_data<<" pos="<<(chunk.data() - message_data)); MDEBUG("part of " << message.size() << ": pos="<<(chunk.data() - message_data) << " len="<<chunk.size()); bool ok = do_send_chunk(std::move(chunk)); // <====== *** all_ok = all_ok && ok; if (!all_ok) { - MDEBUG("do_send() DONE ***FAILED*** from packet="<<message_size<<" B for ptr="<<message_data); + MDEBUG("do_send() DONE ***FAILED*** from packet="<<message_size<<" B for ptr="<<(const void*)message_data); MDEBUG("do_send() SEND was aborted in middle of big package - this is mostly harmless " << " (e.g. peer closed connection) but if it causes trouble tell us at #monero-dev. " << message_size); return false; // partial failure in sending @@ -590,7 +589,7 @@ PRAGMA_WARNING_DISABLE_VS(4355) // (in catch block, or uniq pointer) delete buf; } // each chunk - MDEBUG("do_send() DONE SPLIT from packet="<<message_size<<" B for ptr="<<message_data); + MDEBUG("do_send() DONE SPLIT from packet="<<message_size<<" B for ptr="<<(const void*)message_data); MDEBUG("do_send() m_connection_type = " << m_connection_type); diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 1665fdac7..ffb3f3b7e 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -74,7 +74,13 @@ uint64_t ticks = misc_utils::get_tick_count(); \ boost::value_initialized<command_type::request> req; \ bool parse_res = epee::serialization::load_t_from_json(static_cast<command_type::request&>(req), query_info.m_body); \ - CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse json: \r\n" << query_info.m_body); \ + if (!parse_res) \ + { \ + MERROR("Failed to parse json: \r\n" << query_info.m_body); \ + response_info.m_response_code = 400; \ + response_info.m_response_comment = "Bad request"; \ + return true; \ + } \ uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ boost::value_initialized<command_type::response> resp;\ MINFO(m_conn_context << "calling " << s_pattern); \ @@ -104,7 +110,13 @@ uint64_t ticks = misc_utils::get_tick_count(); \ boost::value_initialized<command_type::request> req; \ bool parse_res = epee::serialization::load_t_from_binary(static_cast<command_type::request&>(req), epee::strspan<uint8_t>(query_info.m_body)); \ - CHECK_AND_ASSERT_MES(parse_res, false, "Failed to parse bin body data, body size=" << query_info.m_body.size()); \ + if (!parse_res) \ + { \ + MERROR("Failed to parse bin body data, body size=" << query_info.m_body.size()); \ + response_info.m_response_code = 400; \ + response_info.m_response_comment = "Bad request"; \ + return true; \ + } \ uint64_t ticks1 = misc_utils::get_tick_count(); \ boost::value_initialized<command_type::response> resp;\ MINFO(m_conn_context << "calling " << s_pattern); \ diff --git a/contrib/epee/include/net/levin_base.h b/contrib/epee/include/net/levin_base.h index ad561c5b6..df59a6c44 100644 --- a/contrib/epee/include/net/levin_base.h +++ b/contrib/epee/include/net/levin_base.h @@ -31,6 +31,7 @@ #include <cstdint> +#include "byte_stream.h" #include "net_utils_base.h" #include "span.h" @@ -72,7 +73,8 @@ namespace levin #define LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED 0 -#define LEVIN_DEFAULT_MAX_PACKET_SIZE 100000000 //100MB by default +#define LEVIN_INITIAL_MAX_PACKET_SIZE 256*1024 // 256 KiB before handshake +#define LEVIN_DEFAULT_MAX_PACKET_SIZE 100000000 //100MB by default after handshake #define LEVIN_PACKET_REQUEST 0x00000001 #define LEVIN_PACKET_RESPONSE 0x00000002 @@ -82,11 +84,12 @@ namespace levin #define LEVIN_PROTOCOL_VER_0 0 #define LEVIN_PROTOCOL_VER_1 1 + template<class t_connection_context = net_utils::connection_context_base> struct levin_commands_handler { - virtual int invoke(int command, const epee::span<const uint8_t> in_buff, byte_slice& buff_out, t_connection_context& context)=0; + virtual int invoke(int command, const epee::span<const uint8_t> in_buff, byte_stream& buff_out, t_connection_context& context)=0; virtual int notify(int command, const epee::span<const uint8_t> in_buff, t_connection_context& context)=0; virtual void callback(t_connection_context& context){}; @@ -124,12 +127,41 @@ namespace levin } } + //! Provides space for levin (p2p) header, so that payload can be sent without copy + class message_writer + { + byte_slice finalize(uint32_t command, uint32_t flags, uint32_t return_code, bool expect_response); + public: + using header = bucket_head2; + + explicit message_writer(std::size_t reserve = 8192); + + message_writer(const message_writer&) = delete; + message_writer(message_writer&&) = default; + ~message_writer() = default; + message_writer& operator=(const message_writer&) = delete; + message_writer& operator=(message_writer&&) = default; + + //! \return Size of payload (excludes header size). + std::size_t payload_size() const noexcept + { + return buffer.size() < sizeof(header) ? 0 : buffer.size() - sizeof(header); + } + + byte_slice finalize_invoke(uint32_t command) { return finalize(command, LEVIN_PACKET_REQUEST, 0, true); } + byte_slice finalize_notify(uint32_t command) { return finalize(command, LEVIN_PACKET_REQUEST, 0, false); } + byte_slice finalize_response(uint32_t command, uint32_t return_code) + { + return finalize(command, LEVIN_PACKET_RESPONSE, return_code, false); + } + + //! Has space for levin header until a finalize method is used + byte_stream buffer; + }; + //! \return Intialized levin header. bucket_head2 make_header(uint32_t command, uint64_t msg_size, uint32_t flags, bool expect_response) noexcept; - //! \return A levin notification message. - byte_slice make_notify(int command, epee::span<const std::uint8_t> payload); - /*! Generate a dummy levin message. \param noise_bytes Total size of the returned `byte_slice`. @@ -139,12 +171,11 @@ namespace levin /*! Generate 1+ levin messages that are identical to the noise message size. - \param noise Each levin message will be identical to the size of this - message. The bytes from this message will be used for padding. + \param noise_size Each levin message will be identical to this value. \return `nullptr` if `noise.size()` is less than the levin header size. Otherwise, a levin notification message OR 2+ levin fragment messages. Each message is `noise.size()` in length. */ - byte_slice make_fragmented_notify(const byte_slice& noise, int command, epee::span<const std::uint8_t> payload); + byte_slice make_fragmented_notify(const std::size_t noise_size, int command, message_writer message); } } diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h index 8cb2be3e1..a6816cafc 100644 --- a/contrib/epee/include/net/levin_protocol_handler_async.h +++ b/contrib/epee/include/net/levin_protocol_handler_async.h @@ -51,6 +51,21 @@ #define MIN_BYTES_WANTED 512 #endif +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); + on_levin_traffic(context, initiator, sent, error, bytes, buf); +} + namespace epee { namespace levin @@ -84,14 +99,14 @@ class async_protocol_handler_config public: typedef t_connection_context connection_context; - uint64_t m_max_packet_size; + uint64_t m_initial_max_packet_size; + uint64_t m_max_packet_size; uint64_t m_invoke_timeout; - int invoke(int command, const epee::span<const uint8_t> in_buff, std::string& buff_out, boost::uuids::uuid connection_id); + int invoke(int command, message_writer in_msg, std::string& buff_out, boost::uuids::uuid connection_id); template<class callback_t> - int invoke_async(int command, const epee::span<const uint8_t> in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); + int invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED); - int notify(int command, const epee::span<const uint8_t> in_buff, boost::uuids::uuid connection_id); int send(epee::byte_slice message, const boost::uuids::uuid& connection_id); bool close(boost::uuids::uuid connection_id); bool update_connection_context(const t_connection_context& contxt); @@ -105,7 +120,7 @@ public: size_t get_in_connections_count(); void set_handler(levin_commands_handler<t_connection_context>* handler, void (*destroy)(levin_commands_handler<t_connection_context>*) = NULL); - async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE), m_invoke_timeout(LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + async_protocol_handler_config():m_pcommands_handler(NULL), m_pcommands_handler_destroy(NULL), m_initial_max_packet_size(LEVIN_INITIAL_MAX_PACKET_SIZE), m_max_packet_size(LEVIN_DEFAULT_MAX_PACKET_SIZE), m_invoke_timeout(LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) {} ~async_protocol_handler_config() { set_handler(NULL, NULL); } void del_out_connections(size_t count); @@ -121,12 +136,17 @@ class async_protocol_handler { std::string m_fragment_buffer; - bool send_message(uint32_t command, epee::span<const uint8_t> in_buff, uint32_t flags, bool expect_response) + bool send_message(byte_slice message) { - const bucket_head2 head = make_header(command, in_buff.size(), flags, expect_response); - if(!m_pservice_endpoint->do_send(byte_slice{as_byte_span(head), in_buff})) + if (message.size() < sizeof(message_writer::header)) + return false; + + message_writer::header head; + std::memcpy(std::addressof(head), message.data(), sizeof(head)); + if(!m_pservice_endpoint->do_send(std::move(message))) return false; + on_levin_traffic(m_connection_context, true, true, false, head.m_cb, head.m_command); MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << head.m_cb << ", flags" << head.m_flags << ", r?=" << head.m_have_to_return_data @@ -145,7 +165,6 @@ public: stream_state_body }; - std::atomic<bool> m_deletion_initiated; std::atomic<bool> m_protocol_released; volatile uint32_t m_invoke_buf_ready; @@ -162,6 +181,7 @@ public: net_utils::i_service_endpoint* m_pservice_endpoint; config_type& m_config; t_connection_context& m_connection_context; + std::atomic<uint64_t> m_max_packet_size; net_utils::buffer m_cache_in_buffer; stream_state m_state; @@ -289,12 +309,12 @@ public: m_current_head(bucket_head2()), m_pservice_endpoint(psnd_hndlr), m_config(config), - m_connection_context(conn_context), + m_connection_context(conn_context), + m_max_packet_size(config.m_initial_max_packet_size), m_cache_in_buffer(4 * 1024), m_state(stream_state_head) { m_close_called = 0; - m_deletion_initiated = false; m_protocol_released = false; m_wait_count = 0; m_oponent_protocol_ver = 0; @@ -307,7 +327,6 @@ public: try { - m_deletion_initiated = true; if(m_connection_initialized) { m_config.del_connection(this); @@ -399,13 +418,14 @@ public: } // these should never fail, but do runtime check for safety - CHECK_AND_ASSERT_MES(m_config.m_max_packet_size >= m_cache_in_buffer.size(), false, "Bad m_cache_in_buffer.size()"); - CHECK_AND_ASSERT_MES(m_config.m_max_packet_size - m_cache_in_buffer.size() >= m_fragment_buffer.size(), false, "Bad m_cache_in_buffer.size() + m_fragment_buffer.size()"); + const uint64_t max_packet_size = m_max_packet_size; + CHECK_AND_ASSERT_MES(max_packet_size >= m_cache_in_buffer.size(), false, "Bad m_cache_in_buffer.size()"); + CHECK_AND_ASSERT_MES(max_packet_size - m_cache_in_buffer.size() >= m_fragment_buffer.size(), false, "Bad m_cache_in_buffer.size() + m_fragment_buffer.size()"); // flipped to subtraction; prevent overflow since m_max_packet_size is variable and public - if(cb > m_config.m_max_packet_size - m_cache_in_buffer.size() - m_fragment_buffer.size()) + if(cb > max_packet_size - m_cache_in_buffer.size() - m_fragment_buffer.size()) { - MWARNING(m_connection_context << "Maximum packet size exceed!, m_max_packet_size = " << m_config.m_max_packet_size + MWARNING(m_connection_context << "Maximum packet size exceed!, m_max_packet_size = " << max_packet_size << ", packet received " << m_cache_in_buffer.size() + cb << ", connection will be closed."); return false; @@ -430,7 +450,7 @@ public: //async call scenario boost::shared_ptr<invoke_response_handler_base> response_handler = m_invoke_response_handlers.front(); response_handler->reset_timer(); - MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb); + MDEBUG(m_connection_context << "LEVIN_PACKET partial msg received. len=" << cb << ", current total " << m_cache_in_buffer.size() << "/" << m_current_head.m_cb << " (" << (100.0f * m_cache_in_buffer.size() / (m_current_head.m_cb ? m_current_head.m_cb : 1)) << "%)"); } } break; @@ -465,6 +485,14 @@ public: temp = std::move(m_fragment_buffer); m_fragment_buffer.clear(); std::memcpy(std::addressof(m_current_head), std::addressof(temp[0]), sizeof(bucket_head2)); + const size_t max_bytes = m_connection_context.get_max_bytes(m_current_head.m_command); + if(m_current_head.m_cb > std::min<size_t>(max_packet_size, max_bytes)) + { + MERROR(m_connection_context << "Maximum packet size exceed!, m_max_packet_size = " << std::min<size_t>(max_packet_size, max_bytes) + << ", packet header received " << m_current_head.m_cb << ", command " << m_current_head.m_command + << ", connection will be closed."); + return false; + } buff_to_invoke = {reinterpret_cast<const uint8_t*>(temp.data()) + sizeof(bucket_head2), temp.size() - sizeof(bucket_head2)}; } @@ -514,22 +542,17 @@ public: { if(m_current_head.m_have_to_return_data) { - byte_slice return_buff; + levin::message_writer return_message{32 * 1024}; const uint32_t return_code = m_config.m_pcommands_handler->invoke( - m_current_head.m_command, buff_to_invoke, return_buff, m_connection_context + m_current_head.m_command, buff_to_invoke, return_message.buffer, m_connection_context ); - bucket_head2 head = make_header(m_current_head.m_command, return_buff.size(), LEVIN_PACKET_RESPONSE, false); - head.m_return_code = SWAP32LE(return_code); + // peer_id remains unset if dropped + if (m_current_head.m_command == m_connection_context.handshake_command() && m_connection_context.handshake_complete()) + m_max_packet_size = m_config.m_max_packet_size; - if(!m_pservice_endpoint->do_send(byte_slice{{epee::as_byte_span(head), epee::to_span(return_buff)}})) + if(!send_message(return_message.finalize_response(m_current_head.m_command, return_code))) return false; - - MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << head.m_cb - << ", flags" << head.m_flags - << ", r?=" << head.m_have_to_return_data - <<", cmd = " << head.m_command - << ", ver=" << head.m_protocol_version); } else m_config.m_pcommands_handler->notify(m_current_head.m_command, buff_to_invoke, m_connection_context); @@ -576,10 +599,11 @@ public: m_cache_in_buffer.erase(sizeof(bucket_head2)); m_state = stream_state_body; m_oponent_protocol_ver = m_current_head.m_protocol_version; - if(m_current_head.m_cb > m_config.m_max_packet_size) + const size_t max_bytes = m_connection_context.get_max_bytes(m_current_head.m_command); + if(m_current_head.m_cb > std::min<size_t>(max_packet_size, max_bytes)) { - LOG_ERROR_CC(m_connection_context, "Maximum packet size exceed!, m_max_packet_size = " << m_config.m_max_packet_size - << ", packet header received " << m_current_head.m_cb + LOG_ERROR_CC(m_connection_context, "Maximum packet size exceed!, m_max_packet_size = " << std::min<size_t>(max_packet_size, max_bytes) + << ", packet header received " << m_current_head.m_cb << ", command " << m_current_head.m_command << ", connection will be closed."); return false; } @@ -605,7 +629,7 @@ public: } template<class callback_t> - bool async_invoke(int command, const epee::span<const uint8_t> in_buff, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) + bool async_invoke(int command, message_writer in_msg, const callback_t &cb, size_t timeout = LEVIN_DEFAULT_TIMEOUT_PRECONFIGURED) { misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler( boost::bind(&async_protocol_handler::finish_outer_call, this)); @@ -616,24 +640,15 @@ public: int err_code = LEVIN_OK; do { - if(m_deletion_initiated) - { - err_code = LEVIN_ERROR_CONNECTION_DESTROYED; - break; - } - CRITICAL_REGION_LOCAL(m_call_lock); - if(m_deletion_initiated) - { - err_code = LEVIN_ERROR_CONNECTION_DESTROYED; - break; - } - boost::interprocess::ipcdetail::atomic_write32(&m_invoke_buf_ready, 0); CRITICAL_REGION_BEGIN(m_invoke_response_handlers_lock); - if(!send_message(command, in_buff, LEVIN_PACKET_REQUEST, true)) + if (command == m_connection_context.handshake_command()) + m_max_packet_size = m_config.m_max_packet_size; + + if(!send_message(in_msg.finalize_invoke(command))) { LOG_ERROR_CC(m_connection_context, "Failed to do_send"); err_code = LEVIN_ERROR_CONNECTION; @@ -659,22 +674,19 @@ public: return true; } - int invoke(int command, const epee::span<const uint8_t> in_buff, std::string& buff_out) + int invoke(int command, message_writer in_msg, std::string& buff_out) { misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler( boost::bind(&async_protocol_handler::finish_outer_call, this)); - if(m_deletion_initiated) - return LEVIN_ERROR_CONNECTION_DESTROYED; - CRITICAL_REGION_LOCAL(m_call_lock); - if(m_deletion_initiated) - return LEVIN_ERROR_CONNECTION_DESTROYED; - boost::interprocess::ipcdetail::atomic_write32(&m_invoke_buf_ready, 0); - if (!send_message(command, in_buff, LEVIN_PACKET_REQUEST, true)) + if (command == m_connection_context.handshake_command()) + m_max_packet_size = m_config.m_max_packet_size; + + if (!send_message(in_msg.finalize_invoke(command))) { LOG_ERROR_CC(m_connection_context, "Failed to send request"); return LEVIN_ERROR_CONNECTION; @@ -683,7 +695,7 @@ public: uint64_t ticks_start = misc_utils::get_tick_count(); size_t prev_size = 0; - while(!boost::interprocess::ipcdetail::atomic_read32(&m_invoke_buf_ready) && !m_deletion_initiated && !m_protocol_released) + while(!boost::interprocess::ipcdetail::atomic_read32(&m_invoke_buf_ready) && !m_protocol_released) { if(m_cache_in_buffer.size() - prev_size >= MIN_BYTES_WANTED) { @@ -700,7 +712,7 @@ public: return LEVIN_ERROR_CONNECTION_DESTROYED; } - if(m_deletion_initiated || m_protocol_released) + if(m_protocol_released) return LEVIN_ERROR_CONNECTION_DESTROYED; CRITICAL_REGION_BEGIN(m_local_inv_buff_lock); @@ -711,31 +723,9 @@ public: return m_invoke_result_code; } - int notify(int command, const epee::span<const uint8_t> in_buff) - { - misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler( - boost::bind(&async_protocol_handler::finish_outer_call, this)); - - if(m_deletion_initiated) - return LEVIN_ERROR_CONNECTION_DESTROYED; - - CRITICAL_REGION_LOCAL(m_call_lock); - - if(m_deletion_initiated) - return LEVIN_ERROR_CONNECTION_DESTROYED; - - if (!send_message(command, in_buff, LEVIN_PACKET_REQUEST, false)) - { - LOG_ERROR_CC(m_connection_context, "Failed to send notify message"); - return -1; - } - - return 1; - } - - /*! Sends `message` without adding a levin header. The message must have - been created with `make_notify`, `make_noise_notify` or - `make_fragmented_notify`. See additional instructions for + /*! Sends `message` without adding a levin header. The message must have been + created with `make_noise_notify`, `make_fragmented_notify`, or + `message_writer::finalize_notify`. See additional instructions for `make_fragmented_notify`. \return 1 on success */ @@ -745,17 +735,11 @@ public: boost::bind(&async_protocol_handler::finish_outer_call, this) ); - if(m_deletion_initiated) - return LEVIN_ERROR_CONNECTION_DESTROYED; - - const std::size_t length = message.size(); - if (!m_pservice_endpoint->do_send(std::move(message))) + if (!send_message(std::move(message))) { LOG_ERROR_CC(m_connection_context, "Failed to send message, dropping it"); return -1; } - - MDEBUG(m_connection_context << "LEVIN_PACKET_SENT. [len=" << (length - sizeof(bucket_head2)) << ", r?=0]"); return 1; } //------------------------------------------------------------------------------------------ @@ -776,36 +760,32 @@ void async_protocol_handler_config<t_connection_context>::del_connection(async_p template<class t_connection_context> void async_protocol_handler_config<t_connection_context>::delete_connections(size_t count, bool incoming) { - std::vector <boost::uuids::uuid> connections; + std::vector<typename connections_map::mapped_type> connections; + + auto scope_exit_handler = misc_utils::create_scope_leave_handler([&connections]{ + for (auto &aph: connections) + aph->finish_outer_call(); + }); + CRITICAL_REGION_BEGIN(m_connects_lock); for (auto& c: m_connects) { if (c.second->m_connection_context.m_is_income == incoming) - connections.push_back(c.first); + if (c.second->start_outer_call()) + connections.push_back(c.second); } // close random connections from the provided set // TODO or better just keep removing random elements (performance) unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); shuffle(connections.begin(), connections.end(), std::default_random_engine(seed)); - while (count > 0 && connections.size() > 0) - { - try - { - auto i = connections.end() - 1; - async_protocol_handler<t_connection_context> *conn = m_connects.at(*i); - del_connection(conn); - conn->close(); - connections.erase(i); - } - catch (const std::out_of_range &e) - { - MWARNING("Connection not found in m_connects, continuing"); - } - --count; - } + for (size_t i = 0; i < connections.size() && i < count; ++i) + m_connects.erase(connections[i]->get_connection_id()); CRITICAL_REGION_END(); + + for (size_t i = 0; i < connections.size() && i < count; ++i) + connections[i]->close(); } //------------------------------------------------------------------------------------------ template<class t_connection_context> @@ -849,41 +829,53 @@ int async_protocol_handler_config<t_connection_context>::find_and_lock_connectio } //------------------------------------------------------------------------------------------ template<class t_connection_context> -int async_protocol_handler_config<t_connection_context>::invoke(int command, const epee::span<const uint8_t> in_buff, std::string& buff_out, boost::uuids::uuid connection_id) +int async_protocol_handler_config<t_connection_context>::invoke(int command, message_writer in_msg, std::string& buff_out, boost::uuids::uuid connection_id) { async_protocol_handler<t_connection_context>* aph; int r = find_and_lock_connection(connection_id, aph); - return LEVIN_OK == r ? aph->invoke(command, in_buff, buff_out) : r; + return LEVIN_OK == r ? aph->invoke(command, std::move(in_msg), buff_out) : r; } //------------------------------------------------------------------------------------------ template<class t_connection_context> template<class callback_t> -int async_protocol_handler_config<t_connection_context>::invoke_async(int command, const epee::span<const uint8_t> in_buff, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout) +int async_protocol_handler_config<t_connection_context>::invoke_async(int command, message_writer in_msg, boost::uuids::uuid connection_id, const callback_t &cb, size_t timeout) { async_protocol_handler<t_connection_context>* aph; int r = find_and_lock_connection(connection_id, aph); - return LEVIN_OK == r ? aph->async_invoke(command, in_buff, cb, timeout) : r; + return LEVIN_OK == r ? aph->async_invoke(command, std::move(in_msg), cb, timeout) : r; } //------------------------------------------------------------------------------------------ template<class t_connection_context> template<class callback_t> bool async_protocol_handler_config<t_connection_context>::foreach_connection(const callback_t &cb) { - CRITICAL_REGION_LOCAL(m_connects_lock); - for(auto& c: m_connects) - { - async_protocol_handler<t_connection_context>* aph = c.second; - if(!cb(aph->get_context_ref())) + std::vector<typename connections_map::mapped_type> conn; + + auto scope_exit_handler = misc_utils::create_scope_leave_handler([&conn]{ + for (auto &aph: conn) + aph->finish_outer_call(); + }); + + CRITICAL_REGION_BEGIN(m_connects_lock); + conn.reserve(m_connects.size()); + for (auto &e: m_connects) + if (e.second->start_outer_call()) + conn.push_back(e.second); + CRITICAL_REGION_END() + + for (auto &aph: conn) + if (!cb(aph->get_context_ref())) return false; - } + return true; } //------------------------------------------------------------------------------------------ template<class t_connection_context> template<class callback_t> bool async_protocol_handler_config<t_connection_context>::for_connection(const boost::uuids::uuid &connection_id, const callback_t &cb) { - CRITICAL_REGION_LOCAL(m_connects_lock); - async_protocol_handler<t_connection_context>* aph = find_connection(connection_id); - if (!aph) + async_protocol_handler<t_connection_context>* aph = nullptr; + if (find_and_lock_connection(connection_id, aph) != LEVIN_OK) return false; + auto scope_exit_handler = misc_utils::create_scope_leave_handler( + boost::bind(&async_protocol_handler<t_connection_context>::finish_outer_call, aph)); if(!cb(aph->get_context_ref())) return false; return true; @@ -928,14 +920,6 @@ void async_protocol_handler_config<t_connection_context>::set_handler(levin_comm } //------------------------------------------------------------------------------------------ template<class t_connection_context> -int async_protocol_handler_config<t_connection_context>::notify(int command, const epee::span<const uint8_t> in_buff, boost::uuids::uuid connection_id) -{ - async_protocol_handler<t_connection_context>* aph; - int r = find_and_lock_connection(connection_id, aph); - return LEVIN_OK == r ? aph->notify(command, in_buff) : r; -} -//------------------------------------------------------------------------------------------ -template<class t_connection_context> int async_protocol_handler_config<t_connection_context>::send(byte_slice message, const boost::uuids::uuid& connection_id) { async_protocol_handler<t_connection_context>* aph; @@ -946,12 +930,14 @@ int async_protocol_handler_config<t_connection_context>::send(byte_slice message template<class t_connection_context> bool async_protocol_handler_config<t_connection_context>::close(boost::uuids::uuid connection_id) { - CRITICAL_REGION_LOCAL(m_connects_lock); - async_protocol_handler<t_connection_context>* aph = find_connection(connection_id); - if (!aph) + async_protocol_handler<t_connection_context>* aph = nullptr; + if (find_and_lock_connection(connection_id, aph) != LEVIN_OK) return false; + auto scope_exit_handler = misc_utils::create_scope_leave_handler( + boost::bind(&async_protocol_handler<t_connection_context>::finish_outer_call, aph)); if (!aph->close()) return false; + CRITICAL_REGION_LOCAL(m_connects_lock); m_connects.erase(connection_id); return true; } diff --git a/contrib/epee/include/net/net_ssl.h b/contrib/epee/include/net/net_ssl.h index 1b1577e77..58cd7e45f 100644 --- a/contrib/epee/include/net/net_ssl.h +++ b/contrib/epee/include/net/net_ssl.h @@ -36,6 +36,7 @@ #include <boost/utility/string_ref.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/ssl.hpp> +#include <boost/filesystem/path.hpp> #include <boost/system/error_code.hpp> #define SSL_FINGERPRINT_SIZE 32 @@ -144,6 +145,9 @@ namespace net_utils bool create_ec_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert); bool create_rsa_ssl_certificate(EVP_PKEY *&pkey, X509 *&cert); + + //! Store private key for `ssl` at `base + ".key"` unencrypted and certificate for `ssl` at `base + ".crt"`. + boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const boost::filesystem::path& base); } } diff --git a/contrib/epee/include/serialization/keyvalue_serialization.h b/contrib/epee/include/serialization/keyvalue_serialization.h index fd343865c..2e4a0faad 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization.h +++ b/contrib/epee/include/serialization/keyvalue_serialization.h @@ -73,7 +73,8 @@ public: \ template<bool is_store, class t_storage> \ bool serialize_map(t_storage& stg, typename t_storage::hsection hparent_section) \ { \ - decltype(*this) &this_ref = *this; + decltype(*this) &this_ref = *this; \ + (void) this_ref; // Suppress unused var warnings. Sometimes this var is used, sometimes not. #define KV_SERIALIZE_N(varialble, val_name) \ epee::serialization::selector<is_store>::serialize(this_ref.varialble, stg, hparent_section, val_name); diff --git a/contrib/epee/include/storages/http_abstract_invoke.h b/contrib/epee/include/storages/http_abstract_invoke.h index c4cb91130..c615b20e6 100644 --- a/contrib/epee/include/storages/http_abstract_invoke.h +++ b/contrib/epee/include/storages/http_abstract_invoke.h @@ -98,7 +98,12 @@ namespace epee return false; } - return serialization::load_t_from_binary(result_struct, epee::strspan<uint8_t>(pri->m_body)); + static const constexpr epee::serialization::portable_storage::limits_t default_http_bin_limits = { + 65536 * 3, // objects + 65536 * 3, // fields + 65536 * 3, // strings + }; + return serialization::load_t_from_binary(result_struct, epee::strspan<uint8_t>(pri->m_body), &default_http_bin_limits); } template<class t_request, class t_response, class t_transport> diff --git a/contrib/epee/include/storages/levin_abstract_invoke2.h b/contrib/epee/include/storages/levin_abstract_invoke2.h index 95f0bb410..383d67cc2 100644 --- a/contrib/epee/include/storages/levin_abstract_invoke2.h +++ b/contrib/epee/include/storages/levin_abstract_invoke2.h @@ -37,21 +37,19 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net" +template<typename context_t> +void on_levin_traffic(const context_t &context, bool initiator, bool sent, bool error, size_t bytes, const char *category); + +template<typename context_t> +void on_levin_traffic(const context_t &context, bool initiator, bool sent, bool error, size_t bytes, int command); + 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); - } + static const constexpr epee::serialization::portable_storage::limits_t default_levin_limits = { + 8192, // objects + 16384, // fields + 16384, // strings + }; } namespace epee @@ -77,7 +75,7 @@ namespace epee return false; } serialization::portable_storage stg_ret; - if(!stg_ret.load_from_binary(buff_to_recv)) + if(!stg_ret.load_from_binary(buff_to_recv, &default_levin_limits)) { LOG_ERROR("Failed to load_from_binary on command " << command); return false; @@ -112,19 +110,18 @@ namespace epee const boost::uuids::uuid &conn_id = context.m_connection_id; typename serialization::portable_storage stg; out_struct.store(stg); - byte_slice buff_to_send; + levin::message_writer to_send{16 * 1024}; std::string buff_to_recv; - stg.store_to_binary(buff_to_send, 16 * 1024); + stg.store_to_binary(to_send.buffer); - on_levin_traffic(context, true, true, false, buff_to_send.size(), command); - int res = transport.invoke(command, boost::string_ref{reinterpret_cast<const char*>(buff_to_send.data()), buff_to_send.size()}, buff_to_recv, conn_id); + int res = transport.invoke(command, std::move(to_send), buff_to_recv, conn_id); if( res <=0 ) { LOG_PRINT_L1("Failed to invoke command " << command << " return code " << res); return false; } typename serialization::portable_storage stg_ret; - if(!stg_ret.load_from_binary(buff_to_recv)) + if(!stg_ret.load_from_binary(buff_to_recv, &default_levin_limits)) { on_levin_traffic(context, true, false, true, buff_to_recv.size(), command); LOG_ERROR("Failed to load_from_binary on command " << command); @@ -140,10 +137,9 @@ namespace epee 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 - byte_slice buff_to_send; - stg.store_to_binary(buff_to_send, 16 * 1024); - on_levin_traffic(context, true, true, false, buff_to_send.size(), command); - int res = transport.invoke_async(command, epee::to_span(buff_to_send), conn_id, [cb, command](int code, const epee::span<const uint8_t> buff, typename t_transport::connection_context& context)->bool + levin::message_writer to_send{16 * 1024}; + stg.store_to_binary(to_send.buffer); + int res = transport.invoke_async(command, std::move(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 ) @@ -155,7 +151,7 @@ namespace epee return false; } serialization::portable_storage stg_ret; - if(!stg_ret.load_from_binary(buff)) + if(!stg_ret.load_from_binary(buff, &default_levin_limits)) { on_levin_traffic(context, true, false, true, buff.size(), command); LOG_ERROR("Failed to load_from_binary on command " << command); @@ -187,11 +183,10 @@ namespace epee const boost::uuids::uuid &conn_id = context.m_connection_id; serialization::portable_storage stg; out_struct.store(stg); - byte_slice buff_to_send; - stg.store_to_binary(buff_to_send); + levin::message_writer to_send; + stg.store_to_binary(to_send.buffer); - on_levin_traffic(context, true, true, false, buff_to_send.size(), command); - int res = transport.notify(command, epee::to_span(buff_to_send), conn_id); + int res = transport.send(to_send.finalize_notify(command), conn_id); if(res <=0 ) { MERROR("Failed to notify command " << command << " return code " << res); @@ -202,10 +197,10 @@ namespace epee //---------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------- template<class t_owner, class t_in_type, class t_out_type, class t_context, class callback_t> - int buff_to_t_adapter(int command, const epee::span<const uint8_t> in_buff, byte_slice& buff_out, callback_t cb, t_context& context ) + int buff_to_t_adapter(int command, const epee::span<const uint8_t> in_buff, byte_stream& buff_out, callback_t cb, t_context& context ) { serialization::portable_storage strg; - if(!strg.load_from_binary(in_buff)) + if(!strg.load_from_binary(in_buff, &default_levin_limits)) { on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load_from_binary in command " << command); @@ -225,12 +220,11 @@ namespace epee serialization::portable_storage strg_out; static_cast<t_out_type&>(out_struct).store(strg_out); - if(!strg_out.store_to_binary(buff_out, 32 * 1024)) + if(!strg_out.store_to_binary(buff_out)) { 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; } @@ -239,7 +233,7 @@ namespace epee int buff_to_t_adapter(t_owner* powner, int command, const epee::span<const uint8_t> in_buff, callback_t cb, t_context& context) { serialization::portable_storage strg; - if(!strg.load_from_binary(in_buff)) + if(!strg.load_from_binary(in_buff, &default_levin_limits)) { on_levin_traffic(context, false, false, true, in_buff.size(), command); LOG_ERROR("Failed to load_from_binary in notify " << command); @@ -257,7 +251,7 @@ namespace epee } #define CHAIN_LEVIN_INVOKE_MAP2(context_type) \ - int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, context_type& context) \ + int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, context_type& context) \ { \ bool handled = false; \ return handle_invoke_map(false, command, in_buff, buff_out, context, handled); \ @@ -266,13 +260,13 @@ namespace epee #define CHAIN_LEVIN_NOTIFY_MAP2(context_type) \ int notify(int command, const epee::span<const uint8_t> in_buff, context_type& context) \ { \ - bool handled = false; epee::byte_slice fake_str; \ - return handle_invoke_map(true, command, in_buff, fake_str, context, handled); \ + bool handled = false; epee::byte_stream fake_str; \ + return handle_invoke_map(true, command, in_buff, fake_str, context, handled); \ } #define CHAIN_LEVIN_INVOKE_MAP() \ - int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, epee::net_utils::connection_context_base& context) \ + int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, epee::net_utils::connection_context_base& context) \ { \ bool handled = false; \ return handle_invoke_map(false, command, in_buff, buff_out, context, handled); \ @@ -292,7 +286,7 @@ namespace epee } #define BEGIN_INVOKE_MAP2(owner_type) \ - template <class t_context> int handle_invoke_map(bool is_notify, int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, t_context& context, bool& handled) \ + template <class t_context> int handle_invoke_map(bool is_notify, int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, t_context& context, bool& handled) \ { \ try { \ typedef owner_type internal_owner_type_name; diff --git a/contrib/epee/include/storages/portable_storage.h b/contrib/epee/include/storages/portable_storage.h index 589e6ad63..655a2eb12 100644 --- a/contrib/epee/include/storages/portable_storage.h +++ b/contrib/epee/include/storages/portable_storage.h @@ -24,24 +24,17 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - - #pragma once -#include <type_traits> - -#include "misc_language.h" #include "portable_storage_base.h" -#include "portable_storage_from_bin.h" -#include "portable_storage_to_json.h" -#include "portable_storage_from_json.h" #include "portable_storage_val_converters.h" +#include "misc_log_ex.h" #include "span.h" -#include "int-util.h" namespace epee { class byte_slice; + class byte_stream; namespace serialization { /************************************************************************/ @@ -54,6 +47,13 @@ namespace epee typedef epee::serialization::harray harray; typedef storage_entry meta_entry; + struct limits_t + { + size_t n_objects; + size_t n_fields; + size_t n_strings; // not counting field names + }; + portable_storage(){} virtual ~portable_storage(){} hsection open_section(const std::string& section_name, hsection hparent_section, bool create_if_notexist = false); @@ -84,8 +84,13 @@ namespace epee //------------------------------------------------------------------------------- bool store_to_binary(byte_slice& target, std::size_t initial_buffer_size = 8192); - bool load_from_binary(const epee::span<const uint8_t> target); - bool load_from_binary(const std::string& target) { return load_from_binary(epee::strspan<uint8_t>(target)); } + bool store_to_binary(byte_stream& ss); + bool load_from_binary(const epee::span<const uint8_t> target, const limits_t *limits = nullptr); + bool load_from_binary(const std::string& target, const limits_t *limits = nullptr) + { + return load_from_binary(epee::strspan<uint8_t>(target), limits); + } + template<class trace_policy> bool dump_as_xml(std::string& targetObj, const std::string& root_name = ""); bool dump_as_json(std::string& targetObj, size_t indent = 0, bool insert_newlines = true); @@ -110,83 +115,13 @@ namespace epee }; #pragma pack(pop) }; - inline - bool portable_storage::dump_as_json(std::string& buff, size_t indent, bool insert_newlines) - { - TRY_ENTRY(); - std::stringstream ss; - epee::serialization::dump_as_json(ss, m_root, indent, insert_newlines); - buff = ss.str(); - return true; - CATCH_ENTRY("portable_storage::dump_as_json", false) - } - inline - bool portable_storage::load_from_json(const std::string& source) - { - TRY_ENTRY(); - return json::load_from_json(source, *this); - CATCH_ENTRY("portable_storage::load_from_json", false) - } - + template<class trace_policy> bool portable_storage::dump_as_xml(std::string& targetObj, const std::string& root_name) { return false;//TODO: don't think i ever again will use xml - ambiguous and "overtagged" format - } - inline - bool portable_storage::load_from_binary(const epee::span<const uint8_t> source) - { - m_root.m_entries.clear(); - if(source.size() < sizeof(storage_block_header)) - { - LOG_ERROR("portable_storage: wrong binary format, packet size = " << source.size() << " less than expected sizeof(storage_block_header)=" << sizeof(storage_block_header)); - return false; - } - storage_block_header* pbuff = (storage_block_header*)source.data(); - if(pbuff->m_signature_a != SWAP32LE(PORTABLE_STORAGE_SIGNATUREA) || - pbuff->m_signature_b != SWAP32LE(PORTABLE_STORAGE_SIGNATUREB) - ) - { - LOG_ERROR("portable_storage: wrong binary format - signature mismatch"); - return false; - } - if(pbuff->m_ver != PORTABLE_STORAGE_FORMAT_VER) - { - LOG_ERROR("portable_storage: wrong binary format - unknown format ver = " << pbuff->m_ver); - return false; - } - TRY_ENTRY(); - throwable_buffer_reader buf_reader(source.data()+sizeof(storage_block_header), source.size()-sizeof(storage_block_header)); - buf_reader.read(m_root); - return true;//TODO: - CATCH_ENTRY("portable_storage::load_from_binary", false); - } - //--------------------------------------------------------------------------------------------------------------- - inline - hsection portable_storage::open_section(const std::string& section_name, hsection hparent_section, bool create_if_notexist) - { - TRY_ENTRY(); - hparent_section = hparent_section ? hparent_section:&m_root; - storage_entry* pentry = find_storage_entry(section_name, hparent_section); - if(!pentry) - { - if(!create_if_notexist) - return nullptr; - return insert_new_section(section_name, hparent_section); - } - CHECK_AND_ASSERT(pentry , nullptr); - //check that section_entry we find is real "CSSection" - if(pentry->type() != typeid(section)) - { - if(create_if_notexist) - *pentry = storage_entry(section());//replace - else - return nullptr; - } - return &boost::get<section>(*pentry); - CATCH_ENTRY("portable_storage::open_section", nullptr); - } - //--------------------------------------------------------------------------------------------------------------- + } + template<class to_type> struct get_value_visitor: boost::static_visitor<void> { @@ -212,20 +147,6 @@ namespace epee //CATCH_ENTRY("portable_storage::template<>get_value", false); } //--------------------------------------------------------------------------------------------------------------- - inline - bool portable_storage::get_value(const std::string& value_name, storage_entry& val, hsection hparent_section) - { - //TRY_ENTRY(); - if(!hparent_section) hparent_section = &m_root; - storage_entry* pentry = find_storage_entry(value_name, hparent_section); - if(!pentry) - return false; - - val = *pentry; - return true; - //CATCH_ENTRY("portable_storage::template<>get_value", false); - } - //--------------------------------------------------------------------------------------------------------------- template<class t_value> bool portable_storage::set_value(const std::string& value_name, t_value&& v, hsection hparent_section) { @@ -247,40 +168,18 @@ namespace epee CATCH_ENTRY("portable_storage::template<>set_value", false); } //--------------------------------------------------------------------------------------------------------------- - inline - storage_entry* portable_storage::find_storage_entry(const std::string& pentry_name, hsection psection) - { - TRY_ENTRY(); - CHECK_AND_ASSERT(psection, nullptr); - auto it = psection->m_entries.find(pentry_name); - if(it == psection->m_entries.end()) - return nullptr; - - return &it->second; - CATCH_ENTRY("portable_storage::find_storage_entry", nullptr); - } - //--------------------------------------------------------------------------------------------------------------- template<class entry_type> storage_entry* portable_storage::insert_new_entry_get_storage_entry(const std::string& pentry_name, hsection psection, entry_type&& entry) { static_assert(std::is_rvalue_reference<entry_type&&>(), "unexpected copy of value"); TRY_ENTRY(); CHECK_AND_ASSERT(psection, nullptr); + CHECK_AND_ASSERT(!pentry_name.empty(), nullptr); auto ins_res = psection->m_entries.emplace(pentry_name, std::forward<entry_type>(entry)); return &ins_res.first->second; CATCH_ENTRY("portable_storage::insert_new_entry_get_storage_entry", nullptr); } //--------------------------------------------------------------------------------------------------------------- - inline - hsection portable_storage::insert_new_section(const std::string& pentry_name, hsection psection) - { - TRY_ENTRY(); - storage_entry* pse = insert_new_entry_get_storage_entry(pentry_name, psection, section()); - if(!pse) return nullptr; - return &boost::get<section>(*pse); - CATCH_ENTRY("portable_storage::insert_new_section", nullptr); - } - //--------------------------------------------------------------------------------------------------------------- template<class to_type> struct get_first_value_visitor: boost::static_visitor<bool> { @@ -334,7 +233,6 @@ namespace epee } }; - template<class t_value> bool portable_storage::get_next_value(harray hval_array, t_value& target) { @@ -392,83 +290,5 @@ namespace epee return true; CATCH_ENTRY("portable_storage::insert_next_value", false); } - //--------------------------------------------------------------------------------------------------------------- - //sections - inline - harray portable_storage::get_first_section(const std::string& sec_name, hsection& h_child_section, hsection hparent_section) - { - TRY_ENTRY(); - if(!hparent_section) hparent_section = &m_root; - storage_entry* pentry = find_storage_entry(sec_name, hparent_section); - if(!pentry) - return nullptr; - if(pentry->type() != typeid(array_entry)) - return nullptr; - array_entry& ar_entry = boost::get<array_entry>(*pentry); - if(ar_entry.type() != typeid(array_entry_t<section>)) - return nullptr; - array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(ar_entry); - section* psec = sec_array.get_first_val(); - if(!psec) - return nullptr; - h_child_section = psec; - return &ar_entry; - CATCH_ENTRY("portable_storage::get_first_section", nullptr); - } - //--------------------------------------------------------------------------------------------------------------- - inline - bool portable_storage::get_next_section(harray hsec_array, hsection& h_child_section) - { - TRY_ENTRY(); - CHECK_AND_ASSERT(hsec_array, false); - if(hsec_array->type() != typeid(array_entry_t<section>)) - return false; - array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(*hsec_array); - h_child_section = sec_array.get_next_val(); - if(!h_child_section) - return false; - return true; - CATCH_ENTRY("portable_storage::get_next_section", false); - } - //--------------------------------------------------------------------------------------------------------------- - inline - harray portable_storage::insert_first_section(const std::string& sec_name, hsection& hinserted_childsection, hsection hparent_section) - { - TRY_ENTRY(); - if(!hparent_section) hparent_section = &m_root; - storage_entry* pentry = find_storage_entry(sec_name, hparent_section); - if(!pentry) - { - pentry = insert_new_entry_get_storage_entry(sec_name, hparent_section, array_entry(array_entry_t<section>())); - if(!pentry) - return nullptr; - } - if(pentry->type() != typeid(array_entry)) - *pentry = storage_entry(array_entry(array_entry_t<section>())); - - array_entry& ar_entry = boost::get<array_entry>(*pentry); - if(ar_entry.type() != typeid(array_entry_t<section>)) - ar_entry = array_entry(array_entry_t<section>()); - - array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(ar_entry); - hinserted_childsection = &sec_array.insert_first_val(section()); - return &ar_entry; - CATCH_ENTRY("portable_storage::insert_first_section", nullptr); - } - //--------------------------------------------------------------------------------------------------------------- - inline - bool portable_storage::insert_next_section(harray hsec_array, hsection& hinserted_childsection) - { - TRY_ENTRY(); - CHECK_AND_ASSERT(hsec_array, false); - CHECK_AND_ASSERT_MES(hsec_array->type() == typeid(array_entry_t<section>), - false, "unexpected type(not 'section') in insert_next_section, type: " << hsec_array->type().name()); - - array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(*hsec_array); - hinserted_childsection = &sec_array.insert_next_value(section()); - return true; - CATCH_ENTRY("portable_storage::insert_next_section", false); - } - //--------------------------------------------------------------------------------------------------------------- } } diff --git a/contrib/epee/include/storages/portable_storage_base.h b/contrib/epee/include/storages/portable_storage_base.h index 1676f41fb..ae0be6a34 100644 --- a/contrib/epee/include/storages/portable_storage_base.h +++ b/contrib/epee/include/storages/portable_storage_base.h @@ -29,10 +29,10 @@ #pragma once #include <boost/variant.hpp> -#include <boost/any.hpp> #include <string> #include <vector> #include <deque> +#include <map> #define PORTABLE_STORAGE_SIGNATUREA 0x01011101 #define PORTABLE_STORAGE_SIGNATUREB 0x01020101 // bender's nightmare diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index eb0eed235..9e7b6ec34 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -29,6 +29,7 @@ #pragma once #include "misc_language.h" +#include "misc_log_ex.h" #include "portable_storage_base.h" #include "portable_storage_bin_utils.h" @@ -37,7 +38,6 @@ #else #define EPEE_PORTABLE_STORAGE_RECURSION_LIMIT_INTERNAL 100 #endif -#define EPEE_PORTABLE_STORAGE_OBJECT_LIMIT_INTERNAL 65536 namespace epee { @@ -46,21 +46,20 @@ namespace epee template<typename T> struct ps_min_bytes { static constexpr const size_t strict = 4096; // actual low bound - static constexpr const size_t rough = 4096; // when we want to be stricter for DoS prevention }; - template<> struct ps_min_bytes<uint64_t> { static constexpr const size_t strict = 8, rough = 8; }; - template<> struct ps_min_bytes<int64_t> { static constexpr const size_t strict = 8, rough = 8; }; - template<> struct ps_min_bytes<uint32_t> { static constexpr const size_t strict = 4, rough = 4; }; - template<> struct ps_min_bytes<int32_t> { static constexpr const size_t strict = 4, rough = 4; }; - template<> struct ps_min_bytes<uint16_t> { static constexpr const size_t strict = 2, rough = 2; }; - template<> struct ps_min_bytes<int16_t> { static constexpr const size_t strict = 2, rough = 2; }; - template<> struct ps_min_bytes<uint8_t> { static constexpr const size_t strict = 1, rough = 1; }; - template<> struct ps_min_bytes<int8_t> { static constexpr const size_t strict = 1, rough = 1; }; - template<> struct ps_min_bytes<double> { static constexpr const size_t strict = 8, rough = 8; }; - template<> struct ps_min_bytes<bool> { static constexpr const size_t strict = 1, rough = 1; }; - template<> struct ps_min_bytes<std::string> { static constexpr const size_t strict = 2, rough = 16; }; - template<> struct ps_min_bytes<section> { static constexpr const size_t strict = 1, rough = 256; }; - template<> struct ps_min_bytes<array_entry> { static constexpr const size_t strict = 1, rough = 128; }; + template<> struct ps_min_bytes<uint64_t> { static constexpr const size_t strict = 8; }; + template<> struct ps_min_bytes<int64_t> { static constexpr const size_t strict = 8; }; + template<> struct ps_min_bytes<uint32_t> { static constexpr const size_t strict = 4; }; + template<> struct ps_min_bytes<int32_t> { static constexpr const size_t strict = 4; }; + template<> struct ps_min_bytes<uint16_t> { static constexpr const size_t strict = 2; }; + template<> struct ps_min_bytes<int16_t> { static constexpr const size_t strict = 2; }; + template<> struct ps_min_bytes<uint8_t> { static constexpr const size_t strict = 1; }; + template<> struct ps_min_bytes<int8_t> { static constexpr const size_t strict = 1; }; + template<> struct ps_min_bytes<double> { static constexpr const size_t strict = 8; }; + template<> struct ps_min_bytes<bool> { static constexpr const size_t strict = 1; }; + template<> struct ps_min_bytes<std::string> { static constexpr const size_t strict = 2; }; + template<> struct ps_min_bytes<section> { static constexpr const size_t strict = 1; }; + template<> struct ps_min_bytes<array_entry> { static constexpr const size_t strict = 1; }; struct throwable_buffer_reader { @@ -83,6 +82,7 @@ namespace epee void read(array_entry &ae); template<class t_type> size_t min_bytes() const; + void set_limits(size_t objects, size_t fields, size_t strings); private: struct recursuion_limitation_guard { @@ -104,6 +104,12 @@ namespace epee size_t m_count; size_t m_recursion_count; size_t m_objects; + size_t m_fields; + size_t m_strings; + + size_t max_objects; + size_t max_fields; + size_t max_strings; }; inline throwable_buffer_reader::throwable_buffer_reader(const void* ptr, size_t sz) @@ -116,6 +122,11 @@ namespace epee m_count = sz; m_recursion_count = 0; m_objects = 0; + m_fields = 0; + m_strings = 0; + max_objects = std::numeric_limits<size_t>::max(); + max_fields = std::numeric_limits<size_t>::max(); + max_strings = std::numeric_limits<size_t>::max(); } inline void throwable_buffer_reader::read(void* target, size_t count) @@ -132,6 +143,7 @@ namespace epee RECURSION_LIMITATION(); uint8_t name_len = 0; read(name_len); + CHECK_AND_ASSERT_THROW_MES(name_len > 0, "Section name is missing"); sce_name.resize(name_len); read((void*)sce_name.data(), name_len); } @@ -163,6 +175,16 @@ namespace epee array_entry_t<type_name> sa; size_t size = read_varint(); CHECK_AND_ASSERT_THROW_MES(size <= m_count / ps_min_bytes<type_name>::strict, "Size sanity check failed"); + if (std::is_same<type_name, section>()) + { + CHECK_AND_ASSERT_THROW_MES(size <= max_objects - m_objects, "Too many objects"); + m_objects += size; + } + else if (std::is_same<type_name, std::string>()) + { + CHECK_AND_ASSERT_THROW_MES(size <= max_strings - m_strings, "Too many strings"); + m_strings += size; + } sa.reserve(size); //TODO: add some optimization here later @@ -229,6 +251,8 @@ namespace epee inline storage_entry throwable_buffer_reader::read_se<std::string>() { RECURSION_LIMITATION(); + CHECK_AND_ASSERT_THROW_MES(m_strings + 1 <= max_strings, "Too many strings"); + m_strings += 1; return storage_entry(read<std::string>()); } @@ -237,6 +261,8 @@ namespace epee inline storage_entry throwable_buffer_reader::read_se<section>() { RECURSION_LIMITATION(); + CHECK_AND_ASSERT_THROW_MES(m_objects < max_objects, "Too many objects"); + ++m_objects; section s;//use extra variable due to vs bug, line "storage_entry se(section()); " can't be compiled in visual studio storage_entry se(std::move(s)); section& section_entry = boost::get<section>(se); @@ -288,14 +314,16 @@ namespace epee RECURSION_LIMITATION(); sec.m_entries.clear(); size_t count = read_varint(); - CHECK_AND_ASSERT_THROW_MES(count < EPEE_PORTABLE_STORAGE_OBJECT_LIMIT_INTERNAL - m_objects, "Too many objects"); - m_objects += count; + CHECK_AND_ASSERT_THROW_MES(count <= max_fields - m_fields, "Too many object fields"); + m_fields += count; while(count--) { //read section name string std::string sec_name; read_sec_name(sec_name); - sec.m_entries.emplace(std::move(sec_name), load_storage_entry()); + const auto insert_loc = sec.m_entries.lower_bound(sec_name); + CHECK_AND_ASSERT_THROW_MES(insert_loc == sec.m_entries.end() || insert_loc->first != sec_name, "duplicate key: " << sec_name); + sec.m_entries.emplace_hint(insert_loc, std::move(sec_name), load_storage_entry()); } } inline @@ -316,5 +344,12 @@ namespace epee RECURSION_LIMITATION(); CHECK_AND_ASSERT_THROW_MES(false, "Reading array entry is not supported"); } + inline + void throwable_buffer_reader::set_limits(size_t objects, size_t fields, size_t strings) + { + max_objects = objects; + max_fields = fields; + max_strings = strings; + } } } diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 2b2dc7ff9..3021598f5 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -26,6 +26,7 @@ #pragma once #include <boost/lexical_cast.hpp> +#include <boost/utility/string_ref.hpp> #include <boost/algorithm/string/predicate.hpp> #include "parserse_base_utils.h" #include "file_io_utils.h" diff --git a/contrib/epee/include/storages/portable_storage_template_helper.h b/contrib/epee/include/storages/portable_storage_template_helper.h index 39f900c8d..7f6596f36 100644 --- a/contrib/epee/include/storages/portable_storage_template_helper.h +++ b/contrib/epee/include/storages/portable_storage_template_helper.h @@ -29,12 +29,15 @@ #include <string> #include "byte_slice.h" -#include "parserse_base_utils.h" +#include "parserse_base_utils.h" /// TODO: (mj-xmr) This will be reduced in an another PR #include "portable_storage.h" #include "file_io_utils.h" +#include "span.h" namespace epee { + class byte_stream; + namespace serialization { //----------------------------------------------------------------------------------------------------------- @@ -85,10 +88,10 @@ namespace epee } //----------------------------------------------------------------------------------------------------------- template<class t_struct> - bool load_t_from_binary(t_struct& out, const epee::span<const uint8_t> binary_buff) + bool load_t_from_binary(t_struct& out, const epee::span<const uint8_t> binary_buff, const epee::serialization::portable_storage::limits_t *limits = NULL) { portable_storage ps; - bool rs = ps.load_from_binary(binary_buff); + bool rs = ps.load_from_binary(binary_buff, limits); if(!rs) return false; @@ -126,5 +129,14 @@ namespace epee store_t_to_binary(str_in, binary_buff, initial_buffer_size); return binary_buff; } + //----------------------------------------------------------------------------------------------------------- + template<class t_struct> + bool store_t_to_binary(t_struct& str_in, byte_stream& binary_buff) + { + portable_storage ps; + str_in.store(ps); + return ps.store_to_binary(binary_buff); + } + } } diff --git a/contrib/epee/include/storages/portable_storage_to_bin.h b/contrib/epee/include/storages/portable_storage_to_bin.h index 137497e19..b82cf532b 100644 --- a/contrib/epee/include/storages/portable_storage_to_bin.h +++ b/contrib/epee/include/storages/portable_storage_to_bin.h @@ -32,6 +32,7 @@ #include "misc_language.h" #include "portable_storage_base.h" #include "portable_storage_bin_utils.h" +#include "misc_log_ex.h" namespace epee { @@ -211,6 +212,7 @@ namespace epee for(const section_pair& se: sec.m_entries) { CHECK_AND_ASSERT_THROW_MES(se.first.size() < std::numeric_limits<uint8_t>::max(), "storage_entry_name is too long: " << se.first.size() << ", val: " << se.first); + CHECK_AND_ASSERT_THROW_MES(!se.first.empty(), "storage_entry_name is empty"); uint8_t len = static_cast<uint8_t>(se.first.size()); strm.write((const char*)&len, sizeof(len)); strm.write(se.first.data(), size_t(len)); diff --git a/contrib/epee/include/storages/portable_storage_val_converters.h b/contrib/epee/include/storages/portable_storage_val_converters.h index e54cda828..96b0c024c 100644 --- a/contrib/epee/include/storages/portable_storage_val_converters.h +++ b/contrib/epee/include/storages/portable_storage_val_converters.h @@ -28,12 +28,17 @@ #pragma once -#include <time.h> #include <boost/regex.hpp> #include "misc_language.h" #include "portable_storage_base.h" +#include "parserse_base_utils.h" #include "warnings.h" +#include "misc_log_ex.h" + +#include <boost/lexical_cast.hpp> +#include <typeinfo> +#include <iomanip> namespace epee { diff --git a/contrib/epee/src/CMakeLists.txt b/contrib/epee/src/CMakeLists.txt index 5e101a86a..132fed355 100644 --- a/contrib/epee/src/CMakeLists.txt +++ b/contrib/epee/src/CMakeLists.txt @@ -26,10 +26,21 @@ # 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. +set(EPEE_INCLUDE_DIR_BASE "${CMAKE_CURRENT_SOURCE_DIR}/../include") + +# Adding headers to the file list, to be able to search for them in IDEs. +file(GLOB EPEE_HEADERS_PUBLIC + "${EPEE_INCLUDE_DIR_BASE}/*.h*" # h* will include hpps as well. + "${EPEE_INCLUDE_DIR_BASE}/**/*.h*" # Any number of subdirs will be included. +) add_library(epee STATIC byte_slice.cpp byte_stream.cpp hex.cpp abstract_http_client.cpp http_auth.cpp mlog.cpp net_helper.cpp net_utils_base.cpp string_tools.cpp wipeable_string.cpp levin_base.cpp memwipe.c connection_basic.cpp network_throttle.cpp network_throttle-detail.cpp mlocker.cpp buffer.cpp net_ssl.cpp - int-util.cpp portable_storage.cpp) + int-util.cpp portable_storage.cpp + misc_language.cpp + file_io_utils.cpp + ${EPEE_HEADERS_PUBLIC} + ) if (USE_READLINE AND (GNU_READLINE_FOUND OR (DEPENDS AND NOT MINGW))) add_library(epee_readline STATIC readline_buffer.cpp) @@ -71,3 +82,6 @@ if (USE_READLINE AND (GNU_READLINE_FOUND OR (DEPENDS AND NOT MINGW))) PRIVATE ${GNU_READLINE_LIBRARY}) endif() + +target_include_directories(epee PUBLIC "${EPEE_INCLUDE_DIR_BASE}") + diff --git a/contrib/epee/src/byte_slice.cpp b/contrib/epee/src/byte_slice.cpp index faf7689be..453b63a4c 100644 --- a/contrib/epee/src/byte_slice.cpp +++ b/contrib/epee/src/byte_slice.cpp @@ -36,6 +36,11 @@ #include "byte_slice.h" #include "byte_stream.h" +namespace +{ + const std::size_t page_size = 4096; +} + namespace epee { struct byte_slice_data @@ -173,16 +178,27 @@ namespace epee : byte_slice(adapt_buffer{}, std::move(buffer)) {} - byte_slice::byte_slice(byte_stream&& stream) noexcept + byte_slice::byte_slice(byte_stream&& stream, const bool shrink) : storage_(nullptr), portion_(stream.data(), stream.size()) { - if (stream.size()) + if (portion_.size()) { - std::uint8_t* const data = stream.take_buffer().release() - sizeof(raw_byte_slice); + byte_buffer buf; + if (shrink && page_size <= stream.available()) + { + buf = byte_buffer_resize(stream.take_buffer(), portion_.size()); + if (!buf) + throw std::bad_alloc{}; + portion_ = {buf.get(), portion_.size()}; + } + else // no need to shrink buffer + buf = stream.take_buffer(); + + std::uint8_t* const data = buf.release() - sizeof(raw_byte_slice); new (data) raw_byte_slice{}; storage_.reset(reinterpret_cast<raw_byte_slice*>(data)); } - else + else // empty stream portion_ = nullptr; } diff --git a/contrib/epee/src/file_io_utils.cpp b/contrib/epee/src/file_io_utils.cpp new file mode 100644 index 000000000..5072adcbc --- /dev/null +++ b/contrib/epee/src/file_io_utils.cpp @@ -0,0 +1,231 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Andrey N. Sabelnikov 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 OWNER 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 "file_io_utils.h" + +#include <fstream> +#include <boost/filesystem/path.hpp> +#include <boost/filesystem/operations.hpp> +#ifdef WIN32 +#include <windows.h> +#include "string_tools.h" +#endif + +// On Windows there is a problem with non-ASCII characters in path and file names +// as far as support by the standard components used is concerned: + +// The various file stream classes, e.g. std::ifstream and std::ofstream, are +// part of the GNU C++ Library / libstdc++. On the most basic level they use the +// fopen() call as defined / made accessible to programs compiled within MSYS2 +// by the stdio.h header file maintained by the MinGW project. + +// The critical point: The implementation of fopen() is part of MSVCRT, the +// Microsoft Visual C/C++ Runtime Library, and this method does NOT offer any +// Unicode support. + +// Monero code that would want to continue to use the normal file stream classes +// but WITH Unicode support could therefore not solve this problem on its own, +// but 2 different projects from 2 different maintaining groups would need changes +// in this particular direction - something probably difficult to achieve and +// with a long time to wait until all new versions / releases arrive. + +// Implemented solution approach: Circumvent the problem by stopping to use std +// file stream classes on Windows and directly use Unicode-capable WIN32 API +// calls. Most of the code doing so is concentrated in this header file here. + +namespace epee +{ +namespace file_io_utils +{ + + bool is_file_exist(const std::string& path) + { + boost::filesystem::path p(path); + return boost::filesystem::exists(p); + } + + + bool save_string_to_file(const std::string& path_to_file, const std::string& str) + { +#ifdef WIN32 + std::wstring wide_path; + try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } + HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + DWORD bytes_written; + DWORD bytes_to_write = (DWORD)str.size(); + BOOL result = WriteFile(file_handle, str.data(), bytes_to_write, &bytes_written, NULL); + CloseHandle(file_handle); + if (bytes_written != bytes_to_write) + result = FALSE; + return result; +#else + try + { + std::ofstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + fstream << str; + fstream.close(); + return true; + } + + catch(...) + { + return false; + } +#endif + } + + + bool get_file_time(const std::string& path_to_file, time_t& ft) + { + boost::system::error_code ec; + ft = boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ec); + if(!ec) + return true; + else + return false; + } + + + bool set_file_time(const std::string& path_to_file, const time_t& ft) + { + boost::system::error_code ec; + boost::filesystem::last_write_time(boost::filesystem::path(path_to_file), ft, ec); + if(!ec) + return true; + else + return false; + } + + + + bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size) + { +#ifdef WIN32 + std::wstring wide_path; + try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } + HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + DWORD file_size = GetFileSize(file_handle, NULL); + if ((file_size == INVALID_FILE_SIZE) || (uint64_t)file_size > (uint64_t)max_size) { + CloseHandle(file_handle); + return false; + } + target_str.resize(file_size); + DWORD bytes_read; + BOOL result = ReadFile(file_handle, &target_str[0], file_size, &bytes_read, NULL); + CloseHandle(file_handle); + if (bytes_read != file_size) + result = FALSE; + return result; +#else + try + { + std::ifstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); + + std::ifstream::pos_type file_size = fstream.tellg(); + + if((uint64_t)file_size > (uint64_t)max_size) // ensure a large domain for comparison, and negative -> too large + return false;//don't go crazy + size_t file_size_t = static_cast<size_t>(file_size); + + target_str.resize(file_size_t); + + fstream.seekg (0, std::ios::beg); + fstream.read((char*)target_str.data(), target_str.size()); + fstream.close(); + return true; + } + + catch(...) + { + return false; + } +#endif + } + + + bool append_string_to_file(const std::string& path_to_file, const std::string& str) + { + // No special Windows implementation because so far not used in Monero code + try + { + std::ofstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file.c_str(), std::ios_base::binary | std::ios_base::out | std::ios_base::app); + fstream << str; + fstream.close(); + return true; + } + + catch(...) + { + return false; + } + } + + + bool get_file_size(const std::string& path_to_file, uint64_t &size) + { +#ifdef WIN32 + std::wstring wide_path; + try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } + HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + return false; + LARGE_INTEGER file_size; + BOOL result = GetFileSizeEx(file_handle, &file_size); + CloseHandle(file_handle); + if (result) { + size = file_size.QuadPart; + } + return size; +#else + try + { + std::ifstream fstream; + fstream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + fstream.open(path_to_file, std::ios_base::binary | std::ios_base::in | std::ios::ate); + size = fstream.tellg(); + fstream.close(); + return true; + } + + catch(...) + { + return false; + } +#endif + } + +} +} diff --git a/contrib/epee/src/levin_base.cpp b/contrib/epee/src/levin_base.cpp index 5ec86b3d6..7c5cd5a78 100644 --- a/contrib/epee/src/levin_base.cpp +++ b/contrib/epee/src/levin_base.cpp @@ -34,6 +34,25 @@ namespace epee { namespace levin { + message_writer::message_writer(const std::size_t reserve) + : buffer() + { + buffer.reserve(reserve); + buffer.put_n(0, sizeof(header)); + } + + byte_slice message_writer::finalize(const uint32_t command, const uint32_t flags, const uint32_t return_code, const bool expect_response) + { + if (buffer.size() < sizeof(header)) + throw std::runtime_error{"levin_writer::finalize already called"}; + + header head = make_header(command, payload_size(), flags, expect_response); + head.m_return_code = SWAP32LE(return_code); + + std::memcpy(buffer.tellp() - buffer.size(), std::addressof(head), sizeof(head)); + return byte_slice{std::move(buffer)}; + } + bucket_head2 make_header(uint32_t command, uint64_t msg_size, uint32_t flags, bool expect_response) noexcept { bucket_head2 head = {0}; @@ -47,12 +66,6 @@ namespace levin return head; } - byte_slice make_notify(int command, epee::span<const std::uint8_t> payload) - { - const bucket_head2 head = make_header(command, payload.size(), LEVIN_PACKET_REQUEST, false); - return byte_slice{epee::as_byte_span(head), payload}; - } - byte_slice make_noise_notify(const std::size_t noise_bytes) { static constexpr const std::uint32_t flags = @@ -68,46 +81,40 @@ namespace levin return byte_slice{std::move(buffer)}; } - byte_slice make_fragmented_notify(const byte_slice& noise_message, int command, epee::span<const std::uint8_t> payload) + byte_slice make_fragmented_notify(const std::size_t noise_size, const int command, message_writer message) { - const size_t noise_size = noise_message.size(); if (noise_size < sizeof(bucket_head2) * 2) return nullptr; - if (payload.size() <= noise_size - sizeof(bucket_head2)) + if (message.buffer.size() <= noise_size) { /* The entire message can be sent at once, and the levin binary parser will ignore extra bytes. So just pad with zeroes and otherwise send a "normal", not fragmented message. */ - const size_t padding = noise_size - sizeof(bucket_head2) - payload.size(); - const span<const uint8_t> padding_bytes{noise_message.end() - padding, padding}; - const bucket_head2 head = make_header(command, noise_size - sizeof(bucket_head2), LEVIN_PACKET_REQUEST, false); - return byte_slice{as_byte_span(head), payload, padding_bytes}; + message.buffer.put_n(0, noise_size - message.buffer.size()); + return message.finalize_notify(command); } // fragment message + const byte_slice payload_bytes = message.finalize_notify(command); + span<const std::uint8_t> payload = to_span(payload_bytes); + const size_t payload_space = noise_size - sizeof(bucket_head2); const size_t expected_fragments = ((payload.size() - 2) / payload_space) + 1; - std::string buffer{}; - buffer.reserve((expected_fragments + 1) * noise_size); // +1 here overselects for internal bucket_head2 value + byte_stream buffer{}; + buffer.reserve(expected_fragments * noise_size); - bucket_head2 head = make_header(0, noise_size - sizeof(bucket_head2), LEVIN_PACKET_BEGIN, false); - buffer.append(reinterpret_cast<const char*>(&head), sizeof(head)); + bucket_head2 head = make_header(0, payload_space, LEVIN_PACKET_BEGIN, false); + buffer.write(as_byte_span(head)); - head.m_command = command; - head.m_flags = LEVIN_PACKET_REQUEST; - head.m_cb = payload.size(); - buffer.append(reinterpret_cast<const char*>(&head), sizeof(head)); + // internal levin header is in payload already - size_t copy_size = payload.remove_prefix(payload_space - sizeof(bucket_head2)); - buffer.append(reinterpret_cast<const char*>(payload.data()) - copy_size, copy_size); + size_t copy_size = payload.remove_prefix(payload_space); + buffer.write(payload.data() - copy_size, copy_size); - head.m_command = 0; head.m_flags = 0; - head.m_cb = noise_size - sizeof(bucket_head2); - while (!payload.empty()) { copy_size = payload.remove_prefix(payload_space); @@ -115,12 +122,12 @@ namespace levin if (payload.empty()) head.m_flags = LEVIN_PACKET_END; - buffer.append(reinterpret_cast<const char*>(&head), sizeof(head)); - buffer.append(reinterpret_cast<const char*>(payload.data()) - copy_size, copy_size); + buffer.write(as_byte_span(head)); + buffer.write(payload.data() - copy_size, copy_size); } const size_t padding = noise_size - copy_size - sizeof(bucket_head2); - buffer.append(reinterpret_cast<const char*>(noise_message.end()) - padding, padding); + buffer.put_n(0, padding); return byte_slice{std::move(buffer)}; } diff --git a/contrib/epee/src/misc_language.cpp b/contrib/epee/src/misc_language.cpp new file mode 100644 index 000000000..6e8f2daef --- /dev/null +++ b/contrib/epee/src/misc_language.cpp @@ -0,0 +1,44 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Andrey N. Sabelnikov 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 OWNER 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 "misc_language.h" + +#include <boost/thread.hpp> + +namespace epee +{ +namespace misc_utils +{ + bool sleep_no_w(long ms ) + { + boost::this_thread::sleep( + boost::get_system_time() + + boost::posix_time::milliseconds( std::max<long>(ms,0) ) ); + + return true; + } +} +} diff --git a/contrib/epee/src/net_ssl.cpp b/contrib/epee/src/net_ssl.cpp index 6ed27efa9..765dadce3 100644 --- a/contrib/epee/src/net_ssl.cpp +++ b/contrib/epee/src/net_ssl.cpp @@ -29,6 +29,8 @@ #include <string.h> #include <thread> #include <boost/asio/ssl.hpp> +#include <boost/cerrno.hpp> +#include <boost/filesystem/operations.hpp> #include <boost/lambda/lambda.hpp> #include <openssl/ssl.h> #include <openssl/pem.h> @@ -567,6 +569,51 @@ bool ssl_support_from_string(ssl_support_t &ssl, boost::string_ref s) return true; } +boost::system::error_code store_ssl_keys(boost::asio::ssl::context& ssl, const boost::filesystem::path& base) +{ + EVP_PKEY* ssl_key = nullptr; + X509* ssl_cert = nullptr; + const auto ctx = ssl.native_handle(); + CHECK_AND_ASSERT_MES(ctx, boost::system::error_code(EINVAL, boost::system::system_category()), "Context is null"); + CHECK_AND_ASSERT_MES(base.has_filename(), boost::system::error_code(EINVAL, boost::system::system_category()), "Need filename"); + if (!(ssl_key = SSL_CTX_get0_privatekey(ctx)) || !(ssl_cert = SSL_CTX_get0_certificate(ctx))) + return {EINVAL, boost::system::system_category()}; + + using file_closer = int(std::FILE*); + boost::system::error_code error{}; + std::unique_ptr<std::FILE, file_closer*> file{nullptr, std::fclose}; + + // write key file unencrypted + { + const boost::filesystem::path key_file{base.string() + ".key"}; + file.reset(std::fopen(key_file.string().c_str(), "wb")); + if (!file) + return {errno, boost::system::system_category()}; + boost::filesystem::permissions(key_file, boost::filesystem::owner_read, error); + if (error) + return error; + if (!PEM_write_PrivateKey(file.get(), ssl_key, nullptr, nullptr, 0, nullptr, nullptr)) + return boost::asio::error::ssl_errors(ERR_get_error()); + if (std::fclose(file.release()) != 0) + return {errno, boost::system::system_category()}; + } + + // write certificate file in standard SSL X.509 unencrypted + const boost::filesystem::path cert_file{base.string() + ".crt"}; + file.reset(std::fopen(cert_file.string().c_str(), "wb")); + if (!file) + return {errno, boost::system::system_category()}; + const auto cert_perms = (boost::filesystem::owner_read | boost::filesystem::group_read | boost::filesystem::others_read); + boost::filesystem::permissions(cert_file, cert_perms, error); + if (error) + return error; + if (!PEM_write_X509(file.get(), ssl_cert)) + return boost::asio::error::ssl_errors(ERR_get_error()); + if (std::fclose(file.release()) != 0) + return {errno, boost::system::system_category()}; + return error; +} + } // namespace } // namespace diff --git a/contrib/epee/src/portable_storage.cpp b/contrib/epee/src/portable_storage.cpp index 4534deff3..b922cc9e3 100644 --- a/contrib/epee/src/portable_storage.cpp +++ b/contrib/epee/src/portable_storage.cpp @@ -1,10 +1,43 @@ +// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Andrey N. Sabelnikov 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 OWNER 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 "byte_slice.h" #include "byte_stream.h" #include "misc_log_ex.h" #include "span.h" #include "storages/portable_storage.h" +#include "storages/portable_storage_to_json.h" +#include "storages/portable_storage_from_json.h" #include "storages/portable_storage_to_bin.h" +#include "storages/portable_storage_from_bin.h" + +#include <boost/utility/string_ref.hpp> + +#include <string> +#include <sstream> namespace epee { @@ -15,15 +48,200 @@ namespace serialization TRY_ENTRY(); byte_stream ss; ss.reserve(initial_buffer_size); + store_to_binary(ss); + target = epee::byte_slice{std::move(ss)}; + return true; + CATCH_ENTRY("portable_storage::store_to_binary", false); + } + + bool portable_storage::store_to_binary(byte_stream& ss) + { + TRY_ENTRY(); storage_block_header sbh{}; sbh.m_signature_a = SWAP32LE(PORTABLE_STORAGE_SIGNATUREA); sbh.m_signature_b = SWAP32LE(PORTABLE_STORAGE_SIGNATUREB); sbh.m_ver = PORTABLE_STORAGE_FORMAT_VER; ss.write(epee::as_byte_span(sbh)); pack_entry_to_buff(ss, m_root); - target = epee::byte_slice{std::move(ss)}; return true; - CATCH_ENTRY("portable_storage::store_to_binary", false) + CATCH_ENTRY("portable_storage::store_to_binary", false); } + + bool portable_storage::dump_as_json(std::string& buff, size_t indent, bool insert_newlines) + { + TRY_ENTRY(); + std::stringstream ss; + epee::serialization::dump_as_json(ss, m_root, indent, insert_newlines); + buff = ss.str(); + return true; + CATCH_ENTRY("portable_storage::dump_as_json", false) + } + + bool portable_storage::load_from_json(const std::string& source) + { + TRY_ENTRY(); + return json::load_from_json(source, *this); + CATCH_ENTRY("portable_storage::load_from_json", false) + } + + bool portable_storage::load_from_binary(const epee::span<const uint8_t> source, const limits_t *limits) + { + m_root.m_entries.clear(); + if(source.size() < sizeof(storage_block_header)) + { + LOG_ERROR("portable_storage: wrong binary format, packet size = " << source.size() << " less than expected sizeof(storage_block_header)=" << sizeof(storage_block_header)); + return false; + } + storage_block_header* pbuff = (storage_block_header*)source.data(); + if(pbuff->m_signature_a != SWAP32LE(PORTABLE_STORAGE_SIGNATUREA) || + pbuff->m_signature_b != SWAP32LE(PORTABLE_STORAGE_SIGNATUREB) + ) + { + LOG_ERROR("portable_storage: wrong binary format - signature mismatch"); + return false; + } + if(pbuff->m_ver != PORTABLE_STORAGE_FORMAT_VER) + { + LOG_ERROR("portable_storage: wrong binary format - unknown format ver = " << pbuff->m_ver); + return false; + } + TRY_ENTRY(); + throwable_buffer_reader buf_reader(source.data()+sizeof(storage_block_header), source.size()-sizeof(storage_block_header)); + if (limits) + buf_reader.set_limits(limits->n_objects, limits->n_fields, limits->n_strings); + buf_reader.read(m_root); + return true;//TODO: + CATCH_ENTRY("portable_storage::load_from_binary", false); + } + + hsection portable_storage::open_section(const std::string& section_name, hsection hparent_section, bool create_if_notexist) + { + TRY_ENTRY(); + hparent_section = hparent_section ? hparent_section:&m_root; + storage_entry* pentry = find_storage_entry(section_name, hparent_section); + if(!pentry) + { + if(!create_if_notexist) + return nullptr; + return insert_new_section(section_name, hparent_section); + } + CHECK_AND_ASSERT(pentry , nullptr); + //check that section_entry we find is real "CSSection" + if(pentry->type() != typeid(section)) + { + if(create_if_notexist) + *pentry = storage_entry(section());//replace + else + return nullptr; + } + return &boost::get<section>(*pentry); + CATCH_ENTRY("portable_storage::open_section", nullptr); + } + + bool portable_storage::get_value(const std::string& value_name, storage_entry& val, hsection hparent_section) + { + //TRY_ENTRY(); + if(!hparent_section) hparent_section = &m_root; + storage_entry* pentry = find_storage_entry(value_name, hparent_section); + if(!pentry) + return false; + + val = *pentry; + return true; + //CATCH_ENTRY("portable_storage::template<>get_value", false); + } + + storage_entry* portable_storage::find_storage_entry(const std::string& pentry_name, hsection psection) + { + TRY_ENTRY(); + CHECK_AND_ASSERT(psection, nullptr); + auto it = psection->m_entries.find(pentry_name); + if(it == psection->m_entries.end()) + return nullptr; + + return &it->second; + CATCH_ENTRY("portable_storage::find_storage_entry", nullptr); + } + + hsection portable_storage::insert_new_section(const std::string& pentry_name, hsection psection) + { + TRY_ENTRY(); + storage_entry* pse = insert_new_entry_get_storage_entry(pentry_name, psection, section()); + if(!pse) return nullptr; + return &boost::get<section>(*pse); + CATCH_ENTRY("portable_storage::insert_new_section", nullptr); + } + + harray portable_storage::get_first_section(const std::string& sec_name, hsection& h_child_section, hsection hparent_section) + { + TRY_ENTRY(); + if(!hparent_section) hparent_section = &m_root; + storage_entry* pentry = find_storage_entry(sec_name, hparent_section); + if(!pentry) + return nullptr; + if(pentry->type() != typeid(array_entry)) + return nullptr; + array_entry& ar_entry = boost::get<array_entry>(*pentry); + if(ar_entry.type() != typeid(array_entry_t<section>)) + return nullptr; + array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(ar_entry); + section* psec = sec_array.get_first_val(); + if(!psec) + return nullptr; + h_child_section = psec; + return &ar_entry; + CATCH_ENTRY("portable_storage::get_first_section", nullptr); + } + + bool portable_storage::get_next_section(harray hsec_array, hsection& h_child_section) + { + TRY_ENTRY(); + CHECK_AND_ASSERT(hsec_array, false); + if(hsec_array->type() != typeid(array_entry_t<section>)) + return false; + array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(*hsec_array); + h_child_section = sec_array.get_next_val(); + if(!h_child_section) + return false; + return true; + CATCH_ENTRY("portable_storage::get_next_section", false); + } + + harray portable_storage::insert_first_section(const std::string& sec_name, hsection& hinserted_childsection, hsection hparent_section) + { + TRY_ENTRY(); + if(!hparent_section) hparent_section = &m_root; + storage_entry* pentry = find_storage_entry(sec_name, hparent_section); + if(!pentry) + { + pentry = insert_new_entry_get_storage_entry(sec_name, hparent_section, array_entry(array_entry_t<section>())); + if(!pentry) + return nullptr; + } + if(pentry->type() != typeid(array_entry)) + *pentry = storage_entry(array_entry(array_entry_t<section>())); + + array_entry& ar_entry = boost::get<array_entry>(*pentry); + if(ar_entry.type() != typeid(array_entry_t<section>)) + ar_entry = array_entry(array_entry_t<section>()); + + array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(ar_entry); + hinserted_childsection = &sec_array.insert_first_val(section()); + return &ar_entry; + CATCH_ENTRY("portable_storage::insert_first_section", nullptr); + } + + bool portable_storage::insert_next_section(harray hsec_array, hsection& hinserted_childsection) + { + TRY_ENTRY(); + CHECK_AND_ASSERT(hsec_array, false); + CHECK_AND_ASSERT_MES(hsec_array->type() == typeid(array_entry_t<section>), + false, "unexpected type(not 'section') in insert_next_section, type: " << hsec_array->type().name()); + + array_entry_t<section>& sec_array = boost::get<array_entry_t<section>>(*hsec_array); + hinserted_childsection = &sec_array.insert_next_value(section()); + return true; + CATCH_ENTRY("portable_storage::insert_next_section", false); + } } } diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp index bcf499963..1047d1696 100644 --- a/contrib/epee/src/readline_buffer.cpp +++ b/contrib/epee/src/readline_buffer.cpp @@ -6,6 +6,7 @@ #include <boost/thread/lock_guard.hpp> #include <boost/algorithm/string.hpp> +static bool same_as_last_line(const std::string&); static void install_line_handler(); static void remove_line_handler(); @@ -175,8 +176,11 @@ static void handle_line(char* line) boost::trim_right(test_line); if(!test_line.empty()) { - add_history(test_line.c_str()); - history_set_pos(history_length); + if (!same_as_last_line(test_line)) + { + add_history(test_line.c_str()); + history_set_pos(history_length); + } if (test_line == "exit" || test_line == "q") exit = true; } @@ -192,6 +196,16 @@ static void handle_line(char* line) return; } +// same_as_last_line returns true, if the last line in the history is +// equal to test_line. +static bool same_as_last_line(const std::string& test_line) +{ + // Note that state->offset == state->length, when a new line was entered. + HISTORY_STATE* state = history_get_history_state(); + return state->length > 0 + && test_line.compare(state->entries[state->length-1]->line) == 0; +} + static char* completion_matches(const char* text, int state) { static size_t list_index; diff --git a/contrib/fuzz_testing/fuzz.sh b/contrib/fuzz_testing/fuzz.sh index efd43c231..5c88c3727 100755 --- a/contrib/fuzz_testing/fuzz.sh +++ b/contrib/fuzz_testing/fuzz.sh @@ -14,8 +14,8 @@ then exit 1 fi case "$type" in - block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin|bulletproof) ;; - *) echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin|bulletproof"; exit 1 ;; + block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin|bulletproof|utf8) ;; + *) echo "usage: $0 block|transaction|signature|cold-outputs|cold-transaction|load-from-binary|load-from-json|base58|parse-url|http-client|levin|bulletproof|utf8"; exit 1 ;; esac if test -d "fuzz-out/$type" diff --git a/docs/ANONYMITY_NETWORKS.md b/docs/ANONYMITY_NETWORKS.md index 3337b5fc3..f8d08b05f 100644 --- a/docs/ANONYMITY_NETWORKS.md +++ b/docs/ANONYMITY_NETWORKS.md @@ -36,10 +36,6 @@ with additional exclusive IPv4 address(es). ## Usage -Anonymity networks have no seed nodes (the feature is still considered -experimental), so a user must specify an address. If configured properly, -additional peers can be found through typical p2p peerlist sharing. - ### Outbound Connections Connecting to an anonymous address requires the command line option @@ -54,8 +50,9 @@ separate process. On most systems the configuration will look like: which tells `monerod` that ".onion" p2p addresses can be forwarded to a socks proxy at IP 127.0.0.1 port 9050 with a max of 10 outgoing connections and ".b32.i2p" p2p addresses can be forwarded to a socks proxy at IP 127.0.0.1 port -9000 with the default max outgoing connections. Since there are no seed nodes -for anonymity connections, peers must be manually specified: +9000 with the default max outgoing connections. + +If desired, peers can be manually specified: ``` --add-exclusive-node rveahdfho7wo4b2m.onion:28083 diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a8916a7d0..7ae4ba750 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -38,8 +38,10 @@ find_package(Miniupnpc REQUIRED) message(STATUS "Using in-tree miniupnpc") +set(UPNPC_NO_INSTALL TRUE CACHE BOOL "Disable miniupnp installation" FORCE) add_subdirectory(miniupnp/miniupnpc) set_property(TARGET libminiupnpc-static PROPERTY FOLDER "external") +set_property(TARGET libminiupnpc-static PROPERTY POSITION_INDEPENDENT_CODE ON) if(MSVC) set_property(TARGET libminiupnpc-static APPEND_STRING PROPERTY COMPILE_FLAGS " -wd4244 -wd4267") elseif(NOT MSVC) diff --git a/external/db_drivers/liblmdb/mdb.c b/external/db_drivers/liblmdb/mdb.c index ba1315401..96c741f66 100644 --- a/external/db_drivers/liblmdb/mdb.c +++ b/external/db_drivers/liblmdb/mdb.c @@ -3461,9 +3461,9 @@ mdb_freelist_save(MDB_txn *txn) } else { x = mdb_mid2l_search(dl, mp->mp_pgno); mdb_tassert(txn, dl[x].mid == mp->mp_pgno); + mdb_dpage_free(env, mp); } dl[x].mptr = NULL; - mdb_dpage_free(env, mp); } { /* squash freed slots out of the dirty list */ @@ -7867,7 +7867,7 @@ put_sub: xdata.mv_size = 0; xdata.mv_data = ""; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - if (flags == MDB_CURRENT) { + if ((flags & (MDB_CURRENT|MDB_APPENDDUP)) == MDB_CURRENT) { xflags = MDB_CURRENT|MDB_NOSPILL; } else { mdb_xcursor_init1(mc, leaf); diff --git a/external/easylogging++/easylogging++.cc b/external/easylogging++/easylogging++.cc index bf877c018..caaf7944c 100644 --- a/external/easylogging++/easylogging++.cc +++ b/external/easylogging++/easylogging++.cc @@ -17,6 +17,7 @@ #define EASYLOGGING_CC #include "easylogging++.h" +#include <atomic> #include <unistd.h> #if defined(AUTO_INITIALIZE_EASYLOGGINGPP) @@ -2035,7 +2036,7 @@ void RegisteredLoggers::unsafeFlushAll(void) { // VRegistry -VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags), m_lowest_priority(INT_MAX) { +VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags) { } /// @brief Sets verbose level. Accepted range is 0-9 @@ -2131,18 +2132,30 @@ static int priority(Level level) { return 7; } +namespace +{ + std::atomic<int> s_lowest_priority{INT_MAX}; +} + +void VRegistry::clearCategories(void) { + const base::threading::ScopedLock scopedLock(lock()); + m_categories.clear(); + m_cached_allowed_categories.clear(); + s_lowest_priority = INT_MAX; +} + void VRegistry::setCategories(const char* categories, bool clear) { base::threading::ScopedLock scopedLock(lock()); auto insert = [&](std::stringstream& ss, Level level) { m_categories.push_back(std::make_pair(ss.str(), level)); m_cached_allowed_categories.clear(); int pri = priority(level); - if (pri > m_lowest_priority) - m_lowest_priority = pri; + if (pri > s_lowest_priority) + s_lowest_priority = pri; }; if (clear) { - m_lowest_priority = 0; + s_lowest_priority = 0; m_categories.clear(); m_cached_allowed_categories.clear(); m_categoriesString.clear(); @@ -2200,9 +2213,9 @@ std::string VRegistry::getCategories() { } bool VRegistry::allowed(Level level, const std::string &category) { - const int pri = priority(level); - if (pri > m_lowest_priority) - return false; + return priority_allowed(priority(level), category); +} +bool VRegistry::priority_allowed(const int pri, const std::string &category) { base::threading::ScopedLock scopedLock(lock()); const std::map<std::string, int>::const_iterator it = m_cached_allowed_categories.find(category); if (it != m_cached_allowed_categories.end()) @@ -3335,6 +3348,14 @@ void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, c // Loggers +bool Loggers::allowed(Level level, const char* cat) +{ + const int pri = base::priority(level); + if (pri > base::s_lowest_priority) + return false; + return ELPP->vRegistry()->priority_allowed(pri, std::string{cat}); +} + Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) { return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable); } diff --git a/external/easylogging++/easylogging++.h b/external/easylogging++/easylogging++.h index 0b65461bc..c4a88339f 100644 --- a/external/easylogging++/easylogging++.h +++ b/external/easylogging++/easylogging++.h @@ -412,7 +412,6 @@ ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStre #include <sstream> #include <memory> #include <type_traits> -#include <atomic> #if ELPP_THREADING_ENABLED # if ELPP_USE_STD_THREADING # include <mutex> @@ -2464,12 +2463,7 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe { return m_level; } - inline void clearCategories(void) { - base::threading::ScopedLock scopedLock(lock()); - m_categories.clear(); - m_cached_allowed_categories.clear(); - m_lowest_priority = INT_MAX; - } + void clearCategories(void); inline void clearModules(void) { base::threading::ScopedLock scopedLock(lock()); @@ -2482,6 +2476,7 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe { void setModules(const char* modules); + bool priority_allowed(int priority, const std::string &category); bool allowed(Level level, const std::string &category); bool allowed(base::type::VerboseLevel vlevel, const char* file); @@ -2513,7 +2508,6 @@ class VRegistry : base::NoCopy, public base::threading::ThreadSafe { std::map<std::string, int> m_cached_allowed_categories; std::string m_categoriesString; std::string m_filenameCommonPrefix; - std::atomic<int> m_lowest_priority; }; } // namespace base class LogMessage { @@ -3868,6 +3862,8 @@ class Helpers : base::StaticClass { /// @brief Static helpers to deal with loggers and their configurations class Loggers : base::StaticClass { public: + /// @brief Determines whether logging will occur at this level and category + static bool allowed(Level leve, const char* cat); /// @brief Gets existing or registers new logger static Logger* getLogger(const std::string& identity, bool registerIfNotAvailable = true); /// @brief Changes default log builder for future loggers diff --git a/external/miniupnp b/external/miniupnp -Subproject 4c700e09526a7d546394e85628c57e9490feefa +Subproject 544e6fcc73c5ad9af48a8985c94f0f1d742ef2e diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index a9a7d035f..5e12fa8ec 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -287,7 +287,7 @@ uint64_t BlockchainDB::add_block( const std::pair<block, blobdata>& blck num_rct_outs += blk.miner_tx.vout.size(); int tx_i = 0; crypto::hash tx_hash = crypto::null_hash; - for (const std::pair<transaction, blobdata_ref>& tx : txs) + for (const std::pair<transaction, blobdata>& tx : txs) { tx_hash = blk.tx_hashes[tx_i]; add_transaction(blk_hash, tx, &tx_hash); diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 0aa2aee6f..5f3b495b0 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -897,7 +897,6 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons throw0(DB_ERROR(lmdb_error("Failed to add tx data to db transaction: ", result).c_str())); const cryptonote::blobdata_ref &blob = txp.second; - MDB_val_sized(blobval, blob); unsigned int unprunable_size = tx.unprunable_size; if (unprunable_size == 0) @@ -3190,9 +3189,8 @@ bool BlockchainLMDB::get_blocks_from(uint64_t start_height, size_t min_block_cou uint64_t size = 0; size_t num_txes = 0; MDB_val_copy<uint64_t> key(start_height); - MDB_val k, v, val_tx_id; + MDB_val v, val_tx_id; uint64_t tx_id = ~0; - MDB_cursor_op op = MDB_SET; for (uint64_t h = start_height; h < blockchain_height && blocks.size() < max_block_count && (size < max_size || blocks.size() < min_block_count); ++h) { MDB_cursor_op op = h == start_height ? MDB_SET : MDB_NEXT; @@ -3314,7 +3312,7 @@ bool BlockchainLMDB::get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::h RCURSOR(txs_prunable_hash); MDB_val_set(v, tx_hash); - MDB_val result, val_tx_prunable_hash; + MDB_val result; auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); if (get_result == 0) { @@ -4311,7 +4309,6 @@ bool BlockchainLMDB::get_output_distribution(uint64_t amount, uint64_t from_heig return false; distribution.resize(db_height - from_height, 0); - bool fret = true; MDB_val_set(k, amount); MDB_val v; MDB_cursor_op op = MDB_SET; @@ -5106,11 +5103,10 @@ void BlockchainLMDB::migrate_0_1() void BlockchainLMDB::migrate_1_2() { LOG_PRINT_L3("BlockchainLMDB::" << __func__); - uint64_t i, z; + uint64_t i; int result; mdb_txn_safe txn(false); - MDB_val k, v; - char *ptr; + MDB_val v; MGINFO_YELLOW("Migrating blockchain from DB version 1 to 2 - this may take a while:"); MINFO("updating txs_pruned and txs_prunable tables..."); @@ -5311,7 +5307,6 @@ void BlockchainLMDB::migrate_2_3() if (result) throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_info: ", result).c_str())); if (!i) { - MDB_stat db_stat; result = mdb_stat(txn, m_block_info, &db_stats); if (result) throw0(DB_ERROR(lmdb_error("Failed to query m_block_info: ", result).c_str())); @@ -5443,7 +5438,6 @@ void BlockchainLMDB::migrate_3_4() if (result) throw0(DB_ERROR(lmdb_error("Failed to open a cursor for blocks: ", result).c_str())); if (!i) { - MDB_stat db_stat; result = mdb_stat(txn, m_block_info, &db_stats); if (result) throw0(DB_ERROR(lmdb_error("Failed to query m_block_info: ", result).c_str())); @@ -5597,7 +5591,6 @@ void BlockchainLMDB::migrate_4_5() if (result) throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_info: ", result).c_str())); if (!i) { - MDB_stat db_stat; result = mdb_stat(txn, m_block_info, &db_stats); if (result) throw0(DB_ERROR(lmdb_error("Failed to query m_block_info: ", result).c_str())); diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index 89b932e4f..99a84606d 100644 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -32,6 +32,7 @@ #include <boost/algorithm/string.hpp> #include <boost/archive/portable_binary_iarchive.hpp> #include <boost/archive/portable_binary_oarchive.hpp> +#include <boost/filesystem/path.hpp> #include "common/unordered_containers_boost_serialization.h" #include "common/command_line.h" #include "common/varint.h" @@ -149,7 +150,7 @@ struct ancestry_state_t { std::unordered_map<crypto::hash, cryptonote::transaction> old_tx_cache; a & old_tx_cache; - for (const auto i: old_tx_cache) + for (const auto& i: old_tx_cache) tx_cache.insert(std::make_pair(i.first, ::tx_data_t(i.second))); } else @@ -161,7 +162,7 @@ struct ancestry_state_t std::unordered_map<uint64_t, cryptonote::block> old_block_cache; a & old_block_cache; block_cache.resize(old_block_cache.size()); - for (const auto i: old_block_cache) + for (const auto& i: old_block_cache) block_cache[i.first] = i.second; } else @@ -575,7 +576,6 @@ int main(int argc, char* argv[]) { add_ancestry(state.ancestry, txid, ancestor{amount, offset}); // find the tx which created this output - bool found = false; crypto::hash output_txid; if (!get_output_txid(state, db, amount, offset, output_txid)) { @@ -693,7 +693,6 @@ int main(int argc, char* argv[]) add_ancestor(ancestry, amount, offset); // find the tx which created this output - bool found = false; crypto::hash output_txid; if (!get_output_txid(state, db, amount, offset, output_txid)) { diff --git a/src/blockchain_utilities/blockchain_blackball.cpp b/src/blockchain_utilities/blockchain_blackball.cpp index a8197483f..18a37434c 100644 --- a/src/blockchain_utilities/blockchain_blackball.cpp +++ b/src/blockchain_utilities/blockchain_blackball.cpp @@ -28,6 +28,7 @@ #include <boost/range/adaptor/transformed.hpp> #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include "common/unordered_containers_boost_serialization.h" #include "common/command_line.h" #include "common/varint.h" @@ -47,9 +48,6 @@ namespace po = boost::program_options; using namespace epee; using namespace cryptonote; -static const char zerokey[8] = {0}; -static const MDB_val zerokval = { sizeof(zerokey), (void *)zerokey }; - static uint64_t records_per_sync = 200; static uint64_t db_flags = 0; static MDB_dbi dbi_relative_rings; @@ -703,7 +701,6 @@ static void get_per_amount_outputs(MDB_txn *txn, uint64_t amount, uint64_t &tota int dbr = mdb_cursor_open(txn, dbi_per_amount, &cur); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for per amount outputs: " + std::string(mdb_strerror(dbr))); MDB_val k, v; - mdb_size_t count = 0; k.mv_size = sizeof(uint64_t); k.mv_data = (void*)&amount; dbr = mdb_cursor_get(cur, &k, &v, MDB_SET); @@ -726,7 +723,6 @@ static void inc_per_amount_outputs(MDB_txn *txn, uint64_t amount, uint64_t total int dbr = mdb_cursor_open(txn, dbi_per_amount, &cur); CHECK_AND_ASSERT_THROW_MES(!dbr, "Failed to open cursor for per amount outputs: " + std::string(mdb_strerror(dbr))); MDB_val k, v; - mdb_size_t count = 0; k.mv_size = sizeof(uint64_t); k.mv_data = (void*)&amount; dbr = mdb_cursor_get(cur, &k, &v, MDB_SET); @@ -1077,7 +1073,6 @@ static std::vector<std::pair<uint64_t, uint64_t>> load_outputs(const std::string s[len - 1] = 0; if (!s[0]) continue; - std::pair<uint64_t, uint64_t> output; uint64_t offset, num_offsets; if (sscanf(s, "@%" PRIu64, &amount) == 1) { @@ -1269,8 +1264,6 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Scanning for spent outputs..."); - size_t done = 0; - const uint64_t start_blackballed_outputs = get_num_spent_outputs(); tools::ringdb ringdb(output_file_path.string(), epee::string_tools::pod_to_hex(get_genesis_block_hash(inputs[0]))); diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index 6199586bf..8c3c3a009 100644 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/range/adaptor/transformed.hpp> +#include <boost/filesystem/path.hpp> #include <boost/algorithm/string.hpp> #include "common/command_line.h" #include "common/varint.h" diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp index 60c069c3b..df2662444 100644 --- a/src/blockchain_utilities/blockchain_import.cpp +++ b/src/blockchain_utilities/blockchain_import.cpp @@ -227,7 +227,7 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path return false; } - uint64_t block_first, block_last; + uint64_t block_first; uint64_t start_height = 1, seek_height; if (opt_resume) start_height = core.get_blockchain_storage().get_current_blockchain_height(); diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 9a9d58c46..b1c599f3a 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -29,6 +29,8 @@ #include <array> #include <lmdb.h> #include <boost/algorithm/string.hpp> +#include <boost/system/error_code.hpp> +#include <boost/filesystem.hpp> #include "common/command_line.h" #include "common/pruning.h" #include "cryptonote_core/cryptonote_core.h" diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index f8763710e..78a662134 100644 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include "common/command_line.h" #include "serialization/crypto.h" #include "cryptonote_core/tx_pool.h" @@ -66,7 +67,6 @@ static std::map<uint64_t, uint64_t> load_outputs(const std::string &filename) s[len - 1] = 0; if (!s[0]) continue; - std::pair<uint64_t, uint64_t> output; uint64_t offset, num_offsets; if (sscanf(s, "@%" PRIu64, &amount) == 1) { diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 1f728b4e5..5f5ca6abf 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index 6e87a0974..8356ef420 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -28,6 +28,7 @@ #include <boost/range/adaptor/transformed.hpp> #include <boost/algorithm/string.hpp> +#include <boost/filesystem/path.hpp> #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_core/tx_pool.h" @@ -180,7 +181,6 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Building usage patterns..."); - size_t done = 0; std::unordered_map<output_data, std::list<reference>> outputs; std::unordered_map<uint64_t,uint64_t> indices; @@ -195,7 +195,7 @@ int main(int argc, char* argv[]) { if (opt_rct_only && out.amount) continue; - uint64_t index = indices[out.amount]++; + indices[out.amount]++; output_data od(out.amount, indices[out.amount], coinbase, height); auto itb = outputs.emplace(od, std::list<reference>()); itb.first->first.info(coinbase, height); diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index c88a630cc..6b48d8723 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -34,6 +34,8 @@ #include "string_tools.h" #include "storages/portable_storage_template_helper.h" // epee json include #include "serialization/keyvalue_serialization.h" +#include <boost/system/error_code.hpp> +#include <boost/filesystem.hpp> #include <functional> #include <vector> diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 4f4efcd81..f0b617798 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -37,6 +37,7 @@ #include <boost/thread/mutex.hpp> #include <boost/algorithm/string/join.hpp> #include <boost/optional.hpp> +#include <boost/utility/string_ref.hpp> using namespace epee; #undef MONERO_DEFAULT_LOG_CATEGORY @@ -124,6 +125,7 @@ static const char *get_record_name(int record_type) case DNS_TYPE_A: return "A"; case DNS_TYPE_TXT: return "TXT"; case DNS_TYPE_AAAA: return "AAAA"; + case DNS_TYPE_TLSA: return "TLSA"; default: return "unknown"; } } @@ -186,6 +188,13 @@ boost::optional<std::string> txt_to_string(const char* src, size_t len) return std::string(src+1, len-1); } +boost::optional<std::string> tlsa_to_string(const char* src, size_t len) +{ + if (len < 4) + return boost::none; + return std::string(src, len); +} + // custom smart pointer. // TODO: see if std::auto_ptr and the like support custom destructors template<typename type, void (*freefunc)(type*)> @@ -326,11 +335,15 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec // destructor takes care of cleanup ub_result_ptr result; + MDEBUG("Performing DNSSEC " << get_record_name(record_type) << " record query for " << url); + // call DNS resolver, blocking. if return value not zero, something went wrong if (!ub_resolve(m_data->m_ub_context, string_copy(url.c_str()), record_type, DNS_CLASS_IN, &result)) { dnssec_available = (result->secure || result->bogus); dnssec_valid = result->secure && !result->bogus; + if (dnssec_available && !dnssec_valid) + MWARNING("Invalid DNSSEC " << get_record_name(record_type) << " record signature for " << url << ": " << result->why_bogus); if (result->havedata) { for (size_t i=0; result->data[i] != NULL; i++) @@ -338,8 +351,9 @@ std::vector<std::string> DNSResolver::get_record(const std::string& url, int rec boost::optional<std::string> res = (*reader)(result->data[i], result->len[i]); if (res) { - MINFO("Found \"" << *res << "\" in " << get_record_name(record_type) << " record for " << url); - addresses.push_back(*res); + // do not dump dns record directly from dns into log + MINFO("Found " << get_record_name(record_type) << " record for " << url); + addresses.push_back(std::move(*res)); } } } @@ -363,6 +377,17 @@ std::vector<std::string> DNSResolver::get_txt_record(const std::string& url, boo return get_record(url, DNS_TYPE_TXT, txt_to_string, dnssec_available, dnssec_valid); } +std::vector<std::string> DNSResolver::get_tlsa_tcp_record(const boost::string_ref url, const boost::string_ref port, bool& dnssec_available, bool& dnssec_valid) +{ + std::string service_addr; + service_addr.reserve(url.size() + port.size() + 7); + service_addr.push_back('_'); + service_addr.append(port.data(), port.size()); + service_addr.append("._tcp."); + service_addr.append(url.data(), url.size()); + return get_record(service_addr, DNS_TYPE_TLSA, tlsa_to_string, dnssec_available, dnssec_valid); +} + std::string DNSResolver::get_dns_format_from_oa_address(const std::string& oa_addr) { std::string addr(oa_addr); @@ -484,36 +509,12 @@ std::string get_account_address_as_str_from_url(const std::string& url, bool& dn return dns_confirm(url, addresses, dnssec_valid); } -namespace -{ - bool dns_records_match(const std::vector<std::string>& a, const std::vector<std::string>& b) - { - if (a.size() != b.size()) return false; - - for (const auto& record_in_a : a) - { - bool ok = false; - for (const auto& record_in_b : b) - { - if (record_in_a == record_in_b) - { - ok = true; - break; - } - } - if (!ok) return false; - } - - return true; - } -} - bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std::vector<std::string> &dns_urls) { // Prevent infinite recursion when distributing if (dns_urls.empty()) return false; - std::vector<std::vector<std::string> > records; + std::vector<std::set<std::string> > records; records.resize(dns_urls.size()); size_t first_index = crypto::rand_idx(dns_urls.size()); @@ -525,7 +526,9 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std for (size_t n = 0; n < dns_urls.size(); ++n) { tpool.submit(&waiter,[n, dns_urls, &records, &avail, &valid](){ - records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]); + const auto res = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]); + for (const auto &s: res) + records[n].insert(s); }); } waiter.wait(); @@ -568,29 +571,31 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std return false; } - int good_records_index = -1; - for (size_t i = 0; i < records.size() - 1; ++i) + typedef std::map<std::set<std::string>, uint32_t> map_t; + map_t record_count; + for (const auto &e: records) { - if (records[i].size() == 0) continue; + if (!e.empty()) + ++record_count[e]; + } - for (size_t j = i + 1; j < records.size(); ++j) - { - if (dns_records_match(records[i], records[j])) - { - good_records_index = i; - break; - } - } - if (good_records_index >= 0) break; + map_t::const_iterator good_record = record_count.end(); + for (map_t::const_iterator i = record_count.begin(); i != record_count.end(); ++i) + { + if (good_record == record_count.end() || i->second > good_record->second) + good_record = i; } - if (good_records_index < 0) + MDEBUG("Found " << (good_record == record_count.end() ? 0 : good_record->second) << "/" << dns_urls.size() << " matching records from " << num_valid_records << " valid records"); + if (good_record == record_count.end() || good_record->second < dns_urls.size() / 2 + 1) { - LOG_PRINT_L0("WARNING: no two DNS TXT records matched"); + LOG_PRINT_L0("WARNING: no majority of DNS TXT records matched (only " << good_record->second << "/" << dns_urls.size() << ")"); return false; } - good_records = records[good_records_index]; + good_records = {}; + for (const auto &s: good_record->first) + good_records.push_back(s); return true; } diff --git a/src/common/dns_utils.h b/src/common/dns_utils.h index 30c4cced2..99e91bc54 100644 --- a/src/common/dns_utils.h +++ b/src/common/dns_utils.h @@ -31,15 +31,17 @@ #include <string> #include <functional> #include <boost/optional/optional_fwd.hpp> +#include <boost/utility/string_ref_fwd.hpp> namespace tools { // RFC defines for record types and classes for DNS, gleaned from ldns source -const static int DNS_CLASS_IN = 1; -const static int DNS_TYPE_A = 1; -const static int DNS_TYPE_TXT = 16; -const static int DNS_TYPE_AAAA = 8; +constexpr const int DNS_CLASS_IN = 1; +constexpr const int DNS_TYPE_A = 1; +constexpr const int DNS_TYPE_TXT = 16; +constexpr const int DNS_TYPE_AAAA = 8; +constexpr const int DNS_TYPE_TLSA = 52; struct DNSResolverData; @@ -106,6 +108,17 @@ public: std::vector<std::string> get_txt_record(const std::string& url, bool& dnssec_available, bool& dnssec_valid); /** + * @brief gets all TLSA TCP records from a DNS query for the supplied URL; + * if no TLSA record present returns an empty vector. + * + * @param url A string containing a URL to query for + * @param port The service port number (as string) to query + * + * @return A vector of strings containing all TLSA records; or an empty vector + */ + std::vector<std::string> get_tlsa_tcp_record(boost::string_ref url, boost::string_ref port, bool& dnssec_available, bool& dnssec_valid); + + /** * @brief Gets a DNS address from OpenAlias format * * If the address looks good, but contains one @ symbol, replace that with a . diff --git a/src/common/i18n.cpp b/src/common/i18n.cpp index ebc367b3a..051220ee1 100644 --- a/src/common/i18n.cpp +++ b/src/common/i18n.cpp @@ -35,6 +35,10 @@ #include "common/i18n.h" #include "translation_files.h" +#include <boost/system/error_code.hpp> +#include <boost/filesystem.hpp> +#include <algorithm> + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "i18n" diff --git a/src/common/updates.cpp b/src/common/updates.cpp index 61a76f5da..af38d7a54 100644 --- a/src/common/updates.cpp +++ b/src/common/updates.cpp @@ -48,7 +48,10 @@ namespace tools static const std::vector<std::string> dns_urls = { "updates.moneropulse.org", "updates.moneropulse.net", - "updates.moneropulse.co", + "updates.moneropulse.fr", + "updates.moneropulse.de", + "updates.moneropulse.no", + "updates.moneropulse.ch", "updates.moneropulse.se" }; diff --git a/src/crypto/hash-ops.h b/src/crypto/hash-ops.h index 7dfc5151d..1cd502994 100644 --- a/src/crypto/hash-ops.h +++ b/src/crypto/hash-ops.h @@ -87,6 +87,10 @@ void hash_extra_jh(const void *data, size_t length, char *hash); void hash_extra_skein(const void *data, size_t length, char *hash); void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash); +bool tree_path(size_t count, size_t idx, uint32_t *path); +bool tree_branch(const char (*hashes)[HASH_SIZE], size_t count, const char *hash, char (*branch)[HASH_SIZE], size_t *depth, uint32_t *path); +bool tree_branch_hash(const char hash[HASH_SIZE], const char (*branch)[HASH_SIZE], size_t depth, uint32_t path, char root[HASH_SIZE]); +bool is_branch_in_tree(const char hash[HASH_SIZE], const char root[HASH_SIZE], const char (*branch)[HASH_SIZE], size_t depth, uint32_t path); #define RX_BLOCK_VERSION 12 void rx_slow_hash_allocate_state(void); diff --git a/src/crypto/rx-slow-hash.c b/src/crypto/rx-slow-hash.c index fa35a32e2..801987e37 100644 --- a/src/crypto/rx-slow-hash.c +++ b/src/crypto/rx-slow-hash.c @@ -264,12 +264,14 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch cache = rx_sp->rs_cache; if (cache == NULL) { - if (cache == NULL) { + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { cache = randomx_alloc_cache(flags | RANDOMX_FLAG_LARGE_PAGES); if (cache == NULL) { mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX cache"); - cache = randomx_alloc_cache(flags); } + } + if (cache == NULL) { + cache = randomx_alloc_cache(flags); if (cache == NULL) local_abort("Couldn't allocate RandomX cache"); } @@ -291,11 +293,14 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch CTHR_MUTEX_LOCK(rx_dataset_mutex); if (!rx_dataset_nomem) { if (rx_dataset == NULL) { - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); - if (rx_dataset == NULL) { - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); - rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_LARGE_PAGES); + if (rx_dataset == NULL) { + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX dataset"); + } } + if (rx_dataset == NULL) + rx_dataset = randomx_alloc_dataset(RANDOMX_FLAG_DEFAULT); if (rx_dataset != NULL) rx_initdata(rx_sp->rs_cache, miners, seedheight); } @@ -311,11 +316,14 @@ void rx_slow_hash(const uint64_t mainheight, const uint64_t seedheight, const ch } CTHR_MUTEX_UNLOCK(rx_dataset_mutex); } - rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); - if(rx_vm == NULL) { //large pages failed - mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); - rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); + if (!(disabled_flags() & RANDOMX_FLAG_LARGE_PAGES)) { + rx_vm = randomx_create_vm(flags | RANDOMX_FLAG_LARGE_PAGES, rx_sp->rs_cache, rx_dataset); + if(rx_vm == NULL) { //large pages failed + mdebug(RX_LOGCAT, "Couldn't use largePages for RandomX VM"); + } } + if (rx_vm == NULL) + rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); if(rx_vm == NULL) {//fallback if everything fails flags = RANDOMX_FLAG_DEFAULT | (miners ? RANDOMX_FLAG_FULL_MEM : 0); rx_vm = randomx_create_vm(flags, rx_sp->rs_cache, rx_dataset); diff --git a/src/crypto/tree-hash.c b/src/crypto/tree-hash.c index 643e95121..8f3ea3339 100644 --- a/src/crypto/tree-hash.c +++ b/src/crypto/tree-hash.c @@ -104,3 +104,154 @@ void tree_hash(const char (*hashes)[HASH_SIZE], size_t count, char *root_hash) { free(ints); } } + +bool tree_path(size_t count, size_t idx, uint32_t *path) +{ + if (count == 0) + return false; + + if (count == 1) { + *path = 0; + } else if (count == 2) { + *path = idx == 0 ? 0 : 1; + } else { + size_t i, j; + + *path = 0; + size_t cnt = tree_hash_cnt( count ); + + for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) { + if (idx == i || idx == i+1) + { + *path = (*path << 1) | (idx == i ? 0 : 1); + idx = j; + } + } + assert(i == count); + + while (cnt > 2) { + cnt >>= 1; + for (i = 0, j = 0; j < cnt; i += 2, ++j) { + if (idx == i || idx == i + 1) + { + *path = (*path << 1) | (idx == i ? 0 : 1); + idx = j; + } + } + } + + if (idx == 0 || idx == 1) + { + *path = (*path << 1) | (idx == 0 ? 0 : 1); + idx = 0; + } + } + return true; +} + +bool tree_branch(const char (*hashes)[HASH_SIZE], size_t count, const char *hash, char (*branch)[HASH_SIZE], size_t *depth, uint32_t *path) +{ + size_t idx; + + if (count == 0) + return false; + + for (idx = 0; idx < count; ++idx) + if (!memcmp(hash, hashes[idx], HASH_SIZE)) + break; + if (idx == count) + return false; + + assert(count > 0); + if (count == 1) { + *depth = 0; + *path = 0; + } else if (count == 2) { + *depth = 1; + *path = idx == 0 ? 0 : 1; + memcpy(branch[0], hashes[idx ^ 1], HASH_SIZE); + } else { + size_t i, j; + + *depth = 0; + *path = 0; + size_t cnt = tree_hash_cnt( count ); + + char *ints = calloc(cnt, HASH_SIZE); // zero out as extra protection for using uninitialized mem + assert(ints); + + memcpy(ints, hashes, (2 * cnt - count) * HASH_SIZE); + + for (i = 2 * cnt - count, j = 2 * cnt - count; j < cnt; i += 2, ++j) { + if (idx == i || idx == i+1) + { + memcpy(branch[*depth], hashes[idx == i ? i + 1 : i], HASH_SIZE); + ++*depth; + *path = (*path << 1) | (idx == i ? 0 : 1); + idx = j; + } + cn_fast_hash(hashes[i], 64, ints + j * HASH_SIZE); + } + assert(i == count); + + while (cnt > 2) { + cnt >>= 1; + for (i = 0, j = 0; j < cnt; i += 2, ++j) { + if (idx == i || idx == i + 1) + { + memcpy(branch[*depth], ints + (idx == i ? i + 1 : i) * HASH_SIZE, HASH_SIZE); + ++*depth; + *path = (*path << 1) | (idx == i ? 0 : 1); + idx = j; + } + cn_fast_hash(ints + i * HASH_SIZE, 64, ints + j * HASH_SIZE); + } + } + + if (idx == 0 || idx == 1) + { + memcpy(branch[*depth], ints + (idx == 0 ? 1 : 0) * HASH_SIZE, HASH_SIZE); + ++*depth; + *path = (*path << 1) | (idx == 0 ? 0 : 1); + idx = 0; + } + + free(ints); + } + return true; +} + +bool tree_branch_hash(const char hash[HASH_SIZE], const char (*branch)[HASH_SIZE], size_t depth, uint32_t path, char root[HASH_SIZE]) +{ + size_t d; + char partial[HASH_SIZE]; + + memcpy(partial, hash, HASH_SIZE); + + for (d = 0; d < depth; ++d) + { + char buffer[2 * HASH_SIZE]; + if ((path >> (depth - d - 1)) & 1) + { + memcpy(buffer, branch[d], HASH_SIZE); + memcpy(buffer + HASH_SIZE, partial, HASH_SIZE); + } + else + { + memcpy(buffer, partial, HASH_SIZE); + memcpy(buffer + HASH_SIZE, branch[d], HASH_SIZE); + } + cn_fast_hash(buffer, 2 * HASH_SIZE, partial); + } + + memcpy(root, partial, HASH_SIZE); + return true; +} + +bool is_branch_in_tree(const char hash[HASH_SIZE], const char root[HASH_SIZE], const char (*branch)[HASH_SIZE], size_t depth, uint32_t path) +{ + char res[HASH_SIZE]; + if (!tree_branch_hash(hash, branch, depth, path, res)) + return false; + return memcmp(res, root, HASH_SIZE) == 0; +} diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt index 113fd9d86..c9fb1433c 100644 --- a/src/cryptonote_basic/CMakeLists.txt +++ b/src/cryptonote_basic/CMakeLists.txt @@ -38,10 +38,12 @@ endif() set(cryptonote_basic_sources account.cpp + connection_context.cpp cryptonote_basic_impl.cpp cryptonote_format_utils.cpp difficulty.cpp hardfork.cpp + merge_mining.cpp miner.cpp) set(cryptonote_basic_headers) @@ -56,6 +58,7 @@ set(cryptonote_basic_private_headers cryptonote_format_utils.h difficulty.h hardfork.h + merge_mining.h miner.h tx_extra.h verification_context.h) diff --git a/src/cryptonote_basic/connection_context.cpp b/src/cryptonote_basic/connection_context.cpp new file mode 100644 index 000000000..a0b8ca1f1 --- /dev/null +++ b/src/cryptonote_basic/connection_context.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2020, 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 "connection_context.h" + +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "p2p/p2p_protocol_defs.h" + +namespace cryptonote +{ + std::size_t cryptonote_connection_context::get_max_bytes(const int command) noexcept + { + switch (command) + { + case nodetool::COMMAND_HANDSHAKE_T<cryptonote::CORE_SYNC_DATA>::ID: + return 65536; + case nodetool::COMMAND_TIMED_SYNC_T<cryptonote::CORE_SYNC_DATA>::ID: + return 65536; + case nodetool::COMMAND_PING::ID: + return 4096; + case nodetool::COMMAND_REQUEST_SUPPORT_FLAGS::ID: + return 4096; + case cryptonote::NOTIFY_NEW_BLOCK::ID: + return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though) + case cryptonote::NOTIFY_NEW_TRANSACTIONS::ID: + return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though) + case cryptonote::NOTIFY_REQUEST_GET_OBJECTS::ID: + return 1024 * 1024 * 2; // 2 MB + case cryptonote::NOTIFY_RESPONSE_GET_OBJECTS::ID: + return 1024 * 1024 * 128; // 128 MB (max packet is a bit less than 100 MB though) + case cryptonote::NOTIFY_REQUEST_CHAIN::ID: + return 512 * 1024; // 512 kB + case cryptonote::NOTIFY_RESPONSE_CHAIN_ENTRY::ID: + return 1024 * 1024 * 4; // 4 MB + case cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::ID: + return 1024 * 1024 * 4; // 4 MB, but it does not includes transaction data + case cryptonote::NOTIFY_REQUEST_FLUFFY_MISSING_TX::ID: + return 1024 * 1024; // 1 MB + case cryptonote::NOTIFY_GET_TXPOOL_COMPLEMENT::ID: + return 1024 * 1024 * 4; // 4 MB + default: + break; + }; + return std::numeric_limits<size_t>::max(); + } +} // cryptonote diff --git a/src/cryptonote_basic/connection_context.h b/src/cryptonote_basic/connection_context.h index 9e012f8f5..ee26a0c07 100644 --- a/src/cryptonote_basic/connection_context.h +++ b/src/cryptonote_basic/connection_context.h @@ -31,6 +31,7 @@ #pragma once #include <unordered_set> #include <atomic> +#include <algorithm> #include <boost/date_time/posix_time/posix_time.hpp> #include "net/net_utils_base.h" #include "copyable_atomic.h" @@ -38,7 +39,6 @@ namespace cryptonote { - struct cryptonote_connection_context: public epee::net_utils::connection_context_base { cryptonote_connection_context(): m_state(state_before_handshake), m_remote_blockchain_height(0), m_last_response_height(0), @@ -55,6 +55,12 @@ namespace cryptonote state_normal }; + static constexpr int handshake_command() noexcept { return 1001; } + bool handshake_complete() const noexcept { return m_state != state_before_handshake; } + + //! \return Maximum number of bytes permissible for `command`. + static size_t get_max_bytes(int command) noexcept; + state m_state; std::vector<std::pair<crypto::hash, uint64_t>> m_needed_objects; std::unordered_set<crypto::hash> m_requested_objects; @@ -71,6 +77,8 @@ namespace cryptonote int m_expect_response; uint64_t m_expect_height; size_t m_num_requested; + epee::copyable_atomic m_new_stripe_notification{0}; + epee::copyable_atomic m_idle_peer_notification{0}; }; inline std::string get_protocol_state_string(cryptonote_connection_context::state s) diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index fcc96883b..3e4532d4e 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -104,7 +104,6 @@ namespace cryptonote uint64_t get_transaction_weight_clawback(const transaction &tx, size_t n_padded_outputs) { - const rct::rctSig &rv = tx.rct_signatures; const uint64_t bp_base = 368; const size_t n_outputs = tx.vout.size(); if (n_padded_outputs <= 2) @@ -728,6 +727,25 @@ namespace cryptonote return true; } //--------------------------------------------------------------- + bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, size_t mm_merkle_tree_depth) + { + CHECK_AND_ASSERT_MES(mm_merkle_tree_depth < 32, false, "merge mining merkle tree depth should be less than 32"); + size_t start_pos = tx_extra.size(); + tx_extra.resize(tx_extra.size() + 3 + 32); + //write tag + tx_extra[start_pos] = TX_EXTRA_MERGE_MINING_TAG; + //write data size + ++start_pos; + tx_extra[start_pos] = 33; + //write depth varint (always one byte here) + ++start_pos; + tx_extra[start_pos] = mm_merkle_tree_depth; + //write data + ++start_pos; + memcpy(&tx_extra[start_pos], &mm_merkle_root, 32); + return true; + } + //--------------------------------------------------------------- bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type) { if (tx_extra.empty()) diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 636a88b9a..b311bd2b2 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -83,6 +83,7 @@ namespace cryptonote std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx); bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys); bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce); + bool add_mm_merkle_root_to_tx_extra(std::vector<uint8_t>& tx_extra, const crypto::hash& mm_merkle_root, size_t mm_merkle_tree_depth); bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type); void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id); void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id); diff --git a/src/cryptonote_basic/merge_mining.cpp b/src/cryptonote_basic/merge_mining.cpp new file mode 100644 index 000000000..fcc74859f --- /dev/null +++ b/src/cryptonote_basic/merge_mining.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2020, 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 <string.h> +#include "misc_log_ex.h" +#include "int-util.h" +#include "crypto/crypto.h" +#include "common/util.h" +#include "merge_mining.h" + +using namespace epee; + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "cn.mm" + +using namespace crypto; + +namespace cryptonote +{ + +//--------------------------------------------------------------- +uint32_t get_aux_slot(const crypto::hash &id, uint32_t nonce, uint32_t n_aux_chains) +{ + CHECK_AND_ASSERT_THROW_MES(n_aux_chains > 0, "n_aux_chains is 0"); + + uint8_t buf[HASH_SIZE + sizeof(uint32_t) + 1]; + memcpy(buf, &id, HASH_SIZE); + uint32_t v = SWAP32LE(nonce); + memcpy(buf + HASH_SIZE, &v, sizeof(uint32_t)); + buf[HASH_SIZE + sizeof(uint32_t)] = config::HASH_KEY_MM_SLOT; + + crypto::hash res; + tools::sha256sum(buf, sizeof(buf), res); + v = *((const uint32_t*)&res); + return SWAP32LE(v) % n_aux_chains; +} +//--------------------------------------------------------------- +uint32_t get_path_from_aux_slot(uint32_t slot, uint32_t n_aux_chains) +{ + CHECK_AND_ASSERT_THROW_MES(n_aux_chains > 0, "n_aux_chains is 0"); + CHECK_AND_ASSERT_THROW_MES(slot < n_aux_chains, "slot >= n_aux_chains"); + + uint32_t path = 0; + CHECK_AND_ASSERT_THROW_MES(tree_path(n_aux_chains, slot, &path), "Failed to get path from aux slot"); + return path; +} +//--------------------------------------------------------------- +uint32_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce) +{ + CHECK_AND_ASSERT_THROW_MES(n_aux_chains > 0, "n_aux_chains is 0"); + + // how many bits to we need to representing n_aux_chains - 1 + uint32_t n_bits = 1; + while ((1u << n_bits) < n_aux_chains && n_bits < 16) + ++n_bits; + CHECK_AND_ASSERT_THROW_MES(n_bits <= 16, "Way too many bits required"); + + const uint32_t depth = (n_bits - 1) | ((n_aux_chains - 1) << 3) | (nonce << (3 + n_bits)); + return depth; +} +//--------------------------------------------------------------- +bool decode_mm_depth(uint32_t depth, uint32_t &n_aux_chains, uint32_t &nonce) +{ + const uint32_t n_bits = 1 + (depth & 7); + n_aux_chains = 1 + (depth >> 3 & ((1 << n_bits) - 1)); + nonce = depth >> (3 + n_bits); + return true; +} +//--------------------------------------------------------------- +} diff --git a/src/cryptonote_basic/merge_mining.h b/src/cryptonote_basic/merge_mining.h new file mode 100644 index 000000000..378438f7c --- /dev/null +++ b/src/cryptonote_basic/merge_mining.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020, 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 <stdint.h> +#include "crypto/crypto.h" + +namespace cryptonote +{ + uint32_t get_aux_slot(const crypto::hash &id, uint32_t nonce, uint32_t n_aux_chains); + uint32_t get_path_from_aux_slot(uint32_t slot, uint32_t n_aux_chains); + uint32_t encode_mm_depth(uint32_t n_aux_chains, uint32_t nonce); + bool decode_mm_depth(uint32_t depth, uint32_t &n_aux_chains, uint32_t &nonce); +} diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp index 29f6dce5a..ae514aac6 100644 --- a/src/cryptonote_basic/miner.cpp +++ b/src/cryptonote_basic/miner.cpp @@ -44,6 +44,7 @@ #include "string_tools.h" #include "storages/portable_storage_template_helper.h" #include "boost/logic/tribool.hpp" +#include <boost/filesystem.hpp> #ifdef __APPLE__ #include <sys/times.h> diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 2cb28b2b1..915835d1b 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -194,7 +194,6 @@ #define CRYPTONOTE_PRUNING_STRIPE_SIZE 4096 // the smaller, the smoother the increase #define CRYPTONOTE_PRUNING_LOG_STRIPES 3 // the higher, the more space saved #define CRYPTONOTE_PRUNING_TIP_BLOCKS 5500 // the smaller, the more space saved -//#define CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED #define RPC_CREDITS_PER_HASH_SCALE ((float)(1<<24)) @@ -235,6 +234,7 @@ namespace config const unsigned char HASH_KEY_CLSAG_AGG_0[] = "CLSAG_agg_0"; const unsigned char HASH_KEY_CLSAG_AGG_1[] = "CLSAG_agg_1"; const char HASH_KEY_MESSAGE_SIGNING[] = "MoneroMessageSignature"; + const unsigned char HASH_KEY_MM_SLOT = 'm'; namespace testnet { diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 5b3b0527b..a3d695b85 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -57,6 +57,7 @@ #include "common/notify.h" #include "common/varint.h" #include "common/pruning.h" +#include "time_helper.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "blockchain" @@ -973,7 +974,7 @@ start: std::pair<bool, uint64_t> Blockchain::check_difficulty_checkpoints() const { uint64_t res = 0; - for (const std::pair<uint64_t, difficulty_type>& i : m_checkpoints.get_difficulty_points()) + for (const std::pair<const uint64_t, difficulty_type>& i : m_checkpoints.get_difficulty_points()) { if (i.first >= m_db->height()) break; @@ -2758,32 +2759,44 @@ void Blockchain::flush_invalid_blocks() m_invalid_blocks.clear(); } //------------------------------------------------------------------ -bool Blockchain::have_block(const crypto::hash& id) const +bool Blockchain::have_block_unlocked(const crypto::hash& id, int *where) const { + // WARNING: this function does not take m_blockchain_lock, and thus should only call read only + // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as + // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must + // lock if it is otherwise needed. LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); if(m_db->block_exists(id)) { LOG_PRINT_L2("block " << id << " found in main chain"); + if (where) *where = HAVE_BLOCK_MAIN_CHAIN; return true; } if(m_db->get_alt_block(id, NULL, NULL)) { LOG_PRINT_L2("block " << id << " found in alternative chains"); + if (where) *where = HAVE_BLOCK_ALT_CHAIN; return true; } if(m_invalid_blocks.count(id)) { LOG_PRINT_L2("block " << id << " found in m_invalid_blocks"); + if (where) *where = HAVE_BLOCK_INVALID; return true; } return false; } //------------------------------------------------------------------ +bool Blockchain::have_block(const crypto::hash& id, int *where) const +{ + CRITICAL_REGION_LOCAL(m_blockchain_lock); + return have_block_unlocked(id, where); +} +//------------------------------------------------------------------ bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify/* = true*/) { LOG_PRINT_L3("Blockchain::" << __func__); @@ -3426,7 +3439,6 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, bool failed = false; for (size_t i = 0; i < tx.vin.size(); i++) { - const txin_to_key& in_to_key = boost::get<txin_to_key>(tx.vin[i]); if(!failed && !results[i]) failed = true; } @@ -4716,6 +4728,8 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector CHECK_AND_ASSERT_MES(weights.empty() || weights.size() == hashes.size(), 0, "Unexpected weights size"); + CRITICAL_REGION_LOCAL(m_blockchain_lock); + // easy case: height >= hashes if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP) return hashes.size(); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 07238b719..5291f1338 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -377,10 +377,12 @@ namespace cryptonote * for a block with the given hash * * @param id the hash to search for + * @param where the type of block, if non NULL * * @return true if the block is known, else false */ - bool have_block(const crypto::hash& id) const; + bool have_block_unlocked(const crypto::hash& id, int *where = NULL) const; + bool have_block(const crypto::hash& id, int *where = NULL) const; /** * @brief gets the total number of transactions on the main chain diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 507c47a51..beae55b35 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -57,6 +57,8 @@ using namespace epee; #include "hardforks/hardforks.h" #include "version.h" +#include <boost/filesystem.hpp> + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "cn" @@ -630,7 +632,7 @@ namespace cryptonote void operator()(std::uint64_t, epee::span<const block> blocks) const { - for (const block bl : blocks) + for (const block& bl : blocks) cmdline.notify("%s", epee::string_tools::pod_to_hex(get_block_hash(bl)).c_str(), NULL); } }; @@ -1625,9 +1627,14 @@ namespace cryptonote return m_mempool.get_transactions_count(include_sensitive_txes); } //----------------------------------------------------------------------------------------------- - bool core::have_block(const crypto::hash& id) const + bool core::have_block_unlocked(const crypto::hash& id, int *where) const + { + return m_blockchain_storage.have_block_unlocked(id, where); + } + //----------------------------------------------------------------------------------------------- + bool core::have_block(const crypto::hash& id, int *where) const { - return m_blockchain_storage.have_block(id); + return m_blockchain_storage.have_block(id, where); } //----------------------------------------------------------------------------------------------- bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) const diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 24d0f9e76..8891540a9 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -55,6 +55,8 @@ PUSH_WARNINGS DISABLE_VS_WARNINGS(4355) +enum { HAVE_BLOCK_MAIN_CHAIN, HAVE_BLOCK_ALT_CHAIN, HAVE_BLOCK_INVALID }; + namespace cryptonote { struct test_options { @@ -543,7 +545,8 @@ namespace cryptonote * * @note see Blockchain::have_block */ - bool have_block(const crypto::hash& id) const; + bool have_block_unlocked(const crypto::hash& id, int *where = NULL) const; + bool have_block(const crypto::hash& id, int *where = NULL) const; /** * @copydoc Blockchain::get_short_chain_history diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index dbdf409b5..73cdd31cd 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -44,10 +44,10 @@ namespace cryptonote typedef std::pair<uint64_t, rct::ctkey> output_entry; std::vector<output_entry> outputs; //index + key + optional ringct commitment - size_t real_output; //index in outputs vector of real output_entry + uint64_t real_output; //index in outputs vector of real output_entry crypto::public_key real_out_tx_key; //incoming real tx public key std::vector<crypto::public_key> real_out_additional_tx_keys; //incoming real tx additional public keys - size_t real_output_in_tx_index; //index in transaction outputs vector + uint64_t real_output_in_tx_index; //index in transaction outputs vector uint64_t amount; //money bool rct; //true if the output is rct rct::key mask; //ringct amount mask diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index d059ab78f..a7e96e23a 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -383,7 +383,6 @@ namespace cryptonote 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; cryptonote::blobdata bl; t_serializable_object_to_blob(tx, bl); if (bl.size() == 0 || !get_transaction_hash(tx, h)) @@ -1041,7 +1040,6 @@ namespace cryptonote return true; }, true, category); - txpool_tx_meta_t meta; for (const key_images_container::value_type& kee : m_spent_key_images) { const crypto::key_image& k_image = kee.first; const std::unordered_set<crypto::hash>& kei_image_set = kee.second; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.h b/src/cryptonote_protocol/cryptonote_protocol_handler.h index 28530f3e7..80dd2bc39 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.h @@ -46,6 +46,8 @@ #include "block_queue.h" #include "common/perf_timer.h" #include "cryptonote_basic/connection_context.h" +#include "net/levin_base.h" +#include "p2p/net_node_common.h" #include <boost/circular_buffer.hpp> PUSH_WARNINGS @@ -195,10 +197,11 @@ namespace cryptonote bool post_notify(typename t_parameter::request& arg, cryptonote_connection_context& context) { LOG_PRINT_L2("[" << epee::net_utils::print_connection_context_short(context) << "] post " << typeid(t_parameter).name() << " -->"); - epee::byte_slice blob; - epee::serialization::store_t_to_binary(arg, blob, 256 * 1024); // optimize for block responses + + epee::levin::message_writer out{256 * 1024}; // optimize for block responses + epee::serialization::store_t_to_binary(arg, out.buffer); //handler_response_blocks_now(blob.size()); // XXX - return m_p2p->invoke_notify_to_peer(t_parameter::ID, epee::to_span(blob), context); + return m_p2p->invoke_notify_to_peer(t_parameter::ID, std::move(out), context); } }; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 22e87465f..afc81f552 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -137,7 +137,42 @@ namespace cryptonote CHECK_AND_ASSERT_MES_CC( context.m_callback_request_count > 0, false, "false callback fired, but context.m_callback_request_count=" << context.m_callback_request_count); --context.m_callback_request_count; - if(context.m_state == cryptonote_connection_context::state_synchronizing) + uint32_t notified = true; + if (context.m_idle_peer_notification.compare_exchange_strong(notified, not notified)) + { + if (context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time != boost::date_time::not_a_date_time) + { + const boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); + const boost::posix_time::time_duration dt = now - context.m_last_request_time; + const auto ms = dt.total_microseconds(); + if (ms > IDLE_PEER_KICK_TIME || (context.m_expect_response && ms > NON_RESPONSIVE_PEER_KICK_TIME)) + { + if (context.m_score-- >= 0) + { + MINFO(context << " kicking idle peer, last update " << (dt.total_microseconds() / 1.e6) << " seconds ago, expecting " << (int)context.m_expect_response); + context.m_last_request_time = boost::date_time::not_a_date_time; + context.m_expect_response = 0; + context.m_expect_height = 0; + context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download + } + else + { + MINFO(context << "dropping idle peer with negative score"); + drop_connection_with_score(context, context.m_expect_response == 0 ? 1 : 5, false); + return false; + } + } + } + } + + notified = true; + if (context.m_new_stripe_notification.compare_exchange_strong(notified, not notified)) + { + if (context.m_state == cryptonote_connection_context::state_normal) + context.m_state = cryptonote_connection_context::state_synchronizing; + } + + if(context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time == boost::posix_time::not_a_date_time) { NOTIFY_REQUEST_CHAIN::request r = {}; context.m_needed_objects.clear(); @@ -338,10 +373,6 @@ namespace cryptonote } context.m_remote_blockchain_height = hshd.current_height; context.m_pruning_seed = hshd.pruning_seed; -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - context.m_pruning_seed = tools::make_pruning_seed(1 + (context.m_remote_address.as<epee::net_utils::ipv4_network_address>().ip()) % (1 << CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES); - LOG_INFO_CC(context, "New connection posing as pruning seed " << epee::string_tools::to_string_hex(context.m_pruning_seed) << ", seed address " << &context.m_pruning_seed); -#endif uint64_t target = m_core.get_target_blockchain_height(); if (target == 0) @@ -549,6 +580,7 @@ namespace cryptonote } std::vector<tx_blob_entry> have_tx; + have_tx.reserve(new_block.tx_hashes.size()); // Instead of requesting missing transactions by hash like BTC, // we do it by index (thanks to a suggestion from moneromooo) because @@ -557,6 +589,7 @@ namespace cryptonote // Also, remember to pepper some whitespace changes around to bother // moneromooo ... only because I <3 him. std::vector<uint64_t> need_tx_indices; + need_tx_indices.reserve(new_block.tx_hashes.size()); transaction tx; crypto::hash tx_hash; @@ -829,6 +862,7 @@ namespace cryptonote } std::vector<crypto::hash> txids; + txids.reserve(b.tx_hashes.size()); NOTIFY_NEW_FLUFFY_BLOCK::request fluffy_response; fluffy_response.b.block = t_serializable_object_to_blob(b); fluffy_response.current_blockchain_height = arg.current_blockchain_height; @@ -1688,7 +1722,7 @@ skip: const uint32_t peer_stripe = tools::get_pruning_stripe(context.m_pruning_seed); if (stripe && peer_stripe && peer_stripe != stripe) return true; - context.m_state = cryptonote_connection_context::state_synchronizing; + context.m_new_stripe_notification = true; LOG_PRINT_CCONTEXT_L2("requesting callback"); ++context.m_callback_request_count; m_p2p->request_callback(context); @@ -1711,7 +1745,6 @@ skip: bool t_cryptonote_protocol_handler<t_core>::kick_idle_peers() { MTRACE("Checking for idle peers..."); - std::vector<std::pair<boost::uuids::uuid, unsigned>> idle_peers; m_p2p->for_each_connection([&](cryptonote_connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags)->bool { if (context.m_state == cryptonote_connection_context::state_synchronizing && context.m_last_request_time != boost::date_time::not_a_date_time) @@ -1721,36 +1754,16 @@ skip: const auto ms = dt.total_microseconds(); if (ms > IDLE_PEER_KICK_TIME || (context.m_expect_response && ms > NON_RESPONSIVE_PEER_KICK_TIME)) { - if (context.m_score-- >= 0) - { - MINFO(context << " kicking idle peer, last update " << (dt.total_microseconds() / 1.e6) << " seconds ago, expecting " << (int)context.m_expect_response); - LOG_PRINT_CCONTEXT_L2("requesting callback"); - context.m_last_request_time = boost::date_time::not_a_date_time; - context.m_expect_response = 0; - context.m_expect_height = 0; - context.m_state = cryptonote_connection_context::state_standby; // we'll go back to adding, then (if we can't), download - ++context.m_callback_request_count; - m_p2p->request_callback(context); - } - else - { - idle_peers.push_back(std::make_pair(context.m_connection_id, context.m_expect_response == 0 ? 1 : 5)); - } + context.m_idle_peer_notification = true; + LOG_PRINT_CCONTEXT_L2("requesting callback"); + ++context.m_callback_request_count; + m_p2p->request_callback(context); + MLOG_PEER_STATE("requesting callback"); } } return true; }); - for (const auto &e: idle_peers) - { - const auto &uuid = e.first; - m_p2p->for_connection(uuid, [&](cryptonote_connection_context& ctx, nodetool::peerid_type peer_id, uint32_t f)->bool{ - MINFO(ctx << "dropping idle peer with negative score"); - drop_connection_with_score(ctx, e.second, false); - return true; - }); - } - return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -1846,10 +1859,8 @@ skip: bool t_cryptonote_protocol_handler<t_core>::should_download_next_span(cryptonote_connection_context& context, bool standby) { std::vector<crypto::hash> hashes; - boost::uuids::uuid span_connection_id; boost::posix_time::ptime request_time; boost::uuids::uuid connection_id; - std::pair<uint64_t, uint64_t> span; bool filled; const uint64_t blockchain_height = m_core.get_current_blockchain_height(); @@ -1875,7 +1886,6 @@ skip: // in standby, be ready to double download early since we're idling anyway // let the fastest peer trigger first - long threshold; const double dl_speed = context.m_max_speed_down; if (standby && dt >= REQUEST_NEXT_SCHEDULED_SPAN_THRESHOLD_STANDBY && dl_speed > 0) { @@ -2189,6 +2199,7 @@ skip: if (span.second > 0) { is_next = true; + req.blocks.reserve(hashes.size()); for (const auto &hash: hashes) { req.blocks.push_back(hash); @@ -2247,6 +2258,7 @@ skip: if (span.second > 0) { is_next = true; + req.blocks.reserve(hashes.size()); for (const auto &hash: hashes) { req.blocks.push_back(hash); @@ -2280,6 +2292,7 @@ skip: return false; } + req.blocks.reserve(req.blocks.size() + span.second); for (size_t n = 0; n < span.second; ++n) { req.blocks.push_back(context.m_needed_objects[n].first); @@ -2570,17 +2583,6 @@ skip: return 1; } - std::unordered_set<crypto::hash> hashes; - for (const auto &h: arg.m_block_ids) - { - if (!hashes.insert(h).second) - { - LOG_ERROR_CCONTEXT("sent duplicate block, dropping connection"); - drop_connection(context, true, false); - return 1; - } - } - uint64_t n_use_blocks = m_core.prevalidate_block_hashes(arg.start_height, arg.m_block_ids, arg.m_block_weights); if (n_use_blocks == 0 || n_use_blocks + HASH_OF_HASHES_STEP <= arg.m_block_ids.size()) { @@ -2590,20 +2592,77 @@ skip: } context.m_needed_objects.clear(); + context.m_needed_objects.reserve(arg.m_block_ids.size()); uint64_t added = 0; std::unordered_set<crypto::hash> blocks_found; + bool first = true; + bool expect_unknown = false; for (size_t i = 0; i < arg.m_block_ids.size(); ++i) { if (!blocks_found.insert(arg.m_block_ids[i]).second) { LOG_ERROR_CCONTEXT("Duplicate blocks in chain entry response, dropping connection"); - drop_connection(context, true, false); + drop_connection_with_score(context, 5, false); return 1; } + int where; + const bool have_block = m_core.have_block_unlocked(arg.m_block_ids[i], &where); + if (first) + { + if (!have_block && !m_block_queue.requested(arg.m_block_ids[i]) && !m_block_queue.have(arg.m_block_ids[i])) + { + LOG_ERROR_CCONTEXT("First block hash is unknown, dropping connection"); + drop_connection_with_score(context, 5, false); + return 1; + } + if (!have_block) + expect_unknown = true; + } + if (!first) + { + // after the first, blocks may be known or unknown, but if they are known, + // they should be at the same height if on the main chain + if (have_block) + { + switch (where) + { + default: + case HAVE_BLOCK_INVALID: + LOG_ERROR_CCONTEXT("Block is invalid or known without known type, dropping connection"); + drop_connection(context, true, false); + return 1; + case HAVE_BLOCK_MAIN_CHAIN: + if (expect_unknown) + { + LOG_ERROR_CCONTEXT("Block is on the main chain, but we did not expect a known block, dropping connection"); + drop_connection_with_score(context, 5, false); + return 1; + } + if (m_core.get_block_id_by_height(arg.start_height + i) != arg.m_block_ids[i]) + { + LOG_ERROR_CCONTEXT("Block is on the main chain, but not at the expected height, dropping connection"); + drop_connection_with_score(context, 5, false); + return 1; + } + break; + case HAVE_BLOCK_ALT_CHAIN: + if (expect_unknown) + { + LOG_ERROR_CCONTEXT("Block is on the main chain, but we did not expect a known block, dropping connection"); + drop_connection_with_score(context, 5, false); + return 1; + } + break; + } + } + else + expect_unknown = true; + } const uint64_t block_weight = arg.m_block_weights.empty() ? 0 : arg.m_block_weights[i]; context.m_needed_objects.push_back(std::make_pair(arg.m_block_ids[i], block_weight)); if (++added == n_use_blocks) break; + first = false; } context.m_last_response_height -= arg.m_block_ids.size() - n_use_blocks; @@ -2634,6 +2693,7 @@ skip: std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> fullConnections, fluffyConnections; m_p2p->for_each_connection([this, &exclude_context, &fullConnections, &fluffyConnections](connection_context& context, nodetool::peerid_type peer_id, uint32_t support_flags) { + // peer_id also filters out connections before handshake if (peer_id && exclude_context.m_connection_id != context.m_connection_id && context.m_remote_address.get_zone() == epee::net_utils::zone::public_) { if(m_core.fluffy_blocks_enabled() && (support_flags & P2P_SUPPORT_FLAG_FLUFFY_BLOCKS)) @@ -2653,15 +2713,15 @@ skip: // send fluffy ones first, we want to encourage people to run that if (!fluffyConnections.empty()) { - epee::byte_slice fluffyBlob; - epee::serialization::store_t_to_binary(fluffy_arg, fluffyBlob, 32 * 1024); - m_p2p->relay_notify_to_list(NOTIFY_NEW_FLUFFY_BLOCK::ID, epee::to_span(fluffyBlob), std::move(fluffyConnections)); + epee::levin::message_writer fluffyBlob{32 * 1024}; + epee::serialization::store_t_to_binary(fluffy_arg, fluffyBlob.buffer); + m_p2p->relay_notify_to_list(NOTIFY_NEW_FLUFFY_BLOCK::ID, std::move(fluffyBlob), std::move(fluffyConnections)); } if (!fullConnections.empty()) { - epee::byte_slice fullBlob; - epee::serialization::store_t_to_binary(arg, fullBlob, 128 * 1024); - m_p2p->relay_notify_to_list(NOTIFY_NEW_BLOCK::ID, epee::to_span(fullBlob), std::move(fullConnections)); + epee::levin::message_writer fullBlob{128 * 1024}; + epee::serialization::store_t_to_binary(arg, fullBlob.buffer); + m_p2p->relay_notify_to_list(NOTIFY_NEW_BLOCK::ID, std::move(fullBlob), std::move(fullConnections)); } return true; diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h index 79c2edf1d..57b1d049c 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler_common.h +++ b/src/cryptonote_protocol/cryptonote_protocol_handler_common.h @@ -30,8 +30,8 @@ #pragma once -#include "p2p/net_node_common.h" #include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "cryptonote_protocol/enums.h" #include "cryptonote_basic/connection_context.h" namespace cryptonote { diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 21363972d..0b065c3c3 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -51,14 +51,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "net.p2p.tx" -namespace -{ - int get_command_from_message(const epee::byte_slice &msg) - { - return msg.size() >= sizeof(epee::levin::bucket_head2) ? SWAP32LE(((epee::levin::bucket_head2*)msg.data())->m_command) : 0; - } -} - namespace cryptonote { namespace levin @@ -167,7 +159,7 @@ namespace levin return get_out_connections(p2p, get_blockchain_height(p2p, core)); } - epee::byte_slice make_tx_payload(std::vector<blobdata>&& txs, const bool pad, const bool fluff) + epee::levin::message_writer make_tx_message(std::vector<blobdata>&& txs, const bool pad, const bool fluff) { NOTIFY_NEW_TRANSACTIONS::request request{}; request.txs = std::move(txs); @@ -201,21 +193,17 @@ namespace levin // if the size of _ moved enough, we might lose byte in size encoding, we don't care } - epee::byte_slice fullBlob; - if (!epee::serialization::store_t_to_binary(request, fullBlob)) + epee::levin::message_writer out; + if (!epee::serialization::store_t_to_binary(request, out.buffer)) throw std::runtime_error{"Failed to serialize to epee binary format"}; - return fullBlob; + return out; } bool make_payload_send_txs(connections& p2p, std::vector<blobdata>&& txs, const boost::uuids::uuid& destination, const bool pad, const bool fluff) { - const epee::byte_slice blob = make_tx_payload(std::move(txs), pad, fluff); - 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::to_span(blob), destination); + epee::byte_slice blob = make_tx_message(std::move(txs), pad, fluff).finalize_notify(NOTIFY_NEW_TRANSACTIONS::ID); + return p2p.send(std::move(blob), destination); } /* The current design uses `asio::strand`s. The documentation isn't as clear @@ -443,7 +431,7 @@ namespace levin zone->p2p->foreach_connection([txs, now, &zone, &source, &in_duration, &out_duration, &next_flush] (detail::p2p_context& context) { // When i2p/tor, only fluff to outbound connections - if (source != context.m_connection_id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) + if (context.handshake_complete() && source != context.m_connection_id && (zone->nzone == epee::net_utils::zone::public_ || !context.m_is_income)) { if (context.fluff_txs.empty()) context.flush_time = now + (context.m_is_income ? in_duration() : out_duration()); @@ -661,10 +649,6 @@ 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()) @@ -824,9 +808,8 @@ namespace levin // Padding is not useful when using noise mode. Send as stem so receiver // forwards in Dandelion++ mode. - const epee::byte_slice payload = make_tx_payload(std::move(txs), false, false); epee::byte_slice message = epee::levin::make_fragmented_notify( - zone_->noise, NOTIFY_NEW_TRANSACTIONS::ID, epee::to_span(payload) + zone_->noise.size(), NOTIFY_NEW_TRANSACTIONS::ID, make_tx_message(std::move(txs), false, false) ); if (CRYPTONOTE_MAX_FRAGMENTS * zone_->noise.size() < message.size()) { diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 5a7560874..abca614cc 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -30,6 +30,7 @@ #include "common/command_line.h" #include "net/parse.h" #include "daemon/command_parser_executor.h" +#include <boost/filesystem.hpp> #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon" @@ -986,17 +987,67 @@ bool t_command_parser_executor::check_blockchain_pruning(const std::vector<std:: bool t_command_parser_executor::set_bootstrap_daemon(const std::vector<std::string>& args) { - const size_t args_count = args.size(); - if (args_count < 1 || args_count > 3) + struct parsed_t + { + std::string address; + std::string user; + std::string password; + std::string proxy; + }; + + boost::optional<parsed_t> parsed = [&args]() -> boost::optional<parsed_t> { + const size_t args_count = args.size(); + if (args_count == 0) + { + return {}; + } + if (args[0] == "auto") + { + if (args_count == 1) + { + return {{args[0], "", "", ""}}; + } + if (args_count == 2) + { + return {{args[0], "", "", args[1]}}; + } + } + else if (args[0] == "none") + { + if (args_count == 1) + { + return {{"", "", "", ""}}; + } + } + else + { + if (args_count == 1) + { + return {{args[0], "", "", ""}}; + } + if (args_count == 2) + { + return {{args[0], "", "", args[1]}}; + } + if (args_count == 3) + { + return {{args[0], args[1], args[2], ""}}; + } + if (args_count == 4) + { + return {{args[0], args[1], args[2], args[3]}}; + } + } + return {}; + }(); + + if (!parsed) { std::cout << "Invalid syntax: Wrong number of parameters. For more details, use the help command." << std::endl; return true; } - return m_executor.set_bootstrap_daemon( - args[0] != "none" ? args[0] : std::string(), - args_count > 1 ? args[1] : std::string(), - args_count > 2 ? args[2] : std::string()); + return m_executor.set_bootstrap_daemon(parsed->address, parsed->user, parsed->password, parsed->proxy); } bool t_command_parser_executor::flush_cache(const std::vector<std::string>& args) diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp index 4768bb842..63f44c4cd 100644 --- a/src/daemon/command_server.cpp +++ b/src/daemon/command_server.cpp @@ -326,7 +326,7 @@ t_command_server::t_command_server( m_command_lookup.set_handler( "set_bootstrap_daemon" , std::bind(&t_command_parser_executor::set_bootstrap_daemon, &m_parser, p::_1) - , "set_bootstrap_daemon (auto | none | host[:port] [username] [password])" + , "set_bootstrap_daemon (auto | none | host[:port] [username] [password]) [proxy_ip:proxy_port]" , "URL of a 'bootstrap' remote daemon that the connected wallets can use while this daemon is still not fully synced.\n" "Use 'auto' to enable automatic public nodes discovering and bootstrap daemon switching" ); diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 04feb55fd..16e6a304c 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1059,7 +1059,6 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, // Print json if requested if (include_json) { - crypto::hash tx_hash, tx_prefix_hash; cryptonote::transaction tx; cryptonote::blobdata blob; std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex; @@ -2275,6 +2274,7 @@ bool t_rpc_command_executor::sync_info() tools::success_msg_writer() << "Next needed pruning seed: " << res.next_needed_pruning_seed; tools::success_msg_writer() << std::to_string(res.peers.size()) << " peers"; + tools::success_msg_writer() << "Remote Host Peer_ID State Prune_Seed Height DL kB/s, Queued Blocks / MB"; for (const auto &p: res.peers) { std::string address = epee::string_tools::pad_string(p.info.address, 24); @@ -2405,7 +2405,8 @@ bool t_rpc_command_executor::check_blockchain_pruning() bool t_rpc_command_executor::set_bootstrap_daemon( const std::string &address, const std::string &username, - const std::string &password) + const std::string &password, + const std::string &proxy) { cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::request req; cryptonote::COMMAND_RPC_SET_BOOTSTRAP_DAEMON::response res; @@ -2414,6 +2415,7 @@ bool t_rpc_command_executor::set_bootstrap_daemon( req.address = address; req.username = username; req.password = password; + req.proxy = proxy; if (m_is_rpc) { diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h index 6fb5d6903..118f04731 100644 --- a/src/daemon/rpc_command_executor.h +++ b/src/daemon/rpc_command_executor.h @@ -168,7 +168,8 @@ public: bool set_bootstrap_daemon( const std::string &address, const std::string &username, - const std::string &password); + const std::string &password, + const std::string &proxy); bool rpc_payments(); diff --git a/src/debug_utilities/cn_deserialize.cpp b/src/debug_utilities/cn_deserialize.cpp index dd4701e4f..c039b93c5 100644 --- a/src/debug_utilities/cn_deserialize.cpp +++ b/src/debug_utilities/cn_deserialize.cpp @@ -133,6 +133,18 @@ int main(int argc, char* argv[]) { std::cout << "Parsed block:" << std::endl; std::cout << cryptonote::obj_to_json_str(block) << std::endl; + bool parsed = cryptonote::parse_tx_extra(block.miner_tx.extra, fields); + if (!parsed) + std::cout << "Failed to parse tx_extra" << std::endl; + + if (!fields.empty()) + { + print_extra_fields(fields); + } + else + { + std::cout << "No fields were found in tx_extra" << std::endl; + } } else if (cryptonote::parse_and_validate_tx_from_blob(blob, tx) || cryptonote::parse_and_validate_tx_base_from_blob(blob, tx)) { diff --git a/src/debug_utilities/dns_checks.cpp b/src/debug_utilities/dns_checks.cpp index 76b66c6cb..138cd4fc1 100644 --- a/src/debug_utilities/dns_checks.cpp +++ b/src/debug_utilities/dns_checks.cpp @@ -131,7 +131,7 @@ int main(int argc, char* argv[]) lookup(LOOKUP_A, {"seeds.moneroseeds.se", "seeds.moneroseeds.ae.org", "seeds.moneroseeds.ch", "seeds.moneroseeds.li"}); - lookup(LOOKUP_TXT, {"updates.moneropulse.org", "updates.moneropulse.net", "updates.moneropulse.co", "updates.moneropulse.se"}); + lookup(LOOKUP_TXT, {"updates.moneropulse.org", "updates.moneropulse.net", "updates.moneropulse.co", "updates.moneropulse.se", "updates.moneropulse.fr", "updates.moneropulse.de", "updates.moneropulse.no", "updates.moneropulse.ch"}); lookup(LOOKUP_TXT, {"checkpoints.moneropulse.org", "checkpoints.moneropulse.net", "checkpoints.moneropulse.co", "checkpoints.moneropulse.se"}); diff --git a/src/device/device_io_hid.cpp b/src/device/device_io_hid.cpp index 7c61c3b1a..7aa5b39bf 100644 --- a/src/device/device_io_hid.cpp +++ b/src/device/device_io_hid.cpp @@ -181,7 +181,6 @@ namespace hw { unsigned char padding_buffer[MAX_BLOCK+1]; unsigned int result; int hid_ret; - unsigned int sw_offset; unsigned int remaining; unsigned int offset = 0; diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index f59be1573..70dc7f539 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -365,15 +365,14 @@ namespace trezor { void device_trezor_base::device_state_initialize_unsafe() { require_connected(); - std::string tmp_session_id; auto initMsg = std::make_shared<messages::management::Initialize>(); const auto data_cleaner = epee::misc_utils::create_scope_leave_handler([&]() { - memwipe(&tmp_session_id[0], tmp_session_id.size()); + if (initMsg->has_session_id()) + memwipe(&(*initMsg->mutable_session_id())[0], initMsg->mutable_session_id()->size()); }); if(!m_device_session_id.empty()) { - tmp_session_id.assign(m_device_session_id.data(), m_device_session_id.size()); - initMsg->set_allocated_session_id(&tmp_session_id); + initMsg->set_allocated_session_id(new std::string(m_device_session_id.data(), m_device_session_id.size())); } m_features = this->client_exchange<messages::management::Features>(initMsg); @@ -382,8 +381,6 @@ namespace trezor { } else { m_device_session_id.clear(); } - - initMsg->release_session_id(); } void device_trezor_base::device_state_reset() @@ -453,18 +450,14 @@ namespace trezor { pin = m_pin; } - std::string pin_field; messages::common::PinMatrixAck m; if (pin) { - pin_field.assign(pin->data(), pin->size()); - m.set_allocated_pin(&pin_field); + m.set_allocated_pin(new std::string(pin->data(), pin->size())); } const auto data_cleaner = epee::misc_utils::create_scope_leave_handler([&]() { - m.release_pin(); - if (!pin_field.empty()){ - memwipe(&pin_field[0], pin_field.size()); - } + if (m.has_pin()) + memwipe(&(*m.mutable_pin())[0], m.mutable_pin()->size()); }); resp = call_raw(&m); @@ -499,7 +492,6 @@ namespace trezor { boost::optional<epee::wipeable_string> passphrase; TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, on_device); - std::string passphrase_field; messages::common::PassphraseAck m; m.set_on_device(on_device); if (!on_device) { @@ -512,16 +504,13 @@ namespace trezor { } if (passphrase) { - passphrase_field.assign(passphrase->data(), passphrase->size()); - m.set_allocated_passphrase(&passphrase_field); + m.set_allocated_passphrase(new std::string(passphrase->data(), passphrase->size())); } } const auto data_cleaner = epee::misc_utils::create_scope_leave_handler([&]() { - m.release_passphrase(); - if (!passphrase_field.empty()){ - memwipe(&passphrase_field[0], passphrase_field.size()); - } + if (m.has_passphrase()) + memwipe(&(m.mutable_passphrase())[0], m.mutable_passphrase()->size()); }); resp = call_raw(&m); diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 4db8f0c8e..0162b23df 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -165,7 +165,7 @@ namespace trezor { // Scoped session closer BOOST_SCOPE_EXIT_ALL(&, this) { - if (open_session){ + if (open_session && this->get_transport()){ this->get_transport()->close(); } }; diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index 288f3ddca..92150b579 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -502,21 +502,9 @@ namespace tx { } void Signer::compute_integrated_indices(TsxData * tsx_data){ - if (m_aux_data == nullptr || m_aux_data->tx_recipients.empty()){ - return; - } - auto & chg = tsx_data->change_dts(); std::string change_hash = hash_addr(&chg.addr(), chg.amount(), chg.is_subaddress()); - std::vector<uint32_t> integrated_indices; - std::set<std::string> integrated_hashes; - for (auto & cur : m_aux_data->tx_recipients){ - if (!cur.has_payment_id){ - continue; - } - integrated_hashes.emplace(hash_addr(&cur.address.m_spend_public_key, &cur.address.m_view_public_key)); - } ssize_t idx = -1; for (auto & cur : tsx_data->outputs()){ @@ -527,8 +515,7 @@ namespace tx { continue; } - c_hash = hash_addr(&cur.addr()); - if (integrated_hashes.find(c_hash) != integrated_hashes.end()){ + if (cur.is_integrated()){ integrated_indices.push_back((uint32_t)idx); } } diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 15f7e5c0a..4aa21b149 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -74,8 +74,6 @@ namespace const command_line::arg_descriptor<bool, false> arg_testnet = {"testnet", genms::tr("Create testnet multisig wallets"), false}; const command_line::arg_descriptor<bool, false> arg_stagenet = {"stagenet", genms::tr("Create stagenet multisig wallets"), false}; const command_line::arg_descriptor<bool, false> arg_create_address_file = {"create-address-file", genms::tr("Create an address file for new wallets"), false}; - - const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; } static bool generate_multisig(uint32_t threshold, uint32_t total, const std::string &basename, network_type nettype, bool create_address_file) diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp index bda18d950..8c79a53ca 100644 --- a/src/mnemonics/electrum-words.cpp +++ b/src/mnemonics/electrum-words.cpp @@ -194,7 +194,6 @@ namespace { epee::wipeable_string trimmed_words = "", word; - const auto &word_map = language->get_word_map(); const auto &trimmed_word_map = language->get_trimmed_word_map(); const uint32_t unique_prefix_length = language->get_unique_prefix_length(); for (std::vector<epee::wipeable_string>::const_iterator it = word_list.begin(); it != word_list.end(); it++) @@ -492,6 +491,14 @@ namespace crypto return "<language not found>"; } + bool is_valid_language(const std::string &language) + { + const std::vector<const Language::Base*> language_instances = get_language_list(); + for (std::vector<const Language::Base*>::const_iterator it = language_instances.begin(); it != language_instances.end(); it++) + if ((*it)->get_english_language_name() == language || (*it)->get_language_name() == language) + return true; + return false; + } } } diff --git a/src/mnemonics/electrum-words.h b/src/mnemonics/electrum-words.h index 8d4c5be66..eb0c99e0b 100644 --- a/src/mnemonics/electrum-words.h +++ b/src/mnemonics/electrum-words.h @@ -124,6 +124,8 @@ namespace crypto * \return the name of the language in English */ std::string get_english_name_for(const std::string &name); + + bool is_valid_language(const std::string &language); } } diff --git a/src/mnemonics/language_base.h b/src/mnemonics/language_base.h index bf8793aa2..1aa869e45 100644 --- a/src/mnemonics/language_base.h +++ b/src/mnemonics/language_base.h @@ -129,7 +129,7 @@ namespace Language if ((*it).size() < unique_prefix_length)
{
if (flags & ALLOW_SHORT_WORDS)
- MWARNING(language_name << " word '" << *it << "' is shorter than its prefix length, " << unique_prefix_length);
+ MINFO(language_name << " word '" << *it << "' is shorter than its prefix length, " << unique_prefix_length);
else
throw std::runtime_error("Too short word in " + language_name + " word list: " + *it);
}
diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt index afcd42ef7..e93e27bcd 100644 --- a/src/net/CMakeLists.txt +++ b/src/net/CMakeLists.txt @@ -26,10 +26,10 @@ # 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. -set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp socks.cpp - socks_connect.cpp tor_address.cpp zmq.cpp) -set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h socks_connect.h - tor_address.h zmq.h) +set(net_sources dandelionpp.cpp error.cpp http.cpp i2p_address.cpp parse.cpp resolve.cpp + socks.cpp socks_connect.cpp tor_address.cpp zmq.cpp) +set(net_headers dandelionpp.h error.h http.cpp i2p_address.h parse.h socks.h resolve.h + socks_connect.h tor_address.h zmq.h) monero_add_library(net ${net_sources} ${net_headers}) target_link_libraries(net common epee ${ZMQ_LIB} ${Boost_ASIO_LIBRARY}) diff --git a/src/net/error.cpp b/src/net/error.cpp index 037f44d52..d2e713bc5 100644 --- a/src/net/error.cpp +++ b/src/net/error.cpp @@ -47,12 +47,18 @@ namespace { switch (net::error(value)) { + case net::error::bogus_dnssec: + return "Invalid response signature from DNSSEC enabled domain"; + case net::error::dns_query_failure: + return "Failed to retrieve desired DNS record"; case net::error::expected_tld: return "Expected top-level domain"; case net::error::invalid_host: return "Host value is not valid"; case net::error::invalid_i2p_address: return "Invalid I2P address"; + case net::error::invalid_mask: + return "CIDR netmask outside of 0-32 range"; case net::error::invalid_port: return "Invalid port value (expected 0-65535)"; case net::error::invalid_tor_address: @@ -71,6 +77,7 @@ namespace switch (net::error(value)) { case net::error::invalid_port: + case net::error::invalid_mask: return std::errc::result_out_of_range; case net::error::expected_tld: case net::error::invalid_tor_address: diff --git a/src/net/error.h b/src/net/error.h index 7c852dd20..746eb0ecb 100644 --- a/src/net/error.h +++ b/src/net/error.h @@ -37,13 +37,16 @@ namespace net enum class error : int { // 0 reserved for success (as per expect<T>) - expected_tld = 1, //!< Expected a tld + bogus_dnssec = 1, //!< Invalid response signature from DNSSEC enabled domain + dns_query_failure, //!< Failed to retrieve desired DNS record + expected_tld, //!< Expected a tld invalid_host, //!< Hostname is not valid invalid_i2p_address, + invalid_mask, //!< Outside of 0-32 range invalid_port, //!< Outside of 0-65535 range invalid_tor_address,//!< Invalid base32 or length unsupported_address,//!< Type not supported by `get_network_address` - invalid_mask, //!< Outside of 0-32 range + }; //! \return `std::error_category` for `net` namespace. diff --git a/src/net/resolve.cpp b/src/net/resolve.cpp new file mode 100644 index 000000000..1b43cf6c4 --- /dev/null +++ b/src/net/resolve.cpp @@ -0,0 +1,71 @@ +// Copyright (c) 2020, 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 "net/resolve.h" + +#include <boost/utility/string_ref.hpp> +#include "common/dns_utils.h" +#include "common/expect.h" +#include "net/error.h" + +namespace net +{ +namespace dnssec +{ + expect<service_response> resolve_hostname(const std::string& addr, const std::string& tlsa_port) + { + // use basic (blocking) unbound for now, possibly refactor later + tools::DNSResolver& resolver = tools::DNSResolver::instance(); + + bool dnssec_available = false; + bool dnssec_valid = false; + std::vector<std::string> ip_records = resolver.get_ipv4(addr, dnssec_available, dnssec_valid); + + if (dnssec_available && !dnssec_valid) + return {net::error::bogus_dnssec}; + + if (ip_records.empty()) + { + ip_records = resolver.get_ipv6(addr, dnssec_available, dnssec_valid); + if (dnssec_available && !dnssec_valid) + return {net::error::bogus_dnssec}; + if (ip_records.empty()) + return {net::error::dns_query_failure}; + } + + std::vector<std::string> tlsa{}; + if (dnssec_available && !tlsa_port.empty()) + { + tlsa = resolver.get_tlsa_tcp_record(addr, tlsa_port, dnssec_available, dnssec_valid); + if (!dnssec_valid) + return {net::error::bogus_dnssec}; + } + return {{std::move(ip_records), std::move(tlsa)}}; + } +} // dnssec +} // net diff --git a/src/net/resolve.h b/src/net/resolve.h new file mode 100644 index 000000000..46bd8e617 --- /dev/null +++ b/src/net/resolve.h @@ -0,0 +1,47 @@ +// Copyright (c) 2020, 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 <string> +#include <vector> + +template<typename> class expect; + +namespace net +{ +namespace dnssec +{ + struct service_response + { + std::vector<std::string> ip; //!< IPv4/6 records in dotted or semicolon notation + std::vector<std::string> tlsa; //!< DANE/TLSA records + }; + + //! \return IP + (optionally) DANE/TLSA records, failing if DNSSEC signature is "bogus" + expect<service_response> resolve_hostname(const std::string& addr, const std::string& tlsa_port = {}); +} // dnssec +} // net diff --git a/src/net/socks.cpp b/src/net/socks.cpp index 3463f452c..c2330bd41 100644 --- a/src/net/socks.cpp +++ b/src/net/socks.cpp @@ -48,7 +48,6 @@ namespace socks { namespace { - constexpr const unsigned v4_reply_size = 8; constexpr const std::uint8_t v4_connect_command = 1; constexpr const std::uint8_t v4tor_resolve_command = 0xf0; constexpr const std::uint8_t v4_request_granted = 90; diff --git a/src/p2p/net_node.cpp b/src/p2p/net_node.cpp index 8dd551d1e..84cc1581e 100644 --- a/src/p2p/net_node.cpp +++ b/src/p2p/net_node.cpp @@ -166,6 +166,7 @@ namespace nodetool const command_line::arg_descriptor<bool> arg_pad_transactions = { "pad-transactions", "Pad relayed transactions to help defend against traffic volume analysis", false }; + const command_line::arg_descriptor<uint32_t> arg_max_connections_per_ip = {"max-connections-per-ip", "Maximum number of connections allowed from the same IP address", 1}; boost::optional<std::vector<proxy>> get_proxies(boost::program_options::variables_map const& vm) { diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 9fba5d636..f2888674b 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -139,6 +139,7 @@ namespace nodetool typedef COMMAND_HANDSHAKE_T<typename t_payload_net_handler::payload_type> COMMAND_HANDSHAKE; typedef COMMAND_TIMED_SYNC_T<typename t_payload_net_handler::payload_type> COMMAND_TIMED_SYNC; + static_assert(p2p_connection_context::handshake_command() == COMMAND_HANDSHAKE::ID, "invalid handshake command id"); typedef epee::net_utils::boosted_tcp_server<epee::levin::async_protocol_handler<p2p_connection_context>> net_server; @@ -257,7 +258,8 @@ namespace nodetool m_igd(no_igd), m_offline(false), is_closing(false), - m_network_id() + m_network_id(), + max_connections(1) {} virtual ~node_server(); @@ -342,10 +344,9 @@ namespace nodetool virtual void on_connection_close(p2p_connection_context& context); 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 bool relay_notify_to_list(int command, epee::levin::message_writer message, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections) final; virtual epee::net_utils::zone send_txs(std::vector<cryptonote::blobdata> txs, const epee::net_utils::zone origin, const boost::uuids::uuid& source, cryptonote::relay_method tx_relay); - 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 invoke_notify_to_peer(int command, epee::levin::message_writer message, const epee::net_utils::connection_context_base& context) final; virtual bool drop_connection(const epee::net_utils::connection_context_base& context); virtual void request_callback(const epee::net_utils::connection_context_base& context); virtual void for_each_connection(std::function<bool(typename t_payload_net_handler::connection_context&, peerid_type, uint32_t)> f); @@ -516,6 +517,8 @@ namespace nodetool epee::net_utils::ssl_support_t m_ssl_support; bool m_enable_dns_blocklist; + + uint32_t max_connections; }; const int64_t default_limit_up = P2P_DEFAULT_LIMIT_RATE_UP; // kB/s @@ -550,6 +553,7 @@ namespace nodetool 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; + extern const command_line::arg_descriptor<uint32_t> arg_max_connections_per_ip; } POP_WARNINGS diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 315ebfdde..e1d6d1e10 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -131,6 +131,7 @@ namespace nodetool 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); + command_line::add_arg(desc, arg_max_connections_per_ip); } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> @@ -234,6 +235,7 @@ namespace nodetool return false; const time_t now = time(nullptr); + bool added = false; CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); time_t limit; @@ -244,7 +246,10 @@ namespace nodetool const std::string host_str = addr.host_str(); auto it = m_blocked_hosts.find(host_str); if (it == m_blocked_hosts.end()) + { m_blocked_hosts[host_str] = limit; + added = true; + } else if (it->second < limit || !add_only) it->second = limit; @@ -275,7 +280,10 @@ namespace nodetool conns.clear(); } - MCLOG_CYAN(el::Level::Info, "global", "Host " << host_str << " blocked."); + if (added) + MCLOG_CYAN(el::Level::Info, "global", "Host " << host_str << " blocked."); + else + MINFO("Host " << host_str << " block time updated."); return true; } //----------------------------------------------------------------------------------- @@ -608,6 +616,8 @@ namespace nodetool return false; } + max_connections = command_line::get_arg(vm, arg_max_connections_per_ip); + return true; } //----------------------------------------------------------------------------------- @@ -887,32 +897,6 @@ namespace nodetool for(const auto& p: m_command_line_peers) m_network_zones.at(p.adr.get_zone()).m_peerlist.append_with_peer_white(p); -// all peers are now setup -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - for (auto& zone : m_network_zones) - { - std::list<peerlist_entry> plw; - while (zone.second.m_peerlist.get_white_peers_count()) - { - plw.push_back(peerlist_entry()); - zone.second.m_peerlist.get_white_peer_by_index(plw.back(), 0); - zone.second.m_peerlist.remove_from_peer_white(plw.back()); - } - for (auto &e:plw) - zone.second.m_peerlist.append_with_peer_white(e); - - std::list<peerlist_entry> plg; - while (zone.second.m_peerlist.get_gray_peers_count()) - { - plg.push_back(peerlist_entry()); - zone.second.m_peerlist.get_gray_peer_by_index(plg.back(), 0); - zone.second.m_peerlist.remove_from_peer_gray(plg.back()); - } - for (auto &e:plg) - zone.second.m_peerlist.append_with_peer_gray(e); - } -#endif - //only in case if we really sure that we have external visible ip m_have_address = true; @@ -1155,6 +1139,7 @@ namespace nodetool pi = context.peer_id = rsp.node_data.peer_id; context.m_rpc_port = rsp.node_data.rpc_port; context.m_rpc_credits_per_hash = rsp.node_data.rpc_credits_per_hash; + context.support_flags = rsp.node_data.support_flags; const auto azone = context.m_remote_address.get_zone(); network_zone& zone = m_network_zones.at(azone); zone.m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_address, context.m_pruning_seed, context.m_rpc_port, context.m_rpc_credits_per_hash); @@ -1188,10 +1173,11 @@ namespace nodetool } else if (!just_take_peerlist) { - try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - flags_context.support_flags = support_flags; - }); + if (context_.support_flags == 0) + try_get_support_flags(context_, [](p2p_connection_context& flags_context, const uint32_t& support_flags) + { + flags_context.support_flags = support_flags; + }); } return hsh_result; @@ -2014,15 +2000,24 @@ namespace nodetool boost::split(ips, record, boost::is_any_of(";")); for (const auto &ip: ips) { + if (ip.empty()) + continue; + auto subnet = net::get_ipv4_subnet_address(ip); + if (subnet) + { + block_subnet(*subnet, DNS_BLOCKLIST_LIFETIME); + ++good; + continue; + } const expect<epee::net_utils::network_address> parsed_addr = net::get_network_address(ip, 0); - if (!parsed_addr) + if (parsed_addr) { - MWARNING("Invalid IP address from DNS blocklist: " << ip << " - " << parsed_addr.error()); - ++bad; + block_host(*parsed_addr, DNS_BLOCKLIST_LIFETIME, true); + ++good; continue; } - block_host(*parsed_addr, DNS_BLOCKLIST_LIFETIME, true); - ++good; + MWARNING("Invalid IP address or subnet from DNS blocklist: " << ip << " - " << parsed_addr.error()); + ++bad; } } if (good > 0) @@ -2117,10 +2112,6 @@ namespace nodetool 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); -#endif } return true; } @@ -2149,6 +2140,7 @@ namespace nodetool LOG_DEBUG_CC(context, "REMOTE PEERLIST: remote peerlist size=" << peerlist_.size()); LOG_TRACE_CC(context, "REMOTE PEERLIST: " << ENDL << print_peerlist_to_string(peerlist_)); + CRITICAL_REGION_LOCAL(m_blocked_hosts_lock); return m_network_zones.at(context.m_remote_address.get_zone()).m_peerlist.merge_peerlist(peerlist_, [this](const peerlist_entry &pe) { return !is_addr_recently_failed(pe.adr) && is_remote_host_allowed(pe.adr); }); @@ -2165,6 +2157,7 @@ namespace nodetool node_data.rpc_port = zone.m_can_pingback ? m_rpc_port : 0; node_data.rpc_credits_per_hash = zone.m_can_pingback ? m_rpc_credits_per_hash : 0; node_data.network_id = m_network_id; + node_data.support_flags = zone.m_config.m_support_flags; return true; } //----------------------------------------------------------------------------------- @@ -2182,8 +2175,9 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::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) + bool node_server<t_payload_net_handler>::relay_notify_to_list(int command, epee::levin::message_writer data_buff, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections) { + epee::byte_slice message = data_buff.finalize_notify(command); std::sort(connections.begin(), connections.end()); auto zone = m_network_zones.begin(); for(const auto& c_id: connections) @@ -2201,7 +2195,7 @@ namespace nodetool ++zone; } if (zone->first == c_id.first) - zone->second.m_net_server.get_config_object().notify(command, data_buff, c_id.second); + zone->second.m_net_server.get_config_object().send(message.clone(), c_id.second); } return true; } @@ -2268,24 +2262,13 @@ namespace nodetool } //----------------------------------------------------------------------------------- template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::invoke_notify_to_peer(int command, const epee::span<const uint8_t> req_buff, const epee::net_utils::connection_context_base& context) + bool node_server<t_payload_net_handler>::invoke_notify_to_peer(const int command, epee::levin::message_writer message, const epee::net_utils::connection_context_base& context) { if(is_filtered_command(context.m_remote_address, command)) return false; network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone()); - int res = zone.m_net_server.get_config_object().notify(command, req_buff, context.m_connection_id); - return res > 0; - } - //----------------------------------------------------------------------------------- - template<class t_payload_net_handler> - bool node_server<t_payload_net_handler>::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) - { - if(is_filtered_command(context.m_remote_address, command)) - return false; - - network_zone& zone = m_network_zones.at(context.m_remote_address.get_zone()); - int res = zone.m_net_server.get_config_object().invoke(command, req_buff, resp_buff, context.m_connection_id); + int res = zone.m_net_server.get_config_object().send(message.finalize_notify(command), context.m_connection_id); return res > 0; } //----------------------------------------------------------------------------------- @@ -2533,6 +2516,7 @@ namespace nodetool context.m_in_timedsync = false; context.m_rpc_port = arg.node_data.rpc_port; context.m_rpc_credits_per_hash = arg.node_data.rpc_credits_per_hash; + context.support_flags = arg.node_data.support_flags; if(arg.node_data.my_port && zone.m_can_pingback) { @@ -2566,10 +2550,11 @@ namespace nodetool }); } - try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) - { - flags_context.support_flags = support_flags; - }); + if (context.support_flags == 0) + try_get_support_flags(context, [](p2p_connection_context& flags_context, const uint32_t& support_flags) + { + flags_context.support_flags = support_flags; + }); //fill response zone.m_peerlist.get_peerlist_head(rsp.local_peerlist_new, true); @@ -2835,8 +2820,7 @@ namespace nodetool if (address.get_zone() != epee::net_utils::zone::public_) return false; // Unable to determine how many connections from host - const size_t max_connections = 1; - size_t count = 0; + uint32_t count = 0; m_network_zones.at(epee::net_utils::zone::public_).m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) { diff --git a/src/p2p/net_node_common.h b/src/p2p/net_node_common.h index 0da758ad4..92b7596ae 100644 --- a/src/p2p/net_node_common.h +++ b/src/p2p/net_node_common.h @@ -40,6 +40,8 @@ #include "net/net_utils_base.h" #include "p2p_protocol_defs.h" +namespace epee { namespace levin { class message_writer; } } + namespace nodetool { @@ -49,10 +51,9 @@ namespace nodetool template<class t_connection_context> 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 bool relay_notify_to_list(int command, epee::levin::message_writer message, 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, cryptonote::relay_method tx_relay)=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 invoke_notify_to_peer(int command, epee::levin::message_writer message, const epee::net_utils::connection_context_base& context)=0; virtual bool drop_connection(const epee::net_utils::connection_context_base& context)=0; virtual void request_callback(const epee::net_utils::connection_context_base& context)=0; virtual uint64_t get_public_connections_count()=0; @@ -71,7 +72,7 @@ namespace nodetool template<class t_connection_context> struct p2p_endpoint_stub: public i_p2p_endpoint<t_connection_context> { - 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 bool relay_notify_to_list(int command, epee::levin::message_writer message, std::vector<std::pair<epee::net_utils::zone, boost::uuids::uuid>> connections) { return false; } @@ -79,11 +80,7 @@ namespace nodetool { return epee::net_utils::zone::invalid; } - 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) - { - return false; - } - 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 invoke_notify_to_peer(int command, epee::levin::message_writer message, const epee::net_utils::connection_context_base& context) { return true; } diff --git a/src/p2p/net_peerlist.h b/src/p2p/net_peerlist.h index c794b0f3b..d8de6abe9 100644 --- a/src/p2p/net_peerlist.h +++ b/src/p2p/net_peerlist.h @@ -110,7 +110,7 @@ namespace nodetool bool get_gray_peer_by_index(peerlist_entry& p, size_t i); template<typename F> bool foreach(bool white, const F &f); void evict_host_from_white_peerlist(const peerlist_entry& pr); - bool append_with_peer_white(const peerlist_entry& pr); + bool append_with_peer_white(const peerlist_entry& pr, bool trust_last_seen = false); bool append_with_peer_gray(const peerlist_entry& pr); bool append_with_peer_anchor(const anchor_peerlist_entry& ple); bool set_peer_just_seen(peerid_type peer, const epee::net_utils::network_address& addr, uint32_t pruning_seed, uint16_t rpc_port, uint32_t rpc_credits_per_hash); @@ -329,12 +329,12 @@ namespace nodetool ple.pruning_seed = pruning_seed; ple.rpc_port = rpc_port; ple.rpc_credits_per_hash = rpc_credits_per_hash; - return append_with_peer_white(ple); + return append_with_peer_white(ple, true); CATCH_ENTRY_L0("peerlist_manager::set_peer_just_seen()", false); } //-------------------------------------------------------------------------------------------------- inline - bool peerlist_manager::append_with_peer_white(const peerlist_entry& ple) + bool peerlist_manager::append_with_peer_white(const peerlist_entry& ple, bool trust_last_seen) { TRY_ENTRY(); if(!is_host_allowed(ple.adr)) @@ -357,7 +357,8 @@ namespace nodetool new_ple.pruning_seed = by_addr_it_wt->pruning_seed; if (by_addr_it_wt->rpc_port && ple.rpc_port == 0) // guard against older nodes not passing RPC port around new_ple.rpc_port = by_addr_it_wt->rpc_port; - new_ple.last_seen = by_addr_it_wt->last_seen; // do not overwrite the last seen timestamp, incoming peer list are untrusted + if (!trust_last_seen) + new_ple.last_seen = by_addr_it_wt->last_seen; // do not overwrite the last seen timestamp, incoming peer lists are untrusted m_peers_white.replace(by_addr_it_wt, new_ple); } //remove from gray list, if need diff --git a/src/p2p/net_peerlist_boost_serialization.h b/src/p2p/net_peerlist_boost_serialization.h index 37a85d526..1cf4cb026 100644 --- a/src/p2p/net_peerlist_boost_serialization.h +++ b/src/p2p/net_peerlist_boost_serialization.h @@ -38,10 +38,6 @@ #include "net/i2p_address.h" #include "p2p/p2p_protocol_defs.h" -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED -#include "common/pruning.h" -#endif - BOOST_CLASS_VERSION(nodetool::peerlist_entry, 3) namespace boost @@ -228,12 +224,6 @@ namespace boost return; } a & pl.pruning_seed; -#ifdef CRYPTONOTE_PRUNING_DEBUG_SPOOF_SEED - if (!typename Archive::is_saving()) - { - pl.pruning_seed = tools::make_pruning_seed(1+pl.adr.as<epee::net_utils::ipv4_network_address>().ip() % (1<<CRYPTONOTE_PRUNING_LOG_STRIPES), CRYPTONOTE_PRUNING_LOG_STRIPES); - } -#endif if (ver < 2) { if (!typename Archive::is_saving()) diff --git a/src/p2p/p2p_protocol_defs.h b/src/p2p/p2p_protocol_defs.h index b439dc47e..12763d4ee 100644 --- a/src/p2p/p2p_protocol_defs.h +++ b/src/p2p/p2p_protocol_defs.h @@ -188,6 +188,7 @@ namespace nodetool uint16_t rpc_port; uint32_t rpc_credits_per_hash; peerid_type peer_id; + uint32_t support_flags; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_VAL_POD_AS_BLOB(network_id) @@ -195,6 +196,7 @@ namespace nodetool KV_SERIALIZE(my_port) KV_SERIALIZE_OPT(rpc_port, (uint16_t)(0)) KV_SERIALIZE_OPT(rpc_credits_per_hash, (uint32_t)0) + KV_SERIALIZE_OPT(support_flags, (uint32_t)0) END_KV_SERIALIZE_MAP() }; diff --git a/src/ringct/multiexp.cc b/src/ringct/multiexp.cc index f69b4a12c..620f7d0dd 100644 --- a/src/ringct/multiexp.cc +++ b/src/ringct/multiexp.cc @@ -372,7 +372,6 @@ std::shared_ptr<straus_cached_data> straus_init_cache(const std::vector<Multiexp if (N == 0) N = data.size(); CHECK_AND_ASSERT_THROW_MES(N <= data.size(), "Bad cache base data"); - ge_cached cached; ge_p1p1 p1; ge_p3 p3; std::shared_ptr<straus_cached_data> cache(new straus_cached_data()); @@ -454,7 +453,6 @@ rct::key straus(const std::vector<MultiexpData> &data, const std::shared_ptr<str std::shared_ptr<straus_cached_data> local_cache = cache == NULL ? straus_init_cache(data) : cache; ge_cached cached; ge_p1p1 p1; - ge_p3 p3; #ifdef TRACK_STRAUS_ZERO_IDENTITY MULTIEXP_PERF(PERF_TIMER_START_UNIT(skip, 1000000)); @@ -587,7 +585,6 @@ std::shared_ptr<pippenger_cached_data> pippenger_init_cache(const std::vector<Mu if (N == 0) N = data.size() - start_offset; CHECK_AND_ASSERT_THROW_MES(N <= data.size() - start_offset, "Bad cache base data"); - ge_cached cached; std::shared_ptr<pippenger_cached_data> cache(new pippenger_cached_data()); cache->size = N; diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 93eb52d4e..f5950c53c 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -725,7 +725,6 @@ namespace rct { CHECK_AND_ASSERT_THROW_MES((kLRki && mscout) || (!kLRki && !mscout), "Only one of kLRki/mscout is present"); keyV tmp(rows + 1); keyV sk(rows + 1); - size_t i; keyM M(cols, tmp); keyV P, C, C_nonzero; @@ -899,7 +898,6 @@ namespace rct { key R; geDsmp P_precomp; geDsmp C_precomp; - geDsmp H_precomp; size_t i = 0; ge_p3 hash8_p3; geDsmp hash_precomp; diff --git a/src/rpc/bootstrap_daemon.cpp b/src/rpc/bootstrap_daemon.cpp index 2fdd28406..ffea906d5 100644 --- a/src/rpc/bootstrap_daemon.cpp +++ b/src/rpc/bootstrap_daemon.cpp @@ -7,6 +7,7 @@ #include "crypto/crypto.h" #include "cryptonote_core/cryptonote_core.h" #include "misc_log_ex.h" +#include "net/parse.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc.bootstrap_daemon" @@ -16,19 +17,23 @@ namespace cryptonote bootstrap_daemon::bootstrap_daemon( std::function<std::map<std::string, bool>()> get_public_nodes, - bool rpc_payment_enabled) + bool rpc_payment_enabled, + const std::string &proxy) : m_selector(new bootstrap_node::selector_auto(std::move(get_public_nodes))) , m_rpc_payment_enabled(rpc_payment_enabled) { + set_proxy(proxy); } bootstrap_daemon::bootstrap_daemon( const std::string &address, boost::optional<epee::net_utils::http::login> credentials, - bool rpc_payment_enabled) + bool rpc_payment_enabled, + const std::string &proxy) : m_selector(nullptr) , m_rpc_payment_enabled(rpc_payment_enabled) { + set_proxy(proxy); if (!set_server(address, std::move(credentials))) { throw std::runtime_error("invalid bootstrap daemon address or credentials"); @@ -78,6 +83,18 @@ namespace cryptonote return success; } + void bootstrap_daemon::set_proxy(const std::string &address) + { + if (!address.empty() && !net::get_tcp_endpoint(address)) + { + throw std::runtime_error("invalid proxy address format"); + } + if (!m_http_client.set_proxy(address)) + { + throw std::runtime_error("failed to set proxy address"); + } + } + bool bootstrap_daemon::set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials /* = boost::none */) { if (!m_http_client.set_server(address, credentials)) diff --git a/src/rpc/bootstrap_daemon.h b/src/rpc/bootstrap_daemon.h index d54042b11..1e4477123 100644 --- a/src/rpc/bootstrap_daemon.h +++ b/src/rpc/bootstrap_daemon.h @@ -8,7 +8,7 @@ #include <boost/thread/mutex.hpp> #include <boost/utility/string_ref.hpp> -#include "net/http_client.h" +#include "net/http.h" #include "storages/http_abstract_invoke.h" #include "bootstrap_node_selector.h" @@ -21,11 +21,13 @@ namespace cryptonote public: bootstrap_daemon( std::function<std::map<std::string, bool>()> get_public_nodes, - bool rpc_payment_enabled); + bool rpc_payment_enabled, + const std::string &proxy); bootstrap_daemon( const std::string &address, boost::optional<epee::net_utils::http::login> credentials, - bool rpc_payment_enabled); + bool rpc_payment_enabled, + const std::string &proxy); std::string address() const noexcept; boost::optional<std::pair<uint64_t, uint64_t>> get_height(); @@ -72,12 +74,14 @@ namespace cryptonote return handle_result(result, result_struct.status); } + void set_proxy(const std::string &address); + private: bool set_server(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials = boost::none); bool switch_server_if_needed(); private: - epee::net_utils::http::http_simple_client m_http_client; + net::http::client m_http_client; const bool m_rpc_payment_enabled; const std::unique_ptr<bootstrap_node::selector> m_selector; boost::mutex m_selector_mutex; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5b2043de6..ac21a3a6b 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -30,6 +30,7 @@ #include <boost/preprocessor/stringize.hpp> #include <boost/uuid/nil_generator.hpp> +#include <boost/filesystem.hpp> #include "include_base_utils.h" #include "string_tools.h" using namespace epee; @@ -44,6 +45,7 @@ using namespace epee; #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/account.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/merge_mining.h" #include "cryptonote_core/tx_sanity_check.h" #include "misc_language.h" #include "net/parse.h" @@ -154,6 +156,7 @@ namespace cryptonote command_line::add_arg(desc, arg_restricted_rpc); command_line::add_arg(desc, arg_bootstrap_daemon_address); command_line::add_arg(desc, arg_bootstrap_daemon_login); + command_line::add_arg(desc, arg_bootstrap_daemon_proxy); cryptonote::rpc_args::init_options(desc, true); command_line::add_arg(desc, arg_rpc_payment_address); command_line::add_arg(desc, arg_rpc_payment_difficulty); @@ -172,7 +175,10 @@ namespace cryptonote , m_rpc_payment_allow_free_loopback(false) {} //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const std::string &username_password) + bool core_rpc_server::set_bootstrap_daemon( + const std::string &address, + const std::string &username_password, + const std::string &proxy) { boost::optional<epee::net_utils::http::login> credentials; const auto loc = username_password.find(':'); @@ -180,7 +186,7 @@ namespace cryptonote { credentials = epee::net_utils::http::login(username_password.substr(0, loc), username_password.substr(loc + 1)); } - return set_bootstrap_daemon(address, credentials); + return set_bootstrap_daemon(address, credentials, proxy); } //------------------------------------------------------------------------------------------------------------------------------ std::map<std::string, bool> core_rpc_server::get_public_nodes(uint32_t credits_per_hash_threshold/* = 0*/) @@ -217,7 +223,10 @@ namespace cryptonote return result; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials) + bool core_rpc_server::set_bootstrap_daemon( + const std::string &address, + const boost::optional<epee::net_utils::http::login> &credentials, + const std::string &proxy) { boost::unique_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex); @@ -233,11 +242,11 @@ namespace cryptonote auto get_nodes = [this]() { return get_public_nodes(credits_per_hash_threshold); }; - m_bootstrap_daemon.reset(new bootstrap_daemon(std::move(get_nodes), rpc_payment_enabled)); + m_bootstrap_daemon.reset(new bootstrap_daemon(std::move(get_nodes), rpc_payment_enabled, proxy)); } else { - m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials, rpc_payment_enabled)); + m_bootstrap_daemon.reset(new bootstrap_daemon(address, credentials, rpc_payment_enabled, proxy)); } m_should_use_bootstrap_daemon = m_bootstrap_daemon.get() != nullptr; @@ -278,6 +287,7 @@ namespace cryptonote } } disable_rpc_ban = rpc_config->disable_rpc_ban; + const std::string data_dir{command_line::get_arg(vm, cryptonote::arg_data_dir)}; std::string address = command_line::get_arg(vm, arg_rpc_payment_address); if (!address.empty() && allow_rpc_payment) { @@ -306,7 +316,7 @@ namespace cryptonote } m_rpc_payment_allow_free_loopback = command_line::get_arg(vm, arg_rpc_payment_allow_free_loopback); m_rpc_payment.reset(new rpc_payment(info.address, diff, credits)); - m_rpc_payment->load(command_line::get_arg(vm, cryptonote::arg_data_dir)); + m_rpc_payment->load(data_dir); m_p2p.set_rpc_credits_per_hash(RPC_CREDITS_PER_HASH_SCALE * (credits / (float)diff)); } @@ -318,8 +328,10 @@ namespace cryptonote MWARNING("The RPC server is accessible from the outside, but no RPC payment was setup. RPC access will be free for all."); } - if (!set_bootstrap_daemon(command_line::get_arg(vm, arg_bootstrap_daemon_address), - command_line::get_arg(vm, arg_bootstrap_daemon_login))) + if (!set_bootstrap_daemon( + command_line::get_arg(vm, arg_bootstrap_daemon_address), + command_line::get_arg(vm, arg_bootstrap_daemon_login), + command_line::get_arg(vm, arg_bootstrap_daemon_proxy))) { MFATAL("Failed to parse bootstrap daemon address"); return false; @@ -333,12 +345,32 @@ namespace cryptonote if (m_rpc_payment) m_net_server.add_idle_handler([this](){ return m_rpc_payment->on_idle(); }, 60 * 1000); + bool store_ssl_key = !restricted && rpc_config->ssl_options && rpc_config->ssl_options.auth.certificate_path.empty(); + const auto ssl_base_path = (boost::filesystem::path{data_dir} / "rpc_ssl").string(); + if (store_ssl_key && boost::filesystem::exists(ssl_base_path + ".crt")) + { + // load key from previous run, password prompted by OpenSSL + store_ssl_key = false; + rpc_config->ssl_options.auth = + epee::net_utils::ssl_authentication_t{ssl_base_path + ".key", ssl_base_path + ".crt"}; + } + auto rng = [](size_t len, uint8_t *ptr){ return crypto::rand(len, ptr); }; - return epee::http_server_impl_base<core_rpc_server, connection_context>::init( + const bool inited = epee::http_server_impl_base<core_rpc_server, connection_context>::init( rng, std::move(port), std::move(bind_ip_str), std::move(bind_ipv6_str), std::move(rpc_config->use_ipv6), std::move(rpc_config->require_ipv4), std::move(rpc_config->access_control_origins), std::move(http_login), std::move(rpc_config->ssl_options) ); + + if (store_ssl_key && inited) + { + // new keys were generated, store for next run + const auto error = epee::net_utils::store_ssl_keys(m_net_server.get_ssl_context(), ssl_base_path); + if (error) + MFATAL("Failed to store HTTP SSL cert/key for " << (restricted ? "restricted " : "") << "RPC server: " << error.message()); + return !bool(error); + } + return inited; } //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::check_payment(const std::string &client_message, uint64_t payment, const std::string &rpc, bool same_ts, std::string &message, uint64_t &credits, std::string &top_hash) @@ -367,7 +399,6 @@ namespace cryptonote message = "Client signature does not verify for " + rpc; return false; } - crypto::public_key local_client; if (!m_rpc_payment->pay(client, ts, payment, rpc, same_ts, credits)) { message = CORE_RPC_STATUS_PAYMENT_REQUIRED; @@ -452,7 +483,7 @@ namespace cryptonote m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height res.top_block_hash = string_tools::pod_to_hex(top_hash); - res.target_height = m_core.get_target_blockchain_height(); + res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height(); store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64); res.target = m_core.get_blockchain_storage().get_difficulty_target(); res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase @@ -1596,15 +1627,15 @@ namespace cryptonote { credentials = epee::net_utils::http::login(req.username, req.password); } - - if (set_bootstrap_daemon(req.address, credentials)) + + if (set_bootstrap_daemon(req.address, credentials, req.proxy)) { res.status = CORE_RPC_STATUS_OK; } else { res.status = "Failed to set bootstrap daemon"; - } + } return true; } @@ -1806,7 +1837,6 @@ namespace cryptonote return false; } } - uint64_t seed_height; crypto::hash seed_hash, next_seed_hash; if (!get_block_template(info.address, req.prev_block.empty() ? NULL : &prev_block, blob_reserve, reserved_offset, wdiff, res.height, res.expected_reward, b, res.seed_height, seed_hash, next_seed_hash, error_resp)) return false; @@ -1828,6 +1858,125 @@ namespace cryptonote return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool core_rpc_server::on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) + { + RPC_TRACKER(add_aux_pow); + bool r; + if (use_bootstrap_daemon_if_necessary<COMMAND_RPC_ADD_AUX_POW>(invoke_http_mode::JON_RPC, "add_aux_pow", req, res, r)) + return r; + + if (req.aux_pow.empty()) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Empty aux pow hash vector"; + return false; + } + + crypto::hash merkle_root; + size_t merkle_tree_depth = 0; + std::vector<std::pair<crypto::hash, crypto::hash>> aux_pow; + std::vector<crypto::hash> aux_pow_raw; + aux_pow.reserve(req.aux_pow.size()); + aux_pow_raw.reserve(req.aux_pow.size()); + for (const auto &s: req.aux_pow) + { + aux_pow.push_back({}); + if (!epee::string_tools::hex_to_pod(s.id, aux_pow.back().first)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Invalid aux pow id"; + return false; + } + if (!epee::string_tools::hex_to_pod(s.hash, aux_pow.back().second)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Invalid aux pow hash"; + return false; + } + aux_pow_raw.push_back(aux_pow.back().second); + } + + size_t path_domain = 1; + while ((1u << path_domain) < aux_pow.size()) + ++path_domain; + uint32_t nonce; + const uint32_t max_nonce = 65535; + bool collision = true; + for (nonce = 0; nonce <= max_nonce; ++nonce) + { + std::vector<bool> slots(aux_pow.size(), false); + collision = false; + for (size_t idx = 0; idx < aux_pow.size(); ++idx) + { + const uint32_t slot = cryptonote::get_aux_slot(aux_pow[idx].first, nonce, aux_pow.size()); + if (slot >= aux_pow.size()) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Computed slot is out of range"; + return false; + } + if (slots[slot]) + { + collision = true; + break; + } + slots[slot] = true; + } + if (!collision) + break; + } + if (collision) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Failed to find a suitable nonce"; + return false; + } + + crypto::tree_hash((const char(*)[crypto::HASH_SIZE])aux_pow_raw.data(), aux_pow_raw.size(), merkle_root.data); + res.merkle_root = epee::string_tools::pod_to_hex(merkle_root); + res.merkle_tree_depth = cryptonote::encode_mm_depth(aux_pow.size(), nonce); + + blobdata blocktemplate_blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.blocktemplate_blob, blocktemplate_blob)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; + error_resp.message = "Invalid blocktemplate_blob"; + return false; + } + + block b; + if (!parse_and_validate_block_from_blob(blocktemplate_blob, b)) + { + error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; + error_resp.message = "Wrong blocktemplate_blob"; + return false; + } + + if (!remove_field_from_tx_extra(b.miner_tx.extra, typeid(cryptonote::tx_extra_merge_mining_tag))) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Error removing existing merkle root"; + return false; + } + if (!add_mm_merkle_root_to_tx_extra(b.miner_tx.extra, merkle_root, merkle_tree_depth)) + { + error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; + error_resp.message = "Error adding merkle root"; + return false; + } + b.invalidate_hashes(); + b.miner_tx.invalidate_hashes(); + + const blobdata block_blob = t_serializable_object_to_blob(b); + const blobdata hashing_blob = get_block_hashing_blob(b); + + res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); + res.blockhashing_blob = string_tools::buff_to_hex_nodelimer(hashing_blob); + res.aux_pow = req.aux_pow; + res.status = CORE_RPC_STATUS_OK; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx) { RPC_TRACKER(submitblock); @@ -2913,7 +3062,7 @@ namespace cryptonote crypto::hash top_hash; m_core.get_blockchain_top(res.height, top_hash); ++res.height; // turn top block height into blockchain height - res.target_height = m_core.get_target_blockchain_height(); + res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height(); res.next_needed_pruning_seed = m_p2p.get_payload_object().get_next_needed_pruning_stripe().second; for (const auto &c: m_p2p.get_payload_object().get_connections()) @@ -3349,6 +3498,12 @@ namespace cryptonote , "" }; + const command_line::arg_descriptor<std::string> core_rpc_server::arg_bootstrap_daemon_proxy = { + "bootstrap-daemon-proxy" + , "<ip>:<port> socks proxy to use for bootstrap daemon connections" + , "" + }; + const command_line::arg_descriptor<std::string> core_rpc_server::arg_rpc_payment_address = { "rpc-payment-address" , "Restrict RPC to clients sending micropayment to this address" diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index dcf6b4e4b..b21e43ab0 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -72,6 +72,7 @@ namespace cryptonote static const command_line::arg_descriptor<bool> arg_rpc_ssl_allow_any_cert; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_address; static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_login; + static const command_line::arg_descriptor<std::string> arg_bootstrap_daemon_proxy; static const command_line::arg_descriptor<std::string> arg_rpc_payment_address; static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_difficulty; static const command_line::arg_descriptor<uint64_t> arg_rpc_payment_credits; @@ -146,6 +147,7 @@ namespace cryptonote MAP_JON_RPC_WE("on_getblockhash", on_getblockhash, COMMAND_RPC_GETBLOCKHASH) MAP_JON_RPC_WE("get_block_template", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) MAP_JON_RPC_WE("getblocktemplate", on_getblocktemplate, COMMAND_RPC_GETBLOCKTEMPLATE) + MAP_JON_RPC_WE("add_aux_pow", on_add_aux_pow, COMMAND_RPC_ADD_AUX_POW) MAP_JON_RPC_WE("submit_block", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE("submitblock", on_submitblock, COMMAND_RPC_SUBMITBLOCK) MAP_JON_RPC_WE_IF("generateblocks", on_generateblocks, COMMAND_RPC_GENERATEBLOCKS, !m_restricted) @@ -226,6 +228,7 @@ namespace cryptonote bool on_getblockcount(const COMMAND_RPC_GETBLOCKCOUNT::request& req, COMMAND_RPC_GETBLOCKCOUNT::response& res, const connection_context *ctx = NULL); bool on_getblockhash(const COMMAND_RPC_GETBLOCKHASH::request& req, COMMAND_RPC_GETBLOCKHASH::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_getblocktemplate(const COMMAND_RPC_GETBLOCKTEMPLATE::request& req, COMMAND_RPC_GETBLOCKTEMPLATE::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); + bool on_add_aux_pow(const COMMAND_RPC_ADD_AUX_POW::request& req, COMMAND_RPC_ADD_AUX_POW::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_generateblocks(const COMMAND_RPC_GENERATEBLOCKS::request& req, COMMAND_RPC_GENERATEBLOCKS::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); bool on_get_last_block_header(const COMMAND_RPC_GET_LAST_BLOCK_HEADER::request& req, COMMAND_RPC_GET_LAST_BLOCK_HEADER::response& res, epee::json_rpc::error& error_resp, const connection_context *ctx = NULL); @@ -268,8 +271,14 @@ private: uint64_t get_block_reward(const block& blk); bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash); std::map<std::string, bool> get_public_nodes(uint32_t credits_per_hash_threshold = 0); - bool set_bootstrap_daemon(const std::string &address, const std::string &username_password); - bool set_bootstrap_daemon(const std::string &address, const boost::optional<epee::net_utils::http::login> &credentials); + bool set_bootstrap_daemon( + const std::string &address, + const std::string &username_password, + const std::string &proxy); + bool set_bootstrap_daemon( + const std::string &address, + const boost::optional<epee::net_utils::http::login> &credentials, + const std::string &proxy); enum invoke_http_mode { JON, BIN, JON_RPC }; template <typename COMMAND_TYPE> bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index bbcb27f1c..5ebe4f654 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 5 +#define CORE_RPC_VERSION_MINOR 6 #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) @@ -938,6 +938,52 @@ namespace cryptonote typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_ADD_AUX_POW + { + struct aux_pow_t + { + std::string id; + std::string hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(id) + KV_SERIALIZE(hash) + END_KV_SERIALIZE_MAP() + }; + + struct request_t: public rpc_request_base + { + blobdata blocktemplate_blob; + std::vector<aux_pow_t> aux_pow; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_request_base) + KV_SERIALIZE(blocktemplate_blob) + KV_SERIALIZE(aux_pow) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t: public rpc_response_base + { + blobdata blocktemplate_blob; + blobdata blockhashing_blob; + std::string merkle_root; + uint32_t merkle_tree_depth; + std::vector<aux_pow_t> aux_pow; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(blocktemplate_blob) + KV_SERIALIZE(blockhashing_blob) + KV_SERIALIZE(merkle_root) + KV_SERIALIZE(merkle_tree_depth) + KV_SERIALIZE(aux_pow) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_SUBMITBLOCK { typedef std::vector<std::string> request; @@ -1613,11 +1659,13 @@ namespace cryptonote std::string address; std::string username; std::string password; + std::string proxy; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(address) KV_SERIALIZE(username) KV_SERIALIZE(password) + KV_SERIALIZE(proxy) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/src/rpc/rpc_payment.cpp b/src/rpc/rpc_payment.cpp index 176f11fa3..bf6584f72 100644 --- a/src/rpc/rpc_payment.cpp +++ b/src/rpc/rpc_payment.cpp @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <boost/archive/portable_binary_iarchive.hpp> +#include <boost/filesystem.hpp> #include "cryptonote_config.h" #include "include_base_utils.h" #include "string_tools.h" diff --git a/src/serialization/binary_archive.h b/src/serialization/binary_archive.h index 80104c8f7..49ca8aa57 100644 --- a/src/serialization/binary_archive.h +++ b/src/serialization/binary_archive.h @@ -98,7 +98,7 @@ template <> struct binary_archive<false> : public binary_archive_base<std::istream, false> { - explicit binary_archive(stream_type &s) : base_type(s) { + explicit binary_archive(stream_type &s) : base_type(s), varint_bug_backward_compatibility_(false) { stream_type::pos_type pos = stream_.tellg(); stream_.seekg(0, std::ios_base::end); eof_pos_ = stream_.tellg(); @@ -173,8 +173,13 @@ struct binary_archive<false> : public binary_archive_base<std::istream, false> assert(stream_.tellg() <= eof_pos_); return eof_pos_ - stream_.tellg(); } + + void enable_varint_bug_backward_compatibility() { varint_bug_backward_compatibility_ = true; } + bool varint_bug_backward_compatibility_enabled() const { return varint_bug_backward_compatibility_; } + protected: std::streamoff eof_pos_; + bool varint_bug_backward_compatibility_; }; template <> @@ -227,6 +232,8 @@ struct binary_archive<true> : public binary_archive_base<std::ostream, true> void write_variant_tag(variant_tag_type t) { serialize_int(t); } + + bool varint_bug_backward_compatibility_enabled() const { return false; } }; POP_WARNINGS diff --git a/src/serialization/container.h b/src/serialization/container.h index d5e75bb4f..a4997c8ae 100644 --- a/src/serialization/container.h +++ b/src/serialization/container.h @@ -32,22 +32,27 @@ namespace serialization { namespace detail { - template <typename Archive, class T> - bool serialize_container_element(Archive& ar, T& e) + template<typename T> + inline constexpr bool use_container_varint() noexcept { - return ::do_serialize(ar, e); + return std::is_integral<T>::value && std::is_unsigned<T>::value && sizeof(T) > 1; } - template <typename Archive> - bool serialize_container_element(Archive& ar, uint32_t& e) + template <typename Archive, class T> + typename std::enable_if<!use_container_varint<T>(), bool>::type + serialize_container_element(Archive& ar, T& e) { - ar.serialize_varint(e); - return true; + return ::do_serialize(ar, e); } - template <typename Archive> - bool serialize_container_element(Archive& ar, uint64_t& e) + template<typename Archive, typename T> + typename std::enable_if<use_container_varint<T>(), bool>::type + serialize_container_element(Archive& ar, T& e) { + static constexpr const bool previously_varint = std::is_same<uint64_t, T>() || std::is_same<uint32_t, T>(); + + if (!previously_varint && ar.varint_bug_backward_compatibility_enabled() && !typename Archive::is_saving()) + return ::do_serialize(ar, e); ar.serialize_varint(e); return true; } diff --git a/src/serialization/json_archive.h b/src/serialization/json_archive.h index 50dd5bbd0..3f98b5101 100644 --- a/src/serialization/json_archive.h +++ b/src/serialization/json_archive.h @@ -84,6 +84,8 @@ struct json_archive_base void end_variant() { end_object(); } Stream &stream() { return stream_; } + bool varint_bug_backward_compatibility_enabled() const { return false; } + protected: void make_indent() { diff --git a/src/serialization/pair.h b/src/serialization/pair.h index 18280d837..44aafa04d 100644 --- a/src/serialization/pair.h +++ b/src/serialization/pair.h @@ -30,21 +30,34 @@ #pragma once #include <memory> +#include <boost/type_traits/make_unsigned.hpp> #include "serialization.h" namespace serialization { namespace detail { + template<typename T> + inline constexpr bool use_pair_varint() noexcept + { + return std::is_integral<T>::value && std::is_unsigned<T>::value && sizeof(T) > 1; + } + template <typename Archive, class T> - bool serialize_pair_element(Archive& ar, T& e) + typename std::enable_if<!use_pair_varint<T>(), bool>::type + serialize_pair_element(Archive& ar, T& e) { return ::do_serialize(ar, e); } - template <typename Archive> - bool serialize_pair_element(Archive& ar, uint64_t& e) + template<typename Archive, typename T> + typename std::enable_if<use_pair_varint<T>(), bool>::type + serialize_pair_element(Archive& ar, T& e) { + static constexpr const bool previously_varint = std::is_same<uint64_t, T>(); + + if (!previously_varint && ar.varint_bug_backward_compatibility_enabled() && !typename Archive::is_saving()) + return ::do_serialize(ar, e); ar.serialize_varint(e); return true; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 0635520c6..da6501183 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -50,6 +50,7 @@ #include <boost/format.hpp> #include <boost/regex.hpp> #include <boost/range/adaptor/transformed.hpp> +#include <boost/filesystem.hpp> #include "include_base_utils.h" #include "console_handler.h" #include "common/i18n.h" @@ -195,7 +196,7 @@ namespace const char* USAGE_SWEEP_BELOW("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_SINGLE("sweep_single [<priority>] [<ring_size>] [outputs=<N>] <key_image> <address> [<payment_id (obsolete)>]"); const char* USAGE_DONATE("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id (obsolete)>]"); - const char* USAGE_SIGN_TRANSFER("sign_transfer [export_raw]"); + const char* USAGE_SIGN_TRANSFER("sign_transfer [export_raw] [<filename>]"); const char* USAGE_SET_LOG("set_log <level>|{+,-,}<categories>"); const char* USAGE_ACCOUNT("account\n" " account new <label text with white spaces allowed>\n" @@ -276,12 +277,13 @@ namespace const char* USAGE_PUBLIC_NODES("public_nodes"); const char* USAGE_WELCOME("welcome"); const char* USAGE_RPC_PAYMENT_INFO("rpc_payment_info"); - const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc"); + const char* USAGE_START_MINING_FOR_RPC("start_mining_for_rpc [<number_of_threads>]"); const char* USAGE_STOP_MINING_FOR_RPC("stop_mining_for_rpc"); const char* USAGE_SHOW_QR_CODE("show_qr_code [<subaddress_index>]"); const char* USAGE_VERSION("version"); const char* USAGE_HELP("help [<command> | all]"); const char* USAGE_APROPOS("apropos <keyword> [<keyword> ...]"); + const char* USAGE_SCAN_TX("scan_tx <txid> [<txid> ...]"); std::string input_line(const std::string& prompt, bool yesno = false) { @@ -1360,7 +1362,7 @@ bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, b size_t n_outputs = m_wallet->import_multisig(info); // Clear line "Height xxx of xxx" std::cout << "\r \r"; - success_msg_writer() << tr("Multisig info imported"); + success_msg_writer() << tr("Multisig info imported. Number of outputs updated: ") << n_outputs; } catch (const std::exception &e) { @@ -2360,6 +2362,24 @@ bool simple_wallet::start_mining_for_rpc(const std::vector<std::string> &args) if (!try_connect_to_daemon()) return true; + bool ok = true; + if(args.size() >= 1) + { + uint16_t num = 0; + ok = string_tools::get_xtype_from_string(num, args[0]); + m_rpc_payment_threads = num; + } + else + { + m_rpc_payment_threads = 0; + } + + if (!ok) + { + PRINT_USAGE(USAGE_START_MINING_FOR_RPC); + return true; + } + LOCK_IDLE_SCOPE(); bool payment_required; @@ -3214,6 +3234,45 @@ bool simple_wallet::apropos(const std::vector<std::string> &args) return true; } +bool simple_wallet::scan_tx(const std::vector<std::string> &args) +{ + if (args.empty()) + { + PRINT_USAGE(USAGE_SCAN_TX); + return true; + } + + // Parse and dedup args + std::unordered_set<crypto::hash> txids; + for (const auto &s : args) { + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(s, txid)) { + fail_msg_writer() << tr("Invalid txid specified: ") << s; + return true; + } + txids.insert(txid); + } + std::vector<crypto::hash> txids_v(txids.begin(), txids.end()); + + if (!m_wallet->is_trusted_daemon()) { + message_writer(console_color_red, true) << tr("WARNING: this operation may reveal the txids to the remote node and affect your privacy"); + if (!command_line::is_yes(input_line("Do you want to continue?", true))) { + message_writer() << tr("You have canceled the operation"); + return true; + } + } + + LOCK_IDLE_SCOPE(); + m_in_manual_refresh.store(true); + try { + m_wallet->scan_tx(txids_v); + } catch (const std::exception &e) { + fail_msg_writer() << e.what(); + } + m_in_manual_refresh.store(false); + return true; +} + simple_wallet::simple_wallet() : m_allow_mismatched_daemon_version(false) , m_refresh_progress_reporter(*this) @@ -3301,7 +3360,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::sign_transfer, _1), tr(USAGE_SIGN_TRANSFER), - tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported.")); + tr("Sign a transaction from a file. If the parameter \"export_raw\" is specified, transaction raw hex data suitable for the daemon RPC /sendrawtransaction is exported.\n" + "Use the parameter <filename> to specify the file to read from. If not specified, the default \"unsigned_monero_tx\" will be used.")); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::submit_transfer, _1), tr("Submit a signed transaction from a file.")); @@ -3763,6 +3823,10 @@ simple_wallet::simple_wallet() boost::bind(&simple_wallet::on_command, this, &simple_wallet::apropos, _1), tr(USAGE_APROPOS), tr("Search all command descriptions for keyword(s)")); + m_cmd_binder.set_handler("scan_tx", + boost::bind(&simple_wallet::on_command, this, &simple_wallet::scan_tx, _1), + tr(USAGE_SCAN_TX), + tr("Scan the transactions given by <txid>(s), processing them and looking for outputs")); 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)); @@ -4519,7 +4583,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) password = *r; welcome = true; // if no block_height is specified, assume its a new account and start it "now" - if(m_wallet->get_refresh_from_block_height() == 0) { + if (command_line::is_arg_defaulted(vm, arg_restore_height)) { { tools::scoped_message_writer wrt = tools::msg_writer(); wrt << tr("No restore height is specified.") << " "; @@ -4765,9 +4829,14 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) if (!m_wallet->check_connection(version)) { if (!silent) - fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << - tr("Daemon either is not started or wrong port was passed. " - "Please make sure daemon is running or change the daemon address using the 'set_daemon' command."); + { + if (m_wallet->is_offline()) + fail_msg_writer() << tr("wallet failed to connect to daemon, because it is set to offline mode"); + else + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << + tr("Daemon either is not started or wrong port was passed. " + "Please make sure daemon is running or change the daemon address using the 'set_daemon' command."); + } return false; } if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) @@ -5932,7 +6001,7 @@ bool simple_wallet::show_balance_unlocked(bool detailed) if (m_wallet->has_multisig_partial_key_images()) extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); else if (m_wallet->has_unknown_key_images()) - extra += tr(" (Some owned outputs have missing key images - import_key_images needed)"); + extra += tr(" (Some owned outputs have missing key images - export_outputs, import_outputs, export_key_images, and import_key_images needed)"); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account]; success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag); @@ -6463,8 +6532,6 @@ void simple_wallet::check_for_inactivity_lock(bool user) //---------------------------------------------------------------------------------------------------- bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std::string>&), const std::vector<std::string> &args) { - const time_t now = time(NULL); - time_t dt = now - m_last_activity_time; m_last_activity_time = time(NULL); m_in_command = true; @@ -7430,7 +7497,6 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) if (local_args.size() == 3) { crypto::hash payment_id; - crypto::hash8 payment_id8; std::string extra_nonce; if (tools::wallet2::parse_long_payment_id(local_args.back(), payment_id)) { @@ -7886,19 +7952,33 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) fail_msg_writer() << tr("This is a watch only wallet"); return true; } - if (args_.size() > 1 || (args_.size() == 1 && args_[0] != "export_raw")) + + bool export_raw = false; + std::string unsigned_filename = "unsigned_monero_tx"; + if (args_.size() > 2 || (args_.size() == 2 && args_[0] != "export_raw")) { PRINT_USAGE(USAGE_SIGN_TRANSFER); return true; } + else if (args_.size() == 2) + { + export_raw = true; + unsigned_filename = args_[1]; + } + else if (args_.size() == 1) + { + if (args_[0] == "export_raw") + export_raw = true; + else + unsigned_filename = args_[0]; + } SCOPED_WALLET_UNLOCK(); - const bool export_raw = args_.size() == 1; std::vector<tools::wallet2::pending_tx> ptx; try { - bool r = m_wallet->sign_tx("unsigned_monero_tx", "signed_monero_tx", ptx, [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); }, export_raw); + bool r = m_wallet->sign_tx(unsigned_filename, "signed_monero_tx", ptx, [&](const tools::wallet2::unsigned_tx_set &tx){ return accept_loaded_tx(tx); }, export_raw); if (!r) { fail_msg_writer() << tr("Failed to sign transaction"); @@ -8596,7 +8676,6 @@ bool simple_wallet::get_transfers(std::vector<std::string>& local_args, std::vec if (!unlocked) { locked_msg = "locked"; - const uint64_t unlock_time = pd.m_unlock_time; if (pd.m_unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER) { uint64_t bh = std::max(pd.m_unlock_time, pd.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE); @@ -9267,7 +9346,7 @@ bool simple_wallet::check_rpc_payment() fail_msg_writer() << tr("Error mining to daemon: ") << error; m_cmd_binder.print_prompt(); }; - bool ret = m_wallet->search_for_rpc_payment(target, startfunc, contfunc, foundfunc, errorfunc); + bool ret = m_wallet->search_for_rpc_payment(target, m_rpc_payment_threads, startfunc, contfunc, foundfunc, errorfunc); if (!ret) { fail_msg_writer() << tr("Failed to start mining for RPC payment"); @@ -9298,7 +9377,7 @@ bool simple_wallet::run() refresh_main(0, ResetNone, true); - m_auto_refresh_enabled = m_wallet->auto_refresh(); + m_auto_refresh_enabled = !m_wallet->is_offline() && m_wallet->auto_refresh(); m_idle_thread = boost::thread([&]{wallet_idle_thread();}); message_writer(console_color_green, false) << "Background refresh thread started"; @@ -9461,7 +9540,7 @@ void simple_wallet::print_accounts() { const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags(); size_t num_untagged_accounts = m_wallet->get_num_subaddress_accounts(); - for (const std::pair<std::string, std::string>& p : account_tags.first) + for (const std::pair<const std::string, std::string>& p : account_tags.first) { const std::string& tag = p.first; print_accounts(tag); @@ -11175,7 +11254,6 @@ void simple_wallet::mms_next(const std::vector<std::string> &args) void simple_wallet::mms_sync(const std::vector<std::string> &args) { - mms::message_store& ms = m_wallet->get_message_store(); if (args.size() != 0) { fail_msg_writer() << tr("Usage: mms sync"); @@ -11273,7 +11351,6 @@ void simple_wallet::mms_export(const std::vector<std::string> &args) return; } LOCK_IDLE_SCOPE(); - mms::message_store& ms = m_wallet->get_message_store(); mms::message m; bool valid_id = get_message_from_arg(args[0], m); if (valid_id) @@ -11342,7 +11419,6 @@ void simple_wallet::mms_show(const std::vector<std::string> &args) return; } LOCK_IDLE_SCOPE(); - mms::message_store& ms = m_wallet->get_message_store(); mms::message m; bool valid_id = get_message_from_arg(args[0], m); if (valid_id) @@ -11648,4 +11724,3 @@ bool simple_wallet::mms(const std::vector<std::string> &args) return true; } // End MMS ------------------------------------------------------------------------------------------------ - diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 61104c87f..8780bee1d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -158,6 +158,7 @@ namespace cryptonote bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool apropos(const std::vector<std::string> &args); + bool scan_tx(const std::vector<std::string> &args); bool start_mining(const std::vector<std::string> &args); bool stop_mining(const std::vector<std::string> &args); bool set_daemon(const std::vector<std::string> &args); @@ -463,6 +464,7 @@ namespace cryptonote std::atomic<bool> m_need_payment; boost::posix_time::ptime m_last_rpc_payment_mining_time; bool m_rpc_payment_mining_requested; + uint32_t m_rpc_payment_threads = 0; bool m_daemon_rpc_payment_message_displayed; float m_rpc_payment_hash_rate; std::atomic<bool> m_suspend_rpc_payment_mining; diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 24f6d37db..b28ffd64c 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -40,6 +40,7 @@ #include <vector> #include <sstream> #include <boost/format.hpp> +#include <boost/filesystem.hpp> using namespace std; diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index b6d52c58d..d8e4aab65 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -139,6 +139,7 @@ void TransactionHistoryImpl::refresh() ti->m_paymentid = payment_id; ti->m_coinbase = pd.m_coinbase; ti->m_amount = pd.m_amount; + ti->m_fee = pd.m_fee; ti->m_direction = TransactionInfo::Direction_In; ti->m_hash = string_tools::pod_to_hex(pd.m_tx_hash); ti->m_blockheight = pd.m_block_height; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 9acc8484c..3bbd9ce0b 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -45,10 +45,8 @@ #include <sstream> #include <unordered_map> -#ifdef WIN32 #include <boost/locale.hpp> #include <boost/filesystem.hpp> -#endif using namespace std; using namespace cryptonote; @@ -791,11 +789,11 @@ bool WalletImpl::close(bool store) return result; } -std::string WalletImpl::seed() const +std::string WalletImpl::seed(const std::string& seed_offset) const { epee::wipeable_string seed; if (m_wallet) - m_wallet->get_seed(seed); + m_wallet->get_seed(seed, seed_offset); return std::string(seed.data(), seed.size()); // TODO } @@ -1168,7 +1166,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { return true; } -bool WalletImpl::exportKeyImages(const string &filename) +bool WalletImpl::exportKeyImages(const string &filename, bool all) { if (m_wallet->watch_only()) { @@ -1178,7 +1176,7 @@ bool WalletImpl::exportKeyImages(const string &filename) try { - if (!m_wallet->export_key_images(filename)) + if (!m_wallet->export_key_images(filename), all) { setStatusError(tr("failed to save file ") + filename); return false; @@ -2104,6 +2102,11 @@ bool WalletImpl::watchOnly() const return m_wallet->watch_only(); } +bool WalletImpl::isDeterministic() const +{ + return m_wallet->is_deterministic(); +} + void WalletImpl::clearStatus() const { boost::lock_guard<boost::mutex> l(m_statusMutex); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 3bf3e759b..0f3614bb4 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -81,7 +81,7 @@ public: const std::string &device_name); Device getDeviceType() const override; bool close(bool store = true); - std::string seed() const override; + std::string seed(const std::string& seed_offset = "") const override; std::string getSeedLanguage() const override; void setSeedLanguage(const std::string &arg) override; // void setListener(Listener *) {} @@ -129,6 +129,7 @@ public: void setRecoveringFromDevice(bool recoveringFromDevice) override; void setSubaddressLookahead(uint32_t major, uint32_t minor) override; bool watchOnly() const override; + bool isDeterministic() const override; bool rescanSpent() override; NetworkType nettype() const override {return static_cast<NetworkType>(m_wallet->nettype());} void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const override; @@ -164,7 +165,7 @@ public: virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; - bool exportKeyImages(const std::string &filename) override; + bool exportKeyImages(const std::string &filename, bool all = false) override; bool importKeyImages(const std::string &filename) override; virtual void disposeTransaction(PendingTransaction * t) override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 44928a422..b1cebedaf 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -446,7 +446,7 @@ struct Wallet }; virtual ~Wallet() = 0; - virtual std::string seed() const = 0; + virtual std::string seed(const std::string& seed_offset = "") const = 0; virtual std::string getSeedLanguage() const = 0; virtual void setSeedLanguage(const std::string &arg) = 0; //! returns wallet status (Status_Ok | Status_Error) @@ -627,6 +627,12 @@ struct Wallet virtual bool watchOnly() const = 0; /** + * @brief isDeterministic - checks if wallet keys are deterministic + * @return - true if deterministic + */ + virtual bool isDeterministic() const = 0; + + /** * @brief blockChainHeight - returns current blockchain height * @return */ @@ -901,9 +907,10 @@ struct Wallet /*! * \brief exportKeyImages - exports key images to file * \param filename + * \param all - export all key images or only those that have not yet been exported * \return - true on success */ - virtual bool exportKeyImages(const std::string &filename) = 0; + virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; /*! * \brief importKeyImages - imports key images from file diff --git a/src/wallet/message_store.cpp b/src/wallet/message_store.cpp index 303b576c7..b7b29420b 100644 --- a/src/wallet/message_store.cpp +++ b/src/wallet/message_store.cpp @@ -30,6 +30,8 @@ #include <boost/archive/portable_binary_iarchive.hpp> #include <boost/format.hpp> #include <boost/algorithm/string.hpp> +#include <boost/system/error_code.hpp> +#include <boost/filesystem.hpp> #include <fstream> #include <sstream> #include "file_io_utils.h" @@ -1340,7 +1342,10 @@ bool message_store::check_for_messages(const multisig_wallet_state &state, std:: } } std::vector<transport_message> transport_messages; - bool r = m_transporter.receive_messages(destinations, transport_messages); + if (!m_transporter.receive_messages(destinations, transport_messages)) + { + return false; + } if (!m_run.load(std::memory_order_relaxed)) { // Stop was called, don't waste time processing the messages diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 48a602bf3..a576c267c 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -32,6 +32,8 @@ #include "rpc/rpc_payment_costs.h" #include "storages/http_abstract_invoke.h" +#include <boost/thread.hpp> + #define RETURN_ON_RPC_RESPONSE_ERROR(r, error, res, method) \ do { \ CHECK_AND_ASSERT_MES(error.code == 0, error.message, error.message); \ diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 51b7f01dd..f5e3fca5f 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -30,6 +30,7 @@ #include <string> #include <boost/thread/mutex.hpp> +#include <boost/thread/recursive_mutex.hpp> #include "include_base_utils.h" #include "net/abstract_http_client.h" #include "rpc/core_rpc_server_commands_defs.h" diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index dfeb987ca..025a2037f 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -42,9 +42,6 @@ #define V1TAG ((uint64_t)798237759845202) -static const char zerokey[8] = {0}; -static const MDB_val zerokeyval = { sizeof(zerokey), (void *)zerokey }; - static int compare_hash32(const MDB_val *a, const MDB_val *b) { uint32_t *va = (uint32_t*) a->mv_data; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d80335db8..0af896c76 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -30,6 +30,7 @@ #include <numeric> #include <tuple> +#include <queue> #include <boost/format.hpp> #include <boost/optional/optional.hpp> #include <boost/algorithm/string/classification.hpp> @@ -344,8 +345,6 @@ std::string get_weight_string(const cryptonote::transaction &tx, size_t blob_siz std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { - namespace ip = boost::asio::ip; - const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); const network_type nettype = testnet ? TESTNET : stagenet ? STAGENET : MAINNET; @@ -367,7 +366,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl // user specified CA file or fingeprints implies enabled SSL by default epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; - if (command_line::get_arg(vm, opts.daemon_ssl_allow_any_cert)) + if (daemon_ssl_allow_any_cert) ssl_options.verification = epee::net_utils::ssl_verification_t::none; else if (!daemon_ssl_ca_file.empty() || !daemon_ssl_allowed_fingerprints.empty()) { @@ -1004,9 +1003,6 @@ bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry, namespace tools { -// for now, limit to 30 attempts. TODO: discuss a good number to limit to. -const size_t MAX_SPLIT_ATTEMPTS = 30; - constexpr const std::chrono::seconds wallet2::rpc_timeout; const char* wallet2::tr(const char* str) { return i18n_translate(str, "tools::wallet2"); } @@ -1602,6 +1598,47 @@ std::string wallet2::get_subaddress_label(const cryptonote::subaddress_index& in return m_subaddress_labels[index.major][index.minor]; } //---------------------------------------------------------------------------------------------------- +void wallet2::scan_tx(const std::vector<crypto::hash> &txids) +{ + // Get the transactions from daemon in batches and add them to a priority queue ordered in chronological order + auto cmp_tx_entry = [](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& l, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& r) + { return l.block_height > r.block_height; }; + + std::priority_queue<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry, std::vector<COMMAND_RPC_GET_TRANSACTIONS::entry>, decltype(cmp_tx_entry)> txq(cmp_tx_entry); + const size_t SLICE_SIZE = 100; // RESTRICTED_TRANSACTIONS_COUNT as defined in rpc/core_rpc_server.cpp, hardcoded in daemon code + for(size_t slice = 0; slice < txids.size(); slice += SLICE_SIZE) { + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + req.decode_as_json = false; + req.prune = true; + + size_t ntxes = slice + SLICE_SIZE > txids.size() ? txids.size() - slice : SLICE_SIZE; + for (size_t i = slice; i < slice + ntxes; ++i) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids[i])); + + { + const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; + req.client = get_client_signature(); + bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, *m_http_client, rpc_timeout); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to get transaction from daemon"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, "Failed to get transaction from daemon"); + } + + for (auto& tx_info : res.txs) + txq.push(tx_info); + } + + // Process the transactions in chronologically ascending order + while(!txq.empty()) { + auto& tx_info = txq.top(); + cryptonote::transaction tx; + crypto::hash tx_hash; + THROW_WALLET_EXCEPTION_IF(!get_pruned_tx(tx_info, tx, tx_hash), error::wallet_internal_error, "Failed to get transaction from daemon (2)"); + process_new_transaction(tx_hash, tx, tx_info.output_indices, tx_info.block_height, 0, tx_info.block_timestamp, false, tx_info.in_pool, tx_info.double_spend_seen, {}, {}); + txq.pop(); + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::set_subaddress_label(const cryptonote::subaddress_index& index, const std::string &label) { THROW_WALLET_EXCEPTION_IF(index.major >= m_subaddress_labels.size(), error::account_index_outofbound); @@ -5669,6 +5706,16 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass if (::serialization::serialize(ar, *this)) if (::serialization::check_stream_state(ar)) loaded = true; + if (!loaded) + { + std::stringstream iss; + iss << cache_data; + binary_archive<false> ar(iss); + ar.enable_varint_bug_backward_compatibility(); + if (::serialization::serialize(ar, *this)) + if (::serialization::check_stream_state(ar)) + loaded = true; + } } catch(...) { } @@ -5988,7 +6035,7 @@ uint64_t wallet2::balance(uint32_t index_major, bool strict) const { uint64_t amount = 0; if(m_light_wallet) - return m_light_wallet_unlocked_balance; + return m_light_wallet_balance; for (const auto& i : balance_per_subaddress(index_major, strict)) amount += i.second; return amount; @@ -6002,7 +6049,7 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t * if (time_to_unlock) *time_to_unlock = 0; if(m_light_wallet) - return m_light_wallet_balance; + return m_light_wallet_unlocked_balance; for (const auto& i : unlocked_balance_per_subaddress(index_major, strict)) { amount += i.second.first; @@ -6042,6 +6089,14 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo found->second += utx.second.m_change; } } + + for (const auto& utx: m_unconfirmed_payments) + { + if (utx.second.m_pd.m_subaddr_index.major == index_major) + { + amount_per_subaddr[utx.second.m_pd.m_subaddr_index.minor] += utx.second.m_pd.m_amount; + } + } } return amount_per_subaddr; } @@ -7984,7 +8039,6 @@ void wallet2::light_wallet_get_outs(std::vector<std::vector<tools::wallet2::get_ // Check if we got enough outputs for each amount for(auto& out: ores.amount_outs) { - const uint64_t out_amount = boost::lexical_cast<uint64_t>(out.amount); THROW_WALLET_EXCEPTION_IF(out.outputs.size() < light_wallet_requested_outputs_count , error::wallet_internal_error, "Not enough outputs for amount: " + boost::lexical_cast<std::string>(out.amount)); MDEBUG(out.outputs.size() << " outputs for amount "+ boost::lexical_cast<std::string>(out.amount) + " received from light wallet node"); } @@ -9778,13 +9832,18 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp TX() : weight(0), needed_fee(0) {} - void add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations) { + /* Add an output to the transaction. + * Returns True if the output was added, False if there are no more available output slots. + */ + bool add(const cryptonote::tx_destination_entry &de, uint64_t amount, unsigned int original_output_index, bool merge_destinations, size_t max_dsts) { if (merge_destinations) { std::vector<cryptonote::tx_destination_entry>::iterator i; i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &de.addr, sizeof(de.addr)); }); if (i == dsts.end()) { + if (dsts.size() >= max_dsts) + return false; dsts.push_back(de); i = dsts.end() - 1; i->amount = 0; @@ -9797,12 +9856,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp std::string("original_output_index too large: ") + std::to_string(original_output_index) + " > " + std::to_string(dsts.size())); if (original_output_index == dsts.size()) { + if (dsts.size() >= max_dsts) + return false; dsts.push_back(de); dsts.back().amount = 0; } THROW_WALLET_EXCEPTION_IF(memcmp(&dsts[original_output_index].addr, &de.addr, sizeof(de.addr)), error::wallet_internal_error, "Mismatched destination address"); dsts[original_output_index].amount += amount; } + return true; } }; std::vector<TX> txes; @@ -10072,6 +10134,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // clear any fake outs we'd already gathered, since we'll need a new set outs.clear(); + bool out_slots_exhausted = false; if (adding_fee) { LOG_PRINT_L2("We need more fee, adding it to fee"); @@ -10084,20 +10147,32 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // we can fully pay that destination LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(dsts[0].amount)); - tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations); + if (!tx.add(dsts[0], dsts[0].amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1)) + { + LOG_PRINT_L2("Didn't pay: ran out of output slots"); + out_slots_exhausted = true; + break; + } available_amount -= dsts[0].amount; dsts[0].amount = 0; pop_index(dsts, 0); ++original_output_index; } - if (available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { + if (!out_slots_exhausted && available_amount > 0 && !dsts.empty() && estimate_tx_weight(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()+1, extra.size(), bulletproof, clsag) < TX_WEIGHT_TARGET(upper_transaction_weight_limit)) { // we can partially fill that destination LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_nettype, dsts[0].is_subaddress, dsts[0].addr) << " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); - tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations); - dsts[0].amount -= available_amount; - available_amount = 0; + if (tx.add(dsts[0], available_amount, original_output_index, m_merge_destinations, BULLETPROOF_MAX_OUTPUTS-1)) + { + dsts[0].amount -= available_amount; + available_amount = 0; + } + else + { + LOG_PRINT_L2("Didn't pay: ran out of output slots"); + out_slots_exhausted = true; + } } } @@ -10105,8 +10180,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit " << upper_transaction_weight_limit); bool try_tx = false; + + // If the new transaction is full, create it and start a new one + if (out_slots_exhausted) + { + LOG_PRINT_L2("Transaction is full, will create it and start a new tx"); + try_tx = true; + } // if we have preferred picks, but haven't yet used all of them, continue - if (preferred_inputs.empty()) + else if (preferred_inputs.empty()) { if (adding_fee) { @@ -10303,8 +10385,6 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, s { MDEBUG("sanity_check: " << ptx_vector.size() << " txes, " << dsts.size() << " destinations"); - hw::device &hwdev = m_account.get_device(); - THROW_WALLET_EXCEPTION_IF(ptx_vector.empty(), error::wallet_internal_error, "No transactions"); // check every party in there does receive at least the required amount @@ -10341,7 +10421,6 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, s for (const auto &r: required) { const account_public_address &address = r.first; - const crypto::public_key &view_pkey = address.m_view_public_key; uint64_t total_received = 0; for (const auto &ptx: ptx_vector) @@ -12459,7 +12538,7 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle bool wallet2::export_key_images(const std::string &filename, bool all) const { PERF_TIMER(export_key_images); - std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images(all); + std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = export_key_images(all); std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; const uint32_t offset = ski.first; @@ -12486,7 +12565,7 @@ bool wallet2::export_key_images(const std::string &filename, bool all) const } //---------------------------------------------------------------------------------------------------- -std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images(bool all) const +std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> wallet2::export_key_images(bool all) const { PERF_TIMER(export_key_images_raw); std::vector<std::pair<crypto::key_image, crypto::signature>> ski; @@ -12983,7 +13062,7 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const +std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const { PERF_TIMER(export_outputs); std::vector<tools::wallet2::transfer_details> outs; @@ -13023,7 +13102,7 @@ std::string wallet2::export_outputs_to_str(bool all) const return magic + ciphertext; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) { PERF_TIMER(import_outputs); @@ -13129,7 +13208,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) try { std::string body(data, headerlen); - std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; try { std::stringstream iss; @@ -13229,7 +13308,6 @@ rct::multisig_kLRki wallet2::get_multisig_composite_kLRki(size_t n, const std::u { CHECK_AND_ASSERT_THROW_MES(n < m_transfers.size(), "Bad transfer index"); - const transfer_details &td = m_transfers[n]; rct::multisig_kLRki kLRki = get_multisig_kLRki(n, rct::skGen()); // pick a L/R pair from every other participant but one diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 68f03db72..e96a6b51c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -327,7 +327,7 @@ private: uint64_t m_block_height; cryptonote::transaction_prefix m_tx; crypto::hash m_txid; - size_t m_internal_output_index; + uint64_t m_internal_output_index; uint64_t m_global_output_index; bool m_spent; bool m_frozen; @@ -338,7 +338,7 @@ private: bool m_rct; bool m_key_image_known; bool m_key_image_request; // view wallets: we want to request it; cold wallets: it was requested - size_t m_pk_index; + uint64_t m_pk_index; cryptonote::subaddress_index m_subaddr_index; bool m_key_image_partial; std::vector<rct::key> m_multisig_k; @@ -1264,6 +1264,8 @@ private: std::string get_spend_proof(const crypto::hash &txid, const std::string &message); bool check_spend_proof(const crypto::hash &txid, const std::string &message, const std::string &sig_str); + void scan_tx(const std::vector<crypto::hash> &txids); + /*! * \brief Generates a proof that proves the reserve of unspent funds * \param account_minreserve When specified, collect outputs only belonging to the given account and prove the smallest reserve above the given amount @@ -1370,9 +1372,9 @@ private: bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<size_t, std::vector<tools::wallet2::transfer_details>> export_outputs(bool all = false) const; + std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> export_outputs(bool all = false) const; std::string export_outputs_to_str(bool all = false) const; - size_t import_outputs(const std::pair<size_t, std::vector<tools::wallet2::transfer_details>> &outputs); + size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; void import_payments(const payment_container &payments); @@ -1380,7 +1382,7 @@ private: std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> export_blockchain() const; void import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc); bool export_key_images(const std::string &filename, bool all = false) const; - std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const; + std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> export_key_images(bool all = false) const; uint64_t import_key_images(const std::vector<std::pair<crypto::key_image, crypto::signature>> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); bool import_key_images(std::vector<crypto::key_image> key_images, size_t offset=0, boost::optional<std::unordered_set<size_t>> selected_transfers=boost::none); @@ -1426,7 +1428,7 @@ private: 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); - bool search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL); + bool search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc = NULL, const std::function<void(const std::string&)> &errorfunc = NULL); template<typename T> void handle_payment_changes(const T &res, std::true_type) { if (res.status == CORE_RPC_STATUS_OK || res.status == CORE_RPC_STATUS_PAYMENT_REQUIRED) m_rpc_payment_state.credits = res.credits; @@ -1549,6 +1551,7 @@ private: void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash); void enable_dns(bool enable) { m_use_dns = enable; } void set_offline(bool offline = true); + bool is_offline() const { return m_offline; } uint64_t credits() const { return m_rpc_payment_state.credits; } void credit_report(uint64_t &expected_spent, uint64_t &discrepancy) const { expected_spent = m_rpc_payment_state.expected_spent; discrepancy = m_rpc_payment_state.discrepancy; } diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index 6527b1384..bf278f695 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -42,6 +42,7 @@ #include "cryptonote_basic/blobdatatype.h" #include "common/i18n.h" #include "common/util.h" +#include "common/threadpool.h" #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2.rpc_payments" @@ -101,7 +102,7 @@ bool wallet2::make_rpc_payment(uint32_t nonce, uint32_t cookie, uint64_t &credit return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::search_for_rpc_payment(uint64_t credits_target, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc) +bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads, const std::function<bool(uint64_t, uint64_t)> &startfunc, const std::function<bool(unsigned)> &contfunc, const std::function<bool(uint64_t)> &foundfunc, const std::function<void(const std::string&)> &errorfunc) { bool need_payment = false; bool payment_required; @@ -139,49 +140,65 @@ bool wallet2::search_for_rpc_payment(uint64_t credits_target, const std::functio continue; } - crypto::hash hash; - const uint32_t local_nonce = nonce++; // wrapping's OK - *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce); - const uint8_t major_version = hashing_blob[0]; - if (major_version >= RX_BLOCK_VERSION) - { - const int miners = 1; - crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash.data, miners, 0); - } - else + if(n_threads == 0) + n_threads = boost::thread::hardware_concurrency(); + + std::vector<crypto::hash> hash(n_threads); + tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool::waiter waiter(tpool); + + const uint32_t local_nonce = nonce += n_threads; // wrapping's OK + for (size_t i = 0; i < n_threads; i++) { - int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0; - crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash, cn_variant, height); + tpool.submit(&waiter, [&, i] { + *(uint32_t*)(hashing_blob.data() + 39) = SWAP32LE(local_nonce-i); + const uint8_t major_version = hashing_blob[0]; + if (major_version >= RX_BLOCK_VERSION) + { + const int miners = 1; + crypto::rx_slow_hash(height, seed_height, seed_hash.data, hashing_blob.data(), hashing_blob.size(), hash[i].data, miners, 0); + } + else + { + int cn_variant = hashing_blob[0] >= 7 ? hashing_blob[0] - 6 : 0; + crypto::cn_slow_hash(hashing_blob.data(), hashing_blob.size(), hash[i], cn_variant, height); + } + }); } - ++n_hashes; - if (cryptonote::check_hash(hash, diff)) + waiter.wait(); + n_hashes += n_threads; + + for(size_t i=0; i < n_threads; i++) { - uint64_t credits, balance; - try + if (cryptonote::check_hash(hash[i], diff)) { - make_rpc_payment(local_nonce, cookie, credits, balance); - if (credits != credits_per_hash_found) + uint64_t credits, balance; + try { - MERROR("Found nonce, but daemon did not credit us with the expected amount"); + make_rpc_payment(local_nonce-i, cookie, credits, balance); + if (credits != credits_per_hash_found) + { + MERROR("Found nonce, but daemon did not credit us with the expected amount"); + if (errorfunc) + errorfunc("Found nonce, but daemon did not credit us with the expected amount"); + return false; + } + MDEBUG("Found nonce " << local_nonce-i << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits"); + if (!foundfunc(credits)) + break; + } + catch (const tools::error::wallet_coded_rpc_error &e) + { + MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); if (errorfunc) - errorfunc("Found nonce, but daemon did not credit us with the expected amount"); - return false; + errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing"); + } + catch (const std::exception &e) + { + MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); + if (errorfunc) + errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing"); } - MDEBUG("Found nonce " << local_nonce << " at diff " << diff << ", gets us " << credits_per_hash_found << ", now " << balance << " credits"); - if (!foundfunc(credits)) - break; - } - catch (const tools::error::wallet_coded_rpc_error &e) - { - MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); - if (errorfunc) - errorfunc("Found nonce, but daemon errored out with error " + std::to_string(e.code()) + ": " + e.status() + ", continuing"); - } - catch (const std::exception &e) - { - MWARNING("Found a local_nonce at diff " << diff << ", but failed to send it to the daemon"); - if (errorfunc) - errorfunc("Found nonce, but daemon errored out with: '" + std::string(e.what()) + "', continuing"); } } } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 03db8b70f..b39a40b64 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -687,7 +687,7 @@ namespace tools { if (!m_wallet) return not_open(er); const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); - for (const std::pair<std::string, std::string>& p : account_tags.first) + for (const std::pair<const std::string, std::string>& p : account_tags.first) { res.account_tags.resize(res.account_tags.size() + 1); auto& info = res.account_tags.back(); @@ -1282,7 +1282,6 @@ namespace tools dests.erase(cd.change_dts.addr); } - size_t n_dummy_outputs = 0; for (auto i = dests.begin(); i != dests.end(); ) { if (i->second.second > 0) @@ -1888,6 +1887,7 @@ namespace tools rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid); rpc_transfers.subaddr_index = {td.m_subaddr_index.major, td.m_subaddr_index.minor}; rpc_transfers.key_image = td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : ""; + rpc_transfers.pubkey = epee::string_tools::pod_to_hex(td.get_public_key()); rpc_transfers.block_height = td.m_block_height; rpc_transfers.frozen = td.m_frozen; rpc_transfers.unlocked = m_wallet->is_transfer_unlocked(td); @@ -2702,7 +2702,7 @@ namespace tools if (!m_wallet) return not_open(er); try { - std::pair<size_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all); + std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all); res.offset = ski.first; res.signed_key_images.resize(ski.second.size()); for (size_t n = 0; n < ski.second.size(); ++n) @@ -3014,6 +3014,41 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_scan_tx(const wallet_rpc::COMMAND_RPC_SCAN_TX::request& req, wallet_rpc::COMMAND_RPC_SCAN_TX::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + + std::vector<crypto::hash> txids; + std::list<std::string>::const_iterator i = req.txids.begin(); + while (i != req.txids.end()) + { + cryptonote::blobdata txid_blob; + if(!epee::string_tools::parse_hexstr_to_binbuff(*i++, txid_blob) || txid_blob.size() != sizeof(crypto::hash)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; + er.message = "TX ID has invalid format"; + return false; + } + + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_blob.data()); + txids.push_back(txid); + } + + try { + m_wallet->scan_tx(txids); + } catch (const std::exception &e) { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); @@ -3119,17 +3154,7 @@ namespace tools } std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename); { - std::vector<std::string> languages; - crypto::ElectrumWords::get_language_list(languages, false); - std::vector<std::string>::iterator it; - - it = std::find(languages.begin(), languages.end(), req.language); - if (it == languages.end()) - { - crypto::ElectrumWords::get_language_list(languages, true); - it = std::find(languages.begin(), languages.end(), req.language); - } - if (it == languages.end()) + if (!crypto::ElectrumWords::is_valid_language(req.language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Unknown language: " + req.language; @@ -3547,6 +3572,17 @@ namespace tools return false; } + if (!req.language.empty()) + { + if (!crypto::ElectrumWords::is_valid_language(req.language)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "The specified seed language is invalid."; + return false; + } + wal->set_seed_language(req.language); + } + // set blockheight if given try { @@ -3690,12 +3726,7 @@ namespace tools er.message = "Wallet was using the old seed language. You need to specify a new seed language."; return false; } - std::vector<std::string> language_list; - std::vector<std::string> language_list_en; - crypto::ElectrumWords::get_language_list(language_list); - crypto::ElectrumWords::get_language_list(language_list_en, true); - if (std::find(language_list.begin(), language_list.end(), req.language) == language_list.end() && - std::find(language_list_en.begin(), language_list_en.end(), req.language) == language_list_en.end()) + if (!crypto::ElectrumWords::is_valid_language(req.language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Wallet was using the old seed language, and the specified new seed language is invalid."; @@ -4438,7 +4469,14 @@ public: wal->stop(); }); - wal->refresh(wal->is_trusted_daemon()); + try + { + wal->refresh(wal->is_trusted_daemon()); + } + catch (const std::exception& e) + { + LOG_ERROR(tools::wallet_rpc_server::tr("Initial refresh failed: ") << e.what()); + } // if we ^C during potentially length load/refresh, there's no server loop yet if (quit) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 6e39eca1e..9f9e3c134 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -131,6 +131,7 @@ namespace tools MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY) MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH) MAP_JON_RPC_WE("auto_refresh", on_auto_refresh, wallet_rpc::COMMAND_RPC_AUTO_REFRESH) + MAP_JON_RPC_WE("scan_tx", on_scan_tx, wallet_rpc::COMMAND_RPC_SCAN_TX) MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT) MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING) MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING) @@ -218,6 +219,7 @@ namespace tools bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_scan_tx(const wallet_rpc::COMMAND_RPC_SCAN_TX::request& req, wallet_rpc::COMMAND_RPC_SCAN_TX::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 81f83fb18..0002508a2 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 20 +#define WALLET_RPC_VERSION_MINOR 21 #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 @@ -1010,6 +1010,7 @@ namespace wallet_rpc std::string tx_hash; cryptonote::subaddress_index subaddr_index; std::string key_image; + std::string pubkey; // owned output public key found uint64_t block_height; bool frozen; bool unlocked; @@ -1021,6 +1022,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(subaddr_index) KV_SERIALIZE(key_image) + KV_SERIALIZE(pubkey); KV_SERIALIZE(block_height) KV_SERIALIZE(frozen) KV_SERIALIZE(unlocked) @@ -2028,6 +2030,26 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_SCAN_TX + { + struct request_t + { + std::list<std::string> txids; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(txids) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_START_MINING { struct request_t @@ -2192,6 +2214,7 @@ namespace wallet_rpc std::string viewkey; std::string password; bool autosave_current; + std::string language; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_OPT(restore_height, (uint64_t)0) @@ -2201,6 +2224,7 @@ namespace wallet_rpc KV_SERIALIZE(viewkey) KV_SERIALIZE(password) KV_SERIALIZE_OPT(autosave_current, true) + KV_SERIALIZE(language) END_KV_SERIALIZE_MAP() }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 224784a18..85fa8f1dd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -80,17 +80,22 @@ file(COPY data/signed_monero_tx DESTINATION data) -add_subdirectory(core_tests) -add_subdirectory(fuzz) -add_subdirectory(crypto) -add_subdirectory(functional_tests) -add_subdirectory(performance_tests) -add_subdirectory(core_proxy) -add_subdirectory(unit_tests) -add_subdirectory(difficulty) -add_subdirectory(block_weight) -add_subdirectory(hash) -add_subdirectory(net_load_tests) +if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ) + add_subdirectory(fuzz) +else () + add_subdirectory(core_tests) + add_subdirectory(fuzz) + add_subdirectory(crypto) + add_subdirectory(functional_tests) + add_subdirectory(performance_tests) + add_subdirectory(core_proxy) + add_subdirectory(unit_tests) + add_subdirectory(difficulty) + add_subdirectory(block_weight) + add_subdirectory(hash) + add_subdirectory(net_load_tests) +endif() + if (BUILD_GUI_DEPS) add_subdirectory(libwallet_api_tests) endif() diff --git a/tests/README.md b/tests/README.md index f6dd25ead..c4ad1ce37 100644 --- a/tests/README.md +++ b/tests/README.md @@ -52,6 +52,11 @@ To run the same tests on a release build, replace `debug` with `release`. [TODO] Functional tests are located under the `tests/functional` directory. +Building all the tests requires installing the following dependencies: +```bash +pip install requests psutil monotonic +``` + First, run a regtest daemon in the offline mode and with a fixed difficulty: ```bash monerod --regtest --offline --fixed-difficulty 1 diff --git a/tests/core_proxy/core_proxy.cpp b/tests/core_proxy/core_proxy.cpp index 8095f03e3..09be8758b 100644 --- a/tests/core_proxy/core_proxy.cpp +++ b/tests/core_proxy/core_proxy.cpp @@ -245,12 +245,17 @@ bool tests::proxy_core::init(const boost::program_options::variables_map& /*vm*/ return true; } -bool tests::proxy_core::have_block(const crypto::hash& id) { +bool tests::proxy_core::have_block_unlocked(const crypto::hash& id, int *where) { if (m_hash2blkidx.end() == m_hash2blkidx.find(id)) return false; + if (where) *where = HAVE_BLOCK_MAIN_CHAIN; return true; } +bool tests::proxy_core::have_block(const crypto::hash& id, int *where) { + return have_block_unlocked(id, where); +} + void tests::proxy_core::build_short_history(std::list<crypto::hash> &m_history, const crypto::hash &m_start) { m_history.push_front(get_block_hash(m_genesis)); /*std::unordered_map<crypto::hash, tests::block_index>::const_iterator cit = m_hash2blkidx.find(m_lastblk); diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index ebc3a89c2..94f148e8c 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -74,7 +74,8 @@ namespace tests bool init(const boost::program_options::variables_map& vm); bool deinit(){return true;} bool get_short_chain_history(std::list<crypto::hash>& ids); - bool have_block(const crypto::hash& id); + bool have_block(const crypto::hash& id, int *where = NULL); + bool have_block_unlocked(const crypto::hash& id, int *where = NULL); 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, 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); diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index d0b55dcf4..d19bdd1f9 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -78,8 +78,6 @@ bool test_transaction_generation_and_ring_signature() tx_source_entry& src = sources.back(); src.amount = 70368744177663; { - tx_output_entry oe; - src.push_output(0, boost::get<txout_to_key>(tx_mine_1.vout[0].target).key, src.amount); src.push_output(1, boost::get<txout_to_key>(tx_mine_2.vout[0].target).key, src.amount); diff --git a/tests/core_tests/tx_pool.cpp b/tests/core_tests/tx_pool.cpp index 1e496f3da..166eb6075 100644 --- a/tests/core_tests/tx_pool.cpp +++ b/tests/core_tests/tx_pool.cpp @@ -165,9 +165,6 @@ bool txpool_double_spend_base::timestamp_change_pause(cryptonote::core& /*c*/, s 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); @@ -393,7 +390,7 @@ bool txpool_double_spend_base::check_changed(cryptonote::core& c, const size_t e } } - for (const std::pair<crypto::hash, uint64_t>& hash : m_all_hashes) + for (const std::pair<const 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)) @@ -411,7 +408,7 @@ bool txpool_double_spend_base::check_changed(cryptonote::core& c, const size_t e for (const crypto::hash& hash : m_no_relay_hashes) difference.erase(hash); - for (const std::pair<crypto::hash, uint64_t>& hash : difference) + for (const std::pair<const crypto::hash, uint64_t>& hash : difference) { if (c.pool_has_tx(hash.first)) { diff --git a/tests/data/fuzz/utf8/UTF8_1 b/tests/data/fuzz/utf8/UTF8_1 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/data/fuzz/utf8/UTF8_1 diff --git a/tests/data/fuzz/utf8/UTF8_2 b/tests/data/fuzz/utf8/UTF8_2 Binary files differnew file mode 100644 index 000000000..bb6982c4f --- /dev/null +++ b/tests/data/fuzz/utf8/UTF8_2 diff --git a/tests/functional_tests/CMakeLists.txt b/tests/functional_tests/CMakeLists.txt index 462fd4b77..337097a34 100644 --- a/tests/functional_tests/CMakeLists.txt +++ b/tests/functional_tests/CMakeLists.txt @@ -64,16 +64,18 @@ target_link_libraries(make_test_signature ${CMAKE_THREAD_LIBS_INIT} ${EXTRA_LIBRARIES}) -execute_process(COMMAND ${PYTHON_EXECUTABLE} "-c" "import requests; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) +monero_add_minimal_executable(cpu_power_test cpu_power_test.cpp) +find_program(PYTHON3_FOUND python3 REQUIRED) + +execute_process(COMMAND ${PYTHON3_FOUND} "-c" "import requests; import psutil; import monotonic; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) if (REQUESTS_OUTPUT STREQUAL "OK") add_test( NAME functional_tests_rpc - COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/functional_tests_rpc.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" all) + COMMAND ${PYTHON3_FOUND} "${CMAKE_CURRENT_SOURCE_DIR}/functional_tests_rpc.py" "${PYTHON3_FOUND}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" all) + add_test( + NAME check_missing_rpc_methods + COMMAND ${PYTHON3_FOUND} "${CMAKE_CURRENT_SOURCE_DIR}/check_missing_rpc_methods.py" "${CMAKE_SOURCE_DIR}") else() - message(WARNING "functional_tests_rpc skipped, needs the 'requests' python module") - set(CTEST_CUSTOM_TESTS_IGNORE ${CTEST_CUSTOM_TESTS_IGNORE} functional_tests_rpc) + message(WARNING "functional_tests_rpc and check_missing_rpc_methods skipped, needs the 'requests', 'psutil' and 'monotonic' python modules") + set(CTEST_CUSTOM_TESTS_IGNORE ${CTEST_CUSTOM_TESTS_IGNORE} functional_tests_rpc check_missing_rpc_methods) endif() - -add_test( - NAME check_missing_rpc_methods - COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/check_missing_rpc_methods.py" "${CMAKE_SOURCE_DIR}") diff --git a/tests/functional_tests/cpu_power_test.cpp b/tests/functional_tests/cpu_power_test.cpp new file mode 100644 index 000000000..54c4bfc9f --- /dev/null +++ b/tests/functional_tests/cpu_power_test.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2014-2021, 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. + +const char * descr = +"This program prints the time (ms) needed to calculate a mathematical challenge. It's purpose \n\ +is to be able to use the result to compare the CPU power available on the machine it's being executed \n\ +and under the given circumstances, which can differ for the same machine. For example: \n\ +The printed value will be different when using 2 of 2 cores, when there an another process \n\ +running on one of the cores core, and when no other intense process running. \n\ + \n\ +The program expects a one argument: a numerical value of the threads to start the calculations on. \n\ + \n\ +Prints: \n\ +Time to calculate a mathematical challenge in MILLISECONDS. \n\ + \n\ +Returns: \n\ +0 on success, \n\ +1 on a missing argument, \n\ +2 on an incorrect format of the argument.\n"; + +#include <iostream> +#include <future> +#include <vector> +#include <sstream> +#include <chrono> + +using namespace std; + +/** +Uses Leibniz Formula for Pi. +https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80 +*/ +static double calcPi(const size_t max_iter) +{ + const double n = 4; + double pi = 0; + double d = 1; + for (size_t i = 1; i < max_iter; ++i) + { + const double a = 2.0 * (i % 2) - 1.0; + pi += a * n / d; + d += 2; + } + return pi; +} + +int main(int argc, const char ** argv) +{ + const size_t max_iter = 1e9; + if (argc < 2) + { + cout << "Please pass the number of threads to run.\n"; + + cout << '\n' << descr << '\n'; + return 1; + } + + // Convert argument to an integer. + int numThreads = 1; + const char * numThreadsStr = argv[1]; + std::istringstream iss(numThreadsStr); + if (! (iss >> numThreads) ) + { + cout << "Incorrect format of number of threads = '" << numThreadsStr << "'\n"; + return 2; + } + // Run the calculation in parallel. + std::vector<std::future<double>> futures; + for(int i = 0; i < numThreads; ++i) + { + futures.push_back(std::async(calcPi, max_iter)); + } + + // Start measuring the time. + const std::chrono::steady_clock::time_point tbegin = std::chrono::steady_clock::now(); + for(auto & e : futures) + { + e.get(); + } + // Stop measuring the time. + const std::chrono::steady_clock::time_point tend = std::chrono::steady_clock::now(); + + // Print the measured duration. + cout << std::chrono::duration_cast<std::chrono::milliseconds>(tend - tbegin).count() << std::endl; + return 0; +} + diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index 1eb6b0ba7..cb2fd66e1 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -31,6 +31,10 @@ from __future__ import print_function import time import os +import math +import monotonic +import util_resources +import multiprocessing """Test daemon mining RPC calls @@ -71,12 +75,20 @@ class MiningTest(): def mine(self, via_daemon): print("Test mining via " + ("daemon" if via_daemon else "wallet")) + cores_init = multiprocessing.cpu_count() # RX init uses all cores + cores_mine = 1 # Mining uses a parametric number of cores + time_pi_single_cpu = self.measure_cpu_power_get_time(cores_mine) + time_pi_all_cores = self.measure_cpu_power_get_time(cores_init) + # This is the last measurement, since it takes very little time and can be placed timewise-closer to the mining itself. + available_ram = self.get_available_ram() # So far no ideas how to use this var, other than printing it + + start = monotonic.monotonic() daemon = Daemon() wallet = Wallet() # check info/height/balance before generating blocks res_info = daemon.get_info() - prev_height = res_info.height + initial_height = res_info.height res_getbalance = wallet.get_balance() prev_balance = res_getbalance.balance @@ -85,29 +97,71 @@ class MiningTest(): if via_daemon: res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1) else: - res = wallet.start_mining(threads_count = 1) + res = wallet.start_mining(threads_count = cores_mine) res_status = daemon.mining_status() assert res_status.active == True - assert res_status.threads_count == 1 + assert res_status.threads_count == cores_mine assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' assert res_status.is_background_mining_enabled == False assert res_status.block_reward >= 600000000000 # wait till we mined a few of them - target_height = prev_height + 5 - height = prev_height - timeout = 60 # randomx is slow to init + target_height = initial_height + 5 + height = initial_height + + """ + Randomx init has high variance on CI machines due to noisy neighbors, + taking up resources in parallel (including by our own jobs). + + Mining is organized in the following scheme: + 1) first loop's pass: RandomX init and mining + 2) every next pass: only mining + Pass 1) takes much more time than pass 2) + Pass 1) uses all cores, pass 2) just one (currently) + For the above reasons both passes need separate timeouts and adjustments. + After the first pass, the timeout is being reset to a lower value. + """ + + def calc_timeout(seconds_constant, time_pi, cores): + """ + The time it took to calculate pi under certain conditions + is proportional to the time it will take to calculate the real job. + + The number of cores used decreases the time almost linearly. + """ + timeout = float(seconds_constant) * time_pi / float(cores) + return timeout + + timeout_base_init = 60 # RX init needs more time + timeout_base_mine = 20 + timeout_init = calc_timeout(timeout_base_init, time_pi_all_cores, cores_init) + timeout_mine = calc_timeout(timeout_base_mine, time_pi_single_cpu, cores_mine) + + msg = "Timeout for {} adjusted for the currently available CPU power, is {:.1f} s" + print(msg.format("init, ", timeout_init)) + print(msg.format("mining,", timeout_mine)) + + timeout = timeout_init + rx_inited = False # Gets initialized in the first pass of the below loop while height < target_height: seen_height = height - for _ in range(timeout): + for _ in range(int(math.ceil(timeout))): time.sleep(1) + seconds_passed = monotonic.monotonic() - start height = daemon.get_info().height if height > seen_height: break else: - assert False, 'Failed to mine successor to block %d (initial block = %d)' % (seen_height, prev_height) - timeout = 10 + assert False, 'Failed to mine successor to block %d (initial block = %d) after %d s. RX initialized = %r' % (seen_height, initial_height, round(seconds_passed), rx_inited) + if not rx_inited: + rx_inited = True + timeout = timeout_mine # Resetting the timeout after first mined block and RX init + self.print_time_taken(start, "RX init + mining 1st block") + else: + self.print_time_taken(start, "mining iteration") + + self.print_time_taken(start, "mining total") if via_daemon: res = daemon.stop_mining() @@ -123,7 +177,7 @@ class MiningTest(): wallet.refresh() res_getbalance = wallet.get_balance() balance = res_getbalance.balance - assert balance >= prev_balance + (new_height - prev_height) * 600000000000 + assert balance >= prev_balance + (new_height - initial_height) * 600000000000 if via_daemon: res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1, do_background_mining = True) @@ -143,6 +197,22 @@ class MiningTest(): res = wallet.stop_mining() res_status = daemon.mining_status() assert res_status.active == False + + def measure_cpu_power_get_time(self, cores): + print("Measuring the currently available CPU power...") + time_pi = util_resources.get_time_pi_seconds(cores) + print("Time taken to calculate Pi on {} core(s) was {:.2f} s.".format(cores, time_pi)) + return time_pi + + def get_available_ram(self): + available_ram = util_resources.available_ram_gb() + threshold_ram = 3 + print("Available RAM =", round(available_ram, 1), "GB") + if available_ram < threshold_ram: + print("Warning! Available RAM =", round(available_ram, 1), + "GB is less than the reasonable threshold =", threshold_ram, + ". The RX init might exceed the calculated timeout.") + return available_ram def submitblock(self): print("Test submitblock") @@ -170,6 +240,10 @@ class MiningTest(): res = daemon.get_height() assert res.height == height + i + 1 assert res.hash == block_hash + + def print_time_taken(self, start, msg_context): + seconds_passed = monotonic.monotonic() - start + print("Time taken for", msg_context, "=", round(seconds_passed, 1), "s.") def test_randomx(self): print("Test RandomX") diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index cec6825f5..3f5f90d3b 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -41,7 +41,6 @@ using namespace cryptonote; namespace { uint64_t const TEST_FEE = 5000000000; // 5 * 10^9 - uint64_t const TEST_DUST_THRESHOLD = 5000000000; // 5 * 10^9 } std::string generate_random_wallet_name() diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py new file mode 100755 index 000000000..e45122e66 --- /dev/null +++ b/tests/functional_tests/util_resources.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2021 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. + +""" + Help determine how much CPU power is available at the given time + by running numerical calculations +""" + +from __future__ import print_function +import subprocess +import psutil + +def available_ram_gb(): + ram_bytes = psutil.virtual_memory().available + kilo = 1024.0 + ram_gb = ram_bytes / kilo**3 + return ram_gb + +def get_time_pi_seconds(cores): + app_path = './cpu_power_test' + time_calc = subprocess.check_output([app_path, str(cores)]) + decoded = time_calc.decode('utf-8') + miliseconds = int(decoded) + + return miliseconds / 1000.0 diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt index a599f86f8..0cf1740ad 100644 --- a/tests/fuzz/CMakeLists.txt +++ b/tests/fuzz/CMakeLists.txt @@ -218,3 +218,13 @@ set_property(TARGET tx-extra_fuzz_tests PROPERTY FOLDER "tests") +monero_add_minimal_executable(utf8_fuzz_tests utf8.cpp fuzzer.cpp) +target_link_libraries(utf8_fuzz_tests + PRIVATE + common + epee + ${Boost_THREAD_LIBRARY} + ${Boost_CHRONO_LIBRARY} + ${CMAKE_THREAD_LIBS_INIT} + ${EXTRA_LIBRARIES} + $ENV{LIB_FUZZING_ENGINE}) diff --git a/tests/fuzz/cold-outputs.cpp b/tests/fuzz/cold-outputs.cpp index 2698a36ba..bd298eb71 100644 --- a/tests/fuzz/cold-outputs.cpp +++ b/tests/fuzz/cold-outputs.cpp @@ -51,7 +51,7 @@ END_INIT_SIMPLE_FUZZER() BEGIN_SIMPLE_FUZZER() std::string s((const char*)buf, len); - std::pair<size_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; std::stringstream iss; iss << s; binary_archive<false> ar(iss); diff --git a/tests/fuzz/levin.cpp b/tests/fuzz/levin.cpp index b090c350b..209a75221 100644 --- a/tests/fuzz/levin.cpp +++ b/tests/fuzz/levin.cpp @@ -52,6 +52,9 @@ namespace struct test_levin_connection_context : public epee::net_utils::connection_context_base { + static constexpr int handshake_command() noexcept { return 1001; } + static constexpr bool handshake_complete() noexcept { return true; } + size_t get_max_bytes(int command) const { return LEVIN_DEFAULT_MAX_PACKET_SIZE; } }; typedef epee::levin::async_protocol_handler_config<test_levin_connection_context> test_levin_protocol_handler_config; @@ -65,13 +68,13 @@ namespace { } - virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, test_levin_connection_context& context) + virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, test_levin_connection_context& context) { m_invoke_counter.inc(); boost::unique_lock<boost::mutex> lock(m_mutex); m_last_command = command; m_last_in_buf = std::string((const char*)in_buff.data(), in_buff.size()); - buff_out = m_invoke_out_buf.clone(); + buff_out.write(epee::to_span(m_invoke_out_buf)); return m_return_code; } diff --git a/tests/fuzz/utf8.cpp b/tests/fuzz/utf8.cpp new file mode 100644 index 000000000..bf304a351 --- /dev/null +++ b/tests/fuzz/utf8.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2017-2020, 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 "include_base_utils.h" +#include "file_io_utils.h" +#include "common/utf8.h" +#include "fuzzer.h" + +BEGIN_INIT_SIMPLE_FUZZER() +END_INIT_SIMPLE_FUZZER() + +BEGIN_SIMPLE_FUZZER() + tools::utf8canonical(std::string((const char*)buf, len), [](wint_t c)->wint_t { return c; }); +END_SIMPLE_FUZZER() diff --git a/tests/net_load_tests/net_load_tests.h b/tests/net_load_tests/net_load_tests.h index e7e0ee247..59eef7bd1 100644 --- a/tests/net_load_tests/net_load_tests.h +++ b/tests/net_load_tests/net_load_tests.h @@ -48,6 +48,9 @@ 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) {} + static constexpr int handshake_command() noexcept { return 1001; } + static constexpr bool handshake_complete() noexcept { return true; } + size_t get_max_bytes(int command) const { return LEVIN_DEFAULT_MAX_PACKET_SIZE; } volatile bool m_closed; }; @@ -64,7 +67,7 @@ namespace net_load_tests { } - virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, test_connection_context& context) + virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, test_connection_context& context) { //m_invoke_counter.inc(); //std::unique_lock<std::mutex> lock(m_mutex); diff --git a/tests/net_load_tests/srv.cpp b/tests/net_load_tests/srv.cpp index 84d97ebd4..fabe14f49 100644 --- a/tests/net_load_tests/srv.cpp +++ b/tests/net_load_tests/srv.cpp @@ -169,7 +169,7 @@ namespace LOG_PRINT_L0("Closing connections. Number of opened connections: " << m_tcp_server.get_config_object().get_connections_count()); size_t count = 0; - bool r = m_tcp_server.get_config_object().foreach_connection([&](test_connection_context& ctx) { + m_tcp_server.get_config_object().foreach_connection([&](test_connection_context& ctx) { if (ctx.m_connection_id != cmd_conn_id) { ++count; diff --git a/tests/performance_tests/performance_tests.h b/tests/performance_tests/performance_tests.h index ae7aabe08..7bedfdd4e 100644 --- a/tests/performance_tests/performance_tests.h +++ b/tests/performance_tests/performance_tests.h @@ -215,7 +215,6 @@ void run_test(const std::string &filter, Params ¶ms, const char* test_name) if (params.stats) { uint64_t mins = min / scale; - uint64_t maxs = max / scale; uint64_t meds = med / scale; uint64_t p95s = quantiles[9] / scale; uint64_t stddevs = stddev / scale; diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 33ef93288..556e0ec40 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -47,6 +47,7 @@ set(unit_tests_sources dns_resolver.cpp epee_boosted_tcp_server.cpp epee_levin_protocol_handler_async.cpp + epee_serialization.cpp epee_utils.cpp expect.cpp fee.cpp diff --git a/tests/unit_tests/bulletproofs.cpp b/tests/unit_tests/bulletproofs.cpp index 7c6e459f5..ee617938c 100644 --- a/tests/unit_tests/bulletproofs.cpp +++ b/tests/unit_tests/bulletproofs.cpp @@ -296,7 +296,6 @@ TEST(bulletproof, weight_pruned) ASSERT_TRUE(tx.version == 2); ASSERT_FALSE(tx.pruned); ASSERT_TRUE(rct::is_rct_bulletproof(tx.rct_signatures.type)); - const uint64_t tx_size = bd.size(); const uint64_t tx_weight = cryptonote::get_transaction_weight(tx); ASSERT_TRUE(parse_and_validate_tx_base_from_blob(bd, pruned_tx)); ASSERT_TRUE(pruned_tx.version == 2); diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp index 7ac87ca7c..b550b35b0 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -33,6 +33,7 @@ #include <string> #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "cryptonote_basic/merge_mining.h" namespace { @@ -99,3 +100,215 @@ TEST(Crypto, verify_32) } } } + +TEST(Crypto, tree_branch) +{ + crypto::hash inputs[6]; + crypto::hash branch[8]; + crypto::hash root, root2; + size_t depth; + uint32_t path, path2; + + auto hasher = [](const crypto::hash &h0, const crypto::hash &h1) -> crypto::hash + { + char buffer[64]; + memcpy(buffer, &h0, 32); + memcpy(buffer + 32, &h1, 32); + crypto::hash res; + cn_fast_hash(buffer, 64, res); + return res; + }; + + for (int n = 0; n < 6; ++n) + { + memset(&inputs[n], 0, 32); + inputs[n].data[0] = n + 1; + } + + // empty + ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 0, crypto::null_hash.data, (char(*)[32])branch, &depth, &path)); + + // one, matching + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 1, inputs[0].data, (char(*)[32])branch, &depth, &path)); + ASSERT_EQ(depth, 0); + ASSERT_EQ(path, 0); + ASSERT_TRUE(crypto::tree_path(1, 0, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 1, root.data); + ASSERT_EQ(root, inputs[0]); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // one, not found + ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 1, inputs[1].data, (char(*)[32])branch, &depth, &path)); + + // two, index 0 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 2, inputs[0].data, (char(*)[32])branch, &depth, &path)); + ASSERT_EQ(depth, 1); + ASSERT_EQ(path, 0); + ASSERT_TRUE(crypto::tree_path(2, 0, &path2)); + ASSERT_EQ(path, path2); + ASSERT_EQ(branch[0], inputs[1]); + crypto::tree_hash((const char(*)[32])inputs, 2, root.data); + ASSERT_EQ(root, hasher(inputs[0], inputs[1])); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // two, index 1 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 2, inputs[1].data, (char(*)[32])branch, &depth, &path)); + ASSERT_EQ(depth, 1); + ASSERT_EQ(path, 1); + ASSERT_TRUE(crypto::tree_path(2, 1, &path2)); + ASSERT_EQ(path, path2); + ASSERT_EQ(branch[0], inputs[0]); + crypto::tree_hash((const char(*)[32])inputs, 2, root.data); + ASSERT_EQ(root, hasher(inputs[0], inputs[1])); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // two, not found + ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 2, inputs[2].data, (char(*)[32])branch, &depth, &path)); + + // a b c 0 + // x y + // z + + // three, index 0 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 3, inputs[0].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 1); + ASSERT_LE(depth, 2); + ASSERT_TRUE(crypto::tree_path(3, 0, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 3, root.data); + ASSERT_EQ(root, hasher(inputs[0], hasher(inputs[1], inputs[2]))); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // three, index 1 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 3, inputs[1].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 1); + ASSERT_LE(depth, 2); + ASSERT_TRUE(crypto::tree_path(3, 1, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 3, root.data); + ASSERT_EQ(root, hasher(inputs[0], hasher(inputs[1], inputs[2]))); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // three, index 2 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 3, inputs[2].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 1); + ASSERT_LE(depth, 2); + ASSERT_TRUE(crypto::tree_path(3, 2, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 3, root.data); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::tree_branch_hash(inputs[2].data, (const char(*)[32])branch, depth, path, root2.data)); + ASSERT_EQ(root, root2); + + // three, not found + ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 3, inputs[3].data, (char(*)[32])branch, &depth, &path)); + + // a b c d e 0 0 0 + // x y + // z + // w + + // five, index 0 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 5, inputs[0].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 2); + ASSERT_LE(depth, 3); + ASSERT_TRUE(crypto::tree_path(5, 0, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 5, root.data); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[5].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // five, index 1 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 5, inputs[1].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 2); + ASSERT_LE(depth, 3); + ASSERT_TRUE(crypto::tree_path(5, 1, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 5, root.data); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[5].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // five, index 2 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 5, inputs[2].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 2); + ASSERT_LE(depth, 3); + ASSERT_TRUE(crypto::tree_path(5, 2, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 5, root.data); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[5].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + // five, index 4 + ASSERT_TRUE(crypto::tree_branch((const char(*)[32])inputs, 5, inputs[4].data, (char(*)[32])branch, &depth, &path)); + ASSERT_GE(depth, 2); + ASSERT_LE(depth, 3); + ASSERT_TRUE(crypto::tree_path(5, 4, &path2)); + ASSERT_EQ(path, path2); + crypto::tree_hash((const char(*)[32])inputs, 5, root.data); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[0].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[1].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[2].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[3].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_TRUE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[5].data, root.data, (const char(*)[32])branch, depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth - 1, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth + 1, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 1)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 2)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 3)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])(branch + 1), depth, path)); + + // five, not found + ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 5, crypto::null_hash.data, (char(*)[32])branch, &depth, &path)); + + // depth encoding roundtrip + for (uint32_t n_chains = 1; n_chains <= 65; ++n_chains) + { + for (uint32_t nonce = 0; nonce < 1024; ++nonce) + { + const uint32_t depth = cryptonote::encode_mm_depth(n_chains, nonce); + uint32_t n_chains_2, nonce_2; + ASSERT_TRUE(cryptonote::decode_mm_depth(depth, n_chains_2, nonce_2)); + ASSERT_EQ(n_chains, n_chains_2); + ASSERT_EQ(nonce, nonce_2); + } + } +} diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index 71098f612..84fc0a29b 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -37,6 +37,7 @@ #include "include_base_utils.h" #include "string_tools.h" #include "net/abstract_tcp_server2.h" +#include "net/levin_protocol_handler_async.h" namespace { @@ -132,3 +133,333 @@ TEST(boosted_tcp_server, worker_threads_are_exception_resistant) ASSERT_TRUE(srv.timed_wait_server_stop(5 * 1000)); ASSERT_TRUE(srv.deinit_server()); } + + +TEST(test_epee_connection, test_lifetime) +{ + struct context_t: epee::net_utils::connection_context_base { + static constexpr size_t get_max_bytes(int) noexcept { return -1; } + static constexpr int handshake_command() noexcept { return 1001; } + static constexpr bool handshake_complete() noexcept { return true; } + }; + + using functional_obj_t = std::function<void ()>; + struct command_handler_t: epee::levin::levin_commands_handler<context_t> { + size_t delay; + functional_obj_t on_connection_close_f; + command_handler_t(size_t delay = 0, + functional_obj_t on_connection_close_f = nullptr + ): + delay(delay), + on_connection_close_f(on_connection_close_f) + {} + virtual int invoke(int, const epee::span<const uint8_t>, epee::byte_stream&, context_t&) override { epee::misc_utils::sleep_no_w(delay); return {}; } + virtual int notify(int, const epee::span<const uint8_t>, context_t&) override { return {}; } + virtual void callback(context_t&) override {} + virtual void on_connection_new(context_t&) override {} + virtual void on_connection_close(context_t&) override { + if (on_connection_close_f) + on_connection_close_f(); + } + virtual ~command_handler_t() override {} + static void destroy(epee::levin::levin_commands_handler<context_t>* ptr) { delete ptr; } + }; + + using handler_t = epee::levin::async_protocol_handler<context_t>; + using connection_t = epee::net_utils::connection<handler_t>; + using connection_ptr = boost::shared_ptr<connection_t>; + using shared_state_t = typename connection_t::shared_state; + using shared_state_ptr = std::shared_ptr<shared_state_t>; + using shared_states_t = std::vector<shared_state_ptr>; + using tag_t = boost::uuids::uuid; + using tags_t = std::vector<tag_t>; + using io_context_t = boost::asio::io_service; + using endpoint_t = boost::asio::ip::tcp::endpoint; + using work_t = boost::asio::io_service::work; + using work_ptr = std::shared_ptr<work_t>; + using workers_t = std::vector<std::thread>; + using server_t = epee::net_utils::boosted_tcp_server<handler_t>; + using lock_t = std::mutex; + using lock_guard_t = std::lock_guard<lock_t>; + using connection_weak_ptr = boost::weak_ptr<connection_t>; + struct shared_conn_t { + lock_t lock; + connection_weak_ptr conn; + }; + using shared_conn_ptr = std::shared_ptr<shared_conn_t>; + + io_context_t io_context; + work_ptr work(std::make_shared<work_t>(io_context)); + + workers_t workers; + while (workers.size() < 4) { + workers.emplace_back([&io_context]{ + io_context.run(); + }); + } + + endpoint_t endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 5262); + server_t server(epee::net_utils::e_connection_type_P2P); + server.init_server(endpoint.port(), + endpoint.address().to_string(), + 0, + "", + false, + true, + epee::net_utils::ssl_support_t::e_ssl_support_disabled + ); + server.run_server(2, false); + server.get_config_shared()->set_handler(new command_handler_t, &command_handler_t::destroy); + + io_context.post([&io_context, &work, &endpoint, &server]{ + auto scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&work]{ + work.reset(); + }); + + shared_state_ptr shared_state(std::make_shared<shared_state_t>()); + shared_state->set_handler(new command_handler_t, &command_handler_t::destroy); + + auto create_connection = [&io_context, &endpoint, &shared_state] { + connection_ptr conn(new connection_t(io_context, shared_state, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + context_t context; + conn->get_context(context); + auto tag = context.m_connection_id; + return tag; + }; + + ASSERT_TRUE(shared_state->get_connections_count() == 0); + auto tag = create_connection(); + ASSERT_TRUE(shared_state->get_connections_count() == 1); + bool success = shared_state->for_connection(tag, [shared_state](context_t& context){ + shared_state->close(context.m_connection_id); + context.m_remote_address.get_zone(); + return true; + }); + ASSERT_TRUE(success); + + ASSERT_TRUE(shared_state->get_connections_count() == 0); + constexpr auto N = 8; + tags_t tags(N); + for(auto &t: tags) + t = create_connection(); + ASSERT_TRUE(shared_state->get_connections_count() == N); + size_t index = 0; + success = shared_state->foreach_connection([&index, shared_state, &tags, &create_connection](context_t& context){ + if (!index) + for (const auto &t: tags) + shared_state->close(t); + + shared_state->close(context.m_connection_id); + context.m_remote_address.get_zone(); + ++index; + + for(auto i = 0; i < N; ++i) + create_connection(); + return true; + }); + ASSERT_TRUE(success); + ASSERT_TRUE(index == N); + ASSERT_TRUE(shared_state->get_connections_count() == N * N); + + index = 0; + success = shared_state->foreach_connection([&index, shared_state](context_t& context){ + shared_state->close(context.m_connection_id); + context.m_remote_address.get_zone(); + ++index; + return true; + }); + ASSERT_TRUE(success); + ASSERT_TRUE(index == N * N); + ASSERT_TRUE(shared_state->get_connections_count() == 0); + + while (shared_state->sock_count); + ASSERT_TRUE(shared_state->get_connections_count() == 0); + constexpr auto DELAY = 30; + constexpr auto TIMEOUT = 1; + server.get_config_shared()->set_handler(new command_handler_t(DELAY), &command_handler_t::destroy); + for (auto i = 0; i < N; ++i) { + tag = create_connection(); + ASSERT_TRUE(shared_state->get_connections_count() == 1); + success = shared_state->invoke_async(1, epee::levin::message_writer{}, tag, [](int, const epee::span<const uint8_t>, context_t&){}, TIMEOUT); + ASSERT_TRUE(success); + while (shared_state->sock_count == 1) { + success = shared_state->foreach_connection([&shared_state, &tag](context_t&){ + return shared_state->request_callback(tag); + }); + ASSERT_TRUE(success); + } + shared_state->close(tag); + ASSERT_TRUE(shared_state->get_connections_count() == 0); + } + + while (shared_state->sock_count); + constexpr auto ZERO_DELAY = 0; + size_t counter = 0; + shared_state->set_handler(new command_handler_t(ZERO_DELAY, + [&counter]{ + ASSERT_TRUE(counter++ == 0); + } + ), + &command_handler_t::destroy + ); + connection_ptr conn(new connection_t(io_context, shared_state, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + ASSERT_TRUE(shared_state->get_connections_count() == 1); + shared_state->del_out_connections(1); + ASSERT_TRUE(shared_state->get_connections_count() == 0); + conn.reset(); + + while (shared_state->sock_count); + shared_conn_ptr shared_conn(std::make_shared<shared_conn_t>()); + shared_state->set_handler(new command_handler_t(ZERO_DELAY, + [shared_state, shared_conn]{ + { + connection_ptr conn; + { + lock_guard_t guard(shared_conn->lock); + conn = std::move(shared_conn->conn.lock()); + } + if (conn) + conn->cancel(); + } + const auto success = shared_state->foreach_connection([](context_t&){ + return true; + }); + ASSERT_TRUE(success); + } + ), + &command_handler_t::destroy + ); + for (auto i = 0; i < N; ++i) { + { + connection_ptr conn(new connection_t(io_context, shared_state, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + lock_guard_t guard(shared_conn->lock); + shared_conn->conn = conn; + } + ASSERT_TRUE(shared_state->get_connections_count() == 1); + shared_state->del_out_connections(1); + ASSERT_TRUE(shared_state->get_connections_count() == 0); + } + + shared_states_t shared_states; + while (shared_states.size() < 2) { + shared_states.emplace_back(std::make_shared<shared_state_t>()); + shared_states.back()->set_handler(new command_handler_t(ZERO_DELAY, + [&shared_states]{ + for (auto &s: shared_states) { + auto success = s->foreach_connection([](context_t&){ + return true; + }); + ASSERT_TRUE(success); + } + } + ), + &command_handler_t::destroy + ); + } + workers_t workers; + + for (auto &s: shared_states) { + workers.emplace_back([&io_context, &s, &endpoint]{ + for (auto i = 0; i < N * N; ++i) { + connection_ptr conn(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + io_context.post([conn]{ + conn->cancel(); + }); + conn.reset(); + s->del_out_connections(1); + while (s->sock_count); + } + }); + } + for (;workers.size(); workers.pop_back()) + workers.back().join(); + + for (auto &s: shared_states) { + workers.emplace_back([&io_context, &s, &endpoint]{ + for (auto i = 0; i < N * N; ++i) { + connection_ptr conn(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + conn->cancel(); + while (conn.use_count() > 1); + s->foreach_connection([&io_context, &s, &endpoint, &conn](context_t& context){ + conn.reset(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + conn->cancel(); + while (conn.use_count() > 1); + conn.reset(); + return true; + }); + while (s->sock_count); + } + }); + } + for (;workers.size(); workers.pop_back()) + workers.back().join(); + + for (auto &s: shared_states) { + workers.emplace_back([&io_context, &s, &endpoint]{ + for (auto i = 0; i < N; ++i) { + connection_ptr conn(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + context_t context; + conn->get_context(context); + auto tag = context.m_connection_id; + conn->cancel(); + while (conn.use_count() > 1); + s->for_connection(tag, [&io_context, &s, &endpoint, &conn](context_t& context){ + conn.reset(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + conn->cancel(); + while (conn.use_count() > 1); + conn.reset(); + return true; + }); + while (s->sock_count); + } + }); + } + for (;workers.size(); workers.pop_back()) + workers.back().join(); + + for (auto &s: shared_states) { + workers.emplace_back([&io_context, &s, &endpoint]{ + for (auto i = 0; i < N; ++i) { + connection_ptr conn(new connection_t(io_context, s, {}, {})); + conn->socket().connect(endpoint); + conn->start({}, {}); + context_t context; + conn->get_context(context); + auto tag = context.m_connection_id; + io_context.post([conn]{ + conn->cancel(); + }); + conn.reset(); + s->close(tag); + while (s->sock_count); + } + }); + } + for (;workers.size(); workers.pop_back()) + workers.back().join(); + + }); + + for (auto& w: workers) { + w.join(); + } + server.send_stop_signal(); + server.timed_wait_server_stop(5 * 1000); + server.deinit_server(); +} diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp index ab6791324..d9de99b8b 100644 --- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp +++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp @@ -43,6 +43,9 @@ namespace { struct test_levin_connection_context : public epee::net_utils::connection_context_base { + static constexpr int handshake_command() noexcept { return 1001; } + static constexpr bool handshake_complete() noexcept { return true; } + size_t get_max_bytes(int command) const { return LEVIN_DEFAULT_MAX_PACKET_SIZE; } }; typedef epee::levin::async_protocol_handler_config<test_levin_connection_context> test_levin_protocol_handler_config; @@ -56,13 +59,13 @@ namespace { } - virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, test_levin_connection_context& context) + virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, test_levin_connection_context& context) { m_invoke_counter.inc(); boost::unique_lock<boost::mutex> lock(m_mutex); m_last_command = command; m_last_in_buf = std::string((const char*)in_buff.data(), in_buff.size()); - buff_out = m_invoke_out_buf.clone(); + buff_out.write(epee::to_span(m_invoke_out_buf)); return m_return_code; } @@ -193,6 +196,7 @@ namespace { m_handler_config.set_handler(m_pcommands_handler, [](epee::levin::levin_commands_handler<test_levin_connection_context> *handler) { delete handler; }); m_handler_config.m_invoke_timeout = invoke_timeout; + m_handler_config.m_initial_max_packet_size = max_packet_size; m_handler_config.m_max_packet_size = max_packet_size; } @@ -430,8 +434,11 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process const int expected_command = 4673261; const std::string in_data(256, 'e'); + epee::levin::message_writer message{}; + message.buffer.write(epee::to_span(in_data)); + const epee::byte_slice noise = epee::levin::make_noise_notify(1024); - const epee::byte_slice notify = epee::levin::make_notify(expected_command, epee::strspan<std::uint8_t>(in_data)); + const epee::byte_slice notify = message.finalize_notify(expected_command); test_connection_ptr conn = create_connection(); @@ -464,11 +471,16 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process const int expected_command = 4673261; const int expected_fragmented_command = 46732; const std::string in_data(256, 'e'); - std::string in_fragmented_data(1024 * 4, 'c'); + + epee::levin::message_writer message{}; + message.buffer.write(epee::to_span(in_data)); + + epee::levin::message_writer in_fragmented_data; + in_fragmented_data.buffer.put_n('c', 1024 * 4); const epee::byte_slice noise = epee::levin::make_noise_notify(1024); - const epee::byte_slice notify = epee::levin::make_notify(expected_command, epee::strspan<std::uint8_t>(in_data)); - epee::byte_slice fragmented = epee::levin::make_fragmented_notify(noise, expected_fragmented_command, epee::strspan<std::uint8_t>(in_fragmented_data)); + const epee::byte_slice notify = message.finalize_notify(expected_command); + epee::byte_slice fragmented = epee::levin::make_fragmented_notify(noise.size(), expected_fragmented_command, std::move(in_fragmented_data)); EXPECT_EQ(5u, fragmented.size() / 1024); EXPECT_EQ(0u, fragmented.size() % 1024); @@ -493,11 +505,13 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_process ASSERT_TRUE(conn->m_protocol_handler.handle_recv(next.data(), next.size())); } - in_fragmented_data.resize(((1024 - sizeof(epee::levin::bucket_head2)) * 5) - sizeof(epee::levin::bucket_head2)); // add padding zeroes + std::string compare_buffer(1024 * 4, 'c'); + compare_buffer.resize(((1024 - sizeof(epee::levin::bucket_head2)) * 5) - sizeof(epee::levin::bucket_head2)); // add padding zeroes + ASSERT_EQ(4u, m_commands_handler.notify_counter()); ASSERT_EQ(0u, m_commands_handler.invoke_counter()); ASSERT_EQ(expected_fragmented_command, m_commands_handler.last_command()); - ASSERT_EQ(in_fragmented_data, m_commands_handler.last_in_buf()); + ASSERT_EQ(compare_buffer, m_commands_handler.last_in_buf()); ASSERT_EQ(0u, conn->send_counter()); ASSERT_TRUE(conn->last_send_data().empty()); diff --git a/tests/unit_tests/epee_serialization.cpp b/tests/unit_tests/epee_serialization.cpp new file mode 100644 index 000000000..95a2b6ecd --- /dev/null +++ b/tests/unit_tests/epee_serialization.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2020, 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 <cstdint> +#include <gtest/gtest.h> + +#include "storages/portable_storage.h" +#include "span.h" + +TEST(epee_binary, two_keys) +{ + static constexpr const std::uint8_t data[] = { + 0x01, 0x11, 0x01, 0x1, 0x01, 0x01, 0x02, 0x1, 0x1, 0x08, 0x01, 'a', + 0x0B, 0x00, 0x01, 'b', 0x0B, 0x00 + }; + + epee::serialization::portable_storage storage{}; + EXPECT_TRUE(storage.load_from_binary(data)); +} + +TEST(epee_binary, duplicate_key) +{ + static constexpr const std::uint8_t data[] = { + 0x01, 0x11, 0x01, 0x1, 0x01, 0x01, 0x02, 0x1, 0x1, 0x08, 0x01, 'a', + 0x0B, 0x00, 0x01, 'a', 0x0B, 0x00 + }; + + epee::serialization::portable_storage storage{}; + EXPECT_FALSE(storage.load_from_binary(data)); +} diff --git a/tests/unit_tests/epee_utils.cpp b/tests/unit_tests/epee_utils.cpp index 256f8c3c2..cbe3c61b1 100644 --- a/tests/unit_tests/epee_utils.cpp +++ b/tests/unit_tests/epee_utils.cpp @@ -1115,11 +1115,13 @@ TEST(ByteStream, ToByteSlice) epee::byte_stream stream; + stream.reserve(128*1024); stream.write(source); EXPECT_EQ(sizeof(source), stream.size()); + EXPECT_EQ(128*1024, stream.capacity()); EXPECT_TRUE(equal(source, byte_span{stream.data(), stream.size()})); - const epee::byte_slice slice{std::move(stream)}; + const epee::byte_slice slice{std::move(stream), true}; EXPECT_EQ(0u, stream.size()); EXPECT_EQ(0u, stream.available()); EXPECT_EQ(0u, stream.capacity()); diff --git a/tests/unit_tests/hardfork.cpp b/tests/unit_tests/hardfork.cpp index 6d7e9a8aa..912651de4 100644 --- a/tests/unit_tests/hardfork.cpp +++ b/tests/unit_tests/hardfork.cpp @@ -432,7 +432,6 @@ TEST(voting, info) // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 static const uint8_t block_versions[] = { 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4 }; - static const uint8_t expected_versions[] = { 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4 }; static const uint8_t expected_thresholds[] = { 0, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 2, 2, 2, 2 }; for (uint64_t h = 0; h < sizeof(block_versions) / sizeof(block_versions[0]); ++h) { diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index 2ad149afe..30d6f8133 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -178,17 +178,17 @@ namespace { using base_type = epee::net_utils::connection_context_base; static_cast<base_type&>(context_) = base_type{random_generator(), {}, is_incoming, false}; + context_.m_state = cryptonote::cryptonote_connection_context::state_normal; handler_.after_init_connection(); } //\return Number of messages processed - std::size_t process_send_queue() + std::size_t process_send_queue(const bool valid = true) { std::size_t count = 0; for ( ; !endpoint_.send_queue_.empty(); ++count, endpoint_.send_queue_.pop_front()) { - // invalid messages shoudn't be possible in this test; - EXPECT_TRUE(handler_.handle_recv(endpoint_.send_queue_.front().data(), endpoint_.send_queue_.front().size())); + EXPECT_EQ(valid, handler_.handle_recv(endpoint_.send_queue_.front().data(), endpoint_.send_queue_.front().size())); } return count; } @@ -238,9 +238,16 @@ namespace return {connection, std::move(request)}; } - virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_slice& buff_out, cryptonote::levin::detail::p2p_context& context) override final + static received_message get_raw_message(std::deque<received_message>& queue) { - buff_out = nullptr; + received_message out{std::move(queue.front())}; + queue.pop_front(); + return out; + } + + virtual int invoke(int command, const epee::span<const uint8_t> in_buff, epee::byte_stream& buff_out, cryptonote::levin::detail::p2p_context& context) override final + { + buff_out.clear(); invoked_.push_back( {context.m_connection_id, command, std::string{reinterpret_cast<const char*>(in_buff.data()), in_buff.size()}} ); @@ -294,6 +301,11 @@ namespace { return get_message<T>(notified_); } + + received_message get_raw_notification() + { + return get_raw_message(notified_); + } }; class levin_notify : public ::testing::Test @@ -322,6 +334,8 @@ namespace EXPECT_EQ(0u, events_.relayed_method_size()); } + cryptonote::levin::connections& get_connections() noexcept { return *connections_; } + void add_connection(const bool is_incoming) { contexts_.emplace_back(io_service_, *connections_, random_generator_, is_incoming); @@ -370,21 +384,50 @@ TEST(make_header, expect_return) EXPECT_EQ(0u, header1.m_flags); } -TEST(make_notify, empty_payload) +TEST(message_writer, invoke_with_empty_payload) { - const epee::byte_slice message = epee::levin::make_notify(443, nullptr); + const epee::byte_slice message = epee::levin::message_writer{}.finalize_invoke(443); + const epee::levin::bucket_head2 header = + epee::levin::make_header(443, 0, LEVIN_PACKET_REQUEST, true); + ASSERT_EQ(sizeof(header), message.size()); + EXPECT_TRUE(std::memcmp(std::addressof(header), message.data(), sizeof(header)) == 0); +} + +TEST(message_writer, invoke_with_payload) +{ + std::string bytes(100, 'a'); + std::generate(bytes.begin(), bytes.end(), crypto::random_device{}); + + epee::levin::message_writer writer{}; + writer.buffer.write(epee::to_span(bytes)); + + const epee::byte_slice message = writer.finalize_invoke(443); + const epee::levin::bucket_head2 header = + epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_REQUEST, true); + + ASSERT_EQ(sizeof(header) + bytes.size(), message.size()); + EXPECT_TRUE(std::memcmp(std::addressof(header), message.data(), sizeof(header)) == 0); + EXPECT_TRUE(std::memcmp(bytes.data(), message.data() + sizeof(header), bytes.size()) == 0); +} + +TEST(message_writer, notify_with_empty_payload) +{ + const epee::byte_slice message = epee::levin::message_writer{}.finalize_notify(443); const epee::levin::bucket_head2 header = epee::levin::make_header(443, 0, LEVIN_PACKET_REQUEST, false); ASSERT_EQ(sizeof(header), message.size()); EXPECT_TRUE(std::memcmp(std::addressof(header), message.data(), sizeof(header)) == 0); } -TEST(make_notify, with_payload) +TEST(message_writer, notify_with_payload) { std::string bytes(100, 'a'); std::generate(bytes.begin(), bytes.end(), crypto::random_device{}); - const epee::byte_slice message = epee::levin::make_notify(443, epee::strspan<std::uint8_t>(bytes)); + epee::levin::message_writer writer{}; + writer.buffer.write(epee::to_span(bytes)); + + const epee::byte_slice message = writer.finalize_notify(443); const epee::levin::bucket_head2 header = epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_REQUEST, false); @@ -393,6 +436,44 @@ TEST(make_notify, with_payload) EXPECT_TRUE(std::memcmp(bytes.data(), message.data() + sizeof(header), bytes.size()) == 0); } +TEST(message_writer, response_with_empty_payload) +{ + const epee::byte_slice message = epee::levin::message_writer{}.finalize_response(443, 1); + epee::levin::bucket_head2 header = + epee::levin::make_header(443, 0, LEVIN_PACKET_RESPONSE, false); + header.m_return_code = SWAP32LE(1); + ASSERT_EQ(sizeof(header), message.size()); + EXPECT_TRUE(std::memcmp(std::addressof(header), message.data(), sizeof(header)) == 0); +} + +TEST(message_writer, response_with_payload) +{ + std::string bytes(100, 'a'); + std::generate(bytes.begin(), bytes.end(), crypto::random_device{}); + + epee::levin::message_writer writer{}; + writer.buffer.write(epee::to_span(bytes)); + + const epee::byte_slice message = writer.finalize_response(443, 6450); + epee::levin::bucket_head2 header = + epee::levin::make_header(443, bytes.size(), LEVIN_PACKET_RESPONSE, false); + header.m_return_code = SWAP32LE(6450); + + ASSERT_EQ(sizeof(header) + bytes.size(), message.size()); + EXPECT_TRUE(std::memcmp(std::addressof(header), message.data(), sizeof(header)) == 0); + EXPECT_TRUE(std::memcmp(bytes.data(), message.data() + sizeof(header), bytes.size()) == 0); +} + +TEST(message_writer, error) +{ + epee::levin::message_writer writer{}; + writer.buffer.clear(); + + EXPECT_THROW(writer.finalize_invoke(0), std::runtime_error); + EXPECT_THROW(writer.finalize_notify(0), std::runtime_error); + EXPECT_THROW(writer.finalize_response(0, 0), std::runtime_error); +} + TEST(make_noise, invalid) { EXPECT_TRUE(epee::levin::make_noise_notify(sizeof(epee::levin::bucket_head2) - 1).empty()); @@ -414,13 +495,13 @@ TEST(make_noise, valid) TEST(make_fragment, invalid) { - EXPECT_TRUE(epee::levin::make_fragmented_notify(nullptr, 0, nullptr).empty()); + EXPECT_TRUE(epee::levin::make_fragmented_notify(0, 0, epee::levin::message_writer{}).empty()); } TEST(make_fragment, single) { const epee::byte_slice noise = epee::levin::make_noise_notify(1024); - const epee::byte_slice fragment = epee::levin::make_fragmented_notify(noise, 11, nullptr); + const epee::byte_slice fragment = epee::levin::make_fragmented_notify(noise.size(), 11, epee::levin::message_writer{}); const epee::levin::bucket_head2 header = epee::levin::make_header(11, 1024 - sizeof(epee::levin::bucket_head2), LEVIN_PACKET_REQUEST, false); @@ -435,8 +516,13 @@ TEST(make_fragment, multiple) std::string bytes(1024 * 3 - 150, 'a'); std::generate(bytes.begin(), bytes.end(), crypto::random_device{}); + epee::levin::message_writer message; + message.buffer.write(epee::to_span(bytes)); + const epee::byte_slice noise = epee::levin::make_noise_notify(1024); - epee::byte_slice fragment = epee::levin::make_fragmented_notify(noise, 114, epee::strspan<std::uint8_t>(bytes)); + epee::byte_slice fragment = epee::levin::make_fragmented_notify(noise.size(), 114, std::move(message)); + + EXPECT_EQ(1024 * 3, fragment.size()); epee::levin::bucket_head2 header = epee::levin::make_header(0, 1024 - sizeof(epee::levin::bucket_head2), LEVIN_PACKET_BEGIN, false); @@ -483,6 +569,7 @@ TEST(make_fragment, multiple) fragment.take_slice(bytes.size()); + EXPECT_EQ(18, fragment.size()); EXPECT_EQ(18, std::count(fragment.cbegin(), fragment.cend(), 0)); } @@ -650,8 +737,9 @@ TEST_F(levin_notify, stem_no_outs_without_padding) ASSERT_LT(0u, io_service_.poll()); EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff)); if (events_.has_stem_txes()) + { EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem)); - + } notifier.run_fluff(); ASSERT_LT(0u, io_service_.poll()); @@ -1020,7 +1108,9 @@ TEST_F(levin_notify, stem_no_outs_with_padding) ASSERT_LT(0u, io_service_.poll()); EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::fluff)); if (events_.has_stem_txes()) + { EXPECT_EQ(txs, events_.take_relayed(cryptonote::relay_method::stem)); + } notifier.run_fluff(); ASSERT_LT(0u, io_service_.poll()); @@ -2140,3 +2230,38 @@ TEST_F(levin_notify, noise_stem) } } } + +TEST_F(levin_notify, command_max_bytes) +{ + static constexpr int ping_command = nodetool::COMMAND_PING::ID; + + add_connection(true); + + std::string payload(4096, 'h'); + epee::byte_slice bytes; + { + epee::levin::message_writer dest{}; + dest.buffer.write(epee::to_span(payload)); + bytes = dest.finalize_notify(ping_command); + } + + EXPECT_EQ(1, get_connections().send(bytes.clone(), contexts_.front().get_id())); + EXPECT_EQ(1u, contexts_.front().process_send_queue(true)); + EXPECT_EQ(1u, receiver_.notified_size()); + + const received_message msg = receiver_.get_raw_notification(); + EXPECT_EQ(ping_command, msg.command); + EXPECT_EQ(contexts_.front().get_id(), msg.connection); + EXPECT_EQ(payload, msg.payload); + + { + payload.push_back('h'); + epee::levin::message_writer dest{}; + dest.buffer.write(epee::to_span(payload)); + bytes = dest.finalize_notify(ping_command); + } + + EXPECT_EQ(1, get_connections().send(std::move(bytes), contexts_.front().get_id())); + EXPECT_EQ(1u, contexts_.front().process_send_queue(false)); + EXPECT_EQ(0u, receiver_.notified_size()); +} diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index dffda5e4e..2836fd948 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -1351,7 +1351,7 @@ TEST(dandelionpp_map, dropped_connection) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(3u, entry.second); for (const boost::uuids::uuid& connection : in_connections) @@ -1409,7 +1409,7 @@ TEST(dandelionpp_map, dropped_connection) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(3u, entry.second); } { @@ -1472,7 +1472,7 @@ TEST(dandelionpp_map, dropped_connection_remapped) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(3u, entry.second); for (const boost::uuids::uuid& connection : in_connections) @@ -1511,7 +1511,7 @@ TEST(dandelionpp_map, dropped_connection_remapped) } EXPECT_EQ(2u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(5u, entry.second); } // select 3 of 3 connections but do not remap existing links @@ -1532,7 +1532,7 @@ TEST(dandelionpp_map, dropped_connection_remapped) } EXPECT_EQ(2u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(5u, entry.second); } // map 8 new incoming connections across 3 outgoing links @@ -1555,7 +1555,7 @@ TEST(dandelionpp_map, dropped_connection_remapped) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(6u, entry.second); } } @@ -1609,7 +1609,7 @@ TEST(dandelionpp_map, dropped_all_connections) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(3u, entry.second); for (const boost::uuids::uuid& connection : in_connections) @@ -1641,7 +1641,7 @@ TEST(dandelionpp_map, dropped_all_connections) } EXPECT_EQ(3u, used.size()); - for (const std::pair<boost::uuids::uuid, std::size_t>& entry : used) + for (const std::pair<const boost::uuids::uuid, std::size_t>& entry : used) EXPECT_EQ(3u, entry.second); } } diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index 0569d3748..8d5c784eb 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -55,7 +55,8 @@ public: bool init(const boost::program_options::variables_map& vm) {return true ;} bool deinit(){return true;} bool get_short_chain_history(std::list<crypto::hash>& ids) const { return true; } - bool have_block(const crypto::hash& id) const {return true;} + bool have_block(const crypto::hash& id, int *where = NULL) const {return false;} + bool have_block_unlocked(const crypto::hash& id, int *where = NULL) const {return false;} 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, 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; } @@ -249,7 +250,6 @@ TEST(ban, subnet) TEST(ban, ignores_port) { - time_t seconds; test_core pr_core; cryptonote::t_cryptonote_protocol_handler<test_core> cprotocol(pr_core, NULL); Server server(cprotocol); @@ -293,6 +293,18 @@ TEST(node_server, bind_same_p2p_port) boost::program_options::variables_map vm; boost::program_options::store(boost::program_options::parse_command_line(1, argv, desc_options), vm); + /* + Reason for choosing '127.0.0.2' as the IP: + + A TCP local socket address that has been bound is unavailable for some time after closing, unless the SO_REUSEADDR flag has been set. + That's why connections with automatically assigned source port 48080/58080 from previous test blocks the next to bind acceptor + so solution is to either set reuse_addr option for each socket in all tests + or use ip different from localhost for acceptors in order to not interfere with automatically assigned source endpoints + + Relevant part about REUSEADDR from man: + https://www.man7.org/linux/man-pages/man7/ip.7.html + */ + vm.find(nodetool::arg_p2p_bind_ip.name)->second = boost::program_options::variable_value(std::string("127.0.0.2"), false); vm.find(nodetool::arg_p2p_bind_port.name)->second = boost::program_options::variable_value(std::string(port), false); boost::program_options::notify(vm); @@ -310,5 +322,597 @@ TEST(node_server, bind_same_p2p_port) EXPECT_TRUE(init(new_node(), port_another)); } +TEST(cryptonote_protocol_handler, race_condition) +{ + struct contexts { + using basic = epee::net_utils::connection_context_base; + using cryptonote = cryptonote::cryptonote_connection_context; + using p2p = nodetool::p2p_connection_context_t<cryptonote>; + }; + using context_t = contexts::p2p; + using handler_t = epee::levin::async_protocol_handler<context_t>; + using connection_t = epee::net_utils::connection<handler_t>; + using connection_ptr = boost::shared_ptr<connection_t>; + using connections_t = std::vector<connection_ptr>; + using shared_state_t = typename connection_t::shared_state; + using shared_state_ptr = std::shared_ptr<shared_state_t>; + using io_context_t = boost::asio::io_service; + using event_t = epee::simple_event; + using ec_t = boost::system::error_code; + auto create_conn_pair = [](connection_ptr in, connection_ptr out) { + using endpoint_t = boost::asio::ip::tcp::endpoint; + using acceptor_t = boost::asio::ip::tcp::acceptor; + io_context_t io_context; + endpoint_t endpoint(boost::asio::ip::address::from_string("127.0.0.1"), 5262); + acceptor_t acceptor(io_context); + ec_t ec; + acceptor.open(endpoint.protocol(), ec); + EXPECT_EQ(ec.value(), 0); + acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); + acceptor.bind(endpoint, ec); + EXPECT_EQ(ec.value(), 0); + acceptor.listen(boost::asio::socket_base::max_listen_connections, ec); + EXPECT_EQ(ec.value(), 0); + out->socket().open(endpoint.protocol(), ec); + EXPECT_EQ(ec.value(), 0); + acceptor.async_accept(in->socket(), [](const ec_t &ec){}); + out->socket().async_connect(endpoint, [](const ec_t &ec){}); + io_context.run(); + acceptor.close(ec); + EXPECT_EQ(ec.value(), 0); + EXPECT_TRUE(in->start(true, true)); + EXPECT_TRUE(out->start(false, true)); + return std::make_pair<>(std::move(in), std::move(out)); + }; + auto get_conn_tag = [](connection_t &conn){ + context_t context; + conn.get_context(context); + return context.m_connection_id; + }; + using work_t = boost::asio::io_service::work; + using work_ptr = std::shared_ptr<work_t>; + using workers_t = std::vector<std::thread>; + using commands_handler_t = epee::levin::levin_commands_handler<context_t>; + using p2p_endpoint_t = nodetool::i_p2p_endpoint<contexts::cryptonote>; + using core_t = cryptonote::core; + using core_ptr = std::unique_ptr<core_t>; + using core_protocol_t = cryptonote::t_cryptonote_protocol_handler<core_t>; + using core_protocol_ptr = std::shared_ptr<core_protocol_t>; + using block_t = cryptonote::block; + using diff_t = cryptonote::difficulty_type; + using reward_t = uint64_t; + using height_t = uint64_t; + struct span { + using blocks = epee::span<const block_t>; + }; + auto get_block_template = []( + core_t &core, + block_t &block, + diff_t &diff, + reward_t &reward + ){ + auto &storage = core.get_blockchain_storage(); + const auto height = storage.get_current_blockchain_height(); + const auto hardfork = storage.get_current_hard_fork_version(); + block.major_version = hardfork; + block.minor_version = storage.get_ideal_hard_fork_version(); + block.prev_id = storage.get_tail_id(); + auto &db = storage.get_db(); + block.timestamp = db.get_top_block_timestamp(); + block.nonce = 0xACAB; + block.miner_tx.vin.clear(); + block.miner_tx.vout.clear(); + block.miner_tx.extra.clear(); + block.miner_tx.version = hardfork >= 4 ? 2 : 1; + block.miner_tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; + block.miner_tx.vin.push_back(cryptonote::txin_gen{height}); + cryptonote::add_tx_pub_key_to_extra(block.miner_tx, {}); + cryptonote::get_block_reward( + db.get_block_weight(height - 1), + {}, + db.get_block_already_generated_coins(height - 1), + reward, + hardfork + ); + block.miner_tx.vout.push_back(cryptonote::tx_out{reward, cryptonote::txout_to_key{}}); + diff = storage.get_difficulty_for_next_block(); + }; + struct stat { + struct chain { + diff_t diff; + reward_t reward; + }; + }; + auto add_block = []( + core_t &core, + const block_t &block, + const stat::chain &stat + ){ + core.get_blockchain_storage().get_db().batch_start({}, {}); + core.get_blockchain_storage().get_db().add_block( + {block, cryptonote::block_to_blob(block)}, + cryptonote::get_transaction_weight(block.miner_tx), + core.get_blockchain_storage().get_next_long_term_block_weight( + cryptonote::get_transaction_weight(block.miner_tx) + ), + stat.diff, + stat.reward, + {} + ); + core.get_blockchain_storage().get_db().batch_stop(); + }; + struct messages { + struct core { + using sync = cryptonote::CORE_SYNC_DATA; + }; + using handshake = nodetool::COMMAND_HANDSHAKE_T<core::sync>; + }; + struct net_node_t: commands_handler_t, p2p_endpoint_t { + using span_t = epee::span<const uint8_t>; + using string_t = std::string; + using zone_t = epee::net_utils::zone; + using uuid_t = boost::uuids::uuid; + using relay_t = cryptonote::relay_method; + using blobs_t = std::vector<cryptonote::blobdata>; + using id_t = nodetool::peerid_type; + using callback_t = std::function<bool(contexts::cryptonote &, id_t, uint32_t)>; + using address_t = epee::net_utils::network_address; + using connections_t = std::vector<std::pair<zone_t, uuid_t>>; + struct bans { + using subnets = std::map<epee::net_utils::ipv4_network_subnet, time_t>; + using hosts = std::map<std::string, time_t>; + }; + struct slice { + using bytes = epee::byte_slice; + }; + shared_state_ptr shared_state; + core_protocol_ptr core_protocol; + virtual int invoke(int command, const span_t in, slice::bytes &out, context_t &context) override { + if (core_protocol) { + if (command == messages::handshake::ID) { + return epee::net_utils::buff_to_t_adapter<void, typename messages::handshake::request, typename messages::handshake::response>( + command, + in, + out, + [this](int command, typename messages::handshake::request &in, typename messages::handshake::response &out, context_t &context){ + core_protocol->process_payload_sync_data(in.payload_data, context, true); + core_protocol->get_payload_sync_data(out.payload_data); + return 1; + }, + context + ); + } + bool handled; + return core_protocol->handle_invoke_map(false, command, in, out, context, handled); + } + else + return {}; + } + virtual int notify(int command, const span_t in, context_t &context) override { + if (core_protocol) { + bool handled; + slice::bytes out; + return core_protocol->handle_invoke_map(true, command, in, out, context, handled); + } + else + return {}; + } + virtual void callback(context_t &context) override { + if (core_protocol) + core_protocol->on_callback(context); + } + virtual void on_connection_new(context_t&) override {} + virtual void on_connection_close(context_t &context) override { + if (core_protocol) + core_protocol->on_connection_close(context); + } + virtual ~net_node_t() override {} + virtual bool add_host_fail(const address_t&, unsigned int = {}) override { + return {}; + } + virtual bool block_host(address_t address, time_t = {}, bool = {}) override { + return {}; + } + virtual bool drop_connection(const contexts::basic& context) override { + if (shared_state) + return shared_state->close(context.m_connection_id); + else + return {}; + } + virtual bool for_connection(const uuid_t& uuid, callback_t f) override { + if (shared_state) + return shared_state->for_connection(uuid,[&f](context_t &context){ + return f(context, context.peer_id, context.support_flags); + }); + else + return {}; + } + virtual bool invoke_command_to_peer(int command, const span_t in, string_t& out, const contexts::basic& context) override { + if (shared_state) + return shared_state->invoke(command, in, out, context.m_connection_id); + else + return {}; + } + virtual bool invoke_notify_to_peer(int command, const span_t in, const contexts::basic& context) override { + if (shared_state) + return shared_state->notify(command, in, context.m_connection_id); + else + return {}; + } + virtual bool relay_notify_to_list(int command, const span_t in, connections_t connections) override { + if (shared_state) { + for (auto &e: connections) + shared_state->notify(command, in, e.second); + } + return {}; + } + virtual bool unblock_host(const address_t&) override { + return {}; + } + virtual zone_t send_txs(blobs_t, const zone_t, const uuid_t&, relay_t) override { + return {}; + } + virtual bans::subnets get_blocked_subnets() override { + return {}; + } + virtual bans::hosts get_blocked_hosts() override { + return {}; + } + virtual uint64_t get_public_connections_count() override { + if (shared_state) + return shared_state->get_connections_count(); + else + return {}; + } + virtual void add_used_stripe_peer(const contexts::cryptonote&) override {} + virtual void clear_used_stripe_peers() override {} + virtual void remove_used_stripe_peer(const contexts::cryptonote&) override {} + virtual void for_each_connection(callback_t f) override { + if (shared_state) + shared_state->foreach_connection([&f](context_t &context){ + return f(context, context.peer_id, context.support_flags); + }); + } + virtual void request_callback(const contexts::basic &context) override { + if (shared_state) + shared_state->request_callback(context.m_connection_id); + } + }; + auto conduct_handshake = [get_conn_tag](net_node_t &net_node, connection_ptr conn){ + event_t handshaked; + net_node.shared_state->for_connection( + get_conn_tag(*conn), + [&handshaked, &net_node](context_t &context){ + typename messages::handshake::request msg; + net_node.core_protocol->get_payload_sync_data(msg.payload_data); + epee::net_utils::async_invoke_remote_command2<typename messages::handshake::response>( + context, + messages::handshake::ID, + msg, + *net_node.shared_state, + [&handshaked, &net_node](int code, const typename messages::handshake::response &msg, context_t &context){ + EXPECT_TRUE(code >= 0); + net_node.core_protocol->process_payload_sync_data(msg.payload_data, context, true); + handshaked.raise(); + }, + P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT + ); + return true; + } + ); + handshaked.wait(); + }; + using path_t = boost::filesystem::path; + auto create_dir = []{ + ec_t ec; + path_t path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path("daemon-%%%%%%%%%%%%%%%%", ec); + if (ec) + return path_t{}; + auto success = boost::filesystem::create_directory(path, ec); + if (not ec && success) + return path; + return path_t{}; + }; + auto remove_tree = [](const path_t &path){ + ec_t ec; + boost::filesystem::remove_all(path, ec); + }; + using options_t = boost::program_options::variables_map; + struct daemon_t { + options_t options; + core_ptr core; + core_protocol_ptr core_protocol; + net_node_t net_node; + shared_state_ptr shared_state; + connections_t conn; + }; + struct daemons_t { + daemon_t main; + daemon_t alt; + }; + using options_description_t = boost::program_options::options_description; + + const auto dir = create_dir(); + ASSERT_TRUE(not dir.empty()); + + daemons_t daemon{ + { + [&dir]{ + options_t options; + boost::program_options::store( + boost::program_options::command_line_parser({ + "--data-dir", + (dir / "main").string(), + "--disable-dns-checkpoints", + "--check-updates=disabled", + "--fixed-difficulty=1", + "--block-sync-size=1", + "--db-sync-mode=fastest:async:50000", + }).options([]{ + options_description_t options_description{}; + cryptonote::core::init_options(options_description); + return options_description; + }()).run(), + options + ); + return options; + }(), + {}, + {}, + {}, + {}, + {}, + }, + { + [&dir]{ + options_t options; + boost::program_options::store( + boost::program_options::command_line_parser({ + "--data-dir", + (dir / "alt").string(), + "--disable-dns-checkpoints", + "--check-updates=disabled", + "--fixed-difficulty=1", + "--block-sync-size=1", + "--db-sync-mode=fastest:async:50000", + }).options([]{ + options_description_t options_description{}; + cryptonote::core::init_options(options_description); + return options_description; + }()).run(), + options + ); + return options; + }(), + {}, + {}, + {}, + {}, + {}, + }, + }; + + io_context_t io_context; + work_ptr work = std::make_shared<work_t>(io_context); + workers_t workers; + while (workers.size() < 4) { + workers.emplace_back([&io_context]{ + io_context.run(); + }); + } + + connection_t::set_rate_up_limit(std::numeric_limits<int64_t>::max()); + connection_t::set_rate_down_limit(std::numeric_limits<int64_t>::max()); + + { + daemon.main.core = core_ptr(new core_t(nullptr)); + daemon.main.core->init(daemon.main.options, nullptr, nullptr); + daemon.main.net_node.core_protocol = daemon.main.core_protocol = core_protocol_ptr(new core_protocol_t( + *daemon.main.core, &daemon.main.net_node, {} + )); + daemon.main.core->set_cryptonote_protocol(daemon.main.core_protocol.get()); + daemon.main.core_protocol->init(daemon.main.options); + daemon.main.net_node.shared_state = daemon.main.shared_state = std::make_shared<shared_state_t>(); + daemon.main.shared_state->set_handler(&daemon.main.net_node); + daemon.alt.shared_state = std::make_shared<shared_state_t>(); + daemon.alt.shared_state->set_handler(&daemon.alt.net_node); + + struct { + event_t prepare; + event_t check; + event_t finish; + } events; + auto connections = create_conn_pair( + connection_ptr(new connection_t(io_context, daemon.main.shared_state, {}, {})), + connection_ptr(new connection_t(io_context, daemon.alt.shared_state, {}, {})) + ); + { + auto conn = connections.first; + auto shared_state = daemon.main.shared_state; + const auto tag = get_conn_tag(*conn); + conn->strand_.post([tag, conn, shared_state, &events]{ + shared_state->for_connection(tag, [](context_t &context){ + context.m_expect_height = -1; + context.m_expect_response = -1; + context.m_last_request_time = boost::date_time::min_date_time; + context.m_score = 0; + context.m_state = contexts::cryptonote::state_synchronizing; + return true; + }); + events.prepare.raise(); + events.check.wait(); + shared_state->for_connection(tag, [](context_t &context){ + EXPECT_TRUE(context.m_expect_height == -1); + EXPECT_TRUE(context.m_expect_response == -1); + EXPECT_TRUE(context.m_last_request_time == boost::date_time::min_date_time); + EXPECT_TRUE(context.m_score == 0); + EXPECT_TRUE(context.m_state == contexts::cryptonote::state_synchronizing); + return true; + }); + events.finish.raise(); + }); + } + events.prepare.wait(); + daemon.main.core_protocol->on_idle(); + events.check.raise(); + events.finish.wait(); + + connections.first->strand_.post([connections]{ + connections.first->cancel(); + }); + connections.second->strand_.post([connections]{ + connections.second->cancel(); + }); + connections.first.reset(); + connections.second.reset(); + while (daemon.main.shared_state->sock_count); + while (daemon.alt.shared_state->sock_count); + daemon.main.core_protocol->deinit(); + daemon.main.core->stop(); + daemon.main.core->deinit(); + daemon.main.net_node.shared_state.reset(); + daemon.main.shared_state.reset(); + daemon.main.core_protocol.reset(); + daemon.main.core.reset(); + daemon.alt.shared_state.reset(); + } + + { + daemon.main.core = core_ptr(new core_t(nullptr)); + daemon.main.core->init(daemon.main.options, nullptr, nullptr); + daemon.main.net_node.core_protocol = daemon.main.core_protocol = core_protocol_ptr(new core_protocol_t( + *daemon.main.core, &daemon.main.net_node, {} + )); + daemon.main.core->set_cryptonote_protocol(daemon.main.core_protocol.get()); + daemon.main.core->set_checkpoints({}); + daemon.main.core_protocol->init(daemon.main.options); + daemon.main.net_node.shared_state = daemon.main.shared_state = std::make_shared<shared_state_t>(); + daemon.main.shared_state->set_handler(&daemon.main.net_node); + daemon.alt.core = core_ptr(new core_t(nullptr)); + daemon.alt.core->init(daemon.alt.options, nullptr, nullptr); + daemon.alt.net_node.core_protocol = daemon.alt.core_protocol = core_protocol_ptr(new core_protocol_t( + *daemon.alt.core, &daemon.alt.net_node, {} + )); + daemon.alt.core->set_cryptonote_protocol(daemon.alt.core_protocol.get()); + daemon.alt.core->set_checkpoints({}); + daemon.alt.core_protocol->init(daemon.alt.options); + daemon.alt.net_node.shared_state = daemon.alt.shared_state = std::make_shared<shared_state_t>(); + daemon.alt.shared_state->set_handler(&daemon.alt.net_node); + + struct { + io_context_t io_context; + work_ptr work; + workers_t workers; + } check; + check.work = std::make_shared<work_t>(check.io_context); + check.workers.emplace_back([&check]{ + check.io_context.run(); + }); + while (daemon.main.conn.size() < 1) { + daemon.main.conn.emplace_back(new connection_t(check.io_context, daemon.main.shared_state, {}, {})); + daemon.alt.conn.emplace_back(new connection_t(io_context, daemon.alt.shared_state, {}, {})); + create_conn_pair(daemon.main.conn.back(), daemon.alt.conn.back()); + conduct_handshake(daemon.alt.net_node, daemon.alt.conn.back()); + } + struct { + event_t prepare; + event_t sync; + event_t finish; + } events; + { + auto conn = daemon.main.conn.back(); + auto shared_state = daemon.main.shared_state; + const auto tag = get_conn_tag(*conn); + conn->strand_.post([tag, conn, shared_state, &events]{ + shared_state->for_connection(tag, [](context_t &context){ + EXPECT_TRUE(context.m_state == contexts::cryptonote::state_normal); + return true; + }); + events.prepare.raise(); + events.sync.wait(); + shared_state->for_connection(tag, [](context_t &context){ + EXPECT_TRUE(context.m_state == contexts::cryptonote::state_normal); + return true; + }); + events.finish.raise(); + }); + } + events.prepare.wait(); + daemon.main.core->get_blockchain_storage().add_block_notify( + [&events](height_t height, span::blocks blocks){ + if (height >= CRYPTONOTE_PRUNING_STRIPE_SIZE) + events.sync.raise(); + } + ); + { + stat::chain stat{ + daemon.alt.core->get_blockchain_storage().get_db().get_block_cumulative_difficulty( + daemon.alt.core->get_current_blockchain_height() - 1 + ), + daemon.alt.core->get_blockchain_storage().get_db().get_block_already_generated_coins( + daemon.alt.core->get_current_blockchain_height() - 1 + ), + }; + while (daemon.alt.core->get_current_blockchain_height() < CRYPTONOTE_PRUNING_STRIPE_SIZE + CRYPTONOTE_PRUNING_TIP_BLOCKS) { + block_t block; + diff_t diff; + reward_t reward; + get_block_template(*daemon.alt.core, block, diff, reward); + stat.diff += diff; + stat.reward = stat.reward < (MONEY_SUPPLY - stat.reward) ? stat.reward + reward : MONEY_SUPPLY; + add_block(*daemon.alt.core, block, stat); + if (daemon.main.core->get_current_blockchain_height() + 1 < CRYPTONOTE_PRUNING_STRIPE_SIZE) + add_block(*daemon.main.core, block, stat); + } + } + while (daemon.main.conn.size() < 2) { + daemon.main.conn.emplace_back(new connection_t(io_context, daemon.main.shared_state, {}, {})); + daemon.alt.conn.emplace_back(new connection_t(io_context, daemon.alt.shared_state, {}, {})); + create_conn_pair(daemon.main.conn.back(), daemon.alt.conn.back()); + conduct_handshake(daemon.alt.net_node, daemon.alt.conn.back()); + } + events.finish.wait(); + + for (;daemon.main.conn.size(); daemon.main.conn.pop_back()) { + auto conn = daemon.main.conn.back(); + conn->strand_.post([conn]{ + conn->cancel(); + }); + } + for (;daemon.alt.conn.size(); daemon.alt.conn.pop_back()) { + auto conn = daemon.alt.conn.back(); + conn->strand_.post([conn]{ + conn->cancel(); + }); + } + while (daemon.main.shared_state->sock_count); + while (daemon.alt.shared_state->sock_count); + daemon.main.core_protocol->deinit(); + daemon.main.core->stop(); + daemon.main.core->deinit(); + daemon.main.net_node.shared_state.reset(); + daemon.main.shared_state.reset(); + daemon.main.core_protocol.reset(); + daemon.main.core.reset(); + daemon.alt.core_protocol->deinit(); + daemon.alt.core->stop(); + daemon.alt.core->deinit(); + daemon.alt.net_node.shared_state.reset(); + daemon.alt.shared_state.reset(); + daemon.alt.core_protocol.reset(); + daemon.alt.core.reset(); + check.work.reset(); + for (auto& w: check.workers) { + w.join(); + } + } + + work.reset(); + for (auto& w: workers) { + w.join(); + } + remove_tree(dir); +} + namespace nodetool { template class node_server<cryptonote::t_cryptonote_protocol_handler<test_core>>; } namespace cryptonote { template class t_cryptonote_protocol_handler<test_core>; } diff --git a/tests/unit_tests/output_selection.cpp b/tests/unit_tests/output_selection.cpp index cff509451..20dc58e03 100644 --- a/tests/unit_tests/output_selection.cpp +++ b/tests/unit_tests/output_selection.cpp @@ -206,7 +206,7 @@ TEST(select_outputs, same_distribution) for (size_t i = 0; i < chain_picks.size(); ++i) chain_norm[i * 100 / chain_picks.size()] += chain_picks[i]; - double max_dev = 0.0, avg_dev = 0.0; + double avg_dev = 0.0; for (size_t i = 0; i < 100; ++i) { const double diff = (double)output_norm[i] - (double)chain_norm[i]; diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp index 9911bd6f4..0e0bf8906 100644 --- a/tests/unit_tests/wipeable_string.cpp +++ b/tests/unit_tests/wipeable_string.cpp @@ -189,20 +189,20 @@ TEST(wipeable_string, parse_hexstr) { boost::optional<epee::wipeable_string> s; - ASSERT_EQ(boost::none, epee::wipeable_string("x").parse_hexstr()); - ASSERT_EQ(boost::none, epee::wipeable_string("x0000000000000000").parse_hexstr()); - ASSERT_EQ(boost::none, epee::wipeable_string("0000000000000000x").parse_hexstr()); - ASSERT_EQ(boost::none, epee::wipeable_string("0").parse_hexstr()); - ASSERT_EQ(boost::none, epee::wipeable_string("000").parse_hexstr()); + ASSERT_TRUE(boost::none == epee::wipeable_string("x").parse_hexstr()); + ASSERT_TRUE(boost::none == epee::wipeable_string("x0000000000000000").parse_hexstr()); + ASSERT_TRUE(boost::none == epee::wipeable_string("0000000000000000x").parse_hexstr()); + ASSERT_TRUE(boost::none == epee::wipeable_string("0").parse_hexstr()); + ASSERT_TRUE(boost::none == epee::wipeable_string("000").parse_hexstr()); ASSERT_TRUE((s = epee::wipeable_string("").parse_hexstr()) != boost::none); - ASSERT_EQ(*s, ""); + ASSERT_TRUE(*s == ""); ASSERT_TRUE((s = epee::wipeable_string("00").parse_hexstr()) != boost::none); - ASSERT_EQ(*s, epee::wipeable_string("", 1)); + ASSERT_TRUE(*s == epee::wipeable_string("", 1)); ASSERT_TRUE((s = epee::wipeable_string("41").parse_hexstr()) != boost::none); - ASSERT_EQ(*s, epee::wipeable_string("A")); + ASSERT_TRUE(*s == epee::wipeable_string("A")); ASSERT_TRUE((s = epee::wipeable_string("414243").parse_hexstr()) != boost::none); - ASSERT_EQ(*s, epee::wipeable_string("ABC")); + ASSERT_TRUE(*s == epee::wipeable_string("ABC")); } TEST(wipeable_string, to_hex) diff --git a/tests/unit_tests/zmq_rpc.cpp b/tests/unit_tests/zmq_rpc.cpp index 59759bed8..66a7f3ac1 100644 --- a/tests/unit_tests/zmq_rpc.cpp +++ b/tests/unit_tests/zmq_rpc.cpp @@ -202,7 +202,6 @@ namespace MASSERT(!expected.empty()); std::size_t actual_height = 0; - crypto::hash actual_id{}; crypto::hash actual_prev_id{}; std::vector<crypto::hash> actual_ids{}; GET_FROM_JSON_OBJECT(pub.second, actual_height, first_height); diff --git a/utils/gpg_keys/anon.asc b/utils/gpg_keys/anon.asc new file mode 100644 index 000000000..83e1a0cd3 --- /dev/null +++ b/utils/gpg_keys/anon.asc @@ -0,0 +1,13 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEAAAAGBYJKwYBBAHaRw8BAQdAtk3N9Qcw9x3s5frY00j/jO10wfOCZ59mTLtN +tsYJ1620GGFub24gPGFub24gW2F0XSBub3doZXJlPoiQBBMWCAA4FiEEn2DdNuXc +KHIjsNT304V8F6p/losFAmAhnpUCGyMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AA +CgkQ04V8F6p/loumGwEAxjmzDa6ZK7zsps0sYbOvlh0tmxByUqZy1GvIaiOpdboA +/2guWA0tzK+fyQS2L2oXzFwh+oTk5bZ2KqnnfCr9a6kDuDgEYCG1oRIKKwYBBAGX +VQEFAQEHQCnTdYrNM/yw++BSJPgPAoImGhrKaqBoe5VBi9TUk+8aAwEIB4h4BBgW +CAAgFiEEn2DdNuXcKHIjsNT304V8F6p/losFAmAhtaECGwwACgkQ04V8F6p/lose +3AD9E3e4olBj7e6t5NtlMkkBzYKs16RNZRMJ5GMOw8lH8NUA/imPWARO2Y4ryswV +LAtQE1x0SCd4wEP8QEta4p2OYscI +=9Mvr +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/health/README.md b/utils/health/README.md index dea46280e..05eb057a1 100644 --- a/utils/health/README.md +++ b/utils/health/README.md @@ -1,22 +1,32 @@ -#Intro +# Intro + This directory contains tools, which can be used for checking the health of the project, like build/run time analyzers, lints, etc. -#Usage +# Usage + Unless it's stated differently, these scripts should be called from a given source directory, where you want the checks to be performed, for instance: `og@ghetto:~/dev/monero$ utils/health/clang-build-time-analyzer-run.sh` -##ClangBuildAnalyzer +## ClangBuildAnalyzer + `utils/health/clang-build-time-analyzer-run.sh` The CBA helps in finding culprints of slow compilation. On the first run, the script will complain about the missing ClangBuildAnalyzer binary and will point you to another script, which is able to clone and build the required binary. -##clang-tidy +## clang-tidy + `utils/health/clang-tidy-run.sh` Performs Lint checks on the source code and stores the result in the build directory. More information on the [home page](https://clang.llvm.org/extra/clang-tidy/). +## include-what-you-use + +`utils/health/clang-include-what-you-use-run.sh` +Analyses the header file hierarchy and delivers hints on how to reduce their complexity. More information on the [home page](https://include-what-you-use.org/). + + +## Valgrind checks -##Valgrind checks `utils/health/valgrind-tests.sh` This script is able to run valgrind's callgrind, cachegrind and memcheck for a given set of executables. It expects ONE PARAMETER, which points to a file with paths to executables and their arguments, written line by line. For example: @@ -29,6 +39,7 @@ build/tests/unit_tests/unit_tests The `*.out` results can be interpreted with the `kcachegrind` tool. The memcheck output is just a readable text file with a summary at the end. -#Footer +# Footer + Responsible: mj-xmr diff --git a/utils/health/clang-include-what-you-use-run.sh b/utils/health/clang-include-what-you-use-run.sh new file mode 100755 index 000000000..655a188bd --- /dev/null +++ b/utils/health/clang-include-what-you-use-run.sh @@ -0,0 +1,75 @@ +#!/bin/bash -e + +# Copyright (c) 2014-2020, 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 What You Use analyses the complexity of your header hierarchy and proposes optimisations. +# User documentation: +# https://github.com/include-what-you-use/include-what-you-use/blob/master/README.md + +# Build variables +PROG="include-what-you-use" +PROG_SHORT="iwyu" +DIR_BUILD="build/clang-$PROG_SHORT" + +RESULT="$PROG_SHORT-result.txt" + +if hash "$PROG"; then + echo "Found: $PROG" +else + echo "Couldn't find: $PROG" + echo "Please run the below command to install $PROG:" + echo "sudo apt install $PROG_SHORT" + exit 1 +fi + +mkdir -p "$DIR_BUILD" && cd "$DIR_BUILD" +rm `find . -name "CMakeCache.txt"` || true + +UWYU_COMMAND="$PROG;-Xiwyu;any;-Xiwyu;iwyu;-Xiwyu;args" # Copy-pasted from the user docs. + +cmake ../.. \ +-DCMAKE_C_COMPILER=clang \ +-DCMAKE_CXX_COMPILER=clang++ \ +-DUSE_CCACHE=ON \ +-DCMAKE_C_INCLUDE_WHAT_YOU_USE="$UWYU_COMMAND" \ +-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE="$UWYU_COMMAND" \ +-DBUILD_SHARED_LIBS=ON \ +-DBUILD_TESTS=ON + +make clean # Clean up to generate the full report +time make -k 2>&1 | tee "$RESULT" # Run the scan. -k means: ignore errors +#time make -k easylogging 2>&1 | tee $RESULT # Quick testing: build a single target +KPI=$(cat "$RESULT" | wc -l) +tar -cJvf "$RESULT.txz" "$RESULT" # Zip the result, because it's huge. +rm -v "$RESULT" + +echo "" +echo "Readable result stored in: $DIR_BUILD/$RESULT.gz" + +echo "$KPI" > "kpis.txt" diff --git a/utils/health/valgrind-tests.sh b/utils/health/valgrind-tests.sh new file mode 100755 index 000000000..9f5e7e7c0 --- /dev/null +++ b/utils/health/valgrind-tests.sh @@ -0,0 +1,161 @@ +#!/bin/bash -e + +# Copyright (c) 2014-2020, 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. + +# This script is able to run valgrind's callgrind, cachegrind and memcheck for a given set of executables. +# It expects ONE PARAMETER, which points to a file with paths to executables and their arguments, written line by line. + +if [ "$#" -ne 1 ]; then + echo "Please provide an argument, which points to a file with paths to executables and their arguments, written line by line. For example:" + echo "" + echo "ls -l -h" + echo "build/tests/unit_tests/unit_tests" + exit 1 +fi + +FILE_IN="$1" +DIR_OUT="build/valgrind-output" # Using build as the base output directory, as it's ignored in .gitignore + +function is_file_or_exit { + FILE="${1}" + if [ -f $FILE ]; then + echo "The input file $FILE exists. Can proceed." + else + echo "The input file $FILE doesn't exist." + exit 1 + fi + return 0 +} + +function is_tool_or_exit { + TOOL="${1}" + if $(hash ${TOOL}); then + echo "${TOOL} is installed. Can proceed." + else + echo "Please install ${TOOL} to continue." + exit 1 + fi + return 0 +} + +function get_tool_out_file_base { + EXE="${1}" + TOOL="${2}" + + EXE_NAME=$(basename $EXE) + local retval="${DIR_OUT}/${EXE_NAME}-${TOOL}" + echo "$retval" +} + +function get_tool_out_file { + EXE="${1}" + TOOL="${2}" + + FILE_OUT_BASE=$(get_tool_out_file_base ${EXE} ${TOOL}) + local retval="--${TOOL}-out-file=${FILE_OUT_BASE}.out" + echo "$retval" +} + +function run_valgrind_4_executable { + EXE="${1}" + ARGS="${2}" + TOOL="${3}" + EXTRA_OPTS="${4}" + FILE_OUT_TOOL="${5}" + FILE_OUT_BASE=$(get_tool_out_file_base ${EXE} ${TOOL}) + + echo "Runnig '${TOOL}' for '${EXE}' with args '${ARGS}'" + echo "EXTRA_OPTS = ${EXTRA_OPTS}" + echo "FILE_OUT_TOOL = ${FILE_OUT_TOOL}" + if ! valgrind --tool=${TOOL} ${FILE_OUT_TOOL} --log-file="${FILE_OUT_BASE}.log" ${EXTRA_OPTS} ${EXE} ${ARGS}; then + echo "FAILED in runnig ${TOOL} for ${EXE} !" + fi +} + +function run_valgrind_4_executable_callgrind { + EXE="${1}" + ARGS="${2}" + TOOL="callgrind" + EXTRA_OPTS="--dump-instr=yes --simulate-cache=yes --collect-jumps=yes" + FILE_OUT_TOOL=$(get_tool_out_file ${EXE} ${TOOL}) + + run_valgrind_4_executable ${EXE} "${ARGS}" ${TOOL} "${EXTRA_OPTS}" ${FILE_OUT_TOOL} +} + +function run_valgrind_4_executable_cachegrind { + EXE="${1}" + ARGS="${2}" + TOOL="cachegrind" + EXTRA_OPTS="" + FILE_OUT_TOOL=$(get_tool_out_file ${EXE} ${TOOL}) + + run_valgrind_4_executable ${EXE} "${ARGS}" ${TOOL} "${EXTRA_OPTS}" ${FILE_OUT_TOOL} +} + +function run_valgrind_4_executable_memcheck { + EXE="${1}" + ARGS="${2}" + TOOL="memcheck" + #EXTRA_OPTS="--leak-check=yes" # Minimalistic + EXTRA_OPTS="--leak-check=full --show-leak-kinds=all --track-origins=yes" + FILE_OUT_TOOL="" # memcheck has no special out file, only the log + + run_valgrind_4_executable ${EXE} "${ARGS}" ${TOOL} "${EXTRA_OPTS}" ${FILE_OUT_TOOL} +} + +function run_valgrind_4_executable_all { + EXE_ARGS_ARR=(${1}) + EXE=${EXE_ARGS_ARR[0]} # First element of the array + ARGS=${EXE_ARGS_ARR[@]:1} # Every next element + + #EXE="ls" # A quick check of the happy path + #EXE="nothere" # A quick check of error handling - no such executable + #EXE=/bin/false # A quick check of error handling - executable returned != 0 + + run_valgrind_4_executable_memcheck ${EXE} "${ARGS}" + run_valgrind_4_executable_cachegrind ${EXE} "${ARGS}" + run_valgrind_4_executable_callgrind ${EXE} "${ARGS}" +} + +is_tool_or_exit valgrind +is_file_or_exit "$FILE_IN" +echo "All OK." +echo "Will perform checks for the following executables and their arguments:" +while IFS= read -r line; do + echo "$line" +done < "$FILE_IN" + +mkdir -p "$DIR_OUT" +while IFS= read -r line; do + echo "$line" + run_valgrind_4_executable_all "$line" +done < "$FILE_IN" + +echo "Done. All data saved in ${DIR_OUT}" + diff --git a/utils/python-rpc/console.py b/utils/python-rpc/console.py index 57a04528b..4b3c18f5d 100755 --- a/utils/python-rpc/console.py +++ b/utils/python-rpc/console.py @@ -4,7 +4,16 @@ from __future__ import print_function import sys import subprocess import socket -import urlparse +try: + import urllib.parse + url_parser = urllib.parse.urlparse +except: + try: + import urlparse + url_parser = urlparse.urlparse + except: + print('urllib or urlparse is needed') + sys.exit(1) import framework.rpc import framework.daemon import framework.wallet @@ -21,7 +30,7 @@ for n in range(1, len(sys.argv)): try: port = int(sys.argv[n]) except: - t = urlparse.urlparse(sys.argv[n], allow_fragments = False) + t = url_parser(sys.argv[n], allow_fragments = False) scheme = t.scheme or scheme host = t.hostname or host port = t.port or port @@ -29,7 +38,7 @@ for n in range(1, len(sys.argv)): raise Exception(USAGE) if port <= 0 or port > 65535: raise Exception(USAGE) - except Exception, e: + except Exception as e: print('Error: ' + str(e)) raise Exception(USAGE) @@ -49,7 +58,7 @@ for n in range(1, len(sys.argv)): } try: res = rpc.send_json_rpc_request(get_version) - except Exception, e: + except Exception as e: raise Exception('Failed to call version RPC: ' + str(e)) if 'version' not in res: diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index 435bc93f8..207565e6f 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -53,6 +53,19 @@ class Daemon(object): return self.rpc.send_json_rpc_request(getblocktemplate) get_block_template = getblocktemplate + def add_aux_pow(self, blocktemplate_blob, aux_pow, client = ""): + add_aux_pow = { + 'method': 'add_aux_pow', + 'params': { + 'blocktemplate_blob': blocktemplate_blob, + 'aux_pow' : aux_pow, + 'client' : client, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(add_aux_pow) + def send_raw_transaction(self, tx_as_hex, do_not_relay = False, do_sanity_checks = True, client = ""): send_raw_transaction = { 'client': client, diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index d97c24143..aca7c82bb 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -1088,3 +1088,14 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(get_version) + + def scan_tx(self, txids): + scan_tx = { + 'method': 'scan_tx', + 'jsonrpc': '2.0', + 'params' : { + 'txids': txids, + }, + 'id': '0' + } + return self.rpc.send_json_rpc_request(scan_tx) |