diff options
98 files changed, 3174 insertions, 1198 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7aae84977..bf7b24f61 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,7 @@ env: CCACHE_SETTINGS: | ccache --max-size=150M ccache --set-config=compression=true + USE_DEVICE_TREZOR_MANDATORY: ON jobs: build-macos: @@ -39,7 +40,9 @@ jobs: key: ccache-${{ runner.os }}-build-${{ github.sha }} restore-keys: ccache-${{ runner.os }}-build- - name: install dependencies - run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc expat libunwind-headers protobuf ccache + run: | + HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc expat libunwind-headers protobuf@21 ccache + brew link protobuf@21 - name: build run: | ${{env.CCACHE_SETTINGS}} @@ -65,7 +68,12 @@ jobs: - uses: msys2/setup-msys2@v2 with: update: true - 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 mingw-w64-x86_64-unbound 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-libusb mingw-w64-x86_64-unbound git + - shell: msys2 {0} + run: | + curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst + curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst + pacman --noconfirm -U mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst - name: build run: | ${{env.CCACHE_SETTINGS}} diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index 27b294503..c7de55cfe 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -18,6 +18,7 @@ env: CCACHE_SETTINGS: | ccache --max-size=150M ccache --set-config=compression=true + USE_DEVICE_TREZOR_MANDATORY: ON jobs: build-cross: diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f634c114..6c1fca051 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1120,7 +1120,7 @@ list(APPEND EXTRA_LIBRARIES ${CMAKE_DL_LIBS}) if (HIDAPI_FOUND OR LibUSB_COMPILE_TEST_PASSED) if (APPLE) if(DEPENDS) - list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework AppKit -framework IOKit") + list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework AppKit -framework IOKit -framework Security") else() find_library(COREFOUNDATION CoreFoundation) find_library(APPKIT AppKit) @@ -590,6 +590,16 @@ Using `depends` might also be easier to compile Monero on Windows than using MSY The produced binaries still link libc dynamically. If the binary is compiled on a current distribution, it might not run on an older distribution with an older installation of libc. Passing `-DBACKCOMPAT=ON` to cmake will make sure that the binary will run on systems having at least libc version 2.17. +### Trezor hardware wallet support + +If you have an issue with building Monero with Trezor support, you can disable it by setting `USE_DEVICE_TREZOR=OFF`, e.g., + +```bash +USE_DEVICE_TREZOR=OFF make release +``` + +For more information, please check out Trezor [src/device_trezor/README.md](src/device_trezor/README.md). + ### Gitian builds See [contrib/gitian/README.md](contrib/gitian/README.md). diff --git a/cmake/CheckTrezor.cmake b/cmake/CheckTrezor.cmake index f600cc0bb..05b8ebd93 100644 --- a/cmake/CheckTrezor.cmake +++ b/cmake/CheckTrezor.cmake @@ -1,8 +1,27 @@ -OPTION(USE_DEVICE_TREZOR "Trezor support compilation" ON) -OPTION(USE_DEVICE_TREZOR_LIBUSB "Trezor LibUSB compilation" ON) -OPTION(USE_DEVICE_TREZOR_UDP_RELEASE "Trezor UdpTransport in release mode" OFF) -OPTION(USE_DEVICE_TREZOR_DEBUG "Trezor Debugging enabled" OFF) -OPTION(TREZOR_DEBUG "Main trezor debugging switch" OFF) +# Function for setting default options default values via env vars +function(_trezor_default_val val_name val_default) + if(NOT DEFINED ENV{${val_name}}) + set(ENV{${val_name}} ${val_default}) + endif() +endfunction() + +# Define default options via env vars +_trezor_default_val(USE_DEVICE_TREZOR ON) +_trezor_default_val(USE_DEVICE_TREZOR_MANDATORY OFF) +_trezor_default_val(USE_DEVICE_TREZOR_PROTOBUF_TEST ON) +_trezor_default_val(USE_DEVICE_TREZOR_LIBUSB ON) +_trezor_default_val(USE_DEVICE_TREZOR_UDP_RELEASE OFF) +_trezor_default_val(USE_DEVICE_TREZOR_DEBUG OFF) +_trezor_default_val(TREZOR_DEBUG OFF) + +# Main options +OPTION(USE_DEVICE_TREZOR "Trezor support compilation" $ENV{USE_DEVICE_TREZOR}) +OPTION(USE_DEVICE_TREZOR_MANDATORY "Trezor compilation is mandatory, fail build if Trezor support cannot be compiled" $ENV{USE_DEVICE_TREZOR_MANDATORY}) +OPTION(USE_DEVICE_TREZOR_PROTOBUF_TEST "Trezor Protobuf test" $ENV{USE_DEVICE_TREZOR_PROTOBUF_TEST}) +OPTION(USE_DEVICE_TREZOR_LIBUSB "Trezor LibUSB compilation" $ENV{USE_DEVICE_TREZOR_LIBUSB}) +OPTION(USE_DEVICE_TREZOR_UDP_RELEASE "Trezor UdpTransport in release mode" $ENV{USE_DEVICE_TREZOR_UDP_RELEASE}) +OPTION(USE_DEVICE_TREZOR_DEBUG "Trezor Debugging enabled" $ENV{USE_DEVICE_TREZOR_DEBUG}) +OPTION(TREZOR_DEBUG "Main Trezor debugging switch" $ENV{TREZOR_DEBUG}) # Helper function to fix cmake < 3.6.0 FindProtobuf variables function(_trezor_protobuf_fix_vars) @@ -30,33 +49,62 @@ function(_trezor_protobuf_fix_vars) endif() endfunction() +macro(trezor_fatal_msg msg) + if ($ENV{USE_DEVICE_TREZOR_MANDATORY}) + message(FATAL_ERROR + "${msg}\n" + "==========================================================================\n" + "[ERROR] To compile without Trezor support, set USE_DEVICE_TREZOR=OFF. " + "It is possible both via cmake variable and environment variable, e.g., " + "`USE_DEVICE_TREZOR=OFF make release`\n" + "For more information, please check src/device_trezor/README.md\n" + ) + else() + message(WARNING + "${msg}\n" + "==========================================================================\n" + "[WARNING] Trezor support cannot be compiled! Skipping Trezor compilation. \n" + "For more information, please check src/device_trezor/README.md\n") + set(USE_DEVICE_TREZOR OFF) + return() # finish this cmake file processing (as this is macro). + endif() +endmacro() + # Use Trezor master switch if (USE_DEVICE_TREZOR) # Protobuf is required to build protobuf messages for Trezor include(FindProtobuf OPTIONAL) - find_package(Protobuf) + + FIND_PACKAGE(Protobuf CONFIG) + if (NOT Protobuf_FOUND) + FIND_PACKAGE(Protobuf) + endif() + _trezor_protobuf_fix_vars() - # Protobuf handling the cache variables set in docker. - if(NOT Protobuf_FOUND AND NOT Protobuf_LIBRARY AND NOT Protobuf_PROTOC_EXECUTABLE AND NOT Protobuf_INCLUDE_DIR) - message(STATUS "Could not find Protobuf") + # Early fail for optional Trezor support + if(${Protobuf_VERSION} GREATER 21) + trezor_fatal_msg("Trezor: Unsupported Protobuf version ${Protobuf_VERSION}. Please, use Protobuf v21.") + elseif(NOT Protobuf_FOUND AND NOT Protobuf_LIBRARY AND NOT Protobuf_PROTOC_EXECUTABLE AND NOT Protobuf_INCLUDE_DIR) + trezor_fatal_msg("Trezor: Could not find Protobuf") elseif(NOT Protobuf_LIBRARY OR NOT EXISTS "${Protobuf_LIBRARY}") - message(STATUS "Protobuf library not found: ${Protobuf_LIBRARY}") + trezor_fatal_msg("Trezor: Protobuf library not found: ${Protobuf_LIBRARY}") unset(Protobuf_FOUND) elseif(NOT Protobuf_PROTOC_EXECUTABLE OR NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}") - message(STATUS "Protobuf executable not found: ${Protobuf_PROTOC_EXECUTABLE}") + trezor_fatal_msg("Trezor: Protobuf executable not found: ${Protobuf_PROTOC_EXECUTABLE}") unset(Protobuf_FOUND) elseif(NOT Protobuf_INCLUDE_DIR OR NOT EXISTS "${Protobuf_INCLUDE_DIR}") - message(STATUS "Protobuf include dir not found: ${Protobuf_INCLUDE_DIR}") + trezor_fatal_msg("Trezor: Protobuf include dir not found: ${Protobuf_INCLUDE_DIR}") unset(Protobuf_FOUND) else() - message(STATUS "Protobuf lib: ${Protobuf_LIBRARY}, inc: ${Protobuf_INCLUDE_DIR}, protoc: ${Protobuf_PROTOC_EXECUTABLE}") + message(STATUS "Trezor: Protobuf lib: ${Protobuf_LIBRARY}, inc: ${Protobuf_INCLUDE_DIR}, protoc: ${Protobuf_PROTOC_EXECUTABLE}") set(Protobuf_INCLUDE_DIRS ${Protobuf_INCLUDE_DIR}) - set(Protobuf_FOUND 1) # override found if all rquired info was provided by variables + set(Protobuf_FOUND 1) # override found if all required info was provided by variables endif() if(TREZOR_DEBUG) set(USE_DEVICE_TREZOR_DEBUG 1) + message(STATUS "Trezor: debug build enabled") endif() # Compile debugging support (for tests) @@ -64,7 +112,7 @@ if (USE_DEVICE_TREZOR) add_definitions(-DWITH_TREZOR_DEBUGGING=1) endif() else() - message(STATUS "Trezor support disabled by USE_DEVICE_TREZOR") + message(STATUS "Trezor: support disabled by USE_DEVICE_TREZOR") endif() if(Protobuf_FOUND AND USE_DEVICE_TREZOR) @@ -85,7 +133,7 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR) endif() if(NOT TREZOR_PYTHON) - message(STATUS "Trezor: Python not found") + trezor_fatal_msg("Trezor: Python not found") endif() endif() @@ -93,27 +141,42 @@ endif() if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) execute_process(COMMAND ${Protobuf_PROTOC_EXECUTABLE} -I "${CMAKE_CURRENT_LIST_DIR}" -I "${Protobuf_INCLUDE_DIR}" "${CMAKE_CURRENT_LIST_DIR}/test-protobuf.proto" --cpp_out ${CMAKE_BINARY_DIR} RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR) if(RET) - message(STATUS "Protobuf test generation failed: ${OUT} ${ERR}") - endif() - - try_compile(Protobuf_COMPILE_TEST_PASSED - "${CMAKE_BINARY_DIR}" - SOURCES - "${CMAKE_BINARY_DIR}/test-protobuf.pb.cc" - "${CMAKE_CURRENT_LIST_DIR}/test-protobuf.cpp" - CMAKE_FLAGS - "-DINCLUDE_DIRECTORIES=${Protobuf_INCLUDE_DIR};${CMAKE_BINARY_DIR}" - "-DCMAKE_CXX_STANDARD=11" - LINK_LIBRARIES ${Protobuf_LIBRARY} - OUTPUT_VARIABLE OUTPUT - ) - if(NOT Protobuf_COMPILE_TEST_PASSED) - message(STATUS "Protobuf Compilation test failed: ${OUTPUT}.") + trezor_fatal_msg("Trezor: Protobuf test generation failed: ${OUT} ${ERR}") + endif() + + if(ANDROID) + set(CMAKE_TRY_COMPILE_LINKER_FLAGS "${CMAKE_TRY_COMPILE_LINKER_FLAGS} -llog") + set(CMAKE_TRY_COMPILE_LINK_LIBRARIES "${CMAKE_TRY_COMPILE_LINK_LIBRARIES} log") + endif() + + if(USE_DEVICE_TREZOR_PROTOBUF_TEST) + # For now, Protobuf v21 is the maximum supported version as v23 requires C++17. TODO: Remove once we move to C++17 + if(${Protobuf_VERSION} GREATER 21) + trezor_fatal_msg("Trezor: Unsupported Protobuf version ${Protobuf_VERSION}. Please, use Protobuf v21.") + endif() + + try_compile(Protobuf_COMPILE_TEST_PASSED + "${CMAKE_BINARY_DIR}" + SOURCES + "${CMAKE_BINARY_DIR}/test-protobuf.pb.cc" + "${CMAKE_CURRENT_LIST_DIR}/test-protobuf.cpp" + CMAKE_FLAGS + CMAKE_EXE_LINKER_FLAGS ${CMAKE_TRY_COMPILE_LINKER_FLAGS} + "-DINCLUDE_DIRECTORIES=${Protobuf_INCLUDE_DIR};${CMAKE_BINARY_DIR}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + LINK_LIBRARIES ${Protobuf_LIBRARY} ${CMAKE_TRY_COMPILE_LINK_LIBRARIES} + OUTPUT_VARIABLE OUTPUT + ) + if(NOT Protobuf_COMPILE_TEST_PASSED) + trezor_fatal_msg("Trezor: Protobuf Compilation test failed: ${OUTPUT}.") + endif() + else () + message(STATUS "Trezor: Protobuf Compilation test skipped, build may fail later") endif() endif() # Try to build protobuf messages -if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON AND Protobuf_COMPILE_TEST_PASSED) +if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) set(ENV{PROTOBUF_INCLUDE_DIRS} "${Protobuf_INCLUDE_DIR}") set(ENV{PROTOBUF_PROTOC_EXECUTABLE} "${Protobuf_PROTOC_EXECUTABLE}") set(TREZOR_PROTOBUF_PARAMS "") @@ -123,59 +186,62 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON AND Protobuf_COMPILE_T execute_process(COMMAND ${TREZOR_PYTHON} tools/build_protob.py ${TREZOR_PROTOBUF_PARAMS} WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../src/device_trezor/trezor RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR) if(RET) - message(WARNING "Trezor protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})." + trezor_fatal_msg("Trezor: protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})." "OUT: ${OUT}, ERR: ${ERR}." "Please read src/device_trezor/trezor/tools/README.md") - else() - message(STATUS "Trezor protobuf messages regenerated out: \"${OUT}.\"") - set(DEVICE_TREZOR_READY 1) - add_definitions(-DDEVICE_TREZOR_READY=1) - add_definitions(-DPROTOBUF_INLINE_NOT_IN_HEADERS=0) + endif() - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-DTREZOR_DEBUG=1) - endif() + message(STATUS "Trezor: protobuf messages regenerated out: \"${OUT}.\"") + set(DEVICE_TREZOR_READY 1) + add_definitions(-DDEVICE_TREZOR_READY=1) + add_definitions(-DPROTOBUF_INLINE_NOT_IN_HEADERS=0) - if(USE_DEVICE_TREZOR_UDP_RELEASE) - add_definitions(-DUSE_DEVICE_TREZOR_UDP_RELEASE=1) - endif() + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DTREZOR_DEBUG=1) + endif() - if (Protobuf_INCLUDE_DIR) - include_directories(${Protobuf_INCLUDE_DIR}) - endif() + if(USE_DEVICE_TREZOR_UDP_RELEASE) + message(STATUS "Trezor: UDP transport enabled (emulator)") + add_definitions(-DUSE_DEVICE_TREZOR_UDP_RELEASE=1) + endif() - # LibUSB support, check for particular version - # Include support only if compilation test passes - if (USE_DEVICE_TREZOR_LIBUSB) - find_package(LibUSB) - endif() + if (Protobuf_INCLUDE_DIR) + include_directories(${Protobuf_INCLUDE_DIR}) + endif() - if (LibUSB_COMPILE_TEST_PASSED) - add_definitions(-DHAVE_TREZOR_LIBUSB=1) - if(LibUSB_INCLUDE_DIRS) - include_directories(${LibUSB_INCLUDE_DIRS}) - endif() - endif() + # LibUSB support, check for particular version + # Include support only if compilation test passes + if (USE_DEVICE_TREZOR_LIBUSB) + find_package(LibUSB) + endif() - set(TREZOR_LIBUSB_LIBRARIES "") - if(LibUSB_COMPILE_TEST_PASSED) - list(APPEND TREZOR_LIBUSB_LIBRARIES ${LibUSB_LIBRARIES} ${LIBUSB_DEP_LINKER}) - message(STATUS "Trezor compatible LibUSB found at: ${LibUSB_INCLUDE_DIRS}") + if (LibUSB_COMPILE_TEST_PASSED) + add_definitions(-DHAVE_TREZOR_LIBUSB=1) + if(LibUSB_INCLUDE_DIRS) + include_directories(${LibUSB_INCLUDE_DIRS}) endif() + endif() + + set(TREZOR_LIBUSB_LIBRARIES "") + if(LibUSB_COMPILE_TEST_PASSED) + list(APPEND TREZOR_LIBUSB_LIBRARIES ${LibUSB_LIBRARIES} ${LIBUSB_DEP_LINKER}) + message(STATUS "Trezor: compatible LibUSB found at: ${LibUSB_INCLUDE_DIRS}") + elseif(USE_DEVICE_TREZOR_LIBUSB AND NOT ANDROID) + trezor_fatal_msg("Trezor: LibUSB not found or test failed, please install libusb-1.0.26") + endif() - if (BUILD_GUI_DEPS) - set(TREZOR_DEP_LIBS "") - set(TREZOR_DEP_LINKER "") + if (BUILD_GUI_DEPS) + set(TREZOR_DEP_LIBS "") + set(TREZOR_DEP_LINKER "") - if (Protobuf_LIBRARY) - list(APPEND TREZOR_DEP_LIBS ${Protobuf_LIBRARY}) - string(APPEND TREZOR_DEP_LINKER " -lprotobuf") - endif() + if (Protobuf_LIBRARY) + list(APPEND TREZOR_DEP_LIBS ${Protobuf_LIBRARY}) + string(APPEND TREZOR_DEP_LINKER " -lprotobuf") + endif() - if (TREZOR_LIBUSB_LIBRARIES) - list(APPEND TREZOR_DEP_LIBS ${TREZOR_LIBUSB_LIBRARIES}) - string(APPEND TREZOR_DEP_LINKER " -lusb-1.0 ${LIBUSB_DEP_LINKER}") - endif() + if (TREZOR_LIBUSB_LIBRARIES) + list(APPEND TREZOR_DEP_LIBS ${TREZOR_LIBUSB_LIBRARIES}) + string(APPEND TREZOR_DEP_LINKER " -lusb-1.0 ${LIBUSB_DEP_LINKER}") endif() endif() endif() diff --git a/cmake/FindLibUSB.cmake b/cmake/FindLibUSB.cmake index f780628f8..f701b6398 100644 --- a/cmake/FindLibUSB.cmake +++ b/cmake/FindLibUSB.cmake @@ -95,11 +95,35 @@ if ( LibUSB_FOUND ) endif ( LibUSB_FOUND ) if ( LibUSB_FOUND ) + if (APPLE) + if(DEPENDS) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES "-framework Foundation -framework IOKit -framework Security") + else() + find_library(COREFOUNDATION CoreFoundation) + find_library(IOKIT IOKit) + find_library(SECURITY_FRAMEWORK Security) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${IOKIT}) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${COREFOUNDATION}) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${SECURITY_FRAMEWORK}) + + if(STATIC) + find_library(OBJC objc.a) + set(LIBUSB_DEP_LINKER ${OBJC}) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${LIBUSB_DEP_LINKER}) + endif() + endif() + endif() + if (WIN32) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES setupapi) + endif() + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${LibUSB_LIBRARIES}) + set(CMAKE_REQUIRED_LIBRARIES ${TEST_COMPILE_EXTRA_LIBRARIES}) + check_library_exists ( "${LibUSB_LIBRARIES}" usb_open "" LibUSB_FOUND ) check_library_exists ( "${LibUSB_LIBRARIES}" libusb_get_device_list "" LibUSB_VERSION_1.0 ) check_library_exists ( "${LibUSB_LIBRARIES}" libusb_get_port_numbers "" LibUSB_VERSION_1.0.16 ) - if((STATIC AND UNIX AND NOT APPLE) OR (DEPENDS AND CMAKE_SYSTEM_NAME STREQUAL "Linux") OR ANDROID) + if((STATIC AND UNIX AND NOT APPLE AND NOT FREEBSD) OR (DEPENDS AND CMAKE_SYSTEM_NAME STREQUAL "Linux") OR ANDROID) find_library(LIBUDEV_LIBRARY udev) if(LIBUDEV_LIBRARY) set(LibUSB_LIBRARIES "${LibUSB_LIBRARIES};${LIBUDEV_LIBRARY}") @@ -111,27 +135,6 @@ if ( LibUSB_FOUND ) # Library 1.0.16+ compilation test. # The check_library_exists does not work well on Apple with shared libs. if (APPLE OR LibUSB_VERSION_1.0.16 OR STATIC) - if (APPLE) - if(DEPENDS) - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES "-framework Foundation -framework IOKit -framework Security") - else() - find_library(COREFOUNDATION CoreFoundation) - find_library(IOKIT IOKit) - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${IOKIT}) - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${COREFOUNDATION}) - - if(STATIC) - find_library(OBJC objc.a) - set(LIBUSB_DEP_LINKER ${OBJC}) - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${LIBUSB_DEP_LINKER}) - endif() - endif() - endif() - if (WIN32) - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES setupapi) - endif() - list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${LibUSB_LIBRARIES}) - try_compile(LibUSB_COMPILE_TEST_PASSED ${CMAKE_BINARY_DIR} "${CMAKE_CURRENT_LIST_DIR}/test-libusb-version.c" diff --git a/contrib/brew/Brewfile b/contrib/brew/Brewfile index c74e7b2a2..c2fae0ffc 100644 --- a/contrib/brew/Brewfile +++ b/contrib/brew/Brewfile @@ -31,4 +31,5 @@ brew "doxygen" brew "graphviz" brew "libunwind-headers" brew "xz" -brew "protobuf" +brew "protobuf@21", link: true +brew "libusb" diff --git a/contrib/depends/hosts/darwin.mk b/contrib/depends/hosts/darwin.mk index 79d449054..d81fd4241 100644 --- a/contrib/depends/hosts/darwin.mk +++ b/contrib/depends/hosts/darwin.mk @@ -1,12 +1,12 @@ -OSX_MIN_VERSION=10.8 +OSX_MIN_VERSION=10.13 LD64_VERSION=609 ifeq (aarch64, $(host_arch)) CC_target=arm64-apple-$(host_os) else CC_target=$(host) endif -darwin_CC=clang -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -B$(host_prefix)/native/bin/$(host)- -darwin_CXX=clang++ -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -mlinker-version=$(LD64_VERSION) -stdlib=libc++ -B$(host_prefix)/native/bin/$(host)- +darwin_CC=clang -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -iwithsysroot/usr/include -iframeworkwithsysroot/System/Library/Frameworks -mlinker-version=$(LD64_VERSION) -B$(host_prefix)/native/bin/$(host)- +darwin_CXX=clang++ -target $(CC_target) -mmacosx-version-min=$(OSX_MIN_VERSION) --sysroot $(host_prefix)/native/SDK/ -iwithsysroot/usr/include/c++/v1 -iwithsysroot/usr/include -iframeworkwithsysroot/System/Library/Frameworks -mlinker-version=$(LD64_VERSION) -stdlib=libc++ -B$(host_prefix)/native/bin/$(host)- darwin_CFLAGS=-pipe darwin_CXXFLAGS=$(darwin_CFLAGS) diff --git a/contrib/depends/packages/darwin_sdk.mk b/contrib/depends/packages/darwin_sdk.mk index d639c422e..3355dcf3a 100644 --- a/contrib/depends/packages/darwin_sdk.mk +++ b/contrib/depends/packages/darwin_sdk.mk @@ -1,8 +1,8 @@ package=darwin_sdk -$(package)_version=11.1 -$(package)_download_path=https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/ -$(package)_file_name=MacOSX$($(package)_version).sdk.tar.xz -$(package)_sha256_hash=68797baaacb52f56f713400de306a58a7ca00b05c3dc6d58f0a8283bcac721f8 +$(package)_version=12.2 +$(package)_download_path=https://bitcoincore.org/depends-sources/sdks +$(package)_file_name=Xcode-12.2-12B45b-extracted-SDK-with-libcxx-headers.tar.gz +$(package)_sha256_hash=df75d30ecafc429e905134333aeae56ac65fac67cb4182622398fd717df77619 define $(package)_stage_cmds mkdir -p $($(package)_staging_dir)/$(host_prefix)/native/SDK &&\ diff --git a/contrib/depends/packages/libusb.mk b/contrib/depends/packages/libusb.mk index c1d9fe6a9..fb6646685 100644 --- a/contrib/depends/packages/libusb.mk +++ b/contrib/depends/packages/libusb.mk @@ -3,8 +3,10 @@ $(package)_version=1.0.26 $(package)_download_path=https://github.com/libusb/libusb/releases/download/v$($(package)_version) $(package)_file_name=$(package)-$($(package)_version).tar.bz2 $(package)_sha256_hash=12ce7a61fc9854d1d2a1ffe095f7b5fac19ddba095c259e6067a46500381b5a5 +$(package)_patches=fix_osx_avail.patch define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/fix_osx_avail.patch &&\ autoreconf -i endef @@ -13,6 +15,7 @@ define $(package)_set_vars $(package)_config_opts_linux=--with-pic --disable-udev $(package)_config_opts_mingw32=--disable-udev $(package)_config_opts_darwin=--disable-udev + $(package)_config_opts_freebsd=--with-pic --disable-udev endef ifneq ($(host_os),darwin) diff --git a/contrib/depends/packages/native_clang.mk b/contrib/depends/packages/native_clang.mk index 115f8f389..4ff21ada0 100644 --- a/contrib/depends/packages/native_clang.mk +++ b/contrib/depends/packages/native_clang.mk @@ -24,6 +24,5 @@ define $(package)_stage_cmds cp lib/libLTO.so $($(package)_staging_prefix_dir)/lib/ && \ cp -rf lib/clang/$($(package)_version)/include/* $($(package)_staging_prefix_dir)/lib/clang/$($(package)_version)/include/ && \ cp bin/dsymutil $($(package)_staging_prefix_dir)/bin/$(host)-dsymutil && \ - if `test -d include/c++/`; then cp -rf include/c++/ $($(package)_staging_prefix_dir)/include/; fi && \ if `test -d lib/c++/`; then cp -rf lib/c++/ $($(package)_staging_prefix_dir)/lib/; fi endef diff --git a/contrib/depends/packages/native_protobuf.mk b/contrib/depends/packages/native_protobuf.mk index 35f648b9a..2dc11b23c 100644 --- a/contrib/depends/packages/native_protobuf.mk +++ b/contrib/depends/packages/native_protobuf.mk @@ -1,8 +1,9 @@ package=protobuf3 -$(package)_version=3.6.1 +$(package)_version=21.12 +$(package)_version_protobuf_cpp=3.21.12 $(package)_download_path=https://github.com/protocolbuffers/protobuf/releases/download/v$($(package)_version)/ -$(package)_file_name=protobuf-cpp-$($(package)_version).tar.gz -$(package)_sha256_hash=b3732e471a9bb7950f090fd0457ebd2536a9ba0891b7f3785919c654fe2a2529 +$(package)_file_name=protobuf-cpp-$($(package)_version_protobuf_cpp).tar.gz +$(package)_sha256_hash=4eab9b524aa5913c6fffb20b2a8abf5ef7f95a80bc0701f3a6dbb4c607f73460 $(package)_cxxflags=-std=c++11 define $(package)_set_vars diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk index ea4a0effd..35dc2abc7 100644 --- a/contrib/depends/packages/packages.mk +++ b/contrib/depends/packages/packages.mk @@ -8,15 +8,15 @@ endif hardware_packages := hidapi protobuf libusb hardware_native_packages := native_protobuf -android_native_packages = android_ndk -android_packages = ncurses readline sodium +android_native_packages = android_ndk $(hardware_native_packages) +android_packages = ncurses readline sodium protobuf darwin_native_packages = $(hardware_native_packages) darwin_packages = ncurses readline sodium $(hardware_packages) # not really native... -freebsd_native_packages = freebsd_base -freebsd_packages = ncurses readline sodium +freebsd_native_packages = freebsd_base $(hardware_native_packages) +freebsd_packages = ncurses readline sodium protobuf libusb linux_packages = eudev ncurses readline sodium $(hardware_packages) linux_native_packages = $(hardware_native_packages) diff --git a/contrib/depends/packages/protobuf.mk b/contrib/depends/packages/protobuf.mk index ddec1eb59..780357c90 100644 --- a/contrib/depends/packages/protobuf.mk +++ b/contrib/depends/packages/protobuf.mk @@ -1,21 +1,17 @@ package=protobuf $(package)_version=$(native_$(package)_version) +$(package)_version_protobuf_cpp=$(native_$(package)_version_protobuf_cpp) $(package)_download_path=$(native_$(package)_download_path) $(package)_file_name=$(native_$(package)_file_name) $(package)_sha256_hash=$(native_$(package)_sha256_hash) $(package)_dependencies=native_$(package) $(package)_cxxflags=-std=c++11 -$(package)_patches=visibility.patch define $(package)_set_vars $(package)_config_opts=--disable-shared --with-protoc=$(build_prefix)/bin/protoc $(package)_config_opts_linux=--with-pic endef -define $(package)_preprocess_cmds - patch -p0 < $($(package)_patch_dir)/visibility.patch -endef - define $(package)_config_cmds $($(package)_autoconf) AR_FLAGS=$($(package)_arflags) endef diff --git a/contrib/depends/patches/libusb/fix_osx_avail.patch b/contrib/depends/patches/libusb/fix_osx_avail.patch new file mode 100644 index 000000000..2474993f5 --- /dev/null +++ b/contrib/depends/patches/libusb/fix_osx_avail.patch @@ -0,0 +1,12 @@ +--- a/libusb/os/darwin_usb.h 2023-03-19 12:07:53 ++++ b/libusb/os/darwin_usb.h 2023-03-19 12:07:47 +@@ -165,7 +165,8 @@ + #define __has_builtin(x) 0 // Compatibility with non-clang compilers. + #endif + #if __has_builtin(__builtin_available) +- #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *) ++// #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *) ++ #define HAS_CAPTURE_DEVICE() 0 + #else + #define HAS_CAPTURE_DEVICE() 0 + #endif diff --git a/contrib/depends/patches/protobuf/visibility.patch b/contrib/depends/patches/protobuf/visibility.patch deleted file mode 100644 index e66d5961f..000000000 --- a/contrib/depends/patches/protobuf/visibility.patch +++ /dev/null @@ -1,159 +0,0 @@ ---- src/google/protobuf/descriptor.cc.O 2018-07-30 22:16:10.000000000 +0000 -+++ src/google/protobuf/descriptor.cc 2022-05-06 13:38:14.827309092 +0000 -@@ -32,6 +32,9 @@ - // Based on original Protocol Buffers design by - // Sanjay Ghemawat, Jeff Dean, and others. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif - #include <algorithm> - #include <functional> - #include <google/protobuf/stubs/hash.h> -@@ -7274,3 +7277,6 @@ - - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/extension_set.cc.O 2018-07-23 20:56:42.000000000 +0000 -+++ src/google/protobuf/extension_set.cc 2022-05-06 14:48:55.369877050 +0000 -@@ -32,6 +32,9 @@ - // Based on original Protocol Buffers design by - // Sanjay Ghemawat, Jeff Dean, and others. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif - #include <google/protobuf/stubs/hash.h> - #include <tuple> - #include <utility> -@@ -1914,3 +1917,6 @@ - } // namespace internal - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/extension_set_heavy.cc.O 2018-07-30 22:16:10.000000000 +0000 -+++ src/google/protobuf/extension_set_heavy.cc 2022-05-06 14:14:27.847320946 +0000 -@@ -35,6 +35,10 @@ - // Contains methods defined in extension_set.h which cannot be part of the - // lite library because they use descriptors or reflection. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif -+ - #include <google/protobuf/stubs/casts.h> - #include <google/protobuf/descriptor.pb.h> - #include <google/protobuf/io/coded_stream.h> -@@ -814,3 +818,6 @@ - } // namespace internal - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/generated_message_reflection.cc.O 2018-07-23 20:56:42.000000000 +0000 -+++ src/google/protobuf/generated_message_reflection.cc 2022-05-06 13:38:49.655540772 +0000 -@@ -32,6 +32,9 @@ - // Based on original Protocol Buffers design by - // Sanjay Ghemawat, Jeff Dean, and others. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif - #include <algorithm> - #include <set> - -@@ -2420,3 +2423,6 @@ - } // namespace internal - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/map_field.cc.O 2018-07-23 20:56:42.000000000 +0000 -+++ src/google/protobuf/map_field.cc 2022-05-06 13:34:44.913905697 +0000 -@@ -28,6 +28,10 @@ - // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif -+ - #include <google/protobuf/map_field.h> - #include <google/protobuf/map_field_inl.h> - -@@ -462,3 +466,6 @@ - } // namespace internal - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/text_format.cc.O 2018-07-30 22:16:11.000000000 +0000 -+++ src/google/protobuf/text_format.cc 2022-05-06 13:34:58.881999517 +0000 -@@ -32,6 +32,10 @@ - // Based on original Protocol Buffers design by - // Sanjay Ghemawat, Jeff Dean, and others. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif -+ - #include <algorithm> - #include <float.h> - #include <math.h> -@@ -2258,3 +2262,6 @@ - - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/wire_format.cc.O 2018-07-23 20:56:42.000000000 +0000 -+++ src/google/protobuf/wire_format.cc 2022-05-06 13:06:23.294219228 +0000 -@@ -32,6 +32,10 @@ - // Based on original Protocol Buffers design by - // Sanjay Ghemawat, Jeff Dean, and others. - -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif -+ - #include <stack> - #include <string> - #include <vector> -@@ -1445,3 +1449,7 @@ - } // namespace internal - } // namespace protobuf - } // namespace google -+ -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif ---- src/google/protobuf/stubs/status.cc.O 2018-07-23 20:56:42.000000000 +0000 -+++ src/google/protobuf/stubs/status.cc 2022-05-06 15:18:53.393208814 +0000 -@@ -27,6 +27,11 @@ - // 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. -+ -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility push(hidden) -+#endif -+ - #include <google/protobuf/stubs/status.h> - - #include <ostream> -@@ -132,3 +137,6 @@ - } // namespace util - } // namespace protobuf - } // namespace google -+#if defined(__APPLE__) && defined(__arm64__) -+#pragma GCC visibility pop -+#endif diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in index 570065560..c312b48c5 100644 --- a/contrib/depends/toolchain.cmake.in +++ b/contrib/depends/toolchain.cmake.in @@ -27,12 +27,6 @@ SET(Terminfo_LIBRARY @prefix@/lib/libtinfo.a) SET(UNBOUND_INCLUDE_DIR @prefix@/include) SET(UNBOUND_LIBRARIES @prefix@/lib/libunbound.a) -if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") -SET(LIBUNWIND_INCLUDE_DIR @prefix@/include) -SET(LIBUNWIND_LIBRARIES @prefix@/lib/libunwind.a) -SET(LIBUNWIND_LIBRARY_DIRS @prefix@/lib) - -if(NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") SET(LIBUSB-1.0_LIBRARY @prefix@/lib/libusb-1.0.a) SET(LIBUDEV_LIBRARY @prefix@/lib/libudev.a) @@ -41,8 +35,11 @@ SET(Protobuf_PROTOC_EXECUTABLE @prefix@/native/bin/protoc CACHE FILEPATH "Path t SET(Protobuf_INCLUDE_DIR @prefix@/include CACHE PATH "Protobuf include dir") SET(Protobuf_INCLUDE_DIRS @prefix@/include CACHE PATH "Protobuf include dir") SET(Protobuf_LIBRARY @prefix@/lib/libprotobuf.a CACHE FILEPATH "Protobuf library") -endif() +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") +SET(LIBUNWIND_INCLUDE_DIR @prefix@/include) +SET(LIBUNWIND_LIBRARIES @prefix@/lib/libunwind.a) +SET(LIBUNWIND_LIBRARY_DIRS @prefix@/lib) endif() SET(ZMQ_INCLUDE_PATH @prefix@/include) @@ -94,7 +91,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") SET(BREW OFF) SET(PORT OFF) SET(CMAKE_OSX_SYSROOT "@prefix@/native/SDK/") - SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.08") + SET(CMAKE_OSX_DEPLOYMENT_TARGET "10.13") SET(CMAKE_CXX_STANDARD 14) SET(LLVM_ENABLE_PIC OFF) SET(LLVM_ENABLE_PIE OFF) diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 8cee399cb..ecbceb566 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -199,8 +199,18 @@ namespace net_utils } } + // This magic var determines the maximum length for when copying the body message in + // memory is faster/more preferable than the round-trip time for one packet + constexpr size_t BODY_NO_COPY_CUTOFF = 128 * 1024; // ~262 KB or ~175 packets + + // Maximum expected total headers bytes + constexpr size_t HEADER_RESERVE_SIZE = 2048; + + const bool do_copy_body = body.size() <= BODY_NO_COPY_CUTOFF; + const size_t req_buff_cap = HEADER_RESERVE_SIZE + (do_copy_body ? body.size() : 0); + std::string req_buff{}; - req_buff.reserve(2048); + req_buff.reserve(req_buff_cap); req_buff.append(method.data(), method.size()).append(" ").append(uri.data(), uri.size()).append(" HTTP/1.1\r\n"); add_field(req_buff, "Host", m_host_buff); add_field(req_buff, "Content-Length", std::to_string(body.size())); @@ -209,9 +219,7 @@ namespace net_utils for(const auto& field : additional_params) add_field(req_buff, field); - for (unsigned sends = 0; sends < 2; ++sends) { - const std::size_t initial_size = req_buff.size(); const auto auth = m_auth.get_auth_field(method, uri); if (auth) add_field(req_buff, *auth); @@ -219,11 +227,21 @@ namespace net_utils req_buff += "\r\n"; //-- - bool res = m_net_client.send(req_buff, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); - if(body.size()) + if (do_copy_body) // small body + { + // Copy headers + body together and potentially send one fewer packet + req_buff.append(body.data(), body.size()); + const bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } + else // large body + { + // Send headers and body seperately to avoid copying heavy body message + bool res = m_net_client.send(req_buff, timeout); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); res = m_net_client.send(body, timeout); - CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + CHECK_AND_ASSERT_MES(res, false, "HTTP_CLIENT: Failed to SEND"); + } m_response_info.clear(); m_state = reciev_machine_state_header; @@ -236,19 +254,11 @@ namespace net_utils return true; } - switch (m_auth.handle_401(m_response_info)) + if (m_auth.handle_401(m_response_info) == http_client_auth::kParseFailure) { - case http_client_auth::kSuccess: - break; - case http_client_auth::kBadPassword: - sends = 2; - break; - default: - case http_client_auth::kParseFailure: LOG_ERROR("Bad server response for authentication"); return false; } - req_buff.resize(initial_size); // rollback for new auth generation } LOG_ERROR("Client has incorrect username/password for server requiring authentication"); return false; diff --git a/contrib/epee/include/serialization/wire.h b/contrib/epee/include/serialization/wire.h new file mode 100644 index 000000000..4dbb0b2f4 --- /dev/null +++ b/contrib/epee/include/serialization/wire.h @@ -0,0 +1,51 @@ +// Copyright (c) 2021-2023, 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 "serialization/wire/fwd.h" +#include "serialization/wire/read.h" +#include "serialization/wire/write.h" + +//! Define functions that list fields in `type` (using virtual interface) +#define WIRE_DEFINE_OBJECT(type, map) \ + void read_bytes(::wire::reader& source, type& dest) \ + { map(source, dest); } \ + \ + void write_bytes(::wire::writer& dest, const type& source) \ + { map(dest, source); } + +//! Define `from_bytes` and `to_bytes` for `this`. +#define WIRE_DEFINE_CONVERSIONS() \ + template<typename R, typename T> \ + std::error_code from_bytes(T&& source) \ + { return ::wire_read::from_bytes<R>(std::forward<T>(source), *this); } \ + \ + template<typename W, typename T> \ + std::error_code to_bytes(T& dest) const \ + { return ::wire_write::to_bytes<W>(dest, *this); } + diff --git a/contrib/epee/include/serialization/wire/adapted/list.h b/contrib/epee/include/serialization/wire/adapted/list.h new file mode 100644 index 000000000..8884193ad --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/list.h @@ -0,0 +1,41 @@ +// Copyright (c) 2023, 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 <list> +#include <type_traits> + +#include "serialization/wire/traits.h" + +namespace wire +{ + template<typename T, typename A> + struct is_array<std::list<T, A>> + : std::true_type + {}; +} diff --git a/contrib/epee/include/serialization/wire/adapted/vector.h b/contrib/epee/include/serialization/wire/adapted/vector.h new file mode 100644 index 000000000..1dd4b0ded --- /dev/null +++ b/contrib/epee/include/serialization/wire/adapted/vector.h @@ -0,0 +1,61 @@ +// Copyright (c) 2022-2023-2023, The Monero Project +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <cstdint> +#include <cstring> +#include <type_traits> +#include <vector> + +#include "byte_slice.h" +#include "serialization/wire/traits.h" + +namespace wire +{ + template<typename T, typename A> + struct is_array<std::vector<T, A>> + : std::true_type + {}; + template<typename A> + struct is_array<std::vector<std::uint8_t, A>> + : std::false_type + {}; + + template<typename R, typename A> + inline void read_bytes(R& source, std::vector<std::uint8_t, A>& dest) + { + const epee::byte_slice bytes = source.binary(); + dest.resize(bytes.size()); + std::memcpy(dest.data(), bytes.data(), bytes.size()); + } + template<typename W, typename A> + inline void write_bytes(W& dest, const std::vector<std::uint8_t, A>& source) + { + dest.binary(epee::to_span(source)); + } +} diff --git a/contrib/epee/include/serialization/wire/error.h b/contrib/epee/include/serialization/wire/error.h new file mode 100644 index 000000000..b4920e53d --- /dev/null +++ b/contrib/epee/include/serialization/wire/error.h @@ -0,0 +1,137 @@ +// Copyright (c) 2022-2023, 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 <exception> +#include <system_error> +#include <type_traits> + +#include "misc_log_ex.h" + +//! Print default `code` message followed by optional message to debug log then throw `code`. +#define WIRE_DLOG_THROW_(code, ...) \ + do \ + { \ + MDEBUG( get_string(code) __VA_ARGS__ ); \ + throw ::wire::exception_t<decltype(code)>{code}; \ + } \ + while (0) + +//! Print default `code` message followed by `msg` to debug log then throw `code`. +#define WIRE_DLOG_THROW(code, msg) \ + WIRE_DLOG_THROW_(code, << ": " << msg) + +namespace wire +{ + namespace error + { + enum class schema : int + { + none = 0, //!< Must be zero for `expect<..>` + array, //!< Expected an array value + array_max_element,//!< Exceeded max array count + array_min_size, //!< Below min element wire size + binary, //!< Expected a binary value of variable length + boolean, //!< Expected a boolean value + enumeration, //!< Expected a value from a specific set + fixed_binary, //!< Expected a binary value of fixed length + integer, //!< Expected an integer value + invalid_key, //!< Key for object is invalid + larger_integer, //!< Expected a larger integer value + maximum_depth, //!< Hit maximum number of object+array tracking + missing_key, //!< Missing required key for object + number, //!< Expected a number (integer or float) value + object, //!< Expected object value + smaller_integer, //!< Expected a smaller integer value + string, //!< Expected string value + }; + + //! \return Error message string. + const char* get_string(schema value) noexcept; + + //! \return Category for `schema_error`. + const std::error_category& schema_category() noexcept; + + //! \return Error code with `value` and `schema_category()`. + inline std::error_code make_error_code(const schema value) noexcept + { + return std::error_code{int(value), schema_category()}; + } + } // error + + //! `std::exception` doesn't require dynamic memory like `std::runtime_error` + struct exception : std::exception + { + exception() noexcept + : std::exception() + {} + + exception(const exception&) = default; + exception& operator=(const exception&) = default; + virtual ~exception() noexcept + {} + + virtual std::error_code code() const noexcept = 0; + }; + + template<typename T> + class exception_t final : public wire::exception + { + static_assert(std::is_enum<T>(), "only enumerated types allowed"); + T value; + + public: + exception_t(T value) noexcept + : value(value) + {} + + exception_t(const exception_t&) = default; + ~exception_t() = default; + exception_t& operator=(const exception_t&) = default; + + const char* what() const noexcept override final + { + static_assert(noexcept(noexcept(get_string(value))), "get_string function must be noexcept"); + return get_string(value); + } + + std::error_code code() const noexcept override final + { + static_assert(noexcept(noexcept(make_error_code(value))), "make_error_code funcion must be noexcept"); + return make_error_code(value); + } + }; +} // wire + +namespace std +{ + template<> + struct is_error_code_enum<wire::error::schema> + : true_type + {}; +} diff --git a/contrib/epee/include/serialization/wire/field.h b/contrib/epee/include/serialization/wire/field.h new file mode 100644 index 000000000..402fa9ad4 --- /dev/null +++ b/contrib/epee/include/serialization/wire/field.h @@ -0,0 +1,141 @@ +// Copyright (c) 2021-2023, 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 <functional> +#include <utility> + +#include "serialization/wire/traits.h" + +//! A required field has the same key name and C/C++ name +#define WIRE_FIELD(name) \ + ::wire::field( #name , std::ref( self . name )) + +//! A required field has the same key name and C/C++ name AND is cheap to copy (faster output). +#define WIRE_FIELD_COPY(name) \ + ::wire::field( #name , self . name ) + +//! The optional field has the same key name and C/C++ name +#define WIRE_OPTIONAL_FIELD(name) \ + ::wire::optional_field( #name , std::ref( self . name )) + +namespace wire +{ + /*! Links `name` to a `value` for object serialization. + + `value_type` is `T` with optional `std::reference_wrapper` removed. + `value_type` needs a `read_bytes` function when parsing with a + `wire::reader` - see `read.h` for more info. `value_type` needs a + `write_bytes` function when writing with a `wire::writer` - see `write.h` + for more info. + + Any `value_type` where `is_optional_on_empty<value_type> == true`, will + automatically be converted to an optional field iff `value_type` has an + `empty()` method that returns `true`. The old output engine omitted fields + when an array was empty, and the standard input macro would ignore the + `false` return for the missing field. For compability reasons, the + input/output engine here matches that behavior. See `wrapper/array.h` to + enforce a required field even when the array is empty or specialize the + `is_optional_on_empty` trait. Only new fields should use this behavior. + + Additional concept requirements for `value_type` when `Required == false`: + * must have an `operator*()` function. + * must have a conversion to bool function that returns true when + `operator*()` is safe to call (and implicitly when the associated field + should be written as opposed to skipped/omitted). + Additional concept requirements for `value_type` when `Required == false` + when reading: + * must have an `emplace()` method that ensures `operator*()` is safe to call. + * must have a `reset()` method to indicate a field was skipped/omitted. + + If a standard type needs custom serialization, one "trick": + ``` + struct custom_tag{}; + void read_bytes(wire::reader&, boost::fusion::pair<custom_tag, std::string&>) + { ... } + void write_bytes(wire::writer&, boost::fusion::pair<custom_tag, const std::string&>) + { ... } + + template<typename F, typename T> + void object_map(F& format, T& self) + { + wire::object(format, + wire::field("foo", boost::fusion::make_pair<custom_tag>(std::ref(self.foo))) + ); + } + ``` + + Basically each input/output format needs a unique type so that the compiler + knows how to "dispatch" the read/write calls. */ + template<typename T, bool Required> + struct field_ + { + using value_type = unwrap_reference_t<T>; + + //! \return True if field is forced optional when `get_value().empty()`. + static constexpr bool optional_on_empty() noexcept + { return is_optional_on_empty<value_type>::value; } + + static constexpr bool is_required() noexcept { return Required && !optional_on_empty(); } + static constexpr std::size_t count() noexcept { return 1; } + + const char* name; + T value; + + constexpr const value_type& get_value() const noexcept { return value; } + value_type& get_value() noexcept { return value; } + }; + + //! Links `name` to `value`. Use `std::ref` if de-serializing. + template<typename T> + constexpr inline field_<T, true> field(const char* name, T value) + { + return {name, std::move(value)}; + } + + //! Links `name` to optional `value`. Use `std::ref` if de-serializing. + template<typename T> + constexpr inline field_<T, false> optional_field(const char* name, T value) + { + return {name, std::move(value)}; + } + + + template<typename T> + inline constexpr bool available(const field_<T, true>& elem) + { + /* The old output engine always skipped fields when it was an empty array, + this follows that behavior. See comments for `field_`. */ + return elem.is_required() || (elem.optional_on_empty() && !wire::empty(elem.get_value())); + } + template<typename T> + inline constexpr bool available(const field_<T, false>& elem) + { + return bool(elem.get_value()); + } +} // wire diff --git a/contrib/epee/include/serialization/wire/fwd.h b/contrib/epee/include/serialization/wire/fwd.h new file mode 100644 index 000000000..f9e79dd1a --- /dev/null +++ b/contrib/epee/include/serialization/wire/fwd.h @@ -0,0 +1,103 @@ +// Copyright (c) 2021-2023, 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 <type_traits> + +//! Declare an enum to be serialized as an integer +#define WIRE_AS_INTEGER(type_) \ + static_assert(std::is_enum<type_>(), "AS_INTEGER only enum types"); \ + template<typename R> \ + inline void read_bytes(R& source, type_& dest) \ + { \ + std::underlying_type<type_>::type temp{}; \ + read_bytes(source, temp); \ + dest = type_(temp); \ + } \ + template<typename W> \ + inline void write_bytes(W& dest, const type_ source) \ + { write_bytes(dest, std::underlying_type<type_>::type(source)); } + +//! Declare functions that list fields in `type` (using virtual interface) +#define WIRE_DECLARE_OBJECT(type) \ + void read_bytes(::wire::reader&, type&); \ + void write_bytes(::wire::writer&, const type&) + +//! Cast readers to `rtype` and writers to `wtype` before code expansion +#define WIRE_BEGIN_MAP_BASE(rtype, wtype) \ + template<typename R> \ + void read_bytes(R& source) \ + { wire_map(std::true_type{}, static_cast<rtype&>(source), *this); } \ + \ + template<typename W> \ + void write_bytes(W& dest) const \ + { wire_map(std::false_type{}, static_cast<wtype&>(dest), *this); } \ + \ + template<typename B, typename F, typename T> \ + static void wire_map(const B is_read, F& format, T& self) \ + { ::wire::object_fwd(is_read, format + +/*! Define `read_bytes`, and `write_bytes` for `this` that forward the + derived format types for max performance. */ +#define WIRE_BEGIN_MAP() \ + WIRE_BEGIN_MAP_BASE(R, W) + +/*! Define `read_bytes`, and `write_bytes` for `this` that forward base format + types to reduce code expansion and executable size. */ +#define WIRE_BEGIN_MAP_ASM_SIZE() \ + WIRE_BEGIN_MAP_BASE(::wire::reader, ::wire::writer) + +//! End object map; omit last `,` +#define WIRE_END_MAP() );} + +namespace wire +{ + struct basic_value; + class reader; + struct writer; + + // defined in `wire/read.h` + template<typename R, typename... T> + void object_fwd(std::true_type is_read, R& source, T&&... fields); + + // defined in `wire/write.h` + template<typename W, typename... T> + void object_fwd(std::false_type is_read, W& dest, T... fields); +} +namespace wire_read +{ + // defined in `wire/read.h` + template<typename R, typename T> + void bytes(R& source, T&& dest); +} +namespace wire_write +{ + // defined in `wire/write.h` + template<typename W, typename T> + void bytes(W& dest, const T& source); +} diff --git a/contrib/epee/include/serialization/wire/traits.h b/contrib/epee/include/serialization/wire/traits.h new file mode 100644 index 000000000..284506a29 --- /dev/null +++ b/contrib/epee/include/serialization/wire/traits.h @@ -0,0 +1,195 @@ +// 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. + +#pragma once + +#include <cstdint> +#include <type_traits> + +#define WIRE_DECLARE_BLOB_NS(type) \ + template<> \ + struct is_blob<type> \ + : std::true_type \ + {} + +#define WIRE_DECLARE_BLOB(type) \ + namespace wire { WIRE_DECLARE_BLOB_NS(type); } + +#define WIRE_DECLARE_OPTIONAL_ROOT(type) \ + template<> \ + struct is_optional_root<type> \ + : std::true_type \ + {} + +namespace wire +{ + template<typename T> + struct unwrap_reference + { + using type = std::remove_cv_t<std::remove_reference_t<T>>; + }; + + template<typename T> + struct unwrap_reference<std::reference_wrapper<T>> + : std::remove_cv<T> + {}; + + template<typename T> + using unwrap_reference_t = typename unwrap_reference<T>::type; + + + /*! Mark `T` as an array for writing, and reading when + `default_min_element_size<T::value_type>::value != 0`. See `array_` in + `wrapper/array.h`. */ + template<typename T> + struct is_array : std::false_type + {}; + + /*! Mark `T` as fixed binary data for reading+writing. Concept requirements + for reading: + * `T` must be compatible with `epee::as_mut_byte_span` (`std::is_pod<T>` + and no padding). + Concept requirements for writing: + * `T` must be compatible with `epee::as_byte_span` (std::is_pod<T>` and + no padding). */ + template<typename T> + struct is_blob : std::false_type + {}; + + /*! Forces field to be optional when empty. Concept requirements for `T` when + `is_optional_on_empty<T>::value == true`: + * must have an `empty()` method that toggles whether the associated + `wire::field_<...>` is omitted by the `wire::writer`. + * must have a `clear()` method where `empty() == true` upon completion, + used by the `wire::reader` when the `wire::field_<...>` is omitted. */ + template<typename T> + struct is_optional_on_empty + : is_array<T> // all array types in old output engine were optional when empty + {}; + + //! When `T` is being read as root object, allow an empty read buffer. + template<typename T> + struct is_optional_root + : std::is_empty<T> + {}; + + //! A constraint for `wire_read::array` where a max of `N` elements can be read. + template<std::size_t N> + struct max_element_count + : std::integral_constant<std::size_t, N> + { + // The threshold is low - min_element_size is a better constraint metric + static constexpr std::size_t max_bytes() noexcept { return 512 * 1024; } // 512 KiB + + //! \return True if `N` C++ objects of type `T` are below `max_bytes()` threshold. + template<typename T> + static constexpr bool check() noexcept + { + return N <= (max_bytes() / sizeof(T)); + } + }; + + //! A constraint for `wire_read::array` where each element must use at least `N` bytes on the wire. + template<std::size_t N> + struct min_element_size + : std::integral_constant<std::size_t, N> + { + static constexpr std::size_t max_ratio() noexcept { return 4; } + + //! \return True if C++ object of type `T` with minimum wire size `N` is below `max_ratio()`. + template<typename T> + static constexpr bool check() noexcept + { + return N != 0 ? ((sizeof(T) / N) <= max_ratio()) : false; + } + }; + + /*! Trait used in `wire/read.h` for default `min_element_size` behavior based + on an array of `T` objects and `R` reader type. This trait can be used + instead of the `wire::array(...)` (and associated macros) functionality, as + it sets a global value. The last argument is for `enable_if`. */ + template<typename R, typename T, typename = void> + struct default_min_element_size + : std::integral_constant<std::size_t, 0> + {}; + + //! If `T` is a blob, a safe default for all formats is the size of the blob + template<typename R, typename T> + struct default_min_element_size<R, T, std::enable_if_t<is_blob<T>::value>> + : std::integral_constant<std::size_t, sizeof(T)> + {}; + + // example usage : `wire::sum(std::size_t(wire::available(fields))...)` + + inline constexpr int sum() noexcept + { + return 0; + } + template<typename T, typename... U> + inline constexpr T sum(const T head, const U... tail) noexcept + { + return head + sum(tail...); + } + + template<typename... T> + using min_element_sizeof = min_element_size<sum(sizeof(T)...)>; + + //! If container has no `reserve(0)` function, this function is used + template<typename... T> + inline void reserve(const T&...) noexcept + {} + + //! Container has `reserve(std::size_t)` function, use it + template<typename T> + inline auto reserve(T& container, const std::size_t count) -> decltype(container.reserve(count)) + { return container.reserve(count); } + + //! If `T` has no `empty()` function, this function is used + template<typename... T> + inline constexpr bool empty(const T&...) noexcept + { + static_assert(sum(is_optional_on_empty<T>::value...) == 0, "type needs empty method"); + return false; + } + + //! `T` has `empty()` function, use it + template<typename T> + inline auto empty(const T& container) -> decltype(container.empty()) + { return container.empty(); } + + //! If `T` has no `clear()` function, this function is used + template<typename... T> + inline void clear(const T&...) noexcept + { + static_assert(sum(is_optional_on_empty<T>::value...) == 0, "type needs clear method"); + } + + //! `T` has `clear()` function, use it + template<typename T> + inline auto clear(T& container) -> decltype(container.clear()) + { return container.clear(); } +} // wire diff --git a/contrib/epee/include/serialization/wire/wrapper/defaulted.h b/contrib/epee/include/serialization/wire/wrapper/defaulted.h new file mode 100644 index 000000000..f9a411c9e --- /dev/null +++ b/contrib/epee/include/serialization/wire/wrapper/defaulted.h @@ -0,0 +1,77 @@ +// Copyright (c) 2021-2023, 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 <functional> +#include <utility> + +#include "serialization/wire/field.h" +#include "serialization/wire/traits.h" + +//! An optional field that is omitted when a default value is used +#define WIRE_FIELD_DEFAULTED(name, default_) \ + ::wire::optional_field( #name , ::wire::defaulted(std::ref( self . name ), default_ )) + +namespace wire +{ + /*! A wrapper that tells `wire::writer`s to skip field generation when default + value, and tells `wire::reader`s to use default value when field not present. */ + template<typename T, typename U> + struct defaulted_ + { + using value_type = unwrap_reference_t<T>; + + T value; + U default_; + + constexpr const value_type& get_value() const noexcept { return value; } + value_type& get_value() noexcept { return value; } + + // concept requirements for optional fields + + constexpr explicit operator bool() const { return get_value() != default_; } + value_type& emplace() noexcept { return get_value(); } + + constexpr const value_type& operator*() const noexcept { return get_value(); } + value_type& operator*() noexcept { return get_value(); } + + void reset() { get_value() = default_; } + }; + + //! Links `value` with `default_`. + template<typename T, typename U> + inline constexpr defaulted_<T, U> defaulted(T value, U default_) + { + return {std::move(value), std::move(default_)}; + } + + /* read/write functions not needed since `defaulted_` meets the concept + requirements for an optional type (optional fields are handled + directly by the generic read/write code because the field name is omitted + entirely when the value is "empty"). */ +} // wire diff --git a/contrib/epee/include/serialization/wire/write.h b/contrib/epee/include/serialization/wire/write.h new file mode 100644 index 000000000..c18f7dbcc --- /dev/null +++ b/contrib/epee/include/serialization/wire/write.h @@ -0,0 +1,287 @@ +// Copyright (c) 2023, 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 <boost/utility/string_ref.hpp> +#include <boost/range/size.hpp> +#include <cstdint> +#include <system_error> +#include <type_traits> + +#include "byte_slice.h" +#include "serialization/wire/error.h" +#include "serialization/wire/field.h" +#include "serialization/wire/traits.h" +#include "span.h" + +/* + Custom types (e.g type `type` in namespace `ns`) can define an output function by: + * `namespace wire { template<> struct is_array<ns::type> : std::true_type {}; }` + * `namespace wire { template<> struct is_blob<ns::type> : std::true_type {}; }` + * `namespace wire { void write_bytes(writer&, const ns::type&); }` + * `namespace ns { void write_bytes(wire::writer&, const type&); }` + + See `wrappers.h` for `is_array` requirements, and `traits.h` for `is_blob` + requirements. `write_bytes` function can also specify derived type for faster + output (i.e. `namespace ns { void write_bytes(wire::epee_writer&, type&); }`). + Using the derived type allows the compiler to de-virtualize and allows for + custom functions not defined by base interface. Using base interface allows + for multiple formats with minimal instruction count. */ + +namespace wire +{ + //! Interface for converting C/C++ objects to "wire" (byte) formats. + struct writer + { + writer() = default; + + virtual ~writer() noexcept; + + //! By default, insist on retrieving array size before writing array + static constexpr std::true_type need_array_size() noexcept { return{}; } + + virtual void boolean(bool) = 0; + + virtual void integer(std::intmax_t) = 0; + virtual void unsigned_integer(std::uintmax_t) = 0; + + virtual void real(double) = 0; + + virtual void string(boost::string_ref) = 0; + virtual void binary(epee::span<const std::uint8_t>) = 0; + + virtual void start_array(std::size_t) = 0; + virtual void end_array() = 0; + + virtual void start_object(std::size_t) = 0; + virtual void key(boost::string_ref) = 0; + virtual void binary_key(epee::span<const std::uint8_t>) = 0; + virtual void end_object() = 0; + + protected: + writer(const writer&) = default; + writer(writer&&) = default; + writer& operator=(const writer&) = default; + writer& operator=(writer&&) = default; + }; + + template<typename W> + inline void write_arithmetic(W& dest, const bool source) + { dest.boolean(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const int source) + { dest.integer(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const long source) + { dest.integer(std::intmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const long long source) + { dest.integer(std::intmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned source) + { dest.unsigned_integer(source); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned long source) + { dest.unsigned_integer(std::uintmax_t(source)); } + + template<typename W> + inline void write_arithmetic(W& dest, const unsigned long long source) + { dest.unsigned_integer(std::uintmax_t(source));} + + template<typename W> + inline void write_arithmetic(W& dest, const double source) + { dest.real(source); } + + // Template both arguments to allow derived writer specializations + template<typename W, typename T> + inline std::enable_if_t<std::is_arithmetic<T>::value> write_bytes(W& dest, const T source) + { write_arithmetic(dest, source); } + + template<typename W> + inline void write_bytes(W& dest, const boost::string_ref source) + { dest.string(source); } + + template<typename W, typename T> + inline std::enable_if_t<is_blob<T>::value> write_bytes(W& dest, const T& source) + { dest.binary(epee::as_byte_span(source)); } + + template<typename W> + inline void write_bytes(W& dest, const epee::span<const std::uint8_t> source) + { dest.binary(source); } + + template<typename W> + inline void write_bytes(W& dest, const epee::byte_slice& source) + { write_bytes(dest, epee::to_span(source)); } + + //! Use `write_bytes(...)` method if available for `T`. + template<typename W, typename T> + inline auto write_bytes(W& dest, const T& source) -> decltype(source.write_bytes(dest)) + { return source.write_bytes(dest); } +} + +namespace wire_write +{ + /*! Don't add a function called `write_bytes` to this namespace, it will + prevent ADL lookup. ADL lookup delays the function searching until the + template is used instead of when its defined. This allows the unqualified + calls to `write_bytes` in this namespace to "find" user functions that are + declared after these functions. */ + + template<typename W, typename T> + inline void bytes(W& dest, const T& source) + { + write_bytes(dest, source); // ADL (searches every associated namespace) + } + + //! Use writer `W` to convert `source` into bytes appended to `dest`. + template<typename W, typename T, typename U> + inline std::error_code to_bytes(T& dest, const U& source) + { + try + { + W out{std::move(dest)}; + bytes(out, source); + dest = out.take_buffer(); + } + catch (const wire::exception& e) + { + dest.clear(); + return e.code(); + } + catch (...) + { + dest.clear(); + throw; + } + return {}; + } + + template<typename T> + inline std::size_t array_size(std::true_type, const T& source) + { return boost::size(source); } + + template<typename T> + inline constexpr std::size_t array_size(std::false_type, const T&) noexcept + { return 0; } + + template<typename W, typename T> + inline void array(W& dest, const T& source) + { + using value_type = typename T::value_type; + static_assert(!std::is_same<value_type, char>::value, "write array of chars as string"); + static_assert(!std::is_same<value_type, std::int8_t>::value, "write array of signed chars as binary"); + static_assert(!std::is_same<value_type, std::uint8_t>::value, "write array of unsigned chars as binary"); + + dest.start_array(array_size(dest.need_array_size(), source)); + for (const auto& elem : source) + bytes(dest, elem); + dest.end_array(); + } + + template<typename W, typename T> + inline bool field(W& dest, const wire::field_<T, true>& field) + { + // Arrays always optional, see `wire/field.h` + if (wire::available(field)) + { + dest.key(field.name); + bytes(dest, field.get_value()); + } + return true; + } + + template<typename W, typename T> + inline bool field(W& dest, const wire::field_<T, false>& field) + { + if (wire::available(field)) + { + dest.key(field.name); + bytes(dest, *field.get_value()); + } + return true; + } + + template<typename W, typename T> + inline std::enable_if_t<std::is_pod<T>::value> dynamic_object_key(W& dest, const T& source) + { + dest.binary_key(epee::as_byte_span(source)); + } + + template<typename W> + inline void dynamic_object_key(W& dest, const boost::string_ref source) + { + dest.key(source); + } + + template<typename W, typename T> + inline void dynamic_object(W& dest, const T& source) + { + dest.start_object(source.size()); + for (const auto& elem : source) + { + dynamic_object_key(dest, elem.first); + bytes(dest, elem.second); + } + dest.end_object(); + } + + template<typename W, typename... T> + inline void object(W& dest, T&&... fields) + { + dest.start_object(wire::sum(std::size_t(wire::available(fields))...)); + const bool dummy[] = {field(dest, std::forward<T>(fields))...}; + dest.end_object(); + (void)dummy; // expand into array to get 0,1,2,etc order + } +} // wire_write + +namespace wire +{ + template<typename W, typename T> + inline std::enable_if_t<is_array<T>::value> write_bytes(W& dest, const T& source) + { + wire_write::array(dest, source); + } + + template<typename W, typename... T> + inline std::enable_if_t<std::is_base_of<writer, W>::value> object(W& dest, T... fields) + { + wire_write::object(dest, std::move(fields)...); + } + + template<typename W, typename... T> + inline void object_fwd(const std::false_type /* is_read */, W& dest, T... fields) + { + wire::object(dest, std::move(fields)...); + } +} diff --git a/contrib/epee/include/span.h b/contrib/epee/include/span.h index 82936a777..bbd5d690c 100644 --- a/contrib/epee/include/span.h +++ b/contrib/epee/include/span.h @@ -147,6 +147,16 @@ namespace epee return {reinterpret_cast<const std::uint8_t*>(src.data()), src.size_bytes()}; } + //! \return `span<std::uint8_t>` from a STL compatible `src`. + template<typename T> + constexpr span<std::uint8_t> to_mut_byte_span(T& src) + { + using value_type = typename T::value_type; + static_assert(!std::is_empty<value_type>(), "empty value types will not work -> sizeof == 1"); + static_assert(!has_padding<value_type>(), "source value type may have padding"); + return {reinterpret_cast<std::uint8_t*>(src.data()), src.size() * sizeof(value_type)}; + } + //! \return `span<const std::uint8_t>` which represents the bytes at `&src`. template<typename T> span<const std::uint8_t> as_byte_span(const T& src) noexcept diff --git a/contrib/epee/include/storages/portable_storage_from_bin.h b/contrib/epee/include/storages/portable_storage_from_bin.h index d8a8a4a49..b0af022f5 100644 --- a/contrib/epee/include/storages/portable_storage_from_bin.h +++ b/contrib/epee/include/storages/portable_storage_from_bin.h @@ -33,6 +33,9 @@ #include "portable_storage_base.h" #include "portable_storage_bin_utils.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "serialization" + #ifdef EPEE_PORTABLE_STORAGE_RECURSION_LIMIT #define EPEE_PORTABLE_STORAGE_RECURSION_LIMIT_INTERNAL EPEE_PORTABLE_STORAGE_RECURSION_LIMIT #else diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 69192ca6b..60acfccb8 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -31,11 +31,13 @@ #include "parserse_base_utils.h" #include "file_io_utils.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "serialization" + #define EPEE_JSON_RECURSION_LIMIT_INTERNAL 100 namespace epee { - using namespace misc_utils::parse; namespace serialization { namespace json @@ -91,7 +93,7 @@ namespace epee switch(*it) { case '"': - match_string2(it, buf_end, name); + misc_utils::parse::match_string2(it, buf_end, name); state = match_state_waiting_separator; break; case '}': @@ -112,7 +114,7 @@ namespace epee if(*it == '"') {//just a named string value started std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); //insert text value stg.set_value(name, std::move(val), current_section); state = match_state_wonder_after_value; @@ -120,7 +122,7 @@ namespace epee {//just a named number value started boost::string_ref val; bool is_v_float = false;bool is_signed = false; - match_number2(it, buf_end, val, is_v_float, is_signed); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed); if(!is_v_float) { if(is_signed) @@ -147,7 +149,7 @@ namespace epee }else if(isalpha(*it) ) {// could be null, true or false boost::string_ref word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "null")) { state = match_state_wonder_after_value; @@ -202,7 +204,7 @@ namespace epee { //mean array of strings std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); h_array = stg.insert_first_value(name, std::move(val), current_section); CHECK_AND_ASSERT_THROW_MES(h_array, " failed to insert values entry"); state = match_state_array_after_value; @@ -211,7 +213,7 @@ namespace epee {//array of numbers value started boost::string_ref val; bool is_v_float = false;bool is_signed_val = false; - match_number2(it, buf_end, val, is_v_float, is_signed_val); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed_val); if(!is_v_float) { if (is_signed_val) @@ -246,7 +248,7 @@ namespace epee }else if(isalpha(*it) ) {// array of booleans boost::string_ref word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "true")) { h_array = stg.insert_first_value(name, true, current_section); @@ -290,7 +292,7 @@ namespace epee if(*it == '"') { std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); bool res = stg.insert_next_value(h_array, std::move(val)); CHECK_AND_ASSERT_THROW_MES(res, "failed to insert values"); state = match_state_array_after_value; @@ -301,7 +303,7 @@ namespace epee {//array of numbers value started boost::string_ref val; bool is_v_float = false;bool is_signed_val = false; - match_number2(it, buf_end, val, is_v_float, is_signed_val); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed_val); bool insert_res = false; if(!is_v_float) { @@ -334,7 +336,7 @@ namespace epee if(isalpha(*it) ) {// array of booleans boost::string_ref word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "true")) { bool r = stg.insert_next_value(h_array, true); diff --git a/docs/ANONYMITY_NETWORKS.md b/docs/ANONYMITY_NETWORKS.md index f8d08b05f..63fae6c39 100644 --- a/docs/ANONYMITY_NETWORKS.md +++ b/docs/ANONYMITY_NETWORKS.md @@ -11,7 +11,7 @@ relying on IPv4 for the remainder of messages to make surrounding node attacks ## Behavior If _any_ anonymity network is enabled, transactions being broadcast that lack -a valid "context" (i.e. the transaction did not come from a p2p connection), +a valid "context" (i.e. the transaction did not come from a P2P connection), will only be sent to peers on anonymity networks. If an anonymity network is enabled but no peers over an anonymity network are available, an error is logged and the transaction is kept for future broadcasting over an anonymity @@ -28,7 +28,7 @@ the hidden service for P2P connections. ## P2P Commands Only handshakes, peer timed syncs and transaction broadcast messages are -supported over anonymity networks. If one `--add-exclusive-node` p2p address +supported over anonymity networks. If one `--add-exclusive-node` P2P address is specified, then no syncing will take place and only transaction broadcasting can occur. It is therefore recommended that `--add-exclusive-node` be combined with additional exclusive IPv4 address(es). @@ -47,16 +47,16 @@ separate process. On most systems the configuration will look like: --tx-proxy i2p,127.0.0.1:9000 ``` -which tells `monerod` that ".onion" p2p addresses can be forwarded to a socks +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 +".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. If desired, peers can be manually specified: ``` ---add-exclusive-node rveahdfho7wo4b2m.onion:28083 ---add-peer rveahdfho7wo4b2m.onion:28083 +--add-exclusive-node 5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion:28083 +--add-peer 5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion:28083 ``` Either option can be listed multiple times, and can specify any mix of Tor, @@ -70,12 +70,12 @@ Receiving anonymity connections is done through the option type, and max connections: ``` ---anonymous-inbound rveahdfho7wo4b2m.onion:28083,127.0.0.1:28083,25 +--anonymous-inbound 5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion:28083,127.0.0.1:28083,25 --anonymous-inbound cmeua5767mz2q5jsaelk2rxhf67agrwuetaso5dzbenyzwlbkg2q.b32.i2p:5000,127.0.0.1:30000 ``` which tells `monerod` that a max of 25 inbound Tor connections are being -received at address "rveahdfho7wo4b2m.onion:28083" and forwarded to `monerod` +received at address "5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion:28083" and forwarded to `monerod` localhost port 28083, and a default max I2P connections are being received at address "cmeua5767mz2q5jsaelk2rxhf67agrwuetaso5dzbenyzwlbkg2q.b32.i2p:5000" and forwarded to `monerod` localhost port 30000. @@ -86,32 +86,32 @@ otherwise the peer will not be notified of the peer address by the proxy. An anonymity network can be configured to forward incoming connections to a `monerod` RPC port - which is independent from the configuration for incoming -P2P anonymity connections. The anonymity network (Tor/i2p) is +P2P anonymity connections. The anonymity network (Tor/I2P) is [configured in the same manner](#configuration), except the localhost port -must be the RPC port (typically 18081 for mainnet) instead of the p2p port: +must be the RPC port (typically 18081 for mainnet) instead of the P2P port: ``` HiddenServiceDir /var/lib/tor/data/monero HiddenServicePort 18081 127.0.0.1:18081 ``` -Then the wallet will be configured to use a Tor/i2p address: +Then the wallet will be configured to use a Tor/I2P address: ``` --proxy 127.0.0.1:9050 ---daemon-address rveahdfho7wo4b2m.onion +--daemon-address 5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion ``` The proxy must match the address type - a Tor proxy will not work properly with -i2p addresses, etc. +I2P addresses, etc. -i2p and onion addresses provide the information necessary to authenticate and +I2P hidden service (b32.i2p) and Tor Hidden service (.onion) addresses provide the information necessary to authenticate and encrypt the connection from end-to-end. If desired, SSL can also be applied to -the connection with `--daemon-address https://rveahdfho7wo4b2m.onion` which +the connection with `--daemon-address https://5tymba6faziy36md5ffy42vatbjzlye4vyr3gyz6lcvdfximnvwpmwqd.onion` which requires a server certificate that is signed by a "root" certificate on the machine running the wallet. Alternatively, `--daemon-cert-file` can be used to specify a certificate to authenticate the server. -Proxies can also be used to connect to "clearnet" (ipv4 addresses or ICANN +Proxies can also be used to connect to "clearnet" (IPv4 addresses or ICANN domains), but `--daemon-cert-file` _must_ be used for authentication and encryption. @@ -204,7 +204,7 @@ If a single I2P/Tor stream is used 2+ times for transmitting a transaction, the operator of the hidden service can conclude that both transactions came from the same source. If the subsequent transactions spend a change output from the earlier transactions, this will also reveal the "real" spend in the ring -signature. This issue was (primarily) raised by @secparam on Twitter. +signature. This issue was (primarily) raised by @secparam on [Twitter](https://twitter.com/secparam/status/1153411657214910469). #### Mitigation @@ -219,7 +219,7 @@ most cases. However, the number of outgoing connections is typically a small fixed number, so there is a decent probability of re-use with the same public key identity. -@secparam (twitter) recommended changing circuits (Tor) as an additional +@secparam ([Twitter](https://twitter.com/secparam/status/1153411968147042304)) recommended changing circuits (Tor) as an additional precaution. This is likely not a good idea - forcibly requesting Tor to change circuits is observable by the ISP. Instead, `monerod` should likely disconnect from peers occasionally. Tor will rotate circuits every ~10 minutes, so diff --git a/external/trezor-common b/external/trezor-common -Subproject bff7fdfe436c727982cc553bdfb29a9021b423b +Subproject bc28c316d05bf1e9ebfe3d7df1ab25831d98d16 diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index f3e962c81..9628a5c4d 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1053,7 +1053,7 @@ public: * @brief fetch a block's already generated coins * * The subclass should return the total coins generated as of the block - * with the given height. + * with the given height, capped to a maximum value of MONEY_SUPPLY. * * If the block does not exist, the subclass should throw BLOCK_DNE * diff --git a/src/blockchain_utilities/blockchain_ancestry.cpp b/src/blockchain_utilities/blockchain_ancestry.cpp index 66dd7813b..36c17357a 100644 --- a/src/blockchain_utilities/blockchain_ancestry.cpp +++ b/src/blockchain_utilities/blockchain_ancestry.cpp @@ -37,10 +37,7 @@ #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -449,9 +446,7 @@ int main(int argc, char* argv[]) // because unlike blockchain_storage constructor, which takes a pointer to // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB *db = new_db(); if (db == NULL) { @@ -472,7 +467,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -716,7 +711,7 @@ int main(int argc, char* argv[]) } done: - core_storage->deinit(); + core_storage->blockchain.deinit(); if (opt_show_cache_stats) MINFO("cache: txes " << std::to_string(cached_txes*100./total_txes) diff --git a/src/blockchain_utilities/blockchain_depth.cpp b/src/blockchain_utilities/blockchain_depth.cpp index 6a06e0a96..f49211233 100644 --- a/src/blockchain_utilities/blockchain_depth.cpp +++ b/src/blockchain_utilities/blockchain_depth.cpp @@ -31,10 +31,7 @@ #include <boost/algorithm/string.hpp> #include "common/command_line.h" #include "common/varint.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -129,16 +126,8 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB *db = new_db(); if (db == NULL) { @@ -159,7 +148,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -327,7 +316,7 @@ done: LOG_PRINT_L0("Average min depth for " << start_txids.size() << " transaction(s): " << cumulative_depth/(float)depths.size()); LOG_PRINT_L0("Median min depth for " << start_txids.size() << " transaction(s): " << epee::misc_utils::median(depths)); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Depth query error", 1); diff --git a/src/blockchain_utilities/blockchain_export.cpp b/src/blockchain_utilities/blockchain_export.cpp index 3d7b3f61a..0611b3640 100644 --- a/src/blockchain_utilities/blockchain_export.cpp +++ b/src/blockchain_utilities/blockchain_export.cpp @@ -29,7 +29,6 @@ #include "bootstrap_file.h" #include "blocksdat_file.h" #include "common/command_line.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/blockchain_db.h" #include "version.h" @@ -38,6 +37,7 @@ #define MONERO_DEFAULT_LOG_CATEGORY "bcutil" namespace po = boost::program_options; +using namespace cryptonote; using namespace epee; int main(int argc, char* argv[]) @@ -129,16 +129,8 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - Blockchain* core_storage = NULL; - tx_memory_pool m_mempool(*core_storage); - core_storage = new Blockchain(m_mempool); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); BlockchainDB* db = new_db(); if (db == NULL) @@ -162,9 +154,9 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); + r = core_storage->blockchain.init(db, opt_testnet ? cryptonote::TESTNET : opt_stagenet ? cryptonote::STAGENET : cryptonote::MAINNET); - if (core_storage->get_blockchain_pruning_seed() && !opt_blocks_dat) + if (core_storage->blockchain.get_blockchain_pruning_seed() && !opt_blocks_dat) { LOG_PRINT_L0("Blockchain is pruned, cannot export"); return 1; @@ -177,12 +169,12 @@ int main(int argc, char* argv[]) if (opt_blocks_dat) { BlocksdatFile blocksdat; - r = blocksdat.store_blockchain_raw(core_storage, NULL, output_file_path, block_stop); + r = blocksdat.store_blockchain_raw(&core_storage->blockchain, NULL, output_file_path, block_stop); } else { BootstrapFile bootstrap; - r = bootstrap.store_blockchain_raw(core_storage, NULL, output_file_path, block_start, block_stop); + r = bootstrap.store_blockchain_raw(&core_storage->blockchain, NULL, output_file_path, block_start, block_stop); } CHECK_AND_ASSERT_MES(r, 1, "Failed to export blockchain raw data"); LOG_PRINT_L0("Blockchain raw data exported OK"); diff --git a/src/blockchain_utilities/blockchain_prune.cpp b/src/blockchain_utilities/blockchain_prune.cpp index 1e4b48b73..1a9618617 100644 --- a/src/blockchain_utilities/blockchain_prune.cpp +++ b/src/blockchain_utilities/blockchain_prune.cpp @@ -35,8 +35,6 @@ #include "common/command_line.h" #include "common/pruning.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "blockchain_db/lmdb/db_lmdb.h" #include "version.h" @@ -562,22 +560,15 @@ int main(int argc, char* argv[]) // Use Blockchain instead of lower-level BlockchainDB for two reasons: // 1. Blockchain has the init() method for easy setup // 2. exporter needs to use get_current_blockchain_height(), get_block_id_by_height(), get_block_by_hash() - // - // cannot match blockchain_storage setup above with just one line, - // e.g. - // Blockchain* core_storage = new Blockchain(NULL); - // because unlike blockchain_storage constructor, which takes a pointer to - // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. MINFO("Initializing source blockchain (BlockchainDB)"); - std::array<std::unique_ptr<Blockchain>, 2> core_storage; - Blockchain *blockchain = NULL; - tx_memory_pool m_mempool(*blockchain); + std::array<std::unique_ptr<BlockchainAndPool>, 2> core_storage{ + std::make_unique<BlockchainAndPool>(), + std::make_unique<BlockchainAndPool>()}; + boost::filesystem::path paths[2]; bool already_pruned = false; for (size_t n = 0; n < core_storage.size(); ++n) { - core_storage[n].reset(new Blockchain(m_mempool)); - BlockchainDB* db = new_db(); if (db == NULL) { @@ -622,12 +613,12 @@ int main(int argc, char* argv[]) MERROR("Error opening database: " << e.what()); return 1; } - r = core_storage[n]->init(db, net_type); + r = core_storage[n]->blockchain.init(db, net_type); std::string source_dest = n == 0 ? "source" : "pruned"; CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize " << source_dest << " blockchain storage"); MINFO(source_dest << " blockchain storage initialized OK"); - if (n == 0 && core_storage[0]->get_blockchain_pruning_seed()) + if (n == 0 && core_storage[0]->blockchain.get_blockchain_pruning_seed()) { if (!opt_copy_pruned_database) { @@ -637,9 +628,9 @@ int main(int argc, char* argv[]) already_pruned = true; } } - core_storage[0]->deinit(); + core_storage[0]->blockchain.deinit(); core_storage[0].reset(NULL); - core_storage[1]->deinit(); + core_storage[1]->blockchain.deinit(); core_storage[1].reset(NULL); MINFO("Pruning..."); diff --git a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp index 4da9c15c1..4a459dc66 100644 --- a/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp +++ b/src/blockchain_utilities/blockchain_prune_known_spent_data.cpp @@ -30,10 +30,7 @@ #include <boost/filesystem.hpp> #include "common/command_line.h" #include "serialization/crypto.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -160,9 +157,8 @@ int main(int argc, char* argv[]) const std::string input = command_line::get_arg(vm, arg_input); LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB *db = new_db(); if (db == NULL) { @@ -182,7 +178,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -280,7 +276,7 @@ int main(int argc, char* argv[]) MINFO("Prunable outputs: " << num_prunable_outputs); LOG_PRINT_L0("Blockchain known spent data pruned OK"); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Error", 1); diff --git a/src/blockchain_utilities/blockchain_stats.cpp b/src/blockchain_utilities/blockchain_stats.cpp index 5e4245ebd..f65054fc5 100644 --- a/src/blockchain_utilities/blockchain_stats.cpp +++ b/src/blockchain_utilities/blockchain_stats.cpp @@ -31,9 +31,7 @@ #include "common/command_line.h" #include "common/varint.h" #include "cryptonote_basic/cryptonote_boost_serialization.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" #include "blockchain_db/blockchain_db.h" #include "version.h" @@ -203,9 +201,8 @@ int main(int argc, char* argv[]) do_diff = command_line::get_arg(vm, arg_diff); LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB *db = new_db(); if (db == NULL) { @@ -225,7 +222,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -381,7 +378,7 @@ plot 'stats.csv' index "DATA" using (timecolumn(1,"%Y-%m-%d")):4 with lines, '' if (currblks) doprint(); - core_storage->deinit(); + core_storage->blockchain.deinit(); return 0; CATCH_ENTRY("Stats reporting error", 1); diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index a5228eb92..0b9686765 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -31,10 +31,7 @@ #include <boost/filesystem/path.hpp> #include "common/command_line.h" #include "common/varint.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/blockchain.h" -#include "blockchain_db/blockchain_db.h" #include "version.h" #undef MONERO_DEFAULT_LOG_CATEGORY @@ -151,9 +148,8 @@ int main(int argc, char* argv[]) // tx_memory_pool, Blockchain's constructor takes tx_memory_pool object. LOG_PRINT_L0("Initializing source blockchain (BlockchainDB)"); const std::string input = command_line::get_arg(vm, arg_input); - std::unique_ptr<Blockchain> core_storage; - tx_memory_pool m_mempool(*core_storage); - core_storage.reset(new Blockchain(m_mempool)); + std::unique_ptr<BlockchainAndPool> core_storage = std::make_unique<BlockchainAndPool>(); + BlockchainDB* db = new_db(); if (db == NULL) { @@ -174,7 +170,7 @@ int main(int argc, char* argv[]) LOG_PRINT_L0("Error opening database: " << e.what()); return 1; } - r = core_storage->init(db, net_type); + r = core_storage->blockchain.init(db, net_type); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize source blockchain storage"); LOG_PRINT_L0("Source blockchain storage initialized OK"); @@ -185,10 +181,10 @@ int main(int argc, char* argv[]) std::unordered_map<uint64_t,uint64_t> indices; LOG_PRINT_L0("Reading blockchain from " << input); - core_storage->for_all_transactions([&](const crypto::hash &hash, const cryptonote::transaction &tx)->bool + core_storage->blockchain.for_all_transactions([&](const crypto::hash &hash, const cryptonote::transaction &tx)->bool { const bool coinbase = tx.vin.size() == 1 && tx.vin[0].type() == typeid(txin_gen); - const uint64_t height = core_storage->get_db().get_tx_block_height(hash); + const uint64_t height = core_storage->blockchain.get_db().get_tx_block_height(hash); // create new outputs for (const auto &out: tx.vout) diff --git a/src/blockchain_utilities/blocksdat_file.h b/src/blockchain_utilities/blocksdat_file.h index 557b395a4..b80440880 100644 --- a/src/blockchain_utilities/blocksdat_file.h +++ b/src/blockchain_utilities/blocksdat_file.h @@ -50,10 +50,6 @@ #include "blockchain_utilities.h" - -using namespace cryptonote; - - class BlocksdatFile { public: @@ -63,7 +59,7 @@ public: protected: - Blockchain* m_blockchain_storage; + cryptonote::Blockchain* m_blockchain_storage; std::ofstream * m_raw_data_file; diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h index 7a1085a56..808ed00bb 100644 --- a/src/blockchain_utilities/bootstrap_file.h +++ b/src/blockchain_utilities/bootstrap_file.h @@ -48,10 +48,6 @@ #include "blockchain_utilities.h" - -using namespace cryptonote; - - class BootstrapFile { public: @@ -66,9 +62,9 @@ public: protected: - Blockchain* m_blockchain_storage; + cryptonote::Blockchain* m_blockchain_storage; - tx_memory_pool* m_tx_pool; + cryptonote::tx_memory_pool* m_tx_pool; typedef std::vector<char> buffer_type; std::ofstream * m_raw_data_file; buffer_type m_buffer; @@ -78,7 +74,7 @@ protected: bool open_writer(const boost::filesystem::path& file_path, uint64_t start_block, uint64_t stop_block); bool initialize_file(uint64_t start_block, uint64_t stop_block); bool close(); - void write_block(block& block); + void write_block(cryptonote::block& block); void flush_chunk(); private: diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 8036c84cd..7c9bd9163 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -4615,40 +4615,9 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti } else { - const uint64_t block_weight = m_db->get_block_weight(db_height - 1); + const uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); + const uint64_t long_term_median = get_long_term_block_weight_median(db_height - nblocks, nblocks); - uint64_t long_term_median; - if (db_height == 1) - { - long_term_median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; - } - else - { - uint64_t nblocks = std::min<uint64_t>(m_long_term_block_weights_window, db_height); - if (nblocks == db_height) - --nblocks; - long_term_median = get_long_term_block_weight_median(db_height - nblocks - 1, nblocks); - } - - m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); - - uint64_t short_term_constraint = m_long_term_effective_median_block_weight; - if (hf_version >= HF_VERSION_2021_SCALING) - short_term_constraint += m_long_term_effective_median_block_weight * 7 / 10; - else - short_term_constraint += m_long_term_effective_median_block_weight * 2 / 5; - uint64_t long_term_block_weight = std::min<uint64_t>(block_weight, short_term_constraint); - - if (db_height == 1) - { - long_term_median = long_term_block_weight; - } - else - { - m_long_term_block_weights_cache_tip_hash = m_db->get_block_hash_from_height(db_height - 1); - m_long_term_block_weights_cache_rolling_median.insert(long_term_block_weight); - long_term_median = m_long_term_block_weights_cache_rolling_median.median(); - } m_long_term_effective_median_block_weight = std::max<uint64_t>(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5, long_term_median); std::vector<uint64_t> weights; diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index a45d3ec60..3ad051fc3 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -113,13 +113,6 @@ namespace cryptonote }; /** - * @brief Blockchain constructor - * - * @param tx_pool a reference to the transaction pool to be kept by the Blockchain - */ - Blockchain(tx_memory_pool& tx_pool); - - /** * @brief Blockchain destructor */ ~Blockchain(); @@ -1236,6 +1229,13 @@ namespace cryptonote mutable rct_ver_cache_t m_rct_ver_cache; /** + * @brief Blockchain constructor + * + * @param tx_pool a reference to the transaction pool to be kept by the Blockchain + */ + Blockchain(tx_memory_pool& tx_pool); + + /** * @brief collects the keys for all outputs being "spent" as an input * * This function makes sure that each "input" in an input (mixins) exists @@ -1608,5 +1608,7 @@ namespace cryptonote * @param already_generated_coins total coins mined by the network so far */ void send_miner_notifications(uint64_t height, const crypto::hash &seed_hash, const crypto::hash &prev_id, uint64_t already_generated_coins); + + friend class BlockchainAndPool; }; } // namespace cryptonote diff --git a/src/cryptonote_core/blockchain_and_pool.h b/src/cryptonote_core/blockchain_and_pool.h new file mode 100644 index 000000000..c0f607f64 --- /dev/null +++ b/src/cryptonote_core/blockchain_and_pool.h @@ -0,0 +1,58 @@ +// Copyright (c) 2023, 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 <memory> + +#include "blockchain.h" +#include "tx_pool.h" + +namespace cryptonote +{ +/** + * @brief Container for safely constructing Blockchain and tx_memory_pool classes + * + * The reason for this class existing is that the constructors for both Blockchain and + * tx_memory_pool take a reference for tx_memory_pool and Blockchain, respectively. Because of this + * circular reference, it is annoying/unsafe to construct these normally. This class guarantees that + * we don't make any silly mistakes with pointers / dangling references. + */ +struct BlockchainAndPool +{ + Blockchain blockchain; + tx_memory_pool tx_pool; + + BlockchainAndPool(): blockchain(tx_pool), tx_pool(blockchain) {} +}; +} diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 7b0c9e495..a5a59c892 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -221,8 +221,9 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- core::core(i_cryptonote_protocol* pprotocol): - m_mempool(m_blockchain_storage), - m_blockchain_storage(m_mempool), + m_bap(), + m_mempool(m_bap.tx_pool), + m_blockchain_storage(m_bap.blockchain), m_miner(this, [this](const cryptonote::block &b, uint64_t height, const crypto::hash *seed_hash, unsigned int threads, crypto::hash &hash) { return cryptonote::get_block_longhash(&m_blockchain_storage, b, hash, height, seed_hash, threads); }), @@ -1558,7 +1559,8 @@ namespace cryptonote return false; } m_blockchain_storage.add_new_block(b, bvc); - cleanup_handle_incoming_blocks(true); + const bool force_sync = m_nettype != FAKECHAIN; + cleanup_handle_incoming_blocks(force_sync); //anyway - update miner template update_miner_block_template(); m_miner.resume(); diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index e0655dfa2..8108dfae0 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -42,8 +42,7 @@ #include "cryptonote_protocol/enums.h" #include "common/download.h" #include "common/command_line.h" -#include "tx_pool.h" -#include "blockchain.h" +#include "blockchain_and_pool.h" #include "cryptonote_basic/miner.h" #include "cryptonote_basic/connection_context.h" #include "warnings.h" @@ -1098,8 +1097,9 @@ namespace cryptonote uint64_t m_test_drop_download_height = 0; //!< height under which to drop incoming blocks, if doing so - tx_memory_pool m_mempool; //!< transaction pool instance - Blockchain m_blockchain_storage; //!< Blockchain instance + BlockchainAndPool m_bap; //! Contains owned instances of Blockchain and tx_memory_pool + tx_memory_pool& m_mempool; //!< ref to transaction pool instance in m_bap + Blockchain& m_blockchain_storage; //!< ref to Blockchain instance in m_bap i_cryptonote_protocol* m_pprotocol; //!< cryptonote protocol instance diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 6fe2eea59..47268efb6 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -98,14 +98,6 @@ namespace cryptonote { public: /** - * @brief Constructor - * - * @param bchs a Blockchain class instance, for getting chain info - */ - tx_memory_pool(Blockchain& bchs); - - - /** * @copydoc add_tx(transaction&, tx_verification_context&, bool, bool, uint8_t) * * @param id the transaction's hash @@ -489,6 +481,13 @@ namespace cryptonote private: /** + * @brief Constructor + * + * @param bchs a Blockchain class instance, for getting chain info + */ + tx_memory_pool(Blockchain& bchs); + + /** * @brief insert key images into m_spent_key_images * * @return true on success, false on error @@ -676,6 +675,8 @@ private: //! Next timestamp that a DB check for relayable txes is allowed std::atomic<time_t> m_next_check; + + friend class BlockchainAndPool; }; } diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index af667dc0c..385c3e5c3 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1167,13 +1167,6 @@ namespace cryptonote m_sync_download_objects_size += size; MDEBUG(context << " downloaded " << size << " bytes worth of blocks"); - /*using namespace boost::chrono; - auto point = steady_clock::now(); - auto time_from_epoh = point.time_since_epoch(); - auto sec = duration_cast< seconds >( time_from_epoh ).count();*/ - - //epee::net_utils::network_throttle_manager::get_global_throttle_inreq().logger_handle_net("log/dr-monero/net/req-all.data", sec, get_avg_block_size()); - if(arg.blocks.empty()) { LOG_ERROR_CCONTEXT("sent wrong NOTIFY_HAVE_OBJECTS: no blocks"); diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index c30fb2b16..2d5614507 100644 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -65,12 +65,16 @@ set(trezor_private_headers) # Protobuf and LibUSB processed by CheckTrezor if(DEVICE_TREZOR_READY) - message(STATUS "Trezor support enabled") + message(STATUS "Trezor: support enabled") if(USE_DEVICE_TREZOR_DEBUG) list(APPEND trezor_headers trezor/debug_link.hpp trezor/messages/messages-debug.pb.h) list(APPEND trezor_sources trezor/debug_link.cpp trezor/messages/messages-debug.pb.cc) - message(STATUS "Trezor debugging enabled") + message(STATUS "Trezor: debugging enabled") + endif() + + if(ANDROID) + set(TREZOR_EXTRA_LIBRARIES "log") endif() monero_private_headers(device_trezor @@ -93,10 +97,11 @@ if(DEVICE_TREZOR_READY) ${Protobuf_LIBRARY} ${TREZOR_LIBUSB_LIBRARIES} PRIVATE - ${EXTRA_LIBRARIES}) + ${EXTRA_LIBRARIES} + ${TREZOR_EXTRA_LIBRARIES}) else() - message(STATUS "Trezor support disabled") + message(STATUS "Trezor: support disabled") monero_private_headers(device_trezor) monero_add_library(device_trezor device_trezor.cpp) target_link_libraries(device_trezor PUBLIC cncrypto) diff --git a/src/device_trezor/README.md b/src/device_trezor/README.md new file mode 100644 index 000000000..ce08c0009 --- /dev/null +++ b/src/device_trezor/README.md @@ -0,0 +1,74 @@ +# Trezor hardware wallet support + +This module adds [Trezor] hardware support to Monero. + + +## Basic information + +Trezor integration is based on the following original proposal: https://github.com/ph4r05/monero-trezor-doc + +A custom high-level transaction signing protocol uses Trezor in a similar way a cold wallet is used. +Transaction is build incrementally on the device. + +Trezor implements the signing protocol in [trezor-firmware] repository, in the [monero](https://github.com/trezor/trezor-firmware/tree/master/core/src/apps/monero) application. +Please, refer to [monero readme](https://github.com/trezor/trezor-firmware/blob/master/core/src/apps/monero/README.md) for more information. + +## Dependencies + +Trezor uses [Protobuf](https://protobuf.dev/) library. As Monero is compiled with C++14, the newest Protobuf library version cannot be compiled because it requires C++17 (through its dependency Abseil library). +This can result in a compilation failure. + +Protobuf v21 is the latest compatible protobuf version. + +If you want to compile Monero with Trezor support, please make sure the Protobuf v21 is installed. + +More about this limitation: [PR #8752](https://github.com/monero-project/monero/pull/8752), +[1](https://github.com/monero-project/monero/pull/8752#discussion_r1246174755), [2](https://github.com/monero-project/monero/pull/8752#discussion_r1246480393) + +### OSX + +To build with installed, but not linked protobuf: + +```bash +CMAKE_PREFIX_PATH=$(find /opt/homebrew/Cellar/protobuf@21 -maxdepth 1 -type d -name "21.*" -print -quit) \ +make release +``` + +or to install and link as a default protobuf version: +```bash +# Either install all requirements as +brew update && brew bundle --file=contrib/brew/Brewfile + +# or install protobufv21 specifically +brew install protobuf@21 && brew link protobuf@21 +``` + +### MSYS32 + +```bash +curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst +curl -O https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst +pacman --noconfirm -U mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst mingw-w64-x86_64-protobuf-21.9-1-any.pkg.tar.zst +``` + +### Other systems + +- install protobufv21 +- point `CMAKE_PREFIX_PATH` environment variable to Protobuf v21 installation. + +## Troubleshooting + +To disable Trezor support, set `USE_DEVICE_TREZOR=OFF`, e.g.: + +```shell +USE_DEVICE_TREZOR=OFF make release +``` + +## Resources: + +- First pull request https://github.com/monero-project/monero/pull/4241 +- Integration proposal https://github.com/ph4r05/monero-trezor-doc +- Integration readme in trezor-firmware https://github.com/trezor/trezor-firmware/blob/master/core/src/apps/monero/README.md + +[Trezor]: https://trezor.io/ +[trezor-firmware]: https://github.com/trezor/trezor-firmware/
\ No newline at end of file diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 9c8148ed6..fa1e7c088 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -165,7 +165,7 @@ namespace trezor { auto res = get_address(); cryptonote::address_parse_info info{}; - bool r = cryptonote::get_account_address_from_str(info, this->network_type, res->address()); + bool r = cryptonote::get_account_address_from_str(info, this->m_network_type, res->address()); CHECK_AND_ASSERT_MES(r, false, "Could not parse returned address. Address parse failed: " + res->address()); CHECK_AND_ASSERT_MES(!info.is_subaddress, false, "Trezor returned a sub address"); @@ -693,14 +693,11 @@ namespace trezor { unsigned device_trezor::client_version() { auto trezor_version = get_version(); - if (trezor_version < pack_version(2, 4, 3)){ - throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update."); + if (trezor_version < pack_version(2, 5, 2)){ + throw exc::TrezorException("Minimal Trezor firmware version is 2.5.2. Please update."); } - unsigned client_version = 3; - if (trezor_version >= pack_version(2, 5, 2)){ - client_version = 4; - } + unsigned client_version = 4; // since 2.5.2 #ifdef WITH_TREZOR_DEBUGGING // Override client version for tests diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index 35bb78927..804ed62c8 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -102,7 +102,7 @@ namespace trezor { bool has_ki_cold_sync() const override { return true; } bool has_tx_cold_sign() const override { return true; } - void set_network_type(cryptonote::network_type network_type) override { this->network_type = network_type; } + void set_network_type(cryptonote::network_type network_type) override { this->m_network_type = network_type; } void set_live_refresh_enabled(bool enabled) { m_live_refresh_enabled = enabled; } bool live_refresh_enabled() const { return m_live_refresh_enabled; } diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index a8a3d9f67..f65870be5 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -300,9 +300,6 @@ namespace trezor { case messages::MessageType_PassphraseRequest: on_passphrase_request(input, dynamic_cast<const messages::common::PassphraseRequest*>(input.m_msg.get())); return true; - case messages::MessageType_Deprecated_PassphraseStateRequest: - on_passphrase_state_request(input, dynamic_cast<const messages::common::Deprecated_PassphraseStateRequest*>(input.m_msg.get())); - return true; case messages::MessageType_PinMatrixRequest: on_pin_request(input, dynamic_cast<const messages::common::PinMatrixRequest*>(input.m_msg.get())); return true; @@ -475,21 +472,9 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); MDEBUG("on_passhprase_request"); - // Backward compatibility, migration clause. - if (msg->has__on_device() && msg->_on_device()){ - messages::common::PassphraseAck m; - resp = call_raw(&m); - return; - } - m_seen_passphrase_entry_message = true; - bool on_device = true; - if (msg->has__on_device() && !msg->_on_device()){ - on_device = false; // do not enter on device, old devices. - } - - if (on_device && m_features && m_features->capabilities_size() > 0){ - on_device = false; + bool on_device = false; + if (m_features){ for (auto it = m_features->capabilities().begin(); it != m_features->capabilities().end(); it++) { if (*it == messages::management::Features::Capability_PassphraseEntry){ on_device = true; @@ -526,18 +511,6 @@ namespace trezor { resp = call_raw(&m); } - void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::Deprecated_PassphraseStateRequest * msg) - { - MDEBUG("on_passhprase_state_request"); - CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); - - if (msg->has_state()) { - m_device_session_id = msg->state(); - } - messages::common::Deprecated_PassphraseStateAck m; - resp = call_raw(&m); - } - #ifdef WITH_TREZOR_DEBUGGING void device_trezor_base::wipe_device() { diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 3ec21e157..df6e42b1f 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -100,7 +100,7 @@ namespace trezor { boost::optional<epee::wipeable_string> m_passphrase; messages::MessageType m_last_msg_type; - cryptonote::network_type network_type; + cryptonote::network_type m_network_type; bool m_reply_with_empty_passphrase; bool m_always_use_empty_passphrase; bool m_seen_passphrase_entry_message; @@ -227,9 +227,9 @@ namespace trezor { } if (network_type){ - msg->set_network_type(static_cast<uint32_t>(network_type.get())); + msg->set_network_type(static_cast<messages::monero::MoneroNetworkType>(network_type.get())); } else { - msg->set_network_type(static_cast<uint32_t>(this->network_type)); + msg->set_network_type(static_cast<messages::monero::MoneroNetworkType>(this->m_network_type)); } } @@ -318,7 +318,6 @@ namespace trezor { void on_button_pressed(); void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); void on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg); - void on_passphrase_state_request(GenericMessage & resp, const messages::common::Deprecated_PassphraseStateRequest * msg); #ifdef WITH_TREZOR_DEBUGGING void set_debug(bool debug){ diff --git a/src/device_trezor/trezor/debug_link.cpp b/src/device_trezor/trezor/debug_link.cpp index ff7cf0ad6..76adcb164 100644 --- a/src/device_trezor/trezor/debug_link.cpp +++ b/src/device_trezor/trezor/debug_link.cpp @@ -67,7 +67,7 @@ namespace trezor{ void DebugLink::input_button(bool button){ messages::debug::DebugLinkDecision decision; - decision.set_yes_no(button); + decision.set_button(button ? messages::debug::DebugLinkDecision_DebugButton_YES : messages::debug::DebugLinkDecision_DebugButton_NO); call(decision, boost::none, true); } diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index 8c6b30787..23a04f9c7 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -154,6 +154,7 @@ namespace trezor{ // Helpers // +#define PROTO_MAGIC_SIZE 3 #define PROTO_HEADER_SIZE 6 static size_t message_size(const google::protobuf::Message &req){ @@ -193,7 +194,7 @@ namespace trezor{ } serialize_message_header(buff, msg_wire_num, msg_size); - if (!req.SerializeToArray(buff + 6, msg_size)){ + if (!req.SerializeToArray(buff + PROTO_HEADER_SIZE, msg_size)){ throw exc::EncodingException("Message serialization error"); } } @@ -252,16 +253,16 @@ namespace trezor{ throw exc::CommunicationException("Read chunk has invalid size"); } - if (memcmp(chunk_buff_raw, "?##", 3) != 0){ + if (memcmp(chunk_buff_raw, "?##", PROTO_MAGIC_SIZE) != 0){ throw exc::CommunicationException("Malformed chunk"); } uint16_t tag; uint32_t len; - nread -= 3 + 6; - deserialize_message_header(chunk_buff_raw + 3, tag, len); + nread -= PROTO_MAGIC_SIZE + PROTO_HEADER_SIZE; + deserialize_message_header(chunk_buff_raw + PROTO_MAGIC_SIZE, tag, len); - epee::wipeable_string data_acc(chunk_buff_raw + 3 + 6, nread); + epee::wipeable_string data_acc(chunk_buff_raw + PROTO_MAGIC_SIZE + PROTO_HEADER_SIZE, nread); data_acc.reserve(len); while(nread < len){ @@ -482,7 +483,7 @@ namespace trezor{ uint16_t msg_tag; uint32_t msg_len; deserialize_message_header(bin_data->data(), msg_tag, msg_len); - if (bin_data->size() != msg_len + 6){ + if (bin_data->size() != msg_len + PROTO_HEADER_SIZE){ throw exc::CommunicationException("Response is not well hexcoded"); } @@ -491,7 +492,7 @@ namespace trezor{ } std::shared_ptr<google::protobuf::Message> msg_wrap(MessageMapper::get_message(msg_tag)); - if (!msg_wrap->ParseFromArray(bin_data->data() + 6, msg_len)){ + if (!msg_wrap->ParseFromArray(bin_data->data() + PROTO_HEADER_SIZE, msg_len)){ throw exc::EncodingException("Response is not well hexcoded"); } msg = msg_wrap; diff --git a/src/net/tor_address.cpp b/src/net/tor_address.cpp index 53b73a839..ad8b399c8 100644 --- a/src/net/tor_address.cpp +++ b/src/net/tor_address.cpp @@ -48,7 +48,6 @@ namespace net constexpr const char tld[] = u8".onion"; constexpr const char unknown_host[] = "<unknown tor host>"; - constexpr const unsigned v2_length = 16; constexpr const unsigned v3_length = 56; constexpr const char base32_alphabet[] = @@ -62,7 +61,7 @@ namespace net host.remove_suffix(sizeof(tld) - 1); //! \TODO v3 has checksum, base32 decoding is required to verify it - if (host.size() != v2_length && host.size() != v3_length) + if (host.size() != v3_length) return {net::error::invalid_tor_address}; if (host.find_first_not_of(base32_alphabet) != boost::string_ref::npos) return {net::error::invalid_tor_address}; @@ -118,7 +117,6 @@ namespace net if (!port.empty() && !epee::string_tools::get_xtype_from_string(porti, std::string{port})) return {net::error::invalid_port}; - static_assert(v2_length <= v3_length, "bad internal host size"); static_assert(v3_length + sizeof(tld) == sizeof(tor_address::host_), "bad internal host size"); return tor_address{host, porti}; } @@ -180,7 +178,6 @@ namespace net bool tor_address::is_same_host(const tor_address& rhs) const noexcept { - //! \TODO v2 and v3 should be comparable - requires base32 return std::strcmp(host_str(), rhs.host_str()) == 0; } diff --git a/src/net/tor_address.h b/src/net/tor_address.h index 3dd320b5d..d04bf5145 100644 --- a/src/net/tor_address.h +++ b/src/net/tor_address.h @@ -71,7 +71,7 @@ namespace net static tor_address unknown() noexcept { return tor_address{}; } /*! - Parse `address` in onion v2 or v3 format with (i.e. x.onion:80) + Parse `address` in onion v3 format with (i.e. x.onion:80) with `default_port` being used iff port is not specified in `address`. */ diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 97fd5fe13..a6162c3f9 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2216,7 +2216,8 @@ namespace cryptonote // Fixing of high orphan issue for most pools // Thanks Boolberry! block b; - if(!parse_and_validate_block_from_blob(blockblob, b)) + crypto::hash blk_id; + if(!parse_and_validate_block_from_blob(blockblob, b, blk_id)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_BLOCKBLOB; error_resp.message = "Wrong block blob"; @@ -2239,6 +2240,7 @@ namespace cryptonote error_resp.message = "Block not accepted"; return false; } + res.block_id = epee::string_tools::pod_to_hex(blk_id); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index c7a669a9f..37f9b8f2f 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 12 +#define CORE_RPC_VERSION_MINOR 13 #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) @@ -1115,8 +1115,11 @@ namespace cryptonote struct response_t: public rpc_response_base { + std::string block_id; + BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_response_base) + KV_SERIALIZE(block_id) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index e41a66d24..6e5d0b1ec 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3186,6 +3186,8 @@ simple_wallet::simple_wallet() " decrypt: same as action, but keeps the spend key encrypted in memory when not needed\n " "unit <monero|millinero|micronero|nanonero|piconero>\n " " Set the default monero (sub-)unit.\n " + "max-reorg-depth <unsigned int>\n " + " Set the maximum amount of blocks to accept in a reorg.\n " "min-outputs-count [n]\n " " Try to keep at least that many outputs of value at least min-outputs-value.\n " "min-outputs-value [n]\n " @@ -3224,6 +3226,8 @@ simple_wallet::simple_wallet() " Device name for hardware wallet.\n " "export-format <\"binary\"|\"ascii\">\n " " Save all exported files as binary (cannot be copied and pasted) or ascii (can be).\n " + "load-deprecated-formats <1|0>\n " + " Whether to enable importing data in deprecated formats.\n " "show-wallet-name-when-locked <1|0>\n " " Set this if you would like to display the wallet name when locked.\n " "enable-multisig-experimental <1|0>\n " diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 78c0f6328..2c85e2b9c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -986,6 +986,39 @@ bool get_pruned_tx(const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry &entry, return false; } +// Given M (threshold) and N (total), calculate the number of private multisig keys each +// signer should have. This value is equal to (N - 1) choose (N - M) +// Prereq: M >= 1 && N >= M && N <= 16 +uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) +{ + THROW_WALLET_EXCEPTION_IF(threshold < 1 || total < threshold || threshold > 16, + tools::error::wallet_internal_error, "Invalid arguments to num_priv_multisig_keys_post_setup"); + + uint64_t n_multisig_keys = 1; + for (uint64_t i = 2; i <= total - 1; ++i) n_multisig_keys *= i; // multiply by (N - 1)! + for (uint64_t i = 2; i <= total - threshold; ++i) n_multisig_keys /= i; // divide by (N - M)! + for (uint64_t i = 2; i <= threshold - 1; ++i) n_multisig_keys /= i; // divide by ((N - 1) - (N - M))! + return n_multisig_keys; +} + +/** + * @brief Derives the chacha key to encrypt wallet cache files given the chacha key to encrypt the wallet keys files + * + * @param keys_data_key the chacha key that encrypts wallet keys files + * @return crypto::chacha_key the chacha key that encrypts the wallet cache files + */ +crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) +{ + static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key cache_key; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; + memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); + cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); + + return cache_key; +} //----------------------------------------------------------------- } //namespace @@ -1394,7 +1427,7 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase, bool raw) const +bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const { bool ready; uint32_t threshold, total; @@ -1408,15 +1441,14 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl std::cout << "This multisig wallet is not yet finalized" << std::endl; return false; } - if (!raw && seed_language.empty()) - { - std::cout << "seed_language not set" << std::endl; - return false; - } + + const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(threshold, total); crypto::secret_key skey; crypto::public_key pkey; const account_keys &keys = get_account().get_keys(); + THROW_WALLET_EXCEPTION_IF(num_expected_ms_keys != keys.m_multisig_keys.size(), + error::wallet_internal_error, "Unexpected number of private multisig keys") epee::wipeable_string data; data.append((const char*)&threshold, sizeof(uint32_t)); data.append((const char*)&total, sizeof(uint32_t)); @@ -1441,18 +1473,7 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl data = encrypt(data, key, true); } - if (raw) - { - seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()}); - } - else - { - if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language)) - { - std::cout << "Failed to encode seed"; - return false; - } - } + seed = epee::to_hex::wipeable_string({(const unsigned char*)data.data(), data.size()}); return true; } @@ -1715,21 +1736,26 @@ void wallet2::sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_ else // l.tx_entry.block_height == r.tx_entry.block_height { // coinbase tx is the first tx in a block + if (cryptonote::is_coinbase(r.tx)) + return false; if (cryptonote::is_coinbase(l.tx)) return true; - if (cryptonote::is_coinbase(r.tx)) + + // in case std::sort is comparing elem to itself + if (l.tx_hash == r.tx_hash) return false; // see which tx hash comes first in the block THROW_WALLET_EXCEPTION_IF(parsed_blocks.find(l.tx_entry.block_height) == parsed_blocks.end(), - error::wallet_internal_error, "Expected block not returned by daemon"); + error::wallet_internal_error, std::string("Expected block not returned by daemon, ") + + "left tx: " + string_tools::pod_to_hex(l.tx_hash) + ", right tx: " + string_tools::pod_to_hex(r.tx_hash)); const auto &blk = parsed_blocks[l.tx_entry.block_height]; for (const auto &tx_hash : blk.tx_hashes) { - if (tx_hash == l.tx_hash) - return true; if (tx_hash == r.tx_hash) return false; + if (tx_hash == l.tx_hash) + return true; } THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Tx hashes not found in block"); return false; @@ -1983,14 +2009,14 @@ bool wallet2::frozen(const multisig_tx_set& txs) const CHECK_AND_ASSERT_THROW_MES(cd.sources.size() == ptx.tx.vin.size(), "mismatched multisg tx set source sizes"); for (size_t src_idx = 0; src_idx < cd.sources.size(); ++src_idx) { - // Check that the key images are consistent between tx vin and construction data + // Extract keys images from tx vin and construction data const crypto::key_image multisig_ki = rct::rct2ki(cd.sources[src_idx].multisig_kLRki.ki); CHECK_AND_ASSERT_THROW_MES(ptx.tx.vin[src_idx].type() == typeid(cryptonote::txin_to_key), "multisig tx cannot be miner"); - const crypto::key_image vin_ki = boost::get<cryptonote::txin_to_key>(ptx.tx.vin[src_idx]).k_image; - CHECK_AND_ASSERT_THROW_MES(multisig_ki == vin_ki, "Mismatched key image b/t vin and construction data"); + const crypto::key_image& vin_ki = boost::get<cryptonote::txin_to_key>(ptx.tx.vin[src_idx]).k_image; - // Add key image to set + // Add key images to set (there will be some overlap) kis_to_sign.insert(multisig_ki); + kis_to_sign.insert(vin_ki); } } // Step 2. Scan all transfers for frozen key images @@ -4340,6 +4366,10 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: crypto::chacha_key key; crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { account.encrypt_viewkey(key); @@ -4372,7 +4402,7 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_key_device_type); json.AddMember("key_on_device", value2, json.GetAllocator()); - value2.SetInt(watch_only ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? + value2.SetInt((watch_only || m_watch_only) ? 1 :0); // WTF ? JSON has different true and false types, and not boolean ?? json.AddMember("watch_only", value2, json.GetAllocator()); value2.SetInt(m_multisig ? 1 :0); @@ -4567,11 +4597,8 @@ void wallet2::setup_keys(const epee::wipeable_string &password) m_account.decrypt_viewkey(key); } - static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); - epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; - memcpy(cache_key_data.data(), &key, HASH_SIZE); - cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; - cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&)m_cache_key); + m_cache_key = derive_cache_key(key); + get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- @@ -4580,9 +4607,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); - rewrite(filename, new_password); if (!filename.empty()) - store(); + store_to(filename, new_password, true); // force rewrite keys file to possible new location } //---------------------------------------------------------------------------------------------------- /*! @@ -5075,6 +5101,10 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) void wallet2::decrypt_keys(const crypto::chacha_key &key) { + // We use m_cache_key as a deterministic test to see if given key corresponds to original password + const crypto::chacha_key cache_key = derive_cache_key(key); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); + m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); } @@ -5212,9 +5242,11 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& offset += sizeof(uint32_t); uint32_t total = *(uint32_t*)(multisig_data.data() + offset); offset += sizeof(uint32_t); - THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed); - THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed); - const size_t n_multisig_keys = total == threshold ? 1 : threshold; + + THROW_WALLET_EXCEPTION_IF(threshold < 1, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(total < threshold, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(threshold > 16, error::invalid_multisig_seed); // doing N choose (N - M + 1) might overflow + const uint64_t n_multisig_keys = num_priv_multisig_keys_post_setup(threshold, total); THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed); std::vector<crypto::secret_key> multisig_keys; @@ -6220,22 +6252,32 @@ void wallet2::store() store_to("", epee::wipeable_string()); } //---------------------------------------------------------------------------------------------------- -void wallet2::store_to(const std::string &path, const epee::wipeable_string &password) +void wallet2::store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys) { trim_hashchain(); + const bool had_old_wallet_files = !m_wallet_file.empty(); + THROW_WALLET_EXCEPTION_IF(!had_old_wallet_files && path.empty(), error::wallet_internal_error, + "Cannot resave wallet to current file since wallet was not loaded from file to begin with"); + // if file is the same, we do: - // 1. save wallet to the *.new file - // 2. remove old wallet file - // 3. rename *.new to wallet_name + // 1. overwrite the keys file iff force_rewrite_keys is specified + // 2. save cache to the *.new file + // 3. rename *.new to wallet_name, replacing old cache file + // else we do: + // 1. prepare new file names with "path" variable + // 2. store new keys files + // 3. remove old keys file + // 4. store new cache file + // 5. remove old cache file // handle if we want just store wallet state to current files (ex store() replacement); - bool same_file = true; - if (!path.empty()) + bool same_file = had_old_wallet_files && path.empty(); + if (had_old_wallet_files && !path.empty()) { - std::string canonical_path = boost::filesystem::canonical(m_wallet_file).string(); - size_t pos = canonical_path.find(path); - same_file = pos != std::string::npos; + const std::string canonical_old_path = boost::filesystem::canonical(m_wallet_file).string(); + const std::string canonical_new_path = boost::filesystem::weakly_canonical(path).string(); + same_file = canonical_old_path == canonical_new_path; } @@ -6256,7 +6298,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } // get wallet cache data - boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(password); + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); THROW_WALLET_EXCEPTION_IF(cache_file_data == boost::none, error::wallet_internal_error, "failed to generate wallet cache data"); const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -6265,12 +6307,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_address_file = m_wallet_file + ".address.txt"; const std::string old_mms_file = m_mms_file; - // save keys to the new file - // if we here, main wallet file is saved and we only need to save keys and address files - if (!same_file) { + if (!same_file) + { prepare_file_names(path); - bool r = store_keys(m_keys_file, password, false); + } + + if (!same_file || force_rewrite_keys) + { + bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + } + + if (!same_file && had_old_wallet_files) + { + bool r = false; if (boost::filesystem::exists(old_address_file)) { // save address to the new file @@ -6283,11 +6333,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } - // remove old wallet file - r = boost::filesystem::remove(old_file); - if (!r) { - LOG_ERROR("error removing file: " << old_file); - } // remove old keys file r = boost::filesystem::remove(old_keys_file); if (!r) { @@ -6301,8 +6346,9 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_mms_file); } } - } else { - // save to new file + } + + // Save cache to new file. If storing to the same file, the temp path has the ".new" extension #ifdef WIN32 // On Windows avoid using std::ofstream which does not work with UTF-8 filenames // The price to pay is temporary higher memory consumption for string stream + binary archive @@ -6322,10 +6368,20 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); #endif + if (same_file) + { // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); } + else if (!same_file && had_old_wallet_files) + { + // remove old wallet file + bool r = boost::filesystem::remove(old_file); + if (!r) { + LOG_ERROR("error removing file: " << old_file); + } + } if (m_message_store.get_active()) { @@ -6335,7 +6391,7 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } //---------------------------------------------------------------------------------------------------- -boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data(const epee::wipeable_string &passwords) +boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() { trim_hashchain(); try @@ -10217,7 +10273,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); - while (needed_fee > test_ptx.fee) { + do { if (use_rct) transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); @@ -10228,7 +10284,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); LOG_PRINT_L2("Made an attempt at a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); - } + } while (needed_fee > test_ptx.fee); LOG_PRINT_L2("Made a final " << get_weight_string(test_ptx.tx, txBlob.size()) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); @@ -10624,7 +10680,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); do { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); + LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee)); // distribute total transferred amount between outputs uint64_t amount_transferred = available_for_fee - needed_fee; uint64_t dt_amount = amount_transferred / outputs; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 9c310f692..d93b9b9fb 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -933,22 +933,32 @@ private: /*! * \brief store_to Stores wallet to another file(s), deleting old ones * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet + * \param force_rewrite_keys if true, always rewrite keys file + * + * Leave both "path" and "password" blank to restore the cache file to the current position in the disk + * (which is the same as calling `store()`). If you want to store the wallet with a new password, + * use the method `change_password()`. + * + * Normally the keys file is not overwritten when storing, except when force_rewrite_keys is true + * or when `path` is a new wallet file. + * + * \throw error::invalid_password If storing keys file and old password is incorrect */ - void store_to(const std::string &path, const epee::wipeable_string &password); + void store_to(const std::string &path, const epee::wipeable_string &password, bool force_rewrite_keys = false); /*! * \brief get_keys_file_data Get wallet keys data which can be stored to a wallet file. - * \param password Password of the encrypted wallet buffer (TODO: probably better save the password in the wallet object?) + * \param password Password that currently locks the wallet * \param watch_only true to include only view key, false to include both spend and view keys * \return Encrypted wallet keys data which can be stored to a wallet file + * \throw error::invalid_password if password does not match current wallet */ boost::optional<wallet2::keys_file_data> get_keys_file_data(const epee::wipeable_string& password, bool watch_only); /*! * \brief get_cache_file_data Get wallet cache data which can be stored to a wallet file. - * \param password Password to protect the wallet cache data (TODO: probably better save the password in the wallet object?) - * \return Encrypted wallet cache data which can be stored to a wallet file + * \return Encrypted wallet cache data which can be stored to a wallet file (using current password) */ - boost::optional<wallet2::cache_file_data> get_cache_file_data(const epee::wipeable_string& password); + boost::optional<wallet2::cache_file_data> get_cache_file_data(); std::string path() const; @@ -1044,7 +1054,7 @@ private: bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; bool has_unknown_key_images() const; - bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; + bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string()) const; bool key_on_device() const { return get_device_type() != hw::device::device_type::SOFTWARE; } hw::device::device_type get_device_type() const { return m_key_device_type; } bool reconnect_device(); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7c46d9887..0cc42ae3f 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -3811,7 +3811,7 @@ namespace tools std::string old_language; // check the given seed - { + if (!req.enable_multisig_experimental) { if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, old_language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -3834,6 +3834,13 @@ namespace tools // process seed_offset if given { + if (req.enable_multisig_experimental && !req.seed_offset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multisig seeds are not compatible with seed offsets"; + return false; + } + if (!req.seed_offset.empty()) { recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset); @@ -3897,7 +3904,27 @@ namespace tools crypto::secret_key recovery_val; try { - recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false); + if (req.enable_multisig_experimental) + { + // Parse multisig seed into raw multisig data + epee::wipeable_string multisig_data; + multisig_data.resize(req.seed.size() / 2); + if (!epee::from_hex::to_buffer(epee::to_mut_byte_span(multisig_data), req.seed)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Multisig seed not represented as hexadecimal string"; + return false; + } + + // Generate multisig wallet + wal->generate(wallet_file, std::move(rc.second).password(), multisig_data, false); + wal->enable_multisig(true); + } + else + { + // Generate normal wallet + recovery_val = wal->generate(wallet_file, std::move(rc.second).password(), recovery_key, true, false, false); + } MINFO("Wallet has been restored.\n"); } catch (const std::exception &e) @@ -3908,7 +3935,7 @@ namespace tools // // Convert the secret key back to seed epee::wipeable_string electrum_words; - if (!crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language)) + if (!req.enable_multisig_experimental && !crypto::ElectrumWords::bytes_to_words(recovery_val, electrum_words, mnemonic_language)) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; er.message = "Failed to encode seed"; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 002db4289..c00271030 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2262,6 +2262,7 @@ namespace wallet_rpc std::string password; std::string language; bool autosave_current; + bool enable_multisig_experimental; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_OPT(restore_height, (uint64_t)0) @@ -2271,6 +2272,7 @@ namespace wallet_rpc KV_SERIALIZE(password) KV_SERIALIZE(language) KV_SERIALIZE_OPT(autosave_current, true) + KV_SERIALIZE_OPT(enable_multisig_experimental, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 39e7ed8a9..00896818c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -72,14 +72,8 @@ else () include_directories(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/gtest/include") endif (GTest_FOUND) -file(COPY - data/wallet_9svHk1.keys - data/wallet_9svHk1 - data/outputs - data/unsigned_monero_tx - data/signed_monero_tx - data/sha256sum - DESTINATION data) +message(STATUS "Copying test data directory...") +file(COPY data DESTINATION .) # Copy data directory from source root to build root if (CMAKE_BUILD_TYPE STREQUAL "fuzz" OR OSSFUZZ) add_subdirectory(fuzz) diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 7cd0d572b..4b00fc63f 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -30,8 +30,6 @@ #include <stdio.h> #include <math.h> -#include "cryptonote_core/blockchain.h" -#include "cryptonote_core/tx_pool.h" #include "cryptonote_core/cryptonote_core.h" #include "blockchain_db/testdb.h" @@ -110,9 +108,6 @@ private: } #define PREFIX_WINDOW(hf_version,window) \ - std::unique_ptr<cryptonote::Blockchain> bc; \ - cryptonote::tx_memory_pool txpool(*bc); \ - bc.reset(new cryptonote::Blockchain(txpool)); \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -121,7 +116,9 @@ private: }; \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)LONG_TERM_BLOCK_WEIGHT_WINDOW), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ } opts; \ - cryptonote::Blockchain *blockchain = bc.get(); \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *blockchain = &bap.blockchain; \ + cryptonote::Blockchain *bc = blockchain; \ bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ if (!r) \ { \ diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp index bd2fc2a9b..7f75aea5b 100644 --- a/tests/core_tests/block_reward.cpp +++ b/tests/core_tests/block_reward.cpp @@ -172,21 +172,21 @@ bool gen_block_reward::generate(std::vector<test_event_entry>& events) const return false; // Test: fee increases block reward - transaction tx_0(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 3 * TESTS_DEFAULT_FEE)); + transaction tx_0(construct_tx_with_fee(events, blk_5r, miner_account, bob_account, MK_COINS(1), 3 * TESTS_DEFAULT_FEE)); MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner_account, tx_0); DO_CALLBACK(events, "mark_checked_block"); // Test: fee from all block transactions increase block reward std::list<transaction> txs_0; - txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 5 * TESTS_DEFAULT_FEE)); - txs_0.push_back(construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 7 * TESTS_DEFAULT_FEE)); + txs_0.push_back(construct_tx_with_fee(events, blk_5r, miner_account, bob_account, MK_COINS(1), 5 * TESTS_DEFAULT_FEE)); + txs_0.push_back(construct_tx_with_fee(events, blk_5r, miner_account, bob_account, MK_COINS(1), 7 * TESTS_DEFAULT_FEE)); MAKE_NEXT_BLOCK_TX_LIST(events, blk_7, blk_6, miner_account, txs_0); DO_CALLBACK(events, "mark_checked_block"); // Test: block reward == transactions fee { - transaction tx_1 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 11 * TESTS_DEFAULT_FEE); - transaction tx_2 = construct_tx_with_fee(events, blk_5, miner_account, bob_account, MK_COINS(1), 13 * TESTS_DEFAULT_FEE); + transaction tx_1 = construct_tx_with_fee(events, blk_5r, miner_account, bob_account, MK_COINS(1), 11 * TESTS_DEFAULT_FEE); + transaction tx_2 = construct_tx_with_fee(events, blk_5r, miner_account, bob_account, MK_COINS(1), 13 * TESTS_DEFAULT_FEE); size_t txs_1_weight = get_transaction_weight(tx_1) + get_transaction_weight(tx_2); uint64_t txs_fee = get_tx_fee(tx_1) + get_tx_fee(tx_2); diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index 37f19d94b..071d2198e 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -580,7 +580,7 @@ bool gen_block_invalid_binary_format::generate(std::vector<test_event_entry>& ev while (diffic < 1500); blk_last = boost::get<block>(events.back()); - MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(30), boost::get<block>(events[1])); + MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(30), blk_last); DO_CALLBACK(events, "corrupt_blocks_boundary"); block blk_test; diff --git a/tests/core_tests/chain_split_1.cpp b/tests/core_tests/chain_split_1.cpp index 8f49b1af8..cffabb9c1 100644 --- a/tests/core_tests/chain_split_1.cpp +++ b/tests/core_tests/chain_split_1.cpp @@ -114,9 +114,9 @@ bool gen_simple_chain_split_1::generate(std::vector<test_event_entry> &events) c REWIND_BLOCKS(events, blk_23r, blk_23, first_miner_account); // 30...N1 GENERATE_ACCOUNT(alice); - MAKE_TX(events, tx_0, first_miner_account, alice, MK_COINS(10), blk_23); // N1+1 - MAKE_TX(events, tx_1, first_miner_account, alice, MK_COINS(20), blk_23); // N1+2 - MAKE_TX(events, tx_2, first_miner_account, alice, MK_COINS(30), blk_23); // N1+3 + MAKE_TX(events, tx_0, first_miner_account, alice, MK_COINS(10), blk_23r); // N1+1 + MAKE_TX(events, tx_1, first_miner_account, alice, MK_COINS(20), blk_23r); // N1+2 + MAKE_TX(events, tx_2, first_miner_account, alice, MK_COINS(30), blk_23r); // N1+3 DO_CALLBACK(events, "check_mempool_1"); // N1+4 MAKE_NEXT_BLOCK_TX1(events, blk_24, blk_23r, first_miner_account, tx_0); // N1+5 DO_CALLBACK(events, "check_mempool_2"); // N1+6 diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 8271ab783..2d9cdcbd8 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -72,37 +72,37 @@ bool gen_chain_switch_1::generate(std::vector<test_event_entry>& events) const MAKE_ACCOUNT(events, recipient_account_3); // 3 MAKE_ACCOUNT(events, recipient_account_4); // 4 REWIND_BLOCKS(events, blk_0r, blk_0, miner_account) // <N blocks> - MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_COINS(5), blk_0); // 5 + N + MAKE_TX(events, tx_00, miner_account, recipient_account_1, MK_COINS(5), blk_0r); // 5 + N MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_00); // 6 + N MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 7 + N REWIND_BLOCKS(events, blk_2r, blk_2, miner_account) // <N blocks> // Transactions to test account balances after switch - MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(7), blk_2); // 8 + 2N - MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2); // 9 + 2N - MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2); // 10 + 2N + MAKE_TX_LIST_START(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(7), blk_2r); // 8 + 2N + MAKE_TX_LIST_START(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(11), blk_2r); // 9 + 2N + MAKE_TX_LIST_START(events, txs_blk_5, miner_account, recipient_account_4, MK_COINS(13), blk_2r); // 10 + 2N std::list<transaction> txs_blk_6; txs_blk_6.push_back(txs_blk_4.front()); // Transactions, that has different order in alt block chains - MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(1), blk_2); // 11 + 2N + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(1), blk_2r); // 11 + 2N txs_blk_5.push_back(txs_blk_3.back()); - MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(2), blk_2); // 12 + 2N + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_1, MK_COINS(2), blk_2r); // 12 + 2N txs_blk_6.push_back(txs_blk_3.back()); - MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(1), blk_2); // 13 + 2N + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_2, MK_COINS(1), blk_2r); // 13 + 2N txs_blk_5.push_back(txs_blk_3.back()); - MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_COINS(2), blk_2); // 14 + 2N + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_2, MK_COINS(2), blk_2r); // 14 + 2N txs_blk_5.push_back(txs_blk_4.back()); - MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_COINS(1), blk_2); // 15 + 2N + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_3, MK_COINS(1), blk_2r); // 15 + 2N txs_blk_6.push_back(txs_blk_3.back()); - MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(2), blk_2); // 16 + 2N + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_3, MK_COINS(2), blk_2r); // 16 + 2N txs_blk_5.push_back(txs_blk_4.back()); - MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_COINS(1), blk_2); // 17 + 2N + MAKE_TX_LIST(events, txs_blk_4, miner_account, recipient_account_4, MK_COINS(1), blk_2r); // 17 + 2N txs_blk_5.push_back(txs_blk_4.back()); - MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_COINS(2), blk_2); // 18 + 2N + MAKE_TX_LIST(events, txs_blk_3, miner_account, recipient_account_4, MK_COINS(2), blk_2r); // 18 + 2N txs_blk_6.push_back(txs_blk_3.back()); MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_blk_3); // 19 + 2N diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 9f6fe5387..9aa0de8e5 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -143,9 +143,8 @@ namespace } -static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) +static std::unique_ptr<cryptonote::BlockchainAndPool> init_blockchain(const std::vector<test_event_entry> & events, cryptonote::network_type nettype) { - std::unique_ptr<cryptonote::Blockchain> bc; v_hardforks_t hardforks; cryptonote::test_options test_options_tmp{nullptr, 0}; const cryptonote::test_options * test_options = &test_options_tmp; @@ -159,10 +158,8 @@ static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector test_options_tmp.hard_forks = hardforks.data(); test_options = &test_options_tmp; - cryptonote::tx_memory_pool txpool(*bc); - bc.reset(new cryptonote::Blockchain(txpool)); + std::unique_ptr<cryptonote::BlockchainAndPool> bap(new BlockchainAndPool()); - cryptonote::Blockchain *blockchain = bc.get(); auto bdb = new TestDB(); BOOST_FOREACH(const test_event_entry &ev, events) @@ -177,9 +174,9 @@ static std::unique_ptr<cryptonote::Blockchain> init_blockchain(const std::vector bdb->add_block(*blk, 1, 1, 1, 0, 0, blk_hash); } - bool r = blockchain->init(bdb, nettype, true, test_options, 2, nullptr); + bool r = bap->blockchain.init(bdb, nettype, true, test_options, 2, nullptr); CHECK_AND_ASSERT_THROW_MES(r, "could not init blockchain from events"); - return bc; + return bap; } void test_generator::get_block_chain(std::vector<block_info>& blockchain, const crypto::hash& head, size_t n) const @@ -393,7 +390,7 @@ bool test_generator::construct_block_manually_tx(cryptonote::block& blk, const c void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& diffic, uint64_t height) { const cryptonote::Blockchain *blockchain = nullptr; - std::unique_ptr<cryptonote::Blockchain> bc; + std::unique_ptr<cryptonote::BlockchainAndPool> bap; if (blk.major_version >= RX_BLOCK_VERSION && diffic > 1) { @@ -403,8 +400,8 @@ void test_generator::fill_nonce(cryptonote::block& blk, const difficulty_type& d } else { - bc = init_blockchain(*m_events, m_nettype); - blockchain = bc.get(); + bap = init_blockchain(*m_events, m_nettype); + blockchain = &bap->blockchain; } } @@ -507,7 +504,7 @@ bool init_spent_output_indices(map_output_idx_t& outs, map_output_t& outs_mine, return true; } -bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector<tx_source_entry::output_entry>& output_entries) +bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_out, size_t nmix, size_t& real_entry_idx, std::vector<tx_source_entry::output_entry>& output_entries, uint64_t cur_height, const boost::optional<fnc_accept_output_t>& fnc_accept = boost::none) { if (out_indices.size() <= nmix) return false; @@ -519,6 +516,10 @@ bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_o const output_index& oi = out_indices[i]; if (oi.spent) continue; + if (oi.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && oi.unlock_time > cur_height + 1) + continue; + if (fnc_accept && !(fnc_accept.get())({.oi=oi, .cur_height=cur_height})) + continue; bool append = false; if (i == sender_out) @@ -545,7 +546,8 @@ bool fill_output_entries(std::vector<output_index>& out_indices, size_t sender_o } bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<test_event_entry>& events, - const block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix) + const block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix, + bool check_unlock_time, const boost::optional<fnc_accept_output_t>& fnc_accept) { map_output_idx_t outs; map_output_t outs_mine; @@ -562,6 +564,7 @@ bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<te return false; // Iterate in reverse is more efficiency + uint64_t head_height = check_unlock_time ? get_block_height(blk_head) : std::numeric_limits<uint64_t>::max() - 1; uint64_t sources_amount = 0; bool sources_found = false; BOOST_REVERSE_FOREACH(const map_output_t::value_type o, outs_mine) @@ -574,13 +577,17 @@ bool fill_tx_sources(std::vector<tx_source_entry>& sources, const std::vector<te continue; if (oi.rct) continue; + if (oi.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && oi.unlock_time > head_height + 1) + continue; + if (fnc_accept && !(fnc_accept.get())({.oi=oi, .cur_height=head_height})) + continue; cryptonote::tx_source_entry ts; ts.amount = oi.amount; ts.real_output_in_tx_index = oi.out_no; ts.real_out_tx_key = get_tx_pub_key_from_extra(*oi.p_tx); // incoming tx public key size_t realOutput; - if (!fill_output_entries(outs[o.first], sender_out, nmix, realOutput, ts.outputs)) + if (!fill_output_entries(outs[o.first], sender_out, nmix, realOutput, ts.outputs, head_height, fnc_accept)) continue; ts.real_output = realOutput; @@ -695,7 +702,7 @@ void block_tracker::global_indices(const cryptonote::transaction *tx, std::vecto } } -void block_tracker::get_fake_outs(size_t num_outs, uint64_t amount, uint64_t global_index, uint64_t cur_height, std::vector<get_outs_entry> &outs){ +void block_tracker::get_fake_outs(size_t num_outs, uint64_t amount, uint64_t global_index, uint64_t cur_height, std::vector<get_outs_entry> &outs, const boost::optional<fnc_accept_output_t>& fnc_accept){ auto & vct = m_outs[amount]; const size_t n_outs = vct.size(); CHECK_AND_ASSERT_THROW_MES(n_outs > 0, "n_outs is 0"); @@ -720,15 +727,16 @@ void block_tracker::get_fake_outs(size_t num_outs, uint64_t amount, uint64_t glo continue; if (oi.out.type() != typeid(cryptonote::txout_to_key)) continue; - if (oi.unlock_time > cur_height) + if (oi.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && oi.unlock_time > cur_height + 1) continue; if (used.find(oi_idx) != used.end()) continue; + if (fnc_accept && !(fnc_accept.get())({.oi=oi, .cur_height=cur_height})) + continue; rct::key comm = oi.commitment(); auto out = boost::get<txout_to_key>(oi.out); - auto item = std::make_tuple(oi.idx, out.key, comm); - outs.push_back(item); + outs.emplace_back(oi.idx, out.key, comm); used.insert(oi_idx); } } @@ -927,13 +935,14 @@ void fill_tx_destinations(const var_addr_t& from, const cryptonote::account_publ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head, const cryptonote::account_base& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources, - std::vector<tx_destination_entry>& destinations) + std::vector<tx_destination_entry>& destinations, bool check_unlock_time, + const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept) { sources.clear(); destinations.clear(); - if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) - throw std::runtime_error("couldn't fill transaction sources"); + if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix, check_unlock_time, fnc_tx_in_accept)) + throw tx_construct_tx_fill_error(); fill_tx_destinations(from, to, amount, fee, sources, destinations, false); } @@ -941,9 +950,10 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector<tx_source_entry>& sources, - std::vector<tx_destination_entry>& destinations) + std::vector<tx_destination_entry>& destinations, bool check_unlock_time, + const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept) { - fill_tx_sources_and_destinations(events, blk_head, from, to.get_keys().m_account_address, amount, fee, nmix, sources, destinations); + fill_tx_sources_and_destinations(events, blk_head, from, to.get_keys().m_account_address, amount, fee, nmix, sources, destinations, check_unlock_time, fnc_tx_in_accept); } cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr, uint64_t amount) @@ -955,10 +965,14 @@ cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr return de; } -std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1, uint64_t am1) +std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1, uint64_t am1, size_t repeat) { std::vector<cryptonote::tx_destination_entry> res; - res.push_back(build_dst(to1, sub1, am1)); + res.reserve(repeat); + for(size_t i = 0; i < repeat; ++i) + { + res.emplace_back(build_dst(to1, sub1, am1)); + } return res; } @@ -1022,25 +1036,27 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, - uint64_t fee, size_t nmix, bool rct, rct::RangeProofType range_proof_type, int bp_version) + uint64_t fee, size_t nmix, bool rct, rct::RangeProofType range_proof_type, int bp_version, + bool check_unlock_time, const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept) { vector<tx_source_entry> sources; vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_head, from, get_address(to), amount, fee, nmix, sources, destinations); + fill_tx_sources_and_destinations(events, blk_head, from, get_address(to), amount, fee, nmix, sources, destinations, check_unlock_time, fnc_tx_in_accept); return construct_tx_rct(from.get_keys(), sources, destinations, from.get_keys().m_account_address, std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); } bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, - const cryptonote::account_base& from, std::vector<cryptonote::tx_destination_entry> destinations, - uint64_t fee, size_t nmix, bool rct, rct::RangeProofType range_proof_type, int bp_version) + const cryptonote::account_base& from, const std::vector<cryptonote::tx_destination_entry>& destinations, + uint64_t fee, size_t nmix, bool rct, rct::RangeProofType range_proof_type, int bp_version, + bool check_unlock_time, const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept) { vector<tx_source_entry> sources; vector<tx_destination_entry> destinations_all; uint64_t amount = sum_amount(destinations); - if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix)) - throw std::runtime_error("couldn't fill transaction sources"); + if (!fill_tx_sources(sources, events, blk_head, from, amount + fee, nmix, check_unlock_time, fnc_tx_in_accept)) + throw tx_construct_tx_fill_error(); fill_tx_destinations(from, destinations, fee, sources, destinations_all, false); diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 31580502d..41f7275d3 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -372,6 +372,13 @@ typedef struct { uint64_t amount; } dest_wrapper_t; +typedef struct { + const output_index &oi; + uint64_t cur_height; +} fnc_accept_output_crate_t; + +typedef std::function<bool(const fnc_accept_output_crate_t &info)> fnc_accept_output_t; + // Daemon functionality class block_tracker { @@ -388,7 +395,7 @@ public: void process(const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t& mtx); void process(const cryptonote::block* blk, const cryptonote::transaction * tx, size_t i); void global_indices(const cryptonote::transaction *tx, std::vector<uint64_t> &indices); - void get_fake_outs(size_t num_outs, uint64_t amount, uint64_t global_index, uint64_t cur_height, std::vector<get_outs_entry> &outs); + void get_fake_outs(size_t num_outs, uint64_t amount, uint64_t global_index, uint64_t cur_height, std::vector<get_outs_entry> &outs, const boost::optional<fnc_accept_output_t>& fnc_accept = boost::none); std::string dump_data(); void dump_data(const std::string & fname); @@ -405,6 +412,19 @@ private: } }; +class tx_construct_error : public std::runtime_error +{ +public: + tx_construct_error(const char *s) : runtime_error(s) { } +}; + +class tx_construct_tx_fill_error : public tx_construct_error +{ +public: + tx_construct_tx_fill_error() : tx_construct_error("Couldn't fill transaction sources") { } + tx_construct_tx_fill_error(const char *s) : tx_construct_error(s) { } +}; + std::string dump_data(const cryptonote::transaction &tx); cryptonote::account_public_address get_address(const var_addr_t& inp); cryptonote::account_public_address get_address(const cryptonote::account_public_address& inp); @@ -416,7 +436,7 @@ inline cryptonote::difficulty_type get_test_difficulty(const boost::optional<uin inline uint64_t current_difficulty_window(const boost::optional<uint8_t>& hf_ver=boost::none){ return !hf_ver || hf_ver.get() <= 1 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2; } cryptonote::tx_destination_entry build_dst(const var_addr_t& to, bool is_subaddr=false, uint64_t amount=0); -std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1=false, uint64_t am1=0); +std::vector<cryptonote::tx_destination_entry> build_dsts(const var_addr_t& to1, bool sub1=false, uint64_t am1=0, size_t repeat=1); std::vector<cryptonote::tx_destination_entry> build_dsts(std::initializer_list<dest_wrapper_t> inps); uint64_t sum_amount(const std::vector<cryptonote::tx_destination_entry>& destinations); uint64_t sum_amount(const std::vector<cryptonote::tx_source_entry>& sources); @@ -428,11 +448,13 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, - uint64_t fee, size_t nmix, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0); + uint64_t fee, size_t nmix, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0, + bool check_unlock_time = true, const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept = boost::none); bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote::transaction& tx, const cryptonote::block& blk_head, - const cryptonote::account_base& from, std::vector<cryptonote::tx_destination_entry> destinations, - uint64_t fee, size_t nmix, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0); + const cryptonote::account_base& from, const std::vector<cryptonote::tx_destination_entry>& destinations, + uint64_t fee, size_t nmix, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0, + bool check_unlock_time = true, const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept = boost::none); bool construct_tx_to_key(cryptonote::transaction& tx, const cryptonote::account_base& from, const var_addr_t& to, uint64_t amount, std::vector<cryptonote::tx_source_entry> &sources, @@ -463,6 +485,10 @@ bool trim_block_chain(std::vector<const cryptonote::block*>& blockchain, const c bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<cryptonote::block>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); bool find_block_chain(const std::vector<test_event_entry>& events, std::vector<const cryptonote::block*>& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); +bool fill_tx_sources(std::vector<cryptonote::tx_source_entry>& sources, const std::vector<test_event_entry>& events, + const cryptonote::block& blk_head, const cryptonote::account_base& from, uint64_t amount, size_t nmix, + bool check_unlock_time = true, const boost::optional<fnc_accept_output_t>& fnc_accept = boost::none); + void fill_tx_destinations(const var_addr_t& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, const std::vector<cryptonote::tx_source_entry> &sources, @@ -486,13 +512,17 @@ void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& event const cryptonote::account_base& from, const cryptonote::account_public_address& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector<cryptonote::tx_source_entry>& sources, - std::vector<cryptonote::tx_destination_entry>& destinations); + std::vector<cryptonote::tx_destination_entry>& destinations, + bool check_unlock_time = true, + const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept = boost::none); void fill_tx_sources_and_destinations(const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, uint64_t fee, size_t nmix, std::vector<cryptonote::tx_source_entry>& sources, - std::vector<cryptonote::tx_destination_entry>& destinations); + std::vector<cryptonote::tx_destination_entry>& destinations, + bool check_unlock_time = true, + const boost::optional<fnc_accept_output_t>& fnc_tx_in_accept = boost::none); uint64_t get_balance(const cryptonote::account_base& addr, const std::vector<cryptonote::block>& blockchain, const map_hash2tx_t& mtx); @@ -883,15 +913,6 @@ inline bool do_replay_file(const std::string& filename) } \ VEC_EVENTS.push_back(BLK_NAME); -#define MAKE_NEXT_BLOCK_TX1_HF(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TX1, HF) \ - cryptonote::block BLK_NAME; \ - { \ - std::list<cryptonote::transaction> tx_list; \ - tx_list.push_back(TX1); \ - generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, tx_list, HF); \ - } \ - VEC_EVENTS.push_back(BLK_NAME); - #define MAKE_NEXT_BLOCK_TX_LIST(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST) \ cryptonote::block BLK_NAME; \ generator.construct_block(BLK_NAME, PREV_BLOCK, MINER_ACC, TXLIST); \ @@ -916,18 +937,12 @@ inline bool do_replay_file(const std::string& filename) #define REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, COUNT) REWIND_BLOCKS_N_HF(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, COUNT, boost::none) #define REWIND_BLOCKS(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC) REWIND_BLOCKS_N(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW) -#define REWIND_BLOCKS_HF(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, HF) REWIND_BLOCKS_N_HF(VEC_EVENTS, BLK_NAME, PREV_BLOCK, MINER_ACC, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, HF) #define MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ cryptonote::transaction TX_NAME; \ construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX); \ VEC_EVENTS.push_back(TX_NAME); -#define MAKE_TX_MIX_RCT(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ - cryptonote::transaction TX_NAME; \ - construct_tx_to_key(VEC_EVENTS, TX_NAME, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX, true, rct::RangeProofPaddedBulletproof); \ - VEC_EVENTS.push_back(TX_NAME); - #define MAKE_TX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX(VEC_EVENTS, TX_NAME, FROM, TO, AMOUNT, 0, HEAD) #define MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ @@ -938,36 +953,12 @@ inline bool do_replay_file(const std::string& filename) VEC_EVENTS.push_back(t); \ } -#define MAKE_TX_MIX_LIST_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ - MAKE_TX_MIX_LIST_RCT_EX(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD, rct::RangeProofPaddedBulletproof, 1) -#define MAKE_TX_MIX_LIST_RCT_EX(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD, RCT_TYPE, BP_VER) \ - { \ - cryptonote::transaction t; \ - construct_tx_to_key(VEC_EVENTS, t, HEAD, FROM, TO, AMOUNT, TESTS_DEFAULT_FEE, NMIX, true, RCT_TYPE, BP_VER); \ - SET_NAME.push_back(t); \ - VEC_EVENTS.push_back(t); \ - } - -#define MAKE_TX_MIX_DEST_LIST_RCT(VEC_EVENTS, SET_NAME, FROM, TO, NMIX, HEAD) \ - MAKE_TX_MIX_DEST_LIST_RCT_EX(VEC_EVENTS, SET_NAME, FROM, TO, NMIX, HEAD, rct::RangeProofPaddedBulletproof, 1) -#define MAKE_TX_MIX_DEST_LIST_RCT_EX(VEC_EVENTS, SET_NAME, FROM, TO, NMIX, HEAD, RCT_TYPE, BP_VER) \ - { \ - cryptonote::transaction t; \ - construct_tx_to_key(VEC_EVENTS, t, HEAD, FROM, TO, TESTS_DEFAULT_FEE, NMIX, true, RCT_TYPE, BP_VER); \ - SET_NAME.push_back(t); \ - VEC_EVENTS.push_back(t); \ - } - #define MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) MAKE_TX_MIX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, 0, HEAD) #define MAKE_TX_LIST_START(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD) \ std::list<cryptonote::transaction> SET_NAME; \ MAKE_TX_LIST(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, HEAD); -#define MAKE_TX_LIST_START_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD) \ - std::list<cryptonote::transaction> SET_NAME; \ - MAKE_TX_MIX_LIST_RCT(VEC_EVENTS, SET_NAME, FROM, TO, AMOUNT, NMIX, HEAD); - #define MAKE_MINER_TX_AND_KEY_AT_HF_MANUALLY(TX, BLK, HF_VERSION, KEY) \ transaction TX; \ if (!construct_miner_tx_manually(get_block_height(BLK) + 1, generator.get_already_generated_coins(BLK), \ diff --git a/tests/core_tests/chaingen001.cpp b/tests/core_tests/chaingen001.cpp index 8738585e0..1dde16ff5 100644 --- a/tests/core_tests/chaingen001.cpp +++ b/tests/core_tests/chaingen001.cpp @@ -120,18 +120,18 @@ bool gen_simple_chain_001::generate(std::vector<test_event_entry> &events) std::cout << "BALANCE = " << get_balance(miner, chain, mtx) << std::endl; REWIND_BLOCKS(events, blk_2r, blk_2, miner); - MAKE_TX_LIST_START(events, txlist_0, miner, alice, MK_COINS(1), blk_2); - MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(2), blk_2); - MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(4), blk_2); + MAKE_TX_LIST_START(events, txlist_0, miner, alice, MK_COINS(1), blk_2r); + MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(2), blk_2r); + MAKE_TX_LIST(events, txlist_0, miner, alice, MK_COINS(4), blk_2r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner, txlist_0); REWIND_BLOCKS(events, blk_3r, blk_3, miner); - MAKE_TX(events, tx_1, miner, alice, MK_COINS(50), blk_3); + MAKE_TX(events, tx_1, miner, alice, MK_COINS(50), blk_3r); MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3r, miner, tx_1); REWIND_BLOCKS(events, blk_4r, blk_4, miner); - MAKE_TX(events, tx_2, miner, alice, MK_COINS(50), blk_4); + MAKE_TX(events, tx_2, miner, alice, MK_COINS(50), blk_4r); MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner, tx_2); REWIND_BLOCKS(events, blk_5r, blk_5, miner); - MAKE_TX(events, tx_3, miner, alice, MK_COINS(50), blk_5); + MAKE_TX(events, tx_3, miner, alice, MK_COINS(50), blk_5r); MAKE_NEXT_BLOCK_TX1(events, blk_6, blk_5r, miner, tx_3); DO_CALLBACK(events, "verify_callback_1"); diff --git a/tests/core_tests/double_spend.h b/tests/core_tests/double_spend.h index b1f071f38..85c855bcf 100644 --- a/tests/core_tests/double_spend.h +++ b/tests/core_tests/double_spend.h @@ -144,7 +144,7 @@ public: MAKE_ACCOUNT(events, bob_account); \ MAKE_ACCOUNT(events, alice_account); \ REWIND_BLOCKS(events, blk_0r, blk_0, miner_account); \ - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); \ + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); \ MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_account, tx_0); \ REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index 4b60da3a3..b6e3726cc 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -126,20 +126,14 @@ bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_even DO_CALLBACK(events, "mark_last_valid_block"); std::vector<cryptonote::tx_source_entry> sources; - cryptonote::tx_source_entry se; - se.amount = tx_0.vout[0].amount; - se.push_output(0, boost::get<cryptonote::txout_to_key>(tx_0.vout[0].target).key, se.amount); - se.real_output = 0; - se.rct = false; - se.real_out_tx_key = get_tx_pub_key_from_extra(tx_0); - se.real_output_in_tx_index = 0; - sources.push_back(se); + CHECK_AND_ASSERT_THROW_MES(fill_tx_sources(sources, events, blk_1r, bob_account, send_amount, 0), "Source find error"); + // Double spend! - sources.push_back(se); + sources.push_back(sources[0]); cryptonote::tx_destination_entry de; de.addr = alice_account.get_keys().m_account_address; - de.amount = 2 * se.amount - TESTS_DEFAULT_FEE; + de.amount = 2 * send_amount - TESTS_DEFAULT_FEE; std::vector<cryptonote::tx_destination_entry> destinations; destinations.push_back(de); diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 42ad0d26a..6ffc3786b 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -123,8 +123,8 @@ bool gen_uint_overflow_1::generate(std::vector<test_event_entry>& events) const events.push_back(blk_2); REWIND_BLOCKS(events, blk_2r, blk_2, miner_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); - MAKE_TX_LIST(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2r); + MAKE_TX_LIST(events, txs_0, miner_account, bob_account, MONEY_SUPPLY, blk_2r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_3, blk_2r, miner_account, txs_0); REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); diff --git a/tests/core_tests/ring_signature_1.cpp b/tests/core_tests/ring_signature_1.cpp index f3f8109c3..218747f21 100644 --- a/tests/core_tests/ring_signature_1.cpp +++ b/tests/core_tests/ring_signature_1.cpp @@ -70,19 +70,19 @@ bool gen_ring_signature_1::generate(std::vector<test_event_entry>& events) const MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_account); // 8 REWIND_BLOCKS(events, blk_5, blk_4, miner_account); // <N blocks> REWIND_BLOCKS(events, blk_5r, blk_5, miner_account); // <N blocks> - MAKE_TX_LIST_START(events, txs_blk_6, miner_account, bob_account, MK_COINS(1), blk_5); // 9 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5); // 10 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5); // 11 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(20) + rnd_20, blk_5); // 12 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 13 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 14 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5); // 15 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 16 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 17 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 18 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5); // 19 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(20) + rnd_20, blk_5); // 20 + 2N - MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_2, MK_COINS(20) + rnd_20, blk_5); // 21 + 2N + MAKE_TX_LIST_START(events, txs_blk_6, miner_account, bob_account, MK_COINS(1), blk_5r); // 9 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5r); // 10 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(11) + rnd_11, blk_5r); // 11 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(20) + rnd_20, blk_5r); // 12 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5r); // 13 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5r); // 14 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, bob_account, MK_COINS(29) + rnd_29, blk_5r); // 15 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5r); // 16 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5r); // 17 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5r); // 18 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(11) + rnd_11, blk_5r); // 19 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_1, MK_COINS(20) + rnd_20, blk_5r); // 20 + 2N + MAKE_TX_LIST(events, txs_blk_6, miner_account, some_account_2, MK_COINS(20) + rnd_20, blk_5r); // 21 + 2N MAKE_NEXT_BLOCK_TX_LIST(events, blk_6, blk_5r, miner_account, txs_blk_6); // 22 + 2N DO_CALLBACK(events, "check_balances_1"); // 23 + 2N REWIND_BLOCKS(events, blk_6r, blk_6, miner_account); // <N blocks> @@ -161,14 +161,14 @@ bool gen_ring_signature_2::generate(std::vector<test_event_entry>& events) const MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_account); // 4 MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_account); // 5 REWIND_BLOCKS(events, blk_3r, blk_3, miner_account); // <N blocks> - MAKE_TX_LIST_START(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 6 + N - MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 7 + N - MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 8 + N - MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3); // 9 + N + MAKE_TX_LIST_START(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3r); // 6 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3r); // 7 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3r); // 8 + N + MAKE_TX_LIST(events, txs_blk_4, miner_account, bob_account, MK_COINS(13), blk_3r); // 9 + N MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, miner_account, txs_blk_4); // 10 + N DO_CALLBACK(events, "check_balances_1"); // 11 + N REWIND_BLOCKS(events, blk_4r, blk_4, miner_account); // <N blocks> - MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(52) - TESTS_DEFAULT_FEE, 3, blk_4); // 12 + 2N + MAKE_TX_MIX(events, tx_0, bob_account, alice_account, MK_COINS(52) - TESTS_DEFAULT_FEE, 3, blk_4r); // 12 + 2N MAKE_NEXT_BLOCK_TX1(events, blk_5, blk_4r, miner_account, tx_0); // 13 + 2N DO_CALLBACK(events, "check_balances_2"); // 14 + 2N diff --git a/tests/core_tests/tx_pool.cpp b/tests/core_tests/tx_pool.cpp index d3dc5d9bb..1b8480526 100644 --- a/tests/core_tests/tx_pool.cpp +++ b/tests/core_tests/tx_pool.cpp @@ -98,7 +98,7 @@ bool txpool_spend_key_public::generate(std::vector<test_event_entry>& events) co INIT_MEMPOOL_TEST(); DO_CALLBACK(events, "check_txpool_spent_keys"); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); DO_CALLBACK(events, "increase_broadcasted_tx_count"); DO_CALLBACK(events, "increase_all_tx_count"); DO_CALLBACK(events, "check_txpool_spent_keys"); @@ -112,7 +112,7 @@ bool txpool_spend_key_all::generate(std::vector<test_event_entry>& events) SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay); DO_CALLBACK(events, "check_txpool_spent_keys"); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); DO_CALLBACK(events, "increase_all_tx_count"); DO_CALLBACK(events, "check_txpool_spent_keys"); @@ -502,7 +502,7 @@ bool txpool_double_spend_norelay::generate(std::vector<test_event_entry>& events SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_do_not_relay); DO_CALLBACK(events, "mark_no_new"); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); DO_CALLBACK(events, "increase_all_tx_count"); DO_CALLBACK(events, "check_txpool_spent_keys"); @@ -539,7 +539,7 @@ bool txpool_double_spend_local::generate(std::vector<test_event_entry>& events) SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_local_relay); DO_CALLBACK(events, "mark_no_new"); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); DO_CALLBACK(events, "increase_all_tx_count"); DO_CALLBACK(events, "check_txpool_spent_keys"); @@ -574,7 +574,7 @@ bool txpool_double_spend_keyimage::generate(std::vector<test_event_entry>& event DO_CALLBACK(events, "mark_no_new"); const std::size_t tx_index1 = events.size(); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_stem); DO_CALLBACK(events, "increase_all_tx_count"); @@ -595,7 +595,7 @@ bool txpool_double_spend_keyimage::generate(std::vector<test_event_entry>& event auto events_copy = events; events_copy.erase(events_copy.begin() + tx_index1); events_copy.erase(events_copy.begin() + tx_index2 - 1); - MAKE_TX(events_copy, tx_temp, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events_copy, tx_temp, miner_account, bob_account, send_amount, blk_0r); tx_1 = tx_temp; } @@ -616,7 +616,7 @@ bool txpool_stem_loop::generate(std::vector<test_event_entry>& events) const SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_stem); DO_CALLBACK(events, "mark_no_new"); - MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0); + MAKE_TX(events, tx_0, miner_account, bob_account, send_amount, blk_0r); DO_CALLBACK(events, "increase_all_tx_count"); DO_CALLBACK(events, "check_txpool_spent_keys"); diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index cfc8f089d..bc8622539 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -141,7 +141,7 @@ namespace { std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_head, from, to, amount, TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_head, from, to, amount, TESTS_DEFAULT_FEE, 0, sources, destinations, false); tx_builder builder; builder.step1_init(1, unlock_time); @@ -191,7 +191,7 @@ bool gen_tx_big_version::generate(std::vector<test_event_entry>& events) const std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(1 + 1, 0); @@ -217,7 +217,7 @@ bool gen_tx_unlock_time::generate(std::vector<test_event_entry>& events) const auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> transaction { - return make_simple_tx_with_unlock_time(events, blk_1, miner_account, miner_account, MK_COINS(1), unlock_time); + return make_simple_tx_with_unlock_time(events, blk_1r, miner_account, miner_account, MK_COINS(1), unlock_time); }; std::list<transaction> txs_0; @@ -266,7 +266,7 @@ bool gen_tx_input_is_not_txin_to_key::generate(std::vector<test_event_entry>& ev { std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -309,7 +309,7 @@ bool gen_tx_no_inputs_has_outputs::generate(std::vector<test_event_entry>& event std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_destinations(miner_account, miner_account.get_keys().m_account_address, MK_COINS(1), TESTS_DEFAULT_FEE, sources, destinations, false); tx_builder builder; builder.step1_init(); @@ -331,7 +331,7 @@ bool gen_tx_has_inputs_no_outputs::generate(std::vector<test_event_entry>& event std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); destinations.clear(); tx_builder builder; @@ -357,7 +357,7 @@ bool gen_tx_invalid_input_amount::generate(std::vector<test_event_entry>& events std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); sources.front().amount++; tx_builder builder; @@ -383,7 +383,7 @@ bool gen_tx_input_wo_key_offsets::generate(std::vector<test_event_entry>& events std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -414,8 +414,8 @@ bool gen_tx_key_offest_points_to_foreign_key::generate(std::vector<test_event_en REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); MAKE_ACCOUNT(events, alice_account); MAKE_ACCOUNT(events, bob_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(15) + 1, blk_1); - MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(15) + 1, blk_1); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(15) + 1, blk_1r); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(15) + 1, blk_1r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); std::vector<tx_source_entry> sources_bob; @@ -451,7 +451,7 @@ bool gen_tx_sender_key_offest_not_exist::generate(std::vector<test_event_entry>& std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -478,8 +478,8 @@ bool gen_tx_mixed_key_offest_not_exist::generate(std::vector<test_event_entry>& REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); MAKE_ACCOUNT(events, alice_account); MAKE_ACCOUNT(events, bob_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); - MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1r); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); std::vector<tx_source_entry> sources; @@ -511,7 +511,7 @@ bool gen_tx_key_image_not_derive_from_tx_key::generate(std::vector<test_event_en std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -547,7 +547,7 @@ bool gen_tx_key_image_is_invalid::generate(std::vector<test_event_entry>& events std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -591,7 +591,7 @@ bool gen_tx_check_input_unlock_time::generate(std::vector<test_event_entry>& eve std::list<transaction> txs_0; auto make_tx_to_acc = [&](size_t acc_idx, uint64_t unlock_time) { - txs_0.push_back(make_simple_tx_with_unlock_time(events, blk_1, miner_account, accounts[acc_idx], + txs_0.push_back(make_simple_tx_with_unlock_time(events, blk_1r, miner_account, accounts[acc_idx], MK_COINS(1) + TESTS_DEFAULT_FEE, unlock_time)); events.push_back(txs_0.back()); }; @@ -641,7 +641,7 @@ bool gen_tx_txout_to_key_has_invalid_key::generate(std::vector<test_event_entry> std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -670,7 +670,7 @@ bool gen_tx_output_with_zero_amount::generate(std::vector<test_event_entry>& eve std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -698,7 +698,7 @@ bool gen_tx_output_is_not_txout_to_key::generate(std::vector<test_event_entry>& std::vector<tx_source_entry> sources; std::vector<tx_destination_entry> destinations; - fill_tx_sources_and_destinations(events, blk_0, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); + fill_tx_sources_and_destinations(events, blk_0r, miner_account, miner_account, MK_COINS(1), TESTS_DEFAULT_FEE, 0, sources, destinations); tx_builder builder; builder.step1_init(); @@ -740,8 +740,8 @@ bool gen_tx_signatures_are_invalid::generate(std::vector<test_event_entry>& even REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); MAKE_ACCOUNT(events, alice_account); MAKE_ACCOUNT(events, bob_account); - MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); - MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1); + MAKE_TX_LIST_START(events, txs_0, miner_account, bob_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1r); + MAKE_TX_LIST(events, txs_0, miner_account, alice_account, MK_COINS(1) + TESTS_DEFAULT_FEE, blk_1r); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); MAKE_TX(events, tx_0, miner_account, miner_account, MK_COINS(60), blk_2); diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index a3b66e835..5378b0254 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -4,6 +4,7 @@ #include "wallet_tools.h" #include <random> +#include <boost/assign.hpp> using namespace std; using namespace epee; @@ -31,11 +32,18 @@ void wallet_accessor_test::set_account(tools::wallet2 * wallet, cryptonote::acco void wallet_accessor_test::process_parsed_blocks(tools::wallet2 * wallet, uint64_t start_height, const std::vector<cryptonote::block_complete_entry> &blocks, const std::vector<tools::wallet2::parsed_block> &parsed_blocks, uint64_t& blocks_added) { - wallet->process_parsed_blocks(start_height, blocks, parsed_blocks, blocks_added); + if (wallet != nullptr) { + wallet->process_parsed_blocks(start_height, blocks, parsed_blocks, blocks_added); + } } void wallet_tools::process_transactions(tools::wallet2 * wallet, const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, block_tracker &bt, const boost::optional<crypto::hash>& blk_tail) { + process_transactions(boost::assign::list_of(wallet), events, blk_head, bt, blk_tail); +} + +void wallet_tools::process_transactions(const std::vector<tools::wallet2*>& wallets, const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, block_tracker &bt, const boost::optional<crypto::hash>& blk_tail) +{ map_hash2tx_t mtx; std::vector<const cryptonote::block*> blockchain; find_block_chain(events, blockchain, mtx, get_block_hash(blk_head)); @@ -44,10 +52,14 @@ void wallet_tools::process_transactions(tools::wallet2 * wallet, const std::vect trim_block_chain(blockchain, blk_tail.get()); } - process_transactions(wallet, blockchain, mtx, bt); + process_transactions(wallets, blockchain, mtx, bt); +} + +void wallet_tools::process_transactions(tools::wallet2 * wallet, const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t & mtx, block_tracker &bt){ + process_transactions(boost::assign::list_of(wallet), blockchain, mtx, bt); } -void wallet_tools::process_transactions(tools::wallet2 * wallet, const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t & mtx, block_tracker &bt) +void wallet_tools::process_transactions(const std::vector<tools::wallet2*>& wallets, const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t & mtx, block_tracker &bt) { uint64_t start_height=0, blocks_added=0; std::vector<cryptonote::block_complete_entry> v_bche; @@ -67,11 +79,12 @@ void wallet_tools::process_transactions(tools::wallet2 * wallet, const std::vect wallet_tools::gen_block_data(bt, bl, mtx, v_bche.back(), v_parsed_block.back(), idx == 1 ? start_height : height); } - if (wallet) + for(auto wallet: wallets) { wallet_accessor_test::process_parsed_blocks(wallet, start_height, v_bche, v_parsed_block, blocks_added); + } } -bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptonote::tx_source_entry>& sources, size_t mixin, const boost::optional<size_t>& num_utxo, const boost::optional<uint64_t>& min_amount, block_tracker &bt, std::vector<size_t> &selected, uint64_t cur_height, ssize_t offset, int step, const boost::optional<fnc_accept_tx_source_t>& fnc_accept) +bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptonote::tx_source_entry>& sources, size_t mixin, const boost::optional<size_t>& num_utxo, const boost::optional<uint64_t>& min_amount, block_tracker &bt, std::vector<size_t> &selected, uint64_t cur_height, ssize_t offset, int step, const boost::optional<fnc_accept_tx_source_t>& fnc_accept, const boost::optional<fnc_accept_output_t>& fnc_in_accept) { CHECK_AND_ASSERT_THROW_MES(step != 0, "Step is zero"); sources.clear(); @@ -84,7 +97,7 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono size_t iters = 0; uint64_t sum = 0; size_t cur_utxo = 0; - bool abort = false; + bool should_abort_search = false; unsigned brk_cond = 0; unsigned brk_thresh = num_utxo && min_amount ? 2 : (num_utxo || min_amount ? 1 : 0); @@ -96,7 +109,7 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono brk_cond += 1; \ } while(0) - for(ssize_t i = roffset; iters < ntrans && !abort; i += step, ++iters) + for(ssize_t i = roffset; iters < ntrans && !should_abort_search; i += step, ++iters) { EVAL_BRK_COND(); if (brk_cond >= brk_thresh) @@ -106,7 +119,7 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono auto & td = transfers[i]; if (td.m_spent) continue; - if (td.m_block_height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW > cur_height) + if (td.m_tx.unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER && td.m_tx.unlock_time > cur_height + 1) continue; if (selected_idx.find((size_t)i) != selected_idx.end()){ MERROR("Should not happen (selected_idx not found): " << i); @@ -119,18 +132,12 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono try { cryptonote::tx_source_entry src; - wallet_tools::gen_tx_src(mixin, cur_height, td, src, bt); + wallet_tools::gen_tx_src(mixin, cur_height, td, src, bt, fnc_in_accept); // Acceptor function - if (fnc_accept){ - tx_source_info_crate_t c_info{.td=&td, .src=&src, .selected_idx=&selected_idx, .selected_kis=&selected_kis, - .ntrans=ntrans, .iters=iters, .sum=sum, .cur_utxo=cur_utxo}; - - bool take_it = (fnc_accept.get())(c_info, abort); - if (!take_it){ + if (fnc_accept && !(fnc_accept.get())({.td=&td, .src=&src, .selected_idx=&selected_idx, .selected_kis=&selected_kis, + .ntrans=ntrans, .iters=iters, .sum=sum, .cur_utxo=cur_utxo}, should_abort_search)) continue; - } - } MDEBUG("Selected " << i << " from tx: " << dump_keys(td.m_txid.data) << " ki: " << dump_keys(td.m_key_image.data) @@ -154,18 +161,22 @@ bool wallet_tools::fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptono } EVAL_BRK_COND(); - return brk_cond >= brk_thresh; + const auto res = brk_cond >= brk_thresh; + if (!res) { + MDEBUG("fill_tx_sources fails, brk_cond: " << brk_cond << ", brk_thresh: " << brk_thresh << ", utxos: " << cur_utxo << ", sum: " << sum); + } + return res; #undef EVAL_BRK_COND } -void wallet_tools::gen_tx_src(size_t mixin, uint64_t cur_height, const tools::wallet2::transfer_details & td, cryptonote::tx_source_entry & src, block_tracker &bt) +void wallet_tools::gen_tx_src(size_t mixin, uint64_t cur_height, const tools::wallet2::transfer_details & td, cryptonote::tx_source_entry & src, block_tracker &bt, const boost::optional<fnc_accept_output_t>& fnc_accept) { CHECK_AND_ASSERT_THROW_MES(mixin != 0, "mixin is zero"); src.amount = td.amount(); src.rct = td.is_rct(); std::vector<tools::wallet2::get_outs_entry> outs; - bt.get_fake_outs(mixin, td.is_rct() ? 0 : td.amount(), td.m_global_output_index, cur_height, outs); + bt.get_fake_outs(mixin, td.is_rct() ? 0 : td.amount(), td.m_global_output_index, cur_height, outs, fnc_accept); for (size_t n = 0; n < mixin; ++n) { diff --git a/tests/core_tests/wallet_tools.h b/tests/core_tests/wallet_tools.h index 1fb8017bf..2201e11f4 100644 --- a/tests/core_tests/wallet_tools.h +++ b/tests/core_tests/wallet_tools.h @@ -68,12 +68,14 @@ public: class wallet_tools { public: - static void gen_tx_src(size_t mixin, uint64_t cur_height, const tools::wallet2::transfer_details & td, cryptonote::tx_source_entry & src, block_tracker &bt); + static void gen_tx_src(size_t mixin, uint64_t cur_height, const tools::wallet2::transfer_details & td, cryptonote::tx_source_entry & src, block_tracker &bt, const boost::optional<fnc_accept_output_t>& fnc_accept = boost::none); static void gen_block_data(block_tracker &bt, const cryptonote::block *bl, const map_hash2tx_t & mtx, cryptonote::block_complete_entry &bche, tools::wallet2::parsed_block &parsed_block, uint64_t &height); static void compute_subaddresses(std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddresses, cryptonote::account_base & creds, size_t account, size_t minors); static void process_transactions(tools::wallet2 * wallet, const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, block_tracker &bt, const boost::optional<crypto::hash>& blk_tail=boost::none); + static void process_transactions(const std::vector<tools::wallet2*>& wallets, const std::vector<test_event_entry>& events, const cryptonote::block& blk_head, block_tracker &bt, const boost::optional<crypto::hash>& blk_tail=boost::none); static void process_transactions(tools::wallet2 * wallet, const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t & mtx, block_tracker &bt); - static bool fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptonote::tx_source_entry>& sources, size_t mixin, const boost::optional<size_t>& num_utxo, const boost::optional<uint64_t>& min_amount, block_tracker &bt, std::vector<size_t> &selected, uint64_t cur_height, ssize_t offset=0, int step=1, const boost::optional<fnc_accept_tx_source_t>& fnc_accept=boost::none); + static void process_transactions(const std::vector<tools::wallet2*>& wallets, const std::vector<const cryptonote::block*>& blockchain, const map_hash2tx_t & mtx, block_tracker &bt); + static bool fill_tx_sources(tools::wallet2 * wallet, std::vector<cryptonote::tx_source_entry>& sources, size_t mixin, const boost::optional<size_t>& num_utxo, const boost::optional<uint64_t>& min_amount, block_tracker &bt, std::vector<size_t> &selected, uint64_t cur_height, ssize_t offset=0, int step=1, const boost::optional<fnc_accept_tx_source_t>& fnc_accept=boost::none, const boost::optional<fnc_accept_output_t>& fnc_in_accept = boost::none); }; cryptonote::account_public_address get_address(const tools::wallet2*); diff --git a/tests/data/wallet_00fd416a b/tests/data/wallet_00fd416a Binary files differnew file mode 100644 index 000000000..a1b7898e6 --- /dev/null +++ b/tests/data/wallet_00fd416a diff --git a/tests/data/wallet_00fd416a.keys b/tests/data/wallet_00fd416a.keys Binary files differnew file mode 100644 index 000000000..6908cce1b --- /dev/null +++ b/tests/data/wallet_00fd416a.keys diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index e98037811..242c58dbe 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -36,6 +36,7 @@ import math import monotonic import util_resources import multiprocessing +import string """Test daemon mining RPC calls @@ -52,6 +53,11 @@ Control the behavior with these environment variables: from framework.daemon import Daemon from framework.wallet import Wallet +def assert_non_null_hash(s): + assert len(s) == 64 # correct length + assert all((c in string.hexdigits for c in s)) # is parseable as hex + assert s != ('0' * 64) # isn't null hash + class MiningTest(): def run_test(self): self.reset() @@ -250,6 +256,8 @@ class MiningTest(): block_hash = hashes[i] assert len(block_hash) == 64 res = daemon.submitblock(blocks[i]) + submitted_block_id = res.block_id + assert_non_null_hash(submitted_block_id) res = daemon.get_height() assert res.height == height + i + 1 assert res.hash == block_hash @@ -346,6 +354,8 @@ class MiningTest(): t0 = time.time() for h in range(len(block_hashes)): res = daemon.submitblock(blocks[h]) + submitted_block_id = res.block_id + assert_non_null_hash(submitted_block_id) t0 = time.time() - t0 res = daemon.get_info() assert height == res.height diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py index 0ca438857..73cc8d643 100755 --- a/tests/functional_tests/multisig.py +++ b/tests/functional_tests/multisig.py @@ -29,6 +29,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function +import random """Test multisig transfers """ @@ -36,50 +37,61 @@ from __future__ import print_function from framework.daemon import Daemon from framework.wallet import Wallet +TEST_CASES = \ +[ +# M N Primary Address + [2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG'], + [2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i'], + [3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP'], + [3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff'], + [2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U'], + [1, 2, '4A8RnBQixry4VXkqeWhmg8L7vWJVDJj4FN9PV4E7Mgad5ZZ6LKQdn8dYJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ4S8RSB'] +] + +PUB_ADDRS = [case[2] for case in TEST_CASES] + class MultisigTest(): def run_test(self): self.reset() - self.mine('45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG', 5) - self.mine('44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i', 5) - self.mine('41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP', 5) - self.mine('44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff', 5) - self.mine('47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U', 5) + for pub_addr in PUB_ADDRS: + self.mine(pub_addr, 4) self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) self.test_states() - self.create_multisig_wallets(2, 2, '45J58b7PmKJFSiNPFFrTdtfMcFGnruP7V4CMuRpX7NsH4j3jGHKAjo3YJP2RePX6HMaSkbvTbrWUFhDNcNcHgtNmQ3gr7sG') - self.import_multisig_info([1, 0], 5) - txid = self.transfer([1, 0]) - self.import_multisig_info([0, 1], 6) - self.check_transaction(txid) - - self.create_multisig_wallets(2, 3, '44G2TQNfsiURKkvxp7gbgaJY8WynZvANnhmyMAwv6WeEbAvyAWMfKXRhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5duN94i') - self.import_multisig_info([0, 2], 5) - txid = self.transfer([0, 2]) - self.import_multisig_info([0, 1, 2], 6) - self.check_transaction(txid) - - self.create_multisig_wallets(3, 3, '41mro238grj56GnrWkakAKTkBy2yDcXYsUZ2iXCM9pe5Ueajd2RRc6Fhh3uBXT2UAKhAsUJ7Fg5zjjF2U1iGciFk5ief4ZP') - self.import_multisig_info([2, 0, 1], 5) - txid = self.transfer([2, 1, 0]) - self.import_multisig_info([0, 2, 1], 6) - self.check_transaction(txid) - - self.create_multisig_wallets(3, 4, '44vZSprQKJQRFe6t1VHgU4ESvq2dv7TjBLVGE7QscKxMdFSiyyPCEV64NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6dakeff') - self.import_multisig_info([0, 2, 3], 5) - txid = self.transfer([0, 2, 3]) - self.import_multisig_info([0, 1, 2, 3], 6) - self.check_transaction(txid) - - self.create_multisig_wallets(2, 4, '47puypSwsV1gvUDratmX4y58fSwikXVehEiBhVLxJA1gRCxHyrRgTDr4NnKUQssFPyWxc2meyt7j63F2S2qtCTRL6aRPj5U') - self.import_multisig_info([1, 2], 5) - txid = self.transfer([1, 2]) - self.import_multisig_info([0, 1, 2, 3], 6) - self.check_transaction(txid) - txid = self.try_transfer_frozen([2, 3]) - self.import_multisig_info([0, 1, 2, 3], 7) - self.check_transaction(txid) + self.fund_addrs_with_normal_wallet(PUB_ADDRS) + + for M, N, pub_addr in TEST_CASES: + assert M <= N + shuffled_participants = list(range(N)) + random.shuffle(shuffled_participants) + shuffled_signers = shuffled_participants[:M] + + expected_outputs = 5 # each wallet owns four mined outputs & one transferred output + + # Create multisig wallet and test transferring + self.create_multisig_wallets(M, N, pub_addr) + self.import_multisig_info(shuffled_signers if M != 1 else shuffled_participants, expected_outputs) + txid = self.transfer(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) + + # If more than 1 signer, try to freeze key image of one signer, make tx using that key + # image on another signer, then have first signer sign multisg_txset. Should fail + if M != 1: + txid = self.try_transfer_frozen(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) + + # Recreate wallet from multisig seed and test transferring + self.remake_some_multisig_wallets_by_multsig_seed(M) + self.import_multisig_info(shuffled_signers if M != 1 else shuffled_participants, expected_outputs) + txid = self.transfer(shuffled_signers) + expected_outputs += 1 + self.import_multisig_info(shuffled_participants, expected_outputs) + self.check_transaction(txid) def reset(self): print('Resetting blockchain') @@ -93,6 +105,11 @@ class MultisigTest(): daemon = Daemon() daemon.generateblocks(address, blocks) + # This method sets up N_total wallets with a threshold of M_threshold doing the following steps: + # * restore_deterministic_wallet(w/ hardcoded seeds) + # * prepare_multisig(enable_multisig_experimental = True) + # * make_multisig() + # * exchange_multisig_keys() def create_multisig_wallets(self, M_threshold, N_total, expected_address): print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet') seeds = [ @@ -103,6 +120,8 @@ class MultisigTest(): ] assert M_threshold <= N_total assert N_total <= len(seeds) + + # restore_deterministic_wallet() & prepare_multisig() self.wallet = [None] * N_total info = [] for i in range(N_total): @@ -114,10 +133,12 @@ class MultisigTest(): assert len(res.multisig_info) > 0 info.append(res.multisig_info) + # Assert that all wallets are multisig for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == False + # make_multisig() with each other's info addresses = [] next_stage = [] for i in range(N_total): @@ -125,6 +146,7 @@ class MultisigTest(): addresses.append(res.address) next_stage.append(res.multisig_info) + # Assert multisig paramaters M/N for each wallet for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == True @@ -132,13 +154,15 @@ class MultisigTest(): assert res.threshold == M_threshold assert res.total == N_total - while True: + # exchange_multisig_keys() + num_exchange_multisig_keys_stages = 0 + while True: # while not all wallets are ready n_ready = 0 for i in range(N_total): res = self.wallet[i].is_multisig() if res.ready == True: n_ready += 1 - assert n_ready == 0 or n_ready == N_total + assert n_ready == 0 or n_ready == N_total # No partial readiness if n_ready == N_total: break info = next_stage @@ -148,10 +172,17 @@ class MultisigTest(): res = self.wallet[i].exchange_multisig_keys(info) next_stage.append(res.multisig_info) addresses.append(res.address) + num_exchange_multisig_keys_stages += 1 + + # We should only need N - M + 1 key exchange rounds + assert num_exchange_multisig_keys_stages == N_total - M_threshold + 1 + + # Assert that the all wallets have expected public address for i in range(N_total): - assert addresses[i] == expected_address + assert addresses[i] == expected_address, addresses[i] self.wallet_address = expected_address + # Assert multisig paramaters M/N and "ready" for each wallet for i in range(N_total): res = self.wallet[i].is_multisig() assert res.multisig == True @@ -159,6 +190,73 @@ class MultisigTest(): assert res.threshold == M_threshold assert res.total == N_total + # We want to test if multisig wallets can receive normal transfers as well and mining transfers + def fund_addrs_with_normal_wallet(self, addrs): + print("Funding multisig wallets with normal wallet-to-wallet transfers") + + # Generate normal deterministic wallet + normal_seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + assert not hasattr(self, 'wallet') or not self.wallet + self.wallet = [Wallet(idx = 0)] + res = self.wallet[0].restore_deterministic_wallet(seed = normal_seed) + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + + self.wallet[0].refresh() + + # Check that we own enough spendable enotes + res = self.wallet[0].incoming_transfers(transfer_type = 'available') + assert 'transfers' in res + num_outs_spendable = 0 + min_out_amount = None + for t in res.transfers: + if not t.spent: + num_outs_spendable += 1 + min_out_amount = min(min_out_amount, t.amount) if min_out_amount is not None else t.amount + assert num_outs_spendable >= 2 * len(addrs) + + # Transfer to addrs and mine to confirm tx + dsts = [{'address': addr, 'amount': int(min_out_amount * 0.95)} for addr in addrs] + res = self.wallet[0].transfer(dsts, get_tx_metadata = True) + tx_hex = res.tx_metadata + res = self.wallet[0].relay_tx(tx_hex) + self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 10) + + def remake_some_multisig_wallets_by_multsig_seed(self, threshold): + N = len(self.wallet) + num_signers_to_remake = random.randint(1, N) # Do at least one + signers_to_remake = list(range(N)) + random.shuffle(signers_to_remake) + signers_to_remake = signers_to_remake[:num_signers_to_remake] + + for i in signers_to_remake: + print("Remaking {}/{} multsig wallet from multisig seed: #{}".format(threshold, N, i+1)) + + otherwise_unused_seed = \ + 'factual wiggle awakened maul sash biscuit pause reinvest fonts sleepless knowledge tossed jewels request gusts dagger gumball onward dotted amended powder cynical strained topic request' + + # Get information about wallet, will compare against later + old_viewkey = self.wallet[i].query_key('view_key').key + old_spendkey = self.wallet[i].query_key('spend_key').key + old_multisig_seed = self.wallet[i].query_key('mnemonic').key + + # Close old wallet and restore w/ random seed so we know that restoring actually did something + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed=otherwise_unused_seed) + mid_viewkey = self.wallet[i].query_key('view_key').key + assert mid_viewkey != old_viewkey + + # Now restore w/ old multisig seed and check against original + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed=old_multisig_seed, enable_multisig_experimental=True) + new_viewkey = self.wallet[i].query_key('view_key').key + new_spendkey = self.wallet[i].query_key('spend_key').key + new_multisig_seed = self.wallet[i].query_key('mnemonic').key + assert new_viewkey == old_viewkey + assert new_spendkey == old_spendkey + assert new_multisig_seed == old_multisig_seed + + self.wallet[i].refresh() + def test_states(self): print('Testing multisig states') seeds = [ @@ -251,7 +349,7 @@ class MultisigTest(): assert res.n_outputs == expected_outputs def transfer(self, signers): - assert len(signers) >= 2 + assert len(signers) >= 1 daemon = Daemon() @@ -364,7 +462,7 @@ class MultisigTest(): print("Attemping to sign with frozen key image. This should fail") try: res = self.wallet[signers[1]].sign_multisig(multisig_txset) - raise ValueError('sign_multisig should not have succeeded w/ fronzen enotes') + raise ValueError('sign_multisig should not have succeeded w/ frozen enotes') except AssertionError: pass diff --git a/tests/trezor/daemon.cpp b/tests/trezor/daemon.cpp index 47c0cde9b..de4f9bc51 100644 --- a/tests/trezor/daemon.cpp +++ b/tests/trezor/daemon.cpp @@ -309,11 +309,10 @@ void mock_daemon::stop_p2p() m_server.send_stop_signal(); } -void mock_daemon::mine_blocks(size_t num_blocks, const std::string &miner_address) +void mock_daemon::mine_blocks(size_t num_blocks, const std::string &miner_address, std::chrono::seconds timeout) { bool blocks_mined = false; const uint64_t start_height = get_height(); - const auto mining_timeout = std::chrono::seconds(360); MDEBUG("Current height before mining: " << start_height); start_mining(miner_address); @@ -331,14 +330,14 @@ void mock_daemon::mine_blocks(size_t num_blocks, const std::string &miner_addres } auto current_time = std::chrono::system_clock::now(); - if (mining_timeout < current_time - mining_started) + if (timeout < current_time - mining_started) { break; } } stop_mining(); - CHECK_AND_ASSERT_THROW_MES(blocks_mined, "Mining failed in the time limit"); + CHECK_AND_ASSERT_THROW_MES(blocks_mined, "Mining failed in the time limit: " << timeout.count()); } constexpr const std::chrono::seconds mock_daemon::rpc_timeout; diff --git a/tests/trezor/daemon.h b/tests/trezor/daemon.h index b87fd5036..3c0b6b78b 100644 --- a/tests/trezor/daemon.h +++ b/tests/trezor/daemon.h @@ -139,7 +139,7 @@ public: void stop_and_deinit(); void try_init_and_run(boost::optional<unsigned> initial_port=boost::none); - void mine_blocks(size_t num_blocks, const std::string &miner_address); + void mine_blocks(size_t num_blocks, const std::string &miner_address, std::chrono::seconds timeout = std::chrono::seconds(360)); void start_mining(const std::string &miner_address, uint64_t threads_count=1, bool do_background_mining=false, bool ignore_battery=true); void stop_mining(); uint64_t get_height(); diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index 10c52ff29..b21da95c0 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -37,6 +37,7 @@ using namespace cryptonote; +#include <cmath> #include <boost/regex.hpp> #include <common/apply_permutation.h> #include "common/util.h" @@ -51,18 +52,20 @@ namespace po = boost::program_options; namespace { - const command_line::arg_descriptor<std::string> arg_filter = { "filter", "Regular expression filter for which tests to run" }; - const command_line::arg_descriptor<std::string> arg_trezor_path = {"trezor_path", "Path to the trezor device to use, has to support debug link", ""}; - const command_line::arg_descriptor<bool> arg_heavy_tests = {"heavy_tests", "Runs expensive tests (volume tests with real device)", false}; - const command_line::arg_descriptor<std::string> arg_chain_path = {"chain_path", "Path to the serialized blockchain, speeds up testing", ""}; - const command_line::arg_descriptor<bool> arg_fix_chain = {"fix_chain", "If chain_patch is given and file cannot be used, it is ignored and overwriten", false}; + const command_line::arg_descriptor<std::string> arg_filter = {"filter", "Regular expression filter for which tests to run" }; + const command_line::arg_descriptor<std::string> arg_trezor_path = {"trezor-path", "Path to the trezor device to use, has to support debug link", ""}; + const command_line::arg_descriptor<bool> arg_heavy_tests = {"heavy-tests", "Runs expensive tests (volume tests)", false}; + const command_line::arg_descriptor<std::string> arg_chain_path = {"chain-path", "Path to the serialized blockchain, speeds up testing", ""}; + const command_line::arg_descriptor<bool> arg_fix_chain = {"fix-chain", "If chain-patch is given and file cannot be used, it is ignored and overwriten", false}; } #define HW_TREZOR_NAME "Trezor" -#define TREZOR_ACCOUNT_ORDERING &m_miner_account, &m_alice_account, &m_bob_account, &m_eve_account -#define TREZOR_COMMON_TEST_CASE(genclass, CORE, BASE) do { \ - rollback_chain(CORE, BASE.head_block()); \ - { \ +#define TEST_DECOY_MINING_COUNTS 3 +#define TREZOR_ACCOUNT_ORDERING &m_miner_account, &m_alice_account, &m_alice2_account, &m_bob_account, &m_eve_account +#define TREZOR_ALL_WALLET_VCT vct_wallets(m_wl_alice.get(), m_wl_alice2.get(), m_wl_bob.get(), m_wl_eve.get()) +#define TREZOR_COMMON_TEST_CASE(genclass, CORE, BASE) do { \ + if (filter.empty() || boost::regex_match(std::string(#genclass), match, boost::regex(filter))) { \ + rollback_chain(CORE, BASE.head_block()); \ genclass ctest; \ BASE.fork(ctest); \ GENERATE_AND_PLAY_INSTANCE(genclass, ctest, *(CORE)); \ @@ -79,17 +82,27 @@ namespace } \ } while(0) +typedef struct { + bool heavy_tests; +} chain_file_opts_t; +static const std::string CUR_CHAIN_MAGIC = "MoneroTrezorTestsEventFile"; +static const unsigned long CUR_CHAIN_VERSION = 2; static device_trezor_test *trezor_device = nullptr; static device_trezor_test *ensure_trezor_test_device(); static void rollback_chain(cryptonote::core * core, const cryptonote::block & head); static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, std::string chain_path, bool fix_chain, const po::variables_map & vm_core); -static long get_env_long(const char * flag_name, boost::optional<long> def = boost::none){ - const char *env_data = getenv(flag_name); +static long get_env_long(const char * env_name, boost::optional<long> def = boost::none){ + const char *env_data = getenv(env_name); return env_data ? atol(env_data) : (def ? def.get() : 0); } +static std::string get_env_string(const char * env_name, boost::optional<std::string> def = boost::none){ + const char *env_data = getenv(env_name); + return env_data ? std::string(env_data) : (def ? def.get() : std::string()); +} + int main(int argc, char* argv[]) { TRY_ENTRY(); @@ -137,9 +150,13 @@ int main(int argc, char* argv[]) hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device()); // shim device for call tracking // Bootstrapping common chain & accounts - const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", HF_VERSION_BULLETPROOF_PLUS); - const uint8_t max_hf = (uint8_t)get_env_long("TEST_MAX_HF", HF_VERSION_BULLETPROOF_PLUS); - auto sync_test = get_env_long("TEST_KI_SYNC", 1); + const std::string trezor_path_env = get_env_string("TREZOR_PATH"); + const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", TREZOR_TEST_MIN_HF_DEFAULT); + const uint8_t max_hf = (uint8_t)get_env_long("TEST_MAX_HF", TREZOR_TEST_MAX_HF_DEFAULT); + const bool mining_enabled = get_env_long("TEST_MINING_ENABLED", TREZOR_TEST_MINING_ENABLED_DEFAULT || heavy_tests) > 0; + const long mining_timeout = get_env_long("TEST_MINING_TIMEOUT", TREZOR_TEST_MINING_TIMEOUT_DEFAULT); + const auto sync_test = get_env_long("TEST_KI_SYNC", TREZOR_TEST_KI_SYNC_DEFAULT); + const bool env_gen_heavy = get_env_long("TEST_GEN_HEAVY", 0) > 0 || heavy_tests; MINFO("Test versions " << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); MINFO("Testing hardforks [" << (int)initial_hf << ", " << (int)max_hf << "], sync-test: " << sync_test); @@ -148,7 +165,8 @@ int main(int argc, char* argv[]) std::shared_ptr<mock_daemon> daemon = nullptr; gen_trezor_base trezor_base; - trezor_base.setup_args(trezor_path, heavy_tests); + trezor_base.setup_args(!trezor_path.empty() ? trezor_path : trezor_path_env, heavy_tests, mining_enabled, mining_timeout); + trezor_base.heavy_test_set(env_gen_heavy || heavy_tests); trezor_base.set_hard_fork(initial_hf); // Arguments for core & daemon @@ -194,6 +212,10 @@ int main(int argc, char* argv[]) TREZOR_COMMON_TEST_CASE(gen_trezor_ki_sync_with_refresh, core, trezor_base); } + TREZOR_COMMON_TEST_CASE(gen_trezor_no_passphrase, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_wallet_passphrase, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_passphrase, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_pin, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo_paymentid_short, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_1utxo_paymentid_short_integrated, core, trezor_base); @@ -205,6 +227,7 @@ int main(int argc, char* argv[]) TREZOR_COMMON_TEST_CASE(gen_trezor_2utxo_sub_acc_to_1norm_2sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_7outs, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_15outs, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_16utxo_to_sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(wallet_api_tests, core, trezor_base); } @@ -242,7 +265,7 @@ static void rollback_chain(cryptonote::core * core, const cryptonote::block & he crypto::hash head_hash = get_block_hash(head), cur_hash{}; uint64_t height = get_block_height(head), cur_height=0; - MDEBUG("Rollbacking to " << height << " to hash " << head_hash); + MDEBUG("Rolling back to " << height << " to hash " << head_hash); do { core->get_blockchain_top(cur_height, cur_hash); @@ -255,11 +278,12 @@ static void rollback_chain(cryptonote::core * core, const cryptonote::block & he } while(true); } -static bool unserialize_chain_from_file(std::vector<test_event_entry>& events, gen_trezor_base &test_base, const std::string& file_path) +static bool deserialize_chain_from_file(std::vector<test_event_entry>& events, gen_trezor_base &test_base, unsigned long &version, const std::string& file_path, chain_file_opts_t& opts) { TRY_ENTRY(); std::ifstream data_file; data_file.open( file_path, std::ios_base::binary | std::ios_base::in); + std::string magic; if(data_file.fail()) return false; try @@ -267,8 +291,20 @@ static bool unserialize_chain_from_file(std::vector<test_event_entry>& events, g boost::archive::portable_binary_iarchive a(data_file); test_base.clear(); + a >> magic; + CHECK_AND_ASSERT_THROW_MES(magic == CUR_CHAIN_MAGIC, "Chain file load error - magic differs"); + + a >> version; + a >> opts.heavy_tests; + a >> magic; + CHECK_AND_ASSERT_THROW_MES(magic == CUR_CHAIN_MAGIC, "Chain file load error - magic differs"); + a >> events; a >> test_base; + a >> magic; + CHECK_AND_ASSERT_THROW_MES(magic == CUR_CHAIN_MAGIC, "Chain file load error - magic differs"); + CHECK_AND_ASSERT_THROW_MES(!data_file.fail(), "Chain file load error - file.fail()"); + CHECK_AND_ASSERT_THROW_MES(!data_file.bad(), "Chain file load error - bad read"); return true; } catch(...) @@ -276,10 +312,10 @@ static bool unserialize_chain_from_file(std::vector<test_event_entry>& events, g MWARNING("Chain deserialization failed"); return false; } - CATCH_ENTRY_L0("unserialize_chain_from_file", false); + CATCH_ENTRY_L0("deserialize_chain_from_file", false); } -static bool serialize_chain_to_file(std::vector<test_event_entry>& events, gen_trezor_base &test_base, const std::string& file_path) +static bool serialize_chain_to_file(std::vector<test_event_entry>& events, gen_trezor_base &test_base, const std::string& file_path, const chain_file_opts_t& opts) { TRY_ENTRY(); std::ofstream data_file; @@ -290,13 +326,19 @@ static bool serialize_chain_to_file(std::vector<test_event_entry>& events, gen_t { boost::archive::portable_binary_oarchive a(data_file); + a << CUR_CHAIN_MAGIC; + a << CUR_CHAIN_VERSION; + a << opts.heavy_tests; + a << CUR_CHAIN_MAGIC; + a << events; a << test_base; + a << CUR_CHAIN_MAGIC; return !data_file.fail(); } catch(...) { - MWARNING("Chain deserialization failed"); + MWARNING("Chain serialization failed"); return false; } return false; @@ -339,19 +381,39 @@ static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, const bool chain_file_exists = do_serialize && boost::filesystem::exists(chain_path); bool loaded = false; bool generated = false; + unsigned long chain_version = 0; + chain_file_opts_t opts = {.heavy_tests=trezor_base.heavy_tests()}; if (chain_file_exists) { - if (!unserialize_chain_from_file(events, trezor_base, chain_path)) - { - MERROR("Failed to deserialize data from file: " << chain_path); - CHECK_AND_ASSERT_THROW_MES(fix_chain, "Chain load error"); - } else + chain_file_opts_t loaded_opts; + const auto fix_suggestion = fix_chain ? "Chain file will be regenerated." : "Use --fix_chain to regenerate chain file"; + bool deserialize_ok = deserialize_chain_from_file(events, trezor_base, chain_version, chain_path, loaded_opts); + if (!deserialize_ok) { + MERROR("Failed to deserialize data from file: " << chain_path << ". " << fix_suggestion); + CHECK_AND_ASSERT_THROW_MES(fix_chain, "Chain load error. " << fix_suggestion); + } + else if (chain_version != CUR_CHAIN_VERSION) { + MERROR("Loaded chain version " << chain_version << " differs from current target" << CUR_CHAIN_VERSION << " in the chain file: " << chain_path << ". " << fix_suggestion); + CHECK_AND_ASSERT_THROW_MES(fix_chain, "Chain load error - versions differ. " << fix_suggestion); + deserialize_ok = false; + } + else if (opts.heavy_tests && !loaded_opts.heavy_tests) { + MERROR("Loaded chain does not include transactions for heavy test, the chain file: " << chain_path << ". " << fix_suggestion); + CHECK_AND_ASSERT_THROW_MES(fix_chain, "Chain does not heavy tx set - versions differ. " << fix_suggestion); + deserialize_ok = false; + } + + if (deserialize_ok) { trezor_base.load(events); generated = true; loaded = true; } + else { + events.clear(); + trezor_base.clear(); + } } if (!generated) @@ -360,11 +422,12 @@ static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, { trezor_base.clear(); generated = trezor_base.generate(events); + trezor_base.fix_hf(events); if (generated && !loaded && do_serialize) { trezor_base.update_trackers(events); - if (!serialize_chain_to_file(events, trezor_base, chain_path)) + if (!serialize_chain_to_file(events, trezor_base, chain_path, opts)) { MERROR("Failed to serialize data to file: " << chain_path); } @@ -385,6 +448,10 @@ static void setup_chain(cryptonote::core * core, gen_trezor_base & trezor_base, } } +static fnc_accept_output_t fnc_trezor_default_acceptor_tx_in = [] (const fnc_accept_output_crate_t &info) -> bool { + return info.oi.is_coin_base || info.oi.blk_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE <= info.cur_height + 1; +}; + static device_trezor_test *ensure_trezor_test_device(){ if (!trezor_device) { trezor_device = new device_trezor_test(); @@ -393,6 +460,20 @@ static device_trezor_test *ensure_trezor_test_device(){ return trezor_device; } +static size_t min_mixin_for_hf(uint8_t hf) +{ + if (hf >= HF_VERSION_MIN_MIXIN_15) + return 15; + if (hf >= HF_VERSION_MIN_MIXIN_10) + return 10; + return 0; +} + +static int bp_version_from_hf(uint8_t hf) +{ + return hf >= HF_VERSION_BULLETPROOF_PLUS ? 4 : hf >= HF_VERSION_CLSAG ? 3 : hf >= HF_VERSION_SMALLER_BP ? 2 : 1; +} + static void add_hforks(std::vector<test_event_entry>& events, const v_hardforks_t& hard_forks) { event_replay_settings repl_set; @@ -426,6 +507,52 @@ static crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2 } } +static void generate_tx_block(std::vector<test_event_entry>& events, std::list<cryptonote::transaction> &txs_lst, cryptonote::block & head, + const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, size_t repeat, + size_t mixin, bool rct, rct::RangeProofType range_proof_type, int bp_version) +{ + const size_t per_tx_limit = 14; + const size_t iters = repeat ? std::ceil(repeat / (double)per_tx_limit) : 1; + + for(size_t j = 0; j < iters; ++j) + { + const auto dests = build_dsts(to, false, amount, j < iters - 1 ? per_tx_limit : repeat - j * per_tx_limit); + + cryptonote::transaction t; + construct_tx_to_key(events, t, head, from, dests, TESTS_DEFAULT_FEE, mixin, rct, range_proof_type, bp_version, true, fnc_trezor_default_acceptor_tx_in); + txs_lst.push_back(t); + events.push_back(t); + } +} + +static void generate_tx_block(std::vector<test_event_entry>& events, std::list<cryptonote::transaction> &txs_lst, cryptonote::block & head, + const cryptonote::account_base& from, const std::vector<cryptonote::tx_destination_entry>& destinations, size_t repeat, + size_t mixin, bool rct, rct::RangeProofType range_proof_type, int bp_version) +{ + CHECK_AND_ASSERT_THROW_MES(destinations.size() < 16, "Keep destinations below 16, BP+ limit"); + for(size_t j = 0; j < repeat; ++j) + { + cryptonote::transaction t; + construct_tx_to_key(events, t, head, from, destinations, TESTS_DEFAULT_FEE, mixin, rct, range_proof_type, bp_version, true, fnc_trezor_default_acceptor_tx_in); + txs_lst.push_back(t); + events.push_back(t); + } +} + +static void generate_tx_block(std::vector<test_event_entry>& events, std::list<cryptonote::transaction> &txs_lst, cryptonote::block & head, + const cryptonote::account_base& from, const cryptonote::account_base& to, uint64_t amount, size_t repeat = 1, + uint8_t hf = 1) +{ + generate_tx_block(events, txs_lst, head, from, to, amount, repeat, min_mixin_for_hf(hf), hf >= 8, rct::RangeProofPaddedBulletproof, bp_version_from_hf(hf)); +} + +static void generate_tx_block(std::vector<test_event_entry>& events, std::list<cryptonote::transaction> &txs_lst, cryptonote::block & head, + const cryptonote::account_base& from, const std::vector<cryptonote::tx_destination_entry>& destinations, size_t repeat = 1, + uint8_t hf = 1) +{ + generate_tx_block(events, txs_lst, head, from, destinations, repeat, min_mixin_for_hf(hf), hf >= 8, rct::RangeProofPaddedBulletproof, bp_version_from_hf(hf)); +} + static void setup_shim(hw::wallet_shim * shim) { shim->get_tx_pub_key_from_received_outs = &get_tx_pub_key_from_received_outs; @@ -593,29 +720,57 @@ static std::vector<tools::wallet2*> vct_wallets(tools::wallet2* w1=nullptr, tool return res; } -static uint64_t get_available_funds(tools::wallet2* wallet, uint32_t account=0) +#define TREZOR_ALL_ACCOUNTS 0xffffff +static uint64_t get_available_funds(tools::wallet2* wallet, uint32_t account=0, uint64_t * nutxos = nullptr, uint64_t * nutxos_rct = nullptr) { tools::wallet2::transfer_container transfers; wallet->get_transfers(transfers); uint64_t sum = 0; for(const auto & cur : transfers) { - sum += !cur.m_spent && cur.m_subaddr_index.major == account ? cur.amount() : 0; + const bool utxo = !cur.m_spent && (account == TREZOR_ALL_ACCOUNTS || cur.m_subaddr_index.major == account); + sum += utxo ? cur.amount() : 0; + if (nutxos) { + *nutxos += utxo; + } + if (nutxos_rct && cur.is_rct()) { + *nutxos_rct += utxo; + } } return sum; } +static std::string wallet_desc_str(tools::wallet2* wallet, uint32_t account=0) +{ + std::stringstream ss; + uint64_t nutxos = 0, nutxos_rct = 0, nutxos_all = 0, nutxos_rct_all = 0; + uint64_t funds = get_available_funds(wallet, account, &nutxos, &nutxos_rct); + uint64_t funds_all = get_available_funds(wallet, TREZOR_ALL_ACCOUNTS, &nutxos_all, &nutxos_rct_all); + ss << "funds: " << std::setfill(' ') << std::setw(12) << ((double)funds / COIN) + << ", utxos: " << std::setfill(' ') << std::setw(3) << nutxos + << ", rct: " << std::setfill(' ') << std::setw(3) << nutxos_rct + << "; all accounts: " << std::setfill(' ') << std::setw(12) << ((double)funds_all / COIN) + << ", utxos: " << std::setfill(' ') << std::setw(3) << nutxos_all + << ", rct: " << std::setfill(' ') << std::setw(3) << nutxos_rct_all; + return ss.str(); +} + // gen_trezor_base const uint64_t gen_trezor_base::m_ts_start = 1397862000; // As default wallet timestamp is 1397516400 const uint64_t gen_trezor_base::m_wallet_ts = m_ts_start - 60*60*24*4; const std::string gen_trezor_base::m_device_name = "Trezor:udp"; +const std::string gen_trezor_base::m_miner_master_seed_str = "8b133a3868993176b613738816247a7f4d357cae555996519cf5b543e9b3554b"; // sha256(miner) const std::string gen_trezor_base::m_master_seed_str = "14821d0bc5659b24cafbc889dc4fc60785ee08b65d71c525f81eeaba4f3a570f"; const std::string gen_trezor_base::m_device_seed = "permit universe parent weapon amused modify essay borrow tobacco budget walnut lunch consider gallery ride amazing frog forget treat market chapter velvet useless topple"; const std::string gen_trezor_base::m_alice_spend_private = m_master_seed_str; const std::string gen_trezor_base::m_alice_view_private = "a6ccd4ac344a295d1387f8d18c81bdd394f1845de84188e204514ef9370fd403"; +const std::string gen_trezor_base::m_alice2_passphrase = "a"; +const std::string gen_trezor_base::m_alice2_master_seed = "0b86cf0e71204ca3cdc389daf0bb1cf654ac7d54edfed68a9faf921ba140a708"; +const std::string gen_trezor_base::m_bob_master_seed = "81b637d8fcd2c6da6359e6963113a1170de795e4b725b84d1e0b4cfd9ec58ce9"; // sha256(bob) +const std::string gen_trezor_base::m_eve_master_seed = "85262adf74518bbb70c7cb94cd6159d91669e5a81edf1efebd543eadbda9fa2b"; // sha256(eve) gen_trezor_base::gen_trezor_base(){ - m_rct_config = {rct::RangeProofPaddedBulletproof, 1}; + m_rct_config = {rct::RangeProofPaddedBulletproof, 4}; m_test_get_tx_key = true; m_network_type = cryptonote::TESTNET; } @@ -623,26 +778,37 @@ gen_trezor_base::gen_trezor_base(){ gen_trezor_base::gen_trezor_base(const gen_trezor_base &other): m_generator(other.m_generator), m_bt(other.m_bt), m_miner_account(other.m_miner_account), m_bob_account(other.m_bob_account), m_alice_account(other.m_alice_account), m_eve_account(other.m_eve_account), - m_hard_forks(other.m_hard_forks), m_trezor(other.m_trezor), m_rct_config(other.m_rct_config), m_top_hard_fork(other.m_top_hard_fork), + m_hard_forks(other.m_hard_forks), m_head(other.m_head), m_events(other.m_events), + m_trezor(other.m_trezor), m_rct_config(other.m_rct_config), m_top_hard_fork(other.m_top_hard_fork), m_heavy_tests(other.m_heavy_tests), m_test_get_tx_key(other.m_test_get_tx_key), m_live_refresh_enabled(other.m_live_refresh_enabled), - m_network_type(other.m_network_type), m_daemon(other.m_daemon) + m_network_type(other.m_network_type), m_daemon(other.m_daemon), m_alice2_account(other.m_alice2_account), + m_trezor_path(other.m_trezor_path), m_trezor_pin(other.m_trezor_pin), m_trezor_passphrase(other.m_trezor_passphrase), + m_trezor_use_passphrase(other.m_trezor_use_passphrase), m_trezor_use_alice2(other.m_trezor_use_alice2), + m_gen_heavy_test_set(other.m_gen_heavy_test_set), + m_mining_enabled(other.m_mining_enabled), + m_mining_timeout(other.m_mining_timeout), + m_no_change_in_tested_tx(other.m_no_change_in_tested_tx) { } -void gen_trezor_base::setup_args(const std::string & trezor_path, bool heavy_tests) +void gen_trezor_base::setup_args(const std::string & trezor_path, bool heavy_tests, bool mining_enabled, long mining_timeout) { m_trezor_path = trezor_path.empty() ? m_device_name : std::string("Trezor:") + trezor_path; m_heavy_tests = heavy_tests; + m_gen_heavy_test_set |= heavy_tests; + m_mining_enabled = mining_enabled; + m_mining_timeout = mining_timeout; } -void gen_trezor_base::setup_trezor() +void gen_trezor_base::setup_trezor(bool use_passphrase, const std::string & pin) { hw::device &hwdev = hw::get_device(m_trezor_path); auto trezor = dynamic_cast<device_trezor_test *>(&hwdev); CHECK_AND_ASSERT_THROW_MES(trezor, "Dynamic cast failed"); - trezor->setup_for_tests(m_trezor_path, m_device_seed, m_network_type); + trezor->set_callback(this); + trezor->setup_for_tests(m_trezor_path, m_device_seed, m_network_type, use_passphrase, pin); m_trezor = trezor; } @@ -658,15 +824,25 @@ void gen_trezor_base::fork(gen_trezor_base & other) other.m_top_hard_fork = m_top_hard_fork; other.m_trezor_path = m_trezor_path; other.m_heavy_tests = m_heavy_tests; + other.m_gen_heavy_test_set = m_gen_heavy_test_set; other.m_rct_config = m_rct_config; other.m_test_get_tx_key = m_test_get_tx_key; other.m_live_refresh_enabled = m_live_refresh_enabled; + other.m_trezor_pin = m_trezor_pin; + other.m_trezor_passphrase = m_trezor_passphrase; + other.m_trezor_use_passphrase = m_trezor_use_passphrase; + other.m_trezor_use_alice2 = m_trezor_use_alice2; + other.m_mining_enabled = m_mining_enabled; + other.m_mining_timeout = m_mining_timeout; + other.m_miner_account = m_miner_account; other.m_bob_account = m_bob_account; other.m_alice_account = m_alice_account; + other.m_alice2_account = m_alice2_account; other.m_eve_account = m_eve_account; other.m_trezor = m_trezor; + other.m_no_change_in_tested_tx = m_no_change_in_tested_tx; other.m_generator.set_events(&other.m_events); other.m_generator.set_network_type(m_network_type); } @@ -688,16 +864,55 @@ void gen_trezor_base::add_shared_events(std::vector<test_event_entry>& events) } } -void gen_trezor_base::init_fields() +void gen_trezor_base::init_accounts() { - m_miner_account.generate(); - DEFAULT_HARDFORKS(m_hard_forks); - crypto::secret_key master_seed{}; - CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_master_seed_str, master_seed), "Hexdecode fails"); + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_miner_master_seed_str, master_seed), "Hexdecode fails: m_miner_master_seed_str"); + + m_miner_account.generate(master_seed, true); + DEFAULT_HARDFORKS(m_hard_forks); + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_master_seed_str, master_seed), "Hexdecode fails: m_master_seed_str"); m_alice_account.generate(master_seed, true); m_alice_account.set_createtime(m_wallet_ts); + + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_alice2_master_seed, master_seed), "Hexdecode fails: m_alice2_master_seed"); + m_alice2_account.generate(master_seed, true); + m_alice2_account.set_createtime(m_wallet_ts); + + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_bob_master_seed, master_seed), "Hexdecode fails: m_bob_master_seed"); + m_bob_account.generate(master_seed, true); + m_bob_account.set_createtime(m_wallet_ts); + + CHECK_AND_ASSERT_THROW_MES(epee::string_tools::hex_to_pod(m_eve_master_seed, master_seed), "Hexdecode fails: m_eve_master_seed"); + m_eve_account.generate(master_seed, true); + m_eve_account.set_createtime(m_wallet_ts); +} + +void gen_trezor_base::init_wallets() +{ + m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true)); + m_wl_alice2.reset(new tools::wallet2(m_network_type, 1, true)); + m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true)); + m_wl_eve.reset(new tools::wallet2(m_network_type, 1, true)); + + wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account); + wallet_accessor_test::set_account(m_wl_alice2.get(), m_alice2_account); + wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account); + wallet_accessor_test::set_account(m_wl_eve.get(), m_eve_account); + + m_wl_alice->expand_subaddresses({5, 20}); + m_wl_alice2->expand_subaddresses({5, 20}); + m_wl_bob->expand_subaddresses({5, 20}); + m_wl_eve->expand_subaddresses({5, 20}); +} + +void gen_trezor_base::log_wallets_desc() +{ + MDEBUG("Wallet for Alice: " << wallet_desc_str(m_wl_alice.get())); + MDEBUG("Wallet for Alice2: " << wallet_desc_str(m_wl_alice2.get())); + MDEBUG("Wallet for Bob: " << wallet_desc_str(m_wl_bob.get())); + MDEBUG("Wallet for Eve: " << wallet_desc_str(m_wl_eve.get())); } void gen_trezor_base::update_client_settings() @@ -710,14 +925,8 @@ void gen_trezor_base::update_client_settings() bool gen_trezor_base::generate(std::vector<test_event_entry>& events) { - init_fields(); - setup_trezor(); - - m_live_refresh_enabled = false; - update_client_settings(); - - m_alice_account.create_from_device(*m_trezor); - m_alice_account.set_createtime(m_wallet_ts); + init_accounts(); + setup_trezor(m_trezor_use_passphrase, m_trezor_pin); // Events, custom genesis so it matches wallet genesis auto & generator = m_generator; // macro shortcut @@ -733,10 +942,6 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) generator.add_block(blk_gen, 0, block_weights, 0, rew); // First event has to be the genesis block - m_bob_account.generate(); - m_eve_account.generate(); - m_bob_account.set_createtime(m_wallet_ts); - m_eve_account.set_createtime(m_wallet_ts); cryptonote::account_base * accounts[] = {TREZOR_ACCOUNT_ORDERING}; for(cryptonote::account_base * ac : accounts){ events.push_back(*ac); @@ -745,6 +950,7 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) // Another block with predefined timestamp. // Carefully set reward and already generated coins so it passes miner_tx check. cryptonote::block blk_0; + cryptonote::block & last_block = blk_0; { std::list<cryptonote::transaction> tx_list; const crypto::hash prev_id = get_block_hash(blk_gen); @@ -754,40 +960,37 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) generator.construct_block(blk_0, 1, prev_id, m_miner_account, m_ts_start, already_generated_coins, block_weights, tx_list); } - events.push_back(blk_0); - MDEBUG("Gen+1 block has time: " << blk_0.timestamp << " blid: " << get_block_hash(blk_0)); + events.push_back(last_block); + MDEBUG("Gen+1 block has time: " << last_block.timestamp << " blid: " << get_block_hash(last_block)); // Generate some spendable funds on the Miner account - REWIND_BLOCKS_N(events, blk_3, blk_0, m_miner_account, 40); - - // Rewind so the miners funds are unlocked for initial transactions. - REWIND_BLOCKS(events, blk_3r, blk_3, m_miner_account); - - // Non-rct transactions Miner -> Bob - MAKE_TX_LIST_START(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(10), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(7), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(7), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(14), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(20), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(2), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(2), blk_3); - MAKE_TX_LIST(events, txs_blk_4, m_miner_account, m_alice_account, MK_COINS(5), blk_3); - MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, blk_3r, m_miner_account, txs_blk_4); - REWIND_BLOCKS(events, blk_4r, blk_4, m_miner_account); // rewind to unlock + REWIND_BLOCKS_N(events, blk_3, last_block, m_miner_account, 20); + last_block = blk_3; + + // Rewind so the miners funds are unlocked for initial transactions, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW blocks + REWIND_BLOCKS_N(events, blk_3r1, last_block, m_miner_account, CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - 10); + last_block = blk_3r1; + last_block = rewind_blocks_with_decoys(events, last_block, 10, 1, TEST_DECOY_MINING_COUNTS); + + MDEBUG("Mining chain generated, height: " << get_block_height(last_block) << ", #events: " << events.size() << ", last block: " << get_block_hash(last_block)); + + // Non-rct transactions Miner -> Alice + // Note that change transaction is spendable after CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE so we batch it to a tx + std::list<cryptonote::transaction> txs_blk_4; + generate_tx_block(events, txs_blk_4, last_block, m_miner_account, m_alice_account, MK_TCOINS(0.1), 20, 1); + MAKE_NEXT_BLOCK_TX_LIST(events, blk_4, last_block, m_miner_account, txs_blk_4); + last_block = blk_4; + MDEBUG("Non-rct transactions generated, height: " << get_block_height(last_block) << ", #events: " << events.size() << ", last block: " << get_block_hash(last_block)); // Hard fork to bulletproofs version, v9. const uint8_t CUR_HF = 9; auto hardfork_height = num_blocks(events); // next block is v9 ADD_HARDFORK(m_hard_forks, CUR_HF, hardfork_height); add_hforks(events, m_hard_forks); - MDEBUG("Hardfork height: " << hardfork_height << " at block: " << get_block_hash(blk_4r)); + MDEBUG("Hardfork " << (int)CUR_HF << " height: " << hardfork_height << ", #events: " << events.size() << " at block: " << get_block_hash(last_block)); // RCT transactions, wallets have to be used, wallet init - m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true)); - m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true)); - wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account); - wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account); - + init_wallets(); auto addr_alice_sub_0_1 = m_wl_alice->get_subaddress({0, 1}); auto addr_alice_sub_0_2 = m_wl_alice->get_subaddress({0, 2}); auto addr_alice_sub_0_3 = m_wl_alice->get_subaddress({0, 3}); @@ -797,40 +1000,50 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) auto addr_alice_sub_1_1 = m_wl_alice->get_subaddress({1, 1}); auto addr_alice_sub_1_2 = m_wl_alice->get_subaddress({1, 2}); - // Miner -> Bob, RCT funds - MAKE_TX_LIST_START_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(50), 10, blk_4); + // Miner -> Alice, RCT funds + std::list<cryptonote::transaction> txs_blk_5; + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, m_alice_account, MK_TCOINS(1), 1, CUR_HF); + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, m_alice_account, MK_TCOINS(0.1), heavy_tests() || heavy_test_set() ? 95 : 0, CUR_HF); - const size_t target_rct = m_heavy_tests ? 105 : 15; - for(size_t i = 0; i < target_rct; ++i) - { - MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(1) >> 2, 10, blk_4); - } - - // Sub-address destinations - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_0_1, true, MK_COINS(1) >> 1), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_0_2, true, MK_COINS(1) >> 1), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_0_3, true, MK_COINS(1) >> 1), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_0_4, true, MK_COINS(1) >> 1), 10, blk_4); + // Simple RCT transactions + MDEBUG("blk_5 construction - RCT set"); + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, m_alice_account, MK_TCOINS(1), 1, CUR_HF); + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, build_dsts({ + { m_alice_account, false, MK_TCOINS(1) }, + { m_alice_account, false, MK_TCOINS(1) }, + { m_alice_account, false, MK_TCOINS(1) }, + { m_alice2_account, false, MK_TCOINS(1) } + }), 1, CUR_HF); // Sub-address destinations + multi out to force use of additional keys - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{addr_alice_sub_0_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_2, true, MK_COINS(1) >> 1}}), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{addr_alice_sub_0_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_2, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_3, true, MK_COINS(1) >> 1}}), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{m_miner_account, false, MK_COINS(1) >> 1}, {addr_alice_sub_0_2, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_3, true, MK_COINS(1) >> 1}}), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{m_miner_account, false, MK_COINS(1) >> 1}, {addr_alice_sub_0_2, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_3, true, MK_COINS(1) >> 1}}), 10, blk_4); + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, build_dsts({ + {addr_alice_sub_0_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_3, true, MK_TCOINS(0.1)}, + {m_miner_account, false, MK_TCOINS(0.1)}, + {addr_alice_sub_0_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_3, true, MK_TCOINS(0.1)}, + {m_miner_account, false, MK_TCOINS(0.1)}, + {addr_alice_sub_0_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_4, true, MK_TCOINS(0.1)}}), 1, CUR_HF); // Transfer to other accounts - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_1_0, true, MK_COINS(1) >> 1), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts(addr_alice_sub_1_1, true, MK_COINS(1) >> 1), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{addr_alice_sub_1_0, true, MK_COINS(1) >> 1}, {addr_alice_sub_1_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_3, true, MK_COINS(1) >> 1}}), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{addr_alice_sub_1_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_1_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_2, true, MK_COINS(1) >> 1}}), 10, blk_4); - MAKE_TX_MIX_DEST_LIST_RCT(events, txs_blk_5, m_miner_account, build_dsts({{addr_alice_sub_1_2, true, MK_COINS(1) >> 1}, {addr_alice_sub_1_1, true, MK_COINS(1) >> 1}, {addr_alice_sub_0_5, true, MK_COINS(1) >> 1}}), 10, blk_4); - - // Simple RCT transactions - MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(7), 10, blk_4); - MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(10), 10, blk_4); - MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(30), 10, blk_4); - MAKE_TX_MIX_LIST_RCT(events, txs_blk_5, m_miner_account, m_alice_account, MK_COINS(40), 10, blk_4); - MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_5, blk_4r, m_miner_account, txs_blk_5, CUR_HF); + generate_tx_block(events, txs_blk_5, last_block, m_miner_account, build_dsts({ + {addr_alice_sub_1_0, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_0, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_3, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_2, true, MK_TCOINS(0.1)}, + {addr_alice_sub_1_1, true, MK_TCOINS(0.1)}, + {addr_alice_sub_0_5, true, MK_TCOINS(0.1)}}), 1, CUR_HF); + MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_5, last_block, m_miner_account, txs_blk_5, CUR_HF); + last_block = blk_5; // Simple transaction check bool resx = rct::verRctSemanticsSimple(txs_blk_5.begin()->rct_signatures); @@ -838,23 +1051,34 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(resx, "Tsx5[0] semantics failed"); CHECK_AND_ASSERT_THROW_MES(resy, "Tsx5[0] non-semantics failed"); - REWIND_BLOCKS_HF(events, blk_5r, blk_5, m_miner_account, CUR_HF); // rewind to unlock + // Rewind blocks so there are more spendable coins + last_block = rewind_blocks_with_decoys(events, last_block, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE + 2, CUR_HF, TEST_DECOY_MINING_COUNTS); // RCT transactions, wallets have to be used - wallet_tools::process_transactions(m_wl_alice.get(), events, blk_5r, m_bt); - wallet_tools::process_transactions(m_wl_bob.get(), events, blk_5r, m_bt); + MDEBUG("Post blk_5 construction"); + wallet_tools::process_transactions(vct_wallets(m_wl_alice.get(), m_wl_alice2.get(), m_wl_bob.get()), events, last_block, m_bt); + log_wallets_desc(); // Send Alice -> Bob, manually constructed. Simple TX test, precondition. cryptonote::transaction tx_1; + uint64_t tx_1_amount = MK_TCOINS(0.1); std::vector<size_t> selected_transfers; std::vector<tx_source_entry> sources; - bool res = wallet_tools::fill_tx_sources(m_wl_alice.get(), sources, TREZOR_TEST_MIXIN, boost::none, MK_COINS(2), m_bt, selected_transfers, num_blocks(events) - 1, 0, 1); - CHECK_AND_ASSERT_THROW_MES(res, "TX Fill sources failed"); + bool res = wallet_tools::fill_tx_sources(m_wl_alice.get(), sources, TREZOR_TEST_MIXIN, boost::none, tx_1_amount + TREZOR_TEST_FEE, m_bt, selected_transfers, get_block_height(last_block), 0, 1); + CHECK_AND_ASSERT_THROW_MES(res, "Tx01: tx fill sources failed"); + + res = construct_tx_to_key(tx_1, m_wl_alice.get(), m_bob_account, tx_1_amount, sources, TREZOR_TEST_FEE, true, rct::RangeProofPaddedBulletproof, 1); + CHECK_AND_ASSERT_THROW_MES(res, "Tx01: tx construction failed"); - construct_tx_to_key(tx_1, m_wl_alice.get(), m_bob_account, MK_COINS(1), sources, TREZOR_TEST_FEE, true, rct::RangeProofPaddedBulletproof, 1); + std::list<cryptonote::transaction> txs_blk_6; + txs_blk_6.push_back(tx_1); events.push_back(tx_1); - MAKE_NEXT_BLOCK_TX1_HF(events, blk_6, blk_5r, m_miner_account, tx_1, CUR_HF); - MDEBUG("Post 1st tsx: " << (num_blocks(events) - 1) << " at block: " << get_block_hash(blk_6)); + + generate_tx_block(events, txs_blk_6, last_block, m_miner_account, m_alice_account, MK_TCOINS(1), 1, CUR_HF); + MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk_6, last_block, m_miner_account, txs_blk_6, CUR_HF); + + last_block = blk_6; + MDEBUG("Post 1st tsx: " << (num_blocks(events) - 1) << " at block: " << get_block_hash(last_block) << ", event: " << events.size()); // Simple transaction check resx = rct::verRctSemanticsSimple(tx_1.rct_signatures); @@ -862,20 +1086,20 @@ bool gen_trezor_base::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(resx, "tx_1 semantics failed"); CHECK_AND_ASSERT_THROW_MES(resy, "tx_1 non-semantics failed"); - REWIND_BLOCKS_N_HF(events, blk_6r, blk_6, m_miner_account, 10, CUR_HF); - wallet_tools::process_transactions(m_wl_alice.get(), events, blk_6, m_bt); - wallet_tools::process_transactions(m_wl_bob.get(), events, blk_6, m_bt); - MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get())); - MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get())); + last_block = rewind_blocks_with_decoys(events, last_block, CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, CUR_HF, TEST_DECOY_MINING_COUNTS); + MDEBUG("Blockchain generation up to " << (int)CUR_HF << " finished at height " << num_blocks(events) << ", #events: " << events.size()); + + wallet_tools::process_transactions(TREZOR_ALL_WALLET_VCT, events, last_block, m_bt); + log_wallets_desc(); - m_head = blk_6r; + m_head = last_block; m_events = events; return true; } void gen_trezor_base::load(std::vector<test_event_entry>& events) { - init_fields(); + init_accounts(); m_events = events; m_generator.set_events(&m_events); m_generator.set_network_type(m_network_type); @@ -910,37 +1134,31 @@ void gen_trezor_base::load(std::vector<test_event_entry>& events) } // Setup wallets, synchronize blocks - m_bob_account.set_createtime(m_wallet_ts); - m_eve_account.set_createtime(m_wallet_ts); - - setup_trezor(); - update_client_settings(); - m_alice_account.create_from_device(*m_trezor); - m_alice_account.set_createtime(m_wallet_ts); - - m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true)); - m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true)); - m_wl_eve.reset(new tools::wallet2(m_network_type, 1, true)); - wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account); - wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account); - wallet_accessor_test::set_account(m_wl_eve.get(), m_eve_account); - - wallet_tools::process_transactions(m_wl_alice.get(), events, m_head, m_bt); - wallet_tools::process_transactions(m_wl_bob.get(), events, m_head, m_bt); - MDEBUG("Available funds on Alice: " << get_available_funds(m_wl_alice.get())); - MDEBUG("Available funds on Bob: " << get_available_funds(m_wl_bob.get())); + init_wallets(); + wallet_tools::process_transactions(TREZOR_ALL_WALLET_VCT, events, m_head, m_bt); + log_wallets_desc(); } void gen_trezor_base::rewind_blocks(std::vector<test_event_entry>& events, size_t rewind_n, uint8_t hf) { - auto & generator = m_generator; // macro shortcut - REWIND_BLOCKS_N_HF(events, blk_new, m_head, m_miner_account, rewind_n, hf); - m_head = blk_new; + m_head = rewind_blocks_with_decoys(events, m_head, rewind_n, hf, TEST_DECOY_MINING_COUNTS); m_events = events; - MDEBUG("Blocks rewound: " << rewind_n << ", #blocks: " << num_blocks(events) << ", #events: " << events.size()); +} + +cryptonote::block gen_trezor_base::rewind_blocks_with_decoys(std::vector<test_event_entry>& events, cryptonote::block & head, size_t rewind_n, uint8_t hf, size_t num_decoys_per_block) +{ + auto & generator = m_generator; // shortcut required by MAKE_NEXT_BLOCK_TX_LIST_HF + cryptonote::block blk_last = head; + for (size_t i = 0; i < rewind_n; ++i) + { + std::list<cryptonote::transaction> txs_lst; + generate_tx_block(events, txs_lst, blk_last, m_miner_account, m_bob_account, MK_TCOINS(0.0001), num_decoys_per_block, hf); + MAKE_NEXT_BLOCK_TX_LIST_HF(events, blk, blk_last, m_miner_account, txs_lst, hf); + blk_last = blk; + } - wallet_tools::process_transactions(m_wl_alice.get(), events, m_head, m_bt); - wallet_tools::process_transactions(m_wl_bob.get(), events, m_head, m_bt); + MDEBUG("Blocks rewound: " << rewind_n << ", #blocks: " << num_blocks(events) << ", #events: " << events.size()); + return blk_last; } void gen_trezor_base::fix_hf(std::vector<test_event_entry>& events) @@ -958,8 +1176,15 @@ void gen_trezor_base::fix_hf(std::vector<test_event_entry>& events) ADD_HARDFORK(m_hard_forks, hf_to_add, hardfork_height); add_top_hfork(events, m_hard_forks); MDEBUG("Hardfork added at height: " << hardfork_height << ", from " << (int)current_hf << " to " << (int)hf_to_add); - rewind_blocks(events, 10, hf_to_add); + + // Wallet2 requires 10 blocks extra to activate HF + rewind_blocks(events, hf_to_add == m_top_hard_fork ? 10 : 1, hf_to_add); + + if (hf_to_add == m_top_hard_fork) { + MDEBUG("Blockchain generation up to " << (int)hf_to_add << " finished at height " << num_blocks(events) << ", #events: " << events.size()); + } } + wallet_tools::process_transactions(TREZOR_ALL_WALLET_VCT, events, m_head, m_bt); } void gen_trezor_base::update_trackers(std::vector<test_event_entry>& events) @@ -967,25 +1192,25 @@ void gen_trezor_base::update_trackers(std::vector<test_event_entry>& events) wallet_tools::process_transactions(nullptr, events, m_head, m_bt); } -void gen_trezor_base::test_setup(std::vector<test_event_entry>& events) -{ - add_shared_events(events); - - setup_trezor(); +void gen_trezor_base::init_trezor_account() { + setup_trezor(m_trezor_use_passphrase, m_trezor_pin); update_client_settings(); - m_alice_account.create_from_device(*m_trezor); - m_alice_account.set_createtime(m_wallet_ts); + if (m_trezor_use_alice2) { + m_alice2_account.create_from_device(*m_trezor); + m_alice2_account.set_createtime(m_wallet_ts); + } else { + m_alice_account.create_from_device(*m_trezor); + m_alice_account.set_createtime(m_wallet_ts); + } +} - m_wl_alice.reset(new tools::wallet2(m_network_type, 1, true)); - m_wl_bob.reset(new tools::wallet2(m_network_type, 1, true)); - m_wl_eve.reset(new tools::wallet2(m_network_type, 1, true)); - wallet_accessor_test::set_account(m_wl_alice.get(), m_alice_account); - wallet_accessor_test::set_account(m_wl_bob.get(), m_bob_account); - wallet_accessor_test::set_account(m_wl_eve.get(), m_eve_account); - wallet_tools::process_transactions(m_wl_alice.get(), events, m_head, m_bt); - wallet_tools::process_transactions(m_wl_bob.get(), events, m_head, m_bt); - wallet_tools::process_transactions(m_wl_eve.get(), events, m_head, m_bt); +void gen_trezor_base::test_setup(std::vector<test_event_entry>& events) +{ + add_shared_events(events); + init_trezor_account(); + init_wallets(); + wallet_tools::process_transactions(TREZOR_ALL_WALLET_VCT, events, m_head, m_bt); } void gen_trezor_base::add_transactions_to_events( @@ -1012,8 +1237,10 @@ void gen_trezor_base::add_transactions_to_events( m_head = blk_new; } -void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std::vector<tools::wallet2::pending_tx>& ptxs, std::vector<cryptonote::address_parse_info>& dsts_info, test_generator &generator, std::vector<tools::wallet2*> wallets, bool is_sweep) +void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std::vector<tools::wallet2::pending_tx>& ptxs, std::vector<cryptonote::address_parse_info>& dsts_info, test_generator &generator, std::vector<tools::wallet2*> wallets, bool is_sweep, size_t sender_idx) { + CHECK_AND_ASSERT_THROW_MES(sender_idx < wallets.size(), "Sender index out of bounds"); + // Construct pending transaction for signature in the Trezor. const uint64_t height_pre = num_blocks(events) - 1; cryptonote::block head_block = get_head_block(events); @@ -1067,6 +1294,7 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std: // TX receive test uint64_t sum_in = 0; uint64_t sum_out = 0; + const cryptonote::account_base * sender_account = &wallets[sender_idx]->get_account(); for(size_t txid = 0; txid < exported_txs.ptx.size(); ++txid) { auto &c_ptx = exported_txs.ptx[txid]; @@ -1097,7 +1325,7 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std: CHECK_AND_ASSERT_THROW_MES(c_tx.rct_signatures.txnFee + cur_sum_out == cur_sum_in, "Tx Input Output amount mismatch"); for (size_t widx = 0; widx < wallets.size(); ++widx) { - const bool sender = widx == 0; + const bool sender = widx == sender_idx; tools::wallet2 *wl = wallets[widx]; wallet_tools::process_transactions(wl, events, m_head, m_bt, boost::make_optional(head_hash)); @@ -1151,8 +1379,8 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std: CHECK_AND_ASSERT_THROW_MES(exp_payment_id.empty() || num_payment_id_checks_done > 0, "No Payment ID checks"); if(!is_sweep){ - CHECK_AND_ASSERT_THROW_MES(num_received == num_outs, "Number of received outputs do not match number of outgoing"); - CHECK_AND_ASSERT_THROW_MES(recv_out_idx.size() == num_outs, "Num of outs received do not match"); + CHECK_AND_ASSERT_THROW_MES((num_received + m_no_change_in_tested_tx) == num_outs, "Number of received outputs do not match number of outgoing"); + CHECK_AND_ASSERT_THROW_MES((recv_out_idx.size() + m_no_change_in_tested_tx) == num_outs, "Num of outs received do not match"); } else { CHECK_AND_ASSERT_THROW_MES(num_received + 1 >= num_outs, "Number of received outputs do not match number of outgoing"); CHECK_AND_ASSERT_THROW_MES(recv_out_idx.size() + 1 >= num_outs, "Num of outs received do not match"); // can have dummy out @@ -1165,7 +1393,8 @@ void gen_trezor_base::test_trezor_tx(std::vector<test_event_entry>& events, std: CHECK_AND_ASSERT_THROW_MES(sum_in == sum_out, "Tx amount mismatch"); // Test get_tx_key feature for stored private tx keys - test_get_tx(events, wallets, exported_txs.ptx, aux_data.tx_device_aux); + CHECK_AND_ASSERT_THROW_MES(sender_account != nullptr, "Sender account is null"); + test_get_tx(events, wallets, sender_account, exported_txs.ptx, aux_data.tx_device_aux); } bool gen_trezor_base::verify_tx_key(const ::crypto::secret_key & tx_priv, const ::crypto::public_key & tx_pub, const subaddresses_t & subs) @@ -1187,6 +1416,7 @@ bool gen_trezor_base::verify_tx_key(const ::crypto::secret_key & tx_priv, const void gen_trezor_base::test_get_tx( std::vector<test_event_entry>& events, std::vector<tools::wallet2*> wallets, + const cryptonote::account_base * account, const std::vector<tools::wallet2::pending_tx> &ptxs, const std::vector<std::string> &aux_tx_info) { @@ -1228,7 +1458,7 @@ void gen_trezor_base::test_get_tx( dev_cold->load_tx_key_data(tx_key_data, aux_tx_info[txid]); CHECK_AND_ASSERT_THROW_MES(std::string(tx_prefix_hash.data, 32) == tx_key_data.tx_prefix_hash, "TX prefix mismatch"); - dev_cold->get_tx_key(tx_keys, tx_key_data, m_alice_account.get_keys().m_view_secret_key); + dev_cold->get_tx_key(tx_keys, tx_key_data, account->get_keys().m_view_secret_key); // alice CHECK_AND_ASSERT_THROW_MES(!tx_keys.empty(), "Empty TX keys"); CHECK_AND_ASSERT_THROW_MES(verify_tx_key(tx_keys[0], tx_pub, all_subs), "Tx pub mismatch"); CHECK_AND_ASSERT_THROW_MES(additional_pub_keys.size() == tx_keys.size() - 1, "Invalid additional keys count"); @@ -1242,11 +1472,16 @@ void gen_trezor_base::test_get_tx( void gen_trezor_base::mine_and_test(std::vector<test_event_entry>& events) { + if (!m_mining_enabled) { + MDEBUG("Mining test disabled"); + return; + } + cryptonote::core * core = daemon()->core(); const uint64_t height_before_mining = daemon()->get_height(); const auto miner_address = cryptonote::get_account_address_as_str(FAKECHAIN, false, get_address(m_miner_account)); - daemon()->mine_blocks(1, miner_address); + daemon()->mine_blocks(1, miner_address, std::chrono::seconds(m_mining_timeout)); const uint64_t cur_height = daemon()->get_height(); CHECK_AND_ASSERT_THROW_MES(height_before_mining < cur_height, "Mining fail"); @@ -1285,6 +1520,15 @@ void gen_trezor_base::set_hard_fork(uint8_t hf) } } +boost::optional<epee::wipeable_string> gen_trezor_base::on_pin_request() { + return boost::make_optional(epee::wipeable_string((m_trezor_pin).data(), (m_trezor_pin).size())); +} + +boost::optional<epee::wipeable_string> gen_trezor_base::on_passphrase_request(bool &on_device) { + on_device = false; + return boost::make_optional(epee::wipeable_string((m_trezor_passphrase).data(), (m_trezor_passphrase).size())); +} + #define TREZOR_TEST_PREFIX() \ test_generator generator(m_generator); \ test_setup(events); \ @@ -1294,7 +1538,7 @@ void gen_trezor_base::set_hard_fork(uint8_t hf) #define TREZOR_TEST_SUFFIX() \ auto _dsts = t_builder->build(); \ auto _dsts_info = t_builder->dest_info(); \ - test_trezor_tx(events, _dsts, _dsts_info, generator, vct_wallets(m_wl_alice.get(), m_wl_bob.get(), m_wl_eve.get())); \ + test_trezor_tx(events, _dsts, _dsts_info, generator, TREZOR_ALL_WALLET_VCT); \ return true #define TREZOR_SKIP_IF_VERSION_LEQ(x) if (m_trezor->get_version() <= x) { MDEBUG("Test skipped"); return true; } @@ -1307,7 +1551,7 @@ tsx_builder * tsx_builder::sources(std::vector<cryptonote::tx_source_entry> & so return this; } -tsx_builder * tsx_builder::compute_sources(boost::optional<size_t> num_utxo, boost::optional<uint64_t> min_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) +tsx_builder * tsx_builder::compute_sources(boost::optional<size_t> num_utxo, uint64_t extra_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) { CHECK_AND_ASSERT_THROW_MES(m_tester, "m_tester wallet empty"); CHECK_AND_ASSERT_THROW_MES(m_from, "m_from wallet empty"); @@ -1326,13 +1570,21 @@ tsx_builder * tsx_builder::compute_sources(boost::optional<size_t> num_utxo, boo return true; }; - fnc_accept_to_use = fnc_acc; - bool res = wallet_tools::fill_tx_sources(m_from, m_sources, m_mixin, num_utxo, min_amount, m_tester->m_bt, m_selected_transfers, m_cur_height, offset, step, fnc_accept_to_use); + const uint64_t needed_amount = m_fee + std::accumulate(m_destinations_orig.begin(), m_destinations_orig.end(), 0, [](int a, const cryptonote::tx_destination_entry& b){return a + b.amount;}); + const uint64_t real_amount = needed_amount + extra_amount; + MDEBUG("Computing sources for tx, |utxos| = " << (num_utxo ? std::to_string(num_utxo.get()) : "*") + << ", sum: " << std::setfill(' ') << std::setw(12) << ((double)real_amount / COIN) + << ", need: " << std::setfill(' ') << std::setw(12) << ((double)needed_amount / COIN) + << ", extra: " << std::setfill(' ') << std::setw(12) << ((double)extra_amount / COIN) + << ", fee: " << std::setfill(' ') << std::setw(12) << ((double)m_fee / COIN)); + + bool res = wallet_tools::fill_tx_sources(m_from, m_sources, m_mixin, num_utxo, real_amount, m_tester->m_bt, m_selected_transfers, m_cur_height, offset, step, fnc_acc, fnc_trezor_default_acceptor_tx_in); + CHECK_AND_ASSERT_THROW_MES(!num_utxo || num_utxo == m_sources.size(), "!!Test error, Number of computed inputs " << m_sources.size() << " does not match desired number " << num_utxo.get()); CHECK_AND_ASSERT_THROW_MES(res, "Tx source fill error"); return this; } -tsx_builder * tsx_builder::compute_sources_to_sub(boost::optional<size_t> num_utxo, boost::optional<uint64_t> min_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) +tsx_builder * tsx_builder::compute_sources_to_sub(boost::optional<size_t> num_utxo, uint64_t extra_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) { fnc_accept_tx_source_t fnc = [&fnc_accept] (const tx_source_info_crate_t &info, bool &abort) -> bool { if (info.td->m_subaddr_index.minor == 0){ @@ -1344,10 +1596,10 @@ tsx_builder * tsx_builder::compute_sources_to_sub(boost::optional<size_t> num_ut return true; }; - return compute_sources(num_utxo, min_amount, offset, step, fnc); + return compute_sources(num_utxo, extra_amount, offset, step, fnc); } -tsx_builder * tsx_builder::compute_sources_to_sub_acc(boost::optional<size_t> num_utxo, boost::optional<uint64_t> min_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) +tsx_builder * tsx_builder::compute_sources_to_sub_acc(boost::optional<size_t> num_utxo, uint64_t extra_amount, ssize_t offset, int step, boost::optional<fnc_accept_tx_source_t> fnc_accept) { fnc_accept_tx_source_t fnc = [&fnc_accept] (const tx_source_info_crate_t &info, bool &abort) -> bool { if (info.td->m_subaddr_index.minor == 0 || info.src->real_out_additional_tx_keys.size() == 0){ @@ -1359,7 +1611,7 @@ tsx_builder * tsx_builder::compute_sources_to_sub_acc(boost::optional<size_t> nu return true; }; - return compute_sources(num_utxo, min_amount, offset, step, fnc); + return compute_sources(num_utxo, extra_amount, offset, step, fnc); } tsx_builder * tsx_builder::destinations(std::vector<cryptonote::tx_destination_entry> &dsts) @@ -1513,11 +1765,11 @@ device_trezor_test::device_trezor_test(): m_tx_sign_ctr(0), m_compute_key_image_ void device_trezor_test::clear_test_counters(){ m_tx_sign_ctr = 0; m_compute_key_image_ctr = 0; + m_live_synced_ki.clear(); } -void device_trezor_test::setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type){ +void device_trezor_test::setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type, bool use_passphrase, const std::string & pin){ this->clear_test_counters(); - this->set_callback(nullptr); this->set_debug(true); // debugging commands on Trezor (auto-confirm transactions) CHECK_AND_ASSERT_THROW_MES(this->set_name(trezor_path), "Could not set device name " << trezor_path); @@ -1527,7 +1779,7 @@ void device_trezor_test::setup_for_tests(const std::string & trezor_path, const CHECK_AND_ASSERT_THROW_MES(this->init(), "Could not initialize the device " << trezor_path); CHECK_AND_ASSERT_THROW_MES(this->connect(), "Could not connect to the device " << trezor_path); this->wipe_device(); - this->load_device(seed); + this->load_device(seed, pin, use_passphrase); this->release(); this->disconnect(); } @@ -1540,6 +1792,7 @@ bool device_trezor_test::compute_key_image(const ::cryptonote::account_keys &ack bool res = device_trezor::compute_key_image(ack, out_key, recv_derivation, real_output_index, received_index, in_ephemeral, ki); m_compute_key_image_ctr += res; + m_live_synced_ki.insert(ki); return res; } @@ -1580,8 +1833,13 @@ bool gen_trezor_ki_sync::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement test interface"); if (!m_live_refresh_enabled) CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_compute_key_image_ctr == 0, "Live refresh should not happen: " << dev_trezor_test->m_compute_key_image_ctr); - else - CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_compute_key_image_ctr == ski.size(), "Live refresh counts invalid"); + else { + CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_compute_key_image_ctr >= ski.size(), "Live refresh counts invalid"); + for(size_t i = 0; i < transfers.size(); ++i) + { + CHECK_AND_ASSERT_THROW_MES(dev_trezor_test->m_live_synced_ki.count(ski[i].first) > 0, "Key image not foind in live sync: " << ski[i].first); + } + } return true; } @@ -1667,13 +1925,16 @@ bool gen_trezor_live_refresh::generate(std::vector<test_event_entry>& events) bool gen_trezor_1utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); + m_no_change_in_tested_tx = true; + const uint64_t amount = MK_TCOINS(1); + const fnc_accept_tx_source_t acceptor = [] (const tx_source_info_crate_t &info, bool &abort) -> bool { return info.td->amount() == amount && info.td->m_subaddr_index.minor == 0; }; t_builder->cur_height(num_blocks(events) - 1) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(boost::none, MK_COINS(1), -1, -1) - ->add_destination(m_eve_account, false, 1000) + ->add_destination(m_eve_account, false, MK_TCOINS(1) - TREZOR_TEST_FEE) // no change ->rct_config(m_rct_config) + ->compute_sources(boost::none, 0, -1, -1, acceptor) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1682,15 +1943,14 @@ bool gen_trezor_1utxo::generate(std::vector<test_event_entry>& events) bool gen_trezor_1utxo_paymentid_short::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); - TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(boost::none, MK_COINS(1), -1, -1) ->add_destination(m_eve_account, false, 1000) ->payment_id(TREZOR_TEST_PAYMENT_ID) ->rct_config(m_rct_config) + ->compute_sources(1, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1699,16 +1959,15 @@ bool gen_trezor_1utxo_paymentid_short::generate(std::vector<test_event_entry>& e bool gen_trezor_1utxo_paymentid_short_integrated::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); - TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(boost::none, MK_COINS(1), -1, -1) ->add_destination(m_eve_account, false, 1000) ->payment_id(TREZOR_TEST_PAYMENT_ID) ->set_integrated(0) ->rct_config(m_rct_config) + ->compute_sources(1, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1721,9 +1980,9 @@ bool gen_trezor_4utxo::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_eve_account, false, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1736,9 +1995,9 @@ bool gen_trezor_4utxo_acc1::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 1) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1751,9 +2010,9 @@ bool gen_trezor_4utxo_to_sub::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1766,10 +2025,10 @@ bool gen_trezor_4utxo_to_2sub::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({1, 3}), true, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1782,10 +2041,10 @@ bool gen_trezor_4utxo_to_1norm_2sub::generate(std::vector<test_event_entry>& eve ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) ->add_destination(m_wl_eve.get(), false, 1000) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->rct_config(m_rct_config) ->build_tx(); @@ -1799,11 +2058,11 @@ bool gen_trezor_2utxo_sub_acc_to_1norm_2sub::generate(std::vector<test_event_ent ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources_to_sub_acc(2, MK_COINS(1) >> 2, -1, -1) ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) ->add_destination(m_wl_eve.get(), false, 1000) ->rct_config(m_rct_config) + ->compute_sources_to_sub_acc(2, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1816,7 +2075,6 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) @@ -1825,6 +2083,7 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) ->add_destination(m_wl_eve.get(), false, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1837,7 +2096,6 @@ bool gen_trezor_4utxo_to_15outs::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(4, MK_COINS(1), -1, -1) ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) @@ -1853,6 +2111,22 @@ bool gen_trezor_4utxo_to_15outs::generate(std::vector<test_event_entry>& events) ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) ->add_destination(m_wl_eve.get(), false, 1000) ->rct_config(m_rct_config) + ->compute_sources(4, MK_TCOINS(0.0001), -1, -1) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); +} + +bool gen_trezor_16utxo_to_sub::generate(std::vector<test_event_entry>& events) +{ + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->rct_config(m_rct_config) + ->compute_sources(16, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1865,9 +2139,9 @@ bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events) ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(110, MK_COINS(1), -1, -1) ->add_destination(m_eve_account, false, 1000) ->rct_config(m_rct_config) + ->compute_sources(110, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); @@ -1880,7 +2154,6 @@ bool gen_trezor_many_utxo_many_txo::generate(std::vector<test_event_entry>& even ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) - ->compute_sources(40, MK_COINS(1), -1, -1) ->add_destination(m_eve_account, false, 1000) ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) @@ -1897,18 +2170,67 @@ bool gen_trezor_many_utxo_many_txo::generate(std::vector<test_event_entry>& even ->add_destination(m_wl_eve->get_subaddress({2, 4}), true, 1000) ->add_destination(m_wl_eve->get_subaddress({3, 4}), true, 1000) ->rct_config(m_rct_config) + ->compute_sources(40, MK_TCOINS(0.0001), -1, -1) ->build_tx(); TREZOR_TEST_SUFFIX(); } -void wallet_api_tests::init() +bool gen_trezor_no_passphrase::generate(std::vector<test_event_entry>& events) +{ + m_trezor_use_passphrase = false; + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->add_destination(m_eve_account, false, 1000) + ->rct_config(m_rct_config) + ->compute_sources(boost::none, MK_TCOINS(0.0001), -1, -1) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); + + return true; +} + +bool gen_trezor_wallet_passphrase::generate(std::vector<test_event_entry>& events) { m_wallet_dir = boost::filesystem::unique_path(); boost::filesystem::create_directories(m_wallet_dir); + + m_trezor_use_alice2 = true; + m_trezor_use_passphrase = true; + m_trezor_passphrase = m_alice2_passphrase; + test_setup(events); + + const auto wallet_path = (m_wallet_dir / "alice2").string(); + const epee::wipeable_string& password = epee::wipeable_string("test-pass"); + m_wl_alice2->store_to(wallet_path, password); + + // Positive load + MDEBUG("Loading wallet with correct passphrase"); + m_wl_alice2->callback(this); + m_wl_alice2->load(wallet_path, password); + + // Negative load + m_trezor_passphrase = "bad-passphrase-0112312312312"; + setup_trezor(m_trezor_use_passphrase, m_trezor_pin); + update_client_settings(); + m_wl_alice2->callback(this); + + MDEBUG("Loading wallet with different passphrase - should fail"); + try { + m_wl_alice2->load(wallet_path, password); + CHECK_AND_ASSERT_THROW_MES(false, "Wallet load should not pass with different passphrase"); + } catch (const tools::error::wallet_internal_error & ex) { + CHECK_AND_ASSERT_THROW_MES(ex.to_string().find("does not match wallet") != std::string::npos, "Unexpected wallet exception"); + } + + return true; } -wallet_api_tests::~wallet_api_tests() +gen_trezor_wallet_passphrase::~gen_trezor_wallet_passphrase() { try { @@ -1917,10 +2239,72 @@ wallet_api_tests::~wallet_api_tests() boost::filesystem::remove_all(m_wallet_dir); } } - catch(...) + catch(...) {} +} + +boost::optional<epee::wipeable_string> gen_trezor_wallet_passphrase::on_device_pin_request() { + return on_pin_request(); +} + +boost::optional<epee::wipeable_string> gen_trezor_wallet_passphrase::on_device_passphrase_request(bool &on_device) { + return on_passphrase_request(on_device); +} + +bool gen_trezor_passphrase::generate(std::vector<test_event_entry>& events) +{ + m_trezor_use_passphrase = true; + m_trezor_use_alice2 = true; + m_trezor_passphrase = m_alice2_passphrase; + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice2.get(), 0) + ->add_destination(m_eve_account, false, 1000) + ->rct_config(m_rct_config) + ->compute_sources(boost::none, MK_TCOINS(0.0001), -1, -1) + ->build_tx(); + + auto _dsts = t_builder->build(); + auto _dsts_info = t_builder->dest_info(); + test_trezor_tx(events, _dsts, _dsts_info, generator, TREZOR_ALL_WALLET_VCT, false, 1); + return true; +} + +bool gen_trezor_pin::generate(std::vector<test_event_entry>& events) +{ + m_trezor_pin = "0000"; + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->add_destination(m_eve_account, false, 1000) + ->rct_config(m_rct_config) + ->compute_sources(boost::none, MK_TCOINS(0.0001), -1, -1) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); + + return true; +} + +void wallet_api_tests::init() +{ + m_wallet_dir = boost::filesystem::unique_path(); + boost::filesystem::create_directories(m_wallet_dir); +} + +wallet_api_tests::~wallet_api_tests() +{ + try { - MERROR("Could not remove wallet directory"); + if (!m_wallet_dir.empty() && boost::filesystem::exists(m_wallet_dir)) + { + boost::filesystem::remove_all(m_wallet_dir); + } } + catch(...) {} } bool wallet_api_tests::generate(std::vector<test_event_entry>& events) @@ -1931,23 +2315,30 @@ bool wallet_api_tests::generate(std::vector<test_event_entry>& events) const auto api_net_type = m_network_type == TESTNET ? Monero::TESTNET : Monero::MAINNET; Monero::WalletManager *wmgr = Monero::WalletManagerFactory::getWalletManager(); - std::unique_ptr<Monero::Wallet> w{wmgr->createWalletFromDevice(wallet_path, "", api_net_type, m_trezor_path, 1)}; + std::unique_ptr<Monero::Wallet> w{wmgr->createWalletFromDevice(wallet_path, "", api_net_type, m_trezor_path, 1, "", 1, this)}; CHECK_AND_ASSERT_THROW_MES(w->init(daemon()->rpc_addr(), 0), "Wallet init fail"); auto walletImpl = dynamic_cast<Monero::WalletImpl *>(w.get()); CHECK_AND_ASSERT_THROW_MES(walletImpl, "Dynamic wallet cast failed"); WalletApiAccessorTest::allow_mismatched_daemon_version(walletImpl, true); + walletImpl->setTrustedDaemon(true); CHECK_AND_ASSERT_THROW_MES(w->refresh(), "Refresh fail"); + + uint64_t spent, unspent; + walletImpl->coldKeyImageSync(spent, unspent); + CHECK_AND_ASSERT_THROW_MES(walletImpl->rescanSpent(), "Rescan spent fail"); + uint64_t balance = w->balance(0); MDEBUG("Balance: " << balance); CHECK_AND_ASSERT_THROW_MES(w->status() == Monero::PendingTransaction::Status_Ok, "Status nok, " << w->errorString()); + const uint64_t tx_amount = MK_TCOINS(0.5); auto addr = get_address(m_eve_account); auto recepient_address = cryptonote::get_account_address_as_str(m_network_type, false, addr); Monero::PendingTransaction * transaction = w->createTransaction(recepient_address, "", - MK_COINS(10), + tx_amount, num_mixin(), Monero::PendingTransaction::Priority_Medium, 0, @@ -1955,14 +2346,23 @@ bool wallet_api_tests::generate(std::vector<test_event_entry>& events) CHECK_AND_ASSERT_THROW_MES(transaction->status() == Monero::PendingTransaction::Status_Ok, "Status nok: " << transaction->status() << ", msg: " << transaction->errorString()); w->refresh(); - CHECK_AND_ASSERT_THROW_MES(w->balance(0) == balance, "Err"); - CHECK_AND_ASSERT_THROW_MES(transaction->amount() == MK_COINS(10), "Err"); - CHECK_AND_ASSERT_THROW_MES(transaction->commit(), "Err"); - CHECK_AND_ASSERT_THROW_MES(w->balance(0) != balance, "Err"); - CHECK_AND_ASSERT_THROW_MES(wmgr->closeWallet(w.get()), "Err"); + CHECK_AND_ASSERT_THROW_MES(w->balance(0) == balance, "Err balance"); + CHECK_AND_ASSERT_THROW_MES(transaction->amount() == tx_amount, "Err amount"); + CHECK_AND_ASSERT_THROW_MES(transaction->commit(), "Err commit"); + CHECK_AND_ASSERT_THROW_MES(w->balance(0) != balance, "Err balance2"); + CHECK_AND_ASSERT_THROW_MES(wmgr->closeWallet(w.get()), "Err close"); (void)w.release(); mine_and_test(events); return true; } +Monero::optional<std::string> wallet_api_tests::onDevicePinRequest() { + return Monero::optional<std::string>(m_trezor_pin); +} + +Monero::optional<std::string> wallet_api_tests::onDevicePassphraseRequest(bool &on_device) { + on_device = false; + return Monero::optional<std::string>(m_trezor_passphrase); +} + diff --git a/tests/trezor/trezor_tests.h b/tests/trezor/trezor_tests.h index 0f9ee30ca..16d7641db 100644 --- a/tests/trezor/trezor_tests.h +++ b/tests/trezor/trezor_tests.h @@ -36,16 +36,24 @@ #include "../core_tests/chaingen.h" #include "../core_tests/wallet_tools.h" -#define TREZOR_TEST_FEE 90000000000 #define TREZOR_TEST_CLSAG_MIXIN 11 #define TREZOR_TEST_HF15_MIXIN 16 #define TREZOR_TEST_MIXIN TREZOR_TEST_CLSAG_MIXIN +#define TREZOR_TEST_MIN_HF_DEFAULT HF_VERSION_BULLETPROOF_PLUS +#define TREZOR_TEST_MAX_HF_DEFAULT HF_VERSION_BULLETPROOF_PLUS +#define TREZOR_TEST_KI_SYNC_DEFAULT 1 +#define TREZOR_TEST_MINING_ENABLED_DEFAULT false +#define TREZOR_TEST_MINING_TIMEOUT_DEFAULT 360 + +#define MK_TCOINS(amount) ((unsigned long long) ((amount) * (COIN))) +#define TREZOR_TEST_FEE_FRAC 0.1 +#define TREZOR_TEST_FEE MK_TCOINS(TREZOR_TEST_FEE_FRAC) /************************************************************************/ /* */ /************************************************************************/ class tsx_builder; -class gen_trezor_base : public test_chain_unit_base +class gen_trezor_base : public test_chain_unit_base, public hw::i_device_callback { public: friend class tsx_builder; @@ -54,7 +62,7 @@ public: gen_trezor_base(const gen_trezor_base &other); virtual ~gen_trezor_base() {}; - virtual void setup_args(const std::string & trezor_path, bool heavy_tests=false); + virtual void setup_args(const std::string & trezor_path, bool heavy_tests, bool mining_enabled, long mining_timeout); virtual bool generate(std::vector<test_event_entry>& events); virtual void load(std::vector<test_event_entry>& events); // load events, init test obj virtual void fix_hf(std::vector<test_event_entry>& events); @@ -76,42 +84,58 @@ public: std::vector<cryptonote::address_parse_info>& dsts_info, test_generator &generator, std::vector<tools::wallet2*> wallets, - bool is_sweep=false); + bool is_sweep=false, + size_t sender_idx=0); virtual void test_get_tx( std::vector<test_event_entry>& events, std::vector<tools::wallet2*> wallets, + const cryptonote::account_base * account, const std::vector<tools::wallet2::pending_tx> &ptxs, const std::vector<std::string> &aux_tx_info); virtual void mine_and_test(std::vector<test_event_entry>& events); virtual void rewind_blocks(std::vector<test_event_entry>& events, size_t rewind_n, uint8_t hf); + virtual cryptonote::block rewind_blocks_with_decoys(std::vector<test_event_entry>& events, cryptonote::block & head, size_t rewind_n, uint8_t hf, size_t num_decoys_per_block); virtual void set_hard_fork(uint8_t hf); crypto::hash head_hash() const { return get_block_hash(m_head); } cryptonote::block head_block() const { return m_head; } bool heavy_tests() const { return m_heavy_tests; } + bool heavy_test_set() const { return m_gen_heavy_test_set; } + void heavy_test_set(bool set) { m_gen_heavy_test_set = set; } void rct_config(rct::RCTConfig rct_config) { m_rct_config = rct_config; } - uint8_t cur_hf() const { return m_hard_forks.size() > 0 ? m_hard_forks.back().first : 0; } + uint8_t cur_hf() const { return !m_hard_forks.empty() ? m_hard_forks.back().first : 0; } size_t num_mixin() const { return m_top_hard_fork >= HF_VERSION_BULLETPROOF_PLUS ? TREZOR_TEST_HF15_MIXIN : TREZOR_TEST_CLSAG_MIXIN; } cryptonote::network_type nettype() const { return m_network_type; } std::shared_ptr<mock_daemon> daemon() const { return m_daemon; } void daemon(std::shared_ptr<mock_daemon> daemon){ m_daemon = std::move(daemon); } + boost::optional<epee::wipeable_string> on_pin_request() override; + boost::optional<epee::wipeable_string> on_passphrase_request(bool &on_device) override; + // Static configuration static const uint64_t m_ts_start; static const uint64_t m_wallet_ts; static const std::string m_device_name; + static const std::string m_miner_master_seed_str; static const std::string m_master_seed_str; static const std::string m_device_seed; static const std::string m_alice_spend_private; static const std::string m_alice_view_private; + static const std::string m_alice2_passphrase; + static const std::string m_alice2_master_seed; + static const std::string m_bob_master_seed; + static const std::string m_eve_master_seed; protected: - virtual void setup_trezor(); - virtual void init_fields(); + virtual void setup_trezor(bool use_passphrase, const std::string & pin); + virtual void init_accounts(); + virtual void init_wallets(); + virtual void init_trezor_account(); + virtual void log_wallets_desc(); virtual void update_client_settings(); virtual bool verify_tx_key(const ::crypto::secret_key & tx_priv, const ::crypto::public_key & tx_pub, const subaddresses_t & subs); @@ -126,17 +150,27 @@ protected: std::vector<test_event_entry> m_events; std::string m_trezor_path; + std::string m_trezor_pin; + std::string m_trezor_passphrase; + bool m_trezor_use_passphrase = true; + bool m_trezor_use_alice2 = false; bool m_heavy_tests; + bool m_gen_heavy_test_set; bool m_test_get_tx_key; rct::RCTConfig m_rct_config; bool m_live_refresh_enabled; + bool m_mining_enabled = TREZOR_TEST_MINING_ENABLED_DEFAULT; + long m_mining_timeout = TREZOR_TEST_MINING_TIMEOUT_DEFAULT; + bool m_no_change_in_tested_tx = false; cryptonote::account_base m_miner_account; cryptonote::account_base m_bob_account; cryptonote::account_base m_alice_account; cryptonote::account_base m_eve_account; + cryptonote::account_base m_alice2_account; hw::trezor::device_trezor * m_trezor; std::unique_ptr<tools::wallet2> m_wl_alice; + std::unique_ptr<tools::wallet2> m_wl_alice2; std::unique_ptr<tools::wallet2> m_wl_bob; std::unique_ptr<tools::wallet2> m_wl_eve; @@ -165,9 +199,9 @@ public: tsx_builder * payment_id(const std::string & payment_id) { m_payment_id = payment_id; return this; } tsx_builder * from(tools::wallet2 *from, uint32_t account=0) { m_from = from; m_account=account; return this; } tsx_builder * sources(std::vector<cryptonote::tx_source_entry> & sources, std::vector<size_t> & selected_transfers); - tsx_builder * compute_sources(boost::optional<size_t> num_utxo=boost::none, boost::optional<uint64_t> min_amount=boost::none, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); - tsx_builder * compute_sources_to_sub(boost::optional<size_t> num_utxo=boost::none, boost::optional<uint64_t> min_amount=boost::none, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); - tsx_builder * compute_sources_to_sub_acc(boost::optional<size_t> num_utxo=boost::none, boost::optional<uint64_t> min_amount=boost::none, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); + tsx_builder * compute_sources(boost::optional<size_t> num_utxo=boost::none, uint64_t extra_amount=0, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); + tsx_builder * compute_sources_to_sub(boost::optional<size_t> num_utxo=boost::none, uint64_t extra_amount=0, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); + tsx_builder * compute_sources_to_sub_acc(boost::optional<size_t> num_utxo=boost::none, uint64_t extra_amount=0, ssize_t offset=-1, int step=1, boost::optional<fnc_accept_tx_source_t> fnc_accept=boost::none); tsx_builder * destinations(std::vector<cryptonote::tx_destination_entry> &dsts); tsx_builder * add_destination(const cryptonote::tx_destination_entry &dst); @@ -208,11 +242,12 @@ class device_trezor_test : public hw::trezor::device_trezor { public: size_t m_tx_sign_ctr; size_t m_compute_key_image_ctr; + std::unordered_set<crypto::key_image> m_live_synced_ki; device_trezor_test(); void clear_test_counters(); - void setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type); + void setup_for_tests(const std::string & trezor_path, const std::string & seed, cryptonote::network_type network_type, bool use_passphrase=false, const std::string & pin = ""); bool compute_key_image(const ::cryptonote::account_keys &ack, const ::crypto::public_key &out_key, const ::crypto::key_derivation &recv_derivation, size_t real_output_index, @@ -315,6 +350,12 @@ public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_16utxo_to_sub : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + class gen_trezor_many_utxo : public gen_trezor_base { public: @@ -327,14 +368,52 @@ public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_no_passphrase : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + +class gen_trezor_wallet_passphrase : public gen_trezor_base, public tools::i_wallet2_callback +{ +public: + ~gen_trezor_wallet_passphrase() override; + bool generate(std::vector<test_event_entry>& events) override; + boost::optional<epee::wipeable_string> on_device_pin_request() override; + boost::optional<epee::wipeable_string> on_device_passphrase_request(bool &on_device) override; +protected: + boost::filesystem::path m_wallet_dir; +}; + +class gen_trezor_passphrase : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + +class gen_trezor_pin : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + // Wallet::API tests -class wallet_api_tests : public gen_trezor_base +class wallet_api_tests : public gen_trezor_base, public Monero::WalletListener { public: - virtual ~wallet_api_tests(); + ~wallet_api_tests() override; void init(); bool generate(std::vector<test_event_entry>& events) override; + Monero::optional<std::string> onDevicePinRequest() override; + Monero::optional<std::string> onDevicePassphraseRequest(bool &on_device) override; + void moneySpent(const std::string &txId, uint64_t amount) override {}; + void moneyReceived(const std::string &txId, uint64_t amount) override {}; + void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override {}; + void newBlock(uint64_t height) override {}; + void updated() override {}; + void refreshed() override {}; + protected: boost::filesystem::path m_wallet_dir; };
\ No newline at end of file diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 57bd568fc..a9f0944c8 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -98,6 +98,7 @@ set(unit_tests_sources output_selection.cpp vercmp.cpp ringdb.cpp + wallet_storage.cpp wipeable_string.cpp is_hdd.cpp aligned.cpp diff --git a/tests/unit_tests/long_term_block_weight.cpp b/tests/unit_tests/long_term_block_weight.cpp index b9ce2b247..5d954bf0c 100644 --- a/tests/unit_tests/long_term_block_weight.cpp +++ b/tests/unit_tests/long_term_block_weight.cpp @@ -106,16 +106,9 @@ static uint32_t lcg() } -struct BlockchainAndPool -{ - cryptonote::tx_memory_pool txpool; - cryptonote::Blockchain bc; - BlockchainAndPool(): txpool(bc), bc(txpool) {} -}; - #define PREFIX_WINDOW(hf_version,window) \ - BlockchainAndPool bap; \ - cryptonote::Blockchain *bc = &bap.bc; \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *bc = &bap.blockchain; \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -407,3 +400,38 @@ TEST(long_term_block_weight, long_growth_spike_and_drop) ASSERT_GT(long_term_effective_median_block_weight, 300000 * 1.07); ASSERT_LT(long_term_effective_median_block_weight, 300000 * 1.09); } + +TEST(long_term_block_weight, cache_matches_true_value) +{ + PREFIX(16); + + // Add big blocks to increase the block weight limit + for (uint64_t h = 0; h <= 2000; ++h) + { + size_t w = bc->get_current_cumulative_block_weight_limit(); + uint64_t ltw = bc->get_next_long_term_block_weight(w); + bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); + bc->update_next_cumulative_weight_limit(); + } + + ASSERT_GT(bc->get_current_cumulative_block_weight_limit() * 10/17 , 300000); + + // Add small blocks to the top of the chain + for (uint64_t h = 2000; h <= 5001; ++h) + { + size_t w = (bc->get_current_cumulative_block_weight_median() * 10/17) - 1000; + uint64_t ltw = bc->get_next_long_term_block_weight(w); + bc->get_db().add_block(std::make_pair(cryptonote::block(), ""), w, ltw, h, h, {}); + bc->update_next_cumulative_weight_limit(); + } + + // get the weight limit + uint64_t weight_limit = bc->get_current_cumulative_block_weight_limit(); + // refresh the cache + bc->m_long_term_block_weights_cache_rolling_median.clear(); + bc->get_long_term_block_weight_median(bc->get_db().height() - TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW); + bc->update_next_cumulative_weight_limit(); + + // make sure the weight limit is the same + ASSERT_EQ(weight_limit, bc->get_current_cumulative_block_weight_limit()); +} diff --git a/tests/unit_tests/net.cpp b/tests/unit_tests/net.cpp index 03072b283..b9555b213 100644 --- a/tests/unit_tests/net.cpp +++ b/tests/unit_tests/net.cpp @@ -74,6 +74,8 @@ namespace "xmrto2bturnore26.onion"; static constexpr const char v3_onion[] = "vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion"; + static constexpr const char v3_onion_2[] = + "zpv4fa3szgel7vf6jdjeugizdclq2vzkelscs2bhbgnlldzzggcen3ad.onion"; } TEST(tor_address, constants) @@ -94,12 +96,10 @@ TEST(tor_address, invalid) EXPECT_TRUE(net::tor_address::make(":").has_error()); EXPECT_TRUE(net::tor_address::make(".onion").has_error()); EXPECT_TRUE(net::tor_address::make(".onion:").has_error()); - EXPECT_TRUE(net::tor_address::make(v2_onion + 1).has_error()); EXPECT_TRUE(net::tor_address::make(v3_onion + 1).has_error()); - EXPECT_TRUE(net::tor_address::make(boost::string_ref{v2_onion, sizeof(v2_onion) - 2}).has_error()); EXPECT_TRUE(net::tor_address::make(boost::string_ref{v3_onion, sizeof(v3_onion) - 2}).has_error()); - EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":-").has_error()); - EXPECT_TRUE(net::tor_address::make(std::string{v2_onion} + ":900a").has_error()); + EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":-").has_error()); + EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":900a").has_error()); EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":65536").has_error()); EXPECT_TRUE(net::tor_address::make(std::string{v3_onion} + ":-1").has_error()); @@ -163,11 +163,11 @@ TEST(tor_address, valid) EXPECT_FALSE(address2.less(*address1)); EXPECT_FALSE(address1->less(address2)); - address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v2_onion} + ":6545")); + address2 = MONERO_UNWRAP(net::tor_address::make(std::string{v3_onion_2} + ":6545")); EXPECT_EQ(6545, address2.port()); - EXPECT_STREQ(v2_onion, address2.host_str()); - EXPECT_EQ(std::string{v2_onion} + ":6545", address2.str().c_str()); + EXPECT_STREQ(v3_onion_2, address2.host_str()); + EXPECT_EQ(std::string{v3_onion_2} + ":6545", address2.str().c_str()); EXPECT_TRUE(address2.is_blockable()); EXPECT_FALSE(address2.equal(*address1)); EXPECT_FALSE(address1->equal(address2)); @@ -244,57 +244,6 @@ namespace }; } -TEST(tor_address, epee_serializev_v2) -{ - epee::byte_slice buffer{}; - { - test_command_tor command{MONERO_UNWRAP(net::tor_address::make(v2_onion, 10))}; - EXPECT_FALSE(command.tor.is_unknown()); - EXPECT_NE(net::tor_address{}, command.tor); - EXPECT_STREQ(v2_onion, command.tor.host_str()); - EXPECT_EQ(10u, command.tor.port()); - - epee::serialization::portable_storage stg{}; - EXPECT_TRUE(command.store(stg)); - EXPECT_TRUE(stg.store_to_binary(buffer)); - } - - test_command_tor command{}; - { - EXPECT_TRUE(command.tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, command.tor); - EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str()); - EXPECT_EQ(0u, command.tor.port()); - - epee::serialization::portable_storage stg{}; - EXPECT_TRUE(stg.load_from_binary(epee::to_span(buffer))); - EXPECT_TRUE(command.load(stg)); - } - EXPECT_FALSE(command.tor.is_unknown()); - EXPECT_NE(net::tor_address{}, command.tor); - EXPECT_STREQ(v2_onion, command.tor.host_str()); - EXPECT_EQ(10u, command.tor.port()); - - // make sure that exceeding max buffer doesn't destroy tor_address::_load - { - epee::serialization::portable_storage stg{}; - stg.load_from_binary(epee::to_span(buffer)); - - std::string host{}; - ASSERT_TRUE(stg.get_value("host", host, stg.open_section("tor", nullptr, false))); - EXPECT_EQ(std::strlen(v2_onion), host.size()); - - host.push_back('k'); - EXPECT_TRUE(stg.set_value("host", std::move(host), stg.open_section("tor", nullptr, false))); - EXPECT_TRUE(command.load(stg)); // poor error reporting from `KV_SERIALIZE` - } - - EXPECT_TRUE(command.tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, command.tor); - EXPECT_STREQ(net::tor_address::unknown_str(), command.tor.host_str()); - EXPECT_EQ(0u, command.tor.port()); -} - TEST(tor_address, epee_serializev_v3) { epee::byte_slice buffer{}; @@ -397,41 +346,6 @@ TEST(tor_address, epee_serialize_unknown) EXPECT_EQ(0u, command.tor.port()); } -TEST(tor_address, boost_serialize_v2) -{ - std::string buffer{}; - { - const net::tor_address tor = MONERO_UNWRAP(net::tor_address::make(v2_onion, 10)); - EXPECT_FALSE(tor.is_unknown()); - EXPECT_NE(net::tor_address{}, tor); - EXPECT_STREQ(v2_onion, tor.host_str()); - EXPECT_EQ(10u, tor.port()); - - std::ostringstream stream{}; - { - boost::archive::portable_binary_oarchive archive{stream}; - archive << tor; - } - buffer = stream.str(); - } - - net::tor_address tor{}; - { - EXPECT_TRUE(tor.is_unknown()); - EXPECT_EQ(net::tor_address{}, tor); - EXPECT_STREQ(net::tor_address::unknown_str(), tor.host_str()); - EXPECT_EQ(0u, tor.port()); - - std::istringstream stream{buffer}; - boost::archive::portable_binary_iarchive archive{stream}; - archive >> tor; - } - EXPECT_FALSE(tor.is_unknown()); - EXPECT_NE(net::tor_address{}, tor); - EXPECT_STREQ(v2_onion, tor.host_str()); - EXPECT_EQ(10u, tor.port()); -} - TEST(tor_address, boost_serialize_v3) { std::string buffer{}; @@ -511,6 +425,9 @@ TEST(get_network_address, onion) address = net::get_network_address(".onion", 0); EXPECT_EQ(net::error::invalid_tor_address, address); + address = net::get_network_address(v2_onion, 1000); + EXPECT_EQ(net::error::invalid_tor_address, address); + address = net::get_network_address(v3_onion, 1000); ASSERT_TRUE(bool(address)); EXPECT_EQ(epee::net_utils::address_type::tor, address->get_type_id()); diff --git a/tests/unit_tests/output_distribution.cpp b/tests/unit_tests/output_distribution.cpp index ab474a7d8..038c19874 100644 --- a/tests/unit_tests/output_distribution.cpp +++ b/tests/unit_tests/output_distribution.cpp @@ -30,10 +30,7 @@ #include "gtest/gtest.h" #include "misc_log_ex.h" #include "rpc/rpc_handler.h" -#include "blockchain_db/blockchain_db.h" #include "cryptonote_core/cryptonote_core.h" -#include "cryptonote_core/tx_pool.h" -#include "cryptonote_core/blockchain.h" #include "blockchain_db/testdb.h" static const uint64_t test_distribution[32] = { @@ -77,9 +74,6 @@ public: bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64_t &start_height, std::vector<uint64_t> &distribution, uint64_t &base) { - std::unique_ptr<cryptonote::Blockchain> bc; - cryptonote::tx_memory_pool txpool(*bc); - bc.reset(new cryptonote::Blockchain(txpool)); struct get_test_options { const std::pair<uint8_t, uint64_t> hard_forks[2]; const cryptonote::test_options test_options = { @@ -87,9 +81,9 @@ bool get_output_distribution(uint64_t amount, uint64_t from, uint64_t to, uint64 }; get_test_options():hard_forks{std::make_pair((uint8_t)1, (uint64_t)0), std::make_pair((uint8_t)0, (uint64_t)0)}{} } opts; - cryptonote::Blockchain *blockchain = bc.get(); - bool r = blockchain->init(new TestDB(test_distribution_size), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); - return r && bc->get_output_distribution(amount, from, to, start_height, distribution, base); + cryptonote::BlockchainAndPool bap; + bool r = bap.blockchain.init(new TestDB(test_distribution_size), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); + return r && bap.blockchain.get_output_distribution(amount, from, to, start_height, distribution, base); } crypto::hash get_block_hash(uint64_t height) diff --git a/tests/unit_tests/scaling_2021.cpp b/tests/unit_tests/scaling_2021.cpp index 024a4b4fd..d90f0f9e6 100644 --- a/tests/unit_tests/scaling_2021.cpp +++ b/tests/unit_tests/scaling_2021.cpp @@ -50,9 +50,6 @@ public: } #define PREFIX_WINDOW(hf_version,window) \ - std::unique_ptr<cryptonote::Blockchain> bc; \ - cryptonote::tx_memory_pool txpool(*bc); \ - bc.reset(new cryptonote::Blockchain(txpool)); \ struct get_test_options { \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const cryptonote::test_options test_options = { \ @@ -61,7 +58,9 @@ public: }; \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ } opts; \ - cryptonote::Blockchain *blockchain = bc.get(); \ + cryptonote::BlockchainAndPool bap; \ + cryptonote::Blockchain *blockchain = &bap.blockchain; \ + cryptonote::Blockchain *bc = blockchain; \ bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \ ASSERT_TRUE(r) diff --git a/tests/unit_tests/wallet_storage.cpp b/tests/unit_tests/wallet_storage.cpp new file mode 100644 index 000000000..dacaff960 --- /dev/null +++ b/tests/unit_tests/wallet_storage.cpp @@ -0,0 +1,266 @@ +// Copyright (c) 2023, 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 "unit_tests_utils.h" +#include "gtest/gtest.h" + +#include "file_io_utils.h" +#include "wallet/wallet2.h" + +using namespace boost::filesystem; +using namespace epee::file_io_utils; + +static constexpr const char WALLET_00fd416a_PRIMARY_ADDRESS[] = + "45p2SngJAPSJbqSiUvYfS3BfhEdxZmv8pDt25oW1LzxrZv9Uq6ARagiFViMGUE3gJk5VPWingCXVf1p2tyAy6SUeSHPhbve"; + +TEST(wallet_storage, store_to_file2file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_file2file"; + const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_file2file"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string password("beepbeep"); + + const auto files_are_expected = [&]() + { + EXPECT_FALSE(is_file_exist(interm_wallet_file.string())); + EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys")); + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + }; + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.store_to(target_wallet_file.string(), password); + files_are_expected(); + } + + files_are_expected(); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.store_to("", ""); + files_are_expected(); + } + + files_are_expected(); +} + +TEST(wallet_storage, store_to_mem2file) +{ + const path target_wallet_file = unit_test::data_dir / "wallet_mem2file"; + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string password("beepbeep2"); + + { + tools::wallet2 w; + w.generate("", password); + w.store_to(target_wallet_file.string(), password); + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password); + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); +} + +TEST(wallet_storage, change_password_same_file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_same"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + epee::wipeable_string old_password("beepbeep"); + epee::wipeable_string new_password("meepmeep"); + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), old_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.change_password(w.get_wallet_file(), old_password, new_password); + } + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), new_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + } + + { + tools::wallet2 w; + EXPECT_THROW(w.load(interm_wallet_file.string(), old_password), tools::error::invalid_password); + } +} + +TEST(wallet_storage, change_password_different_file) +{ + const path source_wallet_file = unit_test::data_dir / "wallet_00fd416a"; + const path interm_wallet_file = unit_test::data_dir / "wallet_00fd416a_copy_change_password_diff"; + const path target_wallet_file = unit_test::data_dir / "wallet_00fd416a_new_change_password_diff"; + + ASSERT_TRUE(is_file_exist(source_wallet_file.string())); + ASSERT_TRUE(is_file_exist(source_wallet_file.string() + ".keys")); + + copy_file(source_wallet_file, interm_wallet_file, copy_option::overwrite_if_exists); + copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys", copy_option::overwrite_if_exists); + + ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); + ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + epee::wipeable_string old_password("beepbeep"); + epee::wipeable_string new_password("meepmeep"); + + { + tools::wallet2 w; + w.load(interm_wallet_file.string(), old_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + w.change_password(target_wallet_file.string(), old_password, new_password); + } + + EXPECT_FALSE(is_file_exist(interm_wallet_file.string())); + EXPECT_FALSE(is_file_exist(interm_wallet_file.string() + ".keys")); + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), new_password); + const std::string primary_address = w.get_address_as_str(); + EXPECT_EQ(WALLET_00fd416a_PRIMARY_ADDRESS, primary_address); + } +} + +TEST(wallet_storage, change_password_in_memory) +{ + const epee::wipeable_string password1("monero"); + const epee::wipeable_string password2("means money"); + const epee::wipeable_string password_wrong("is traceable"); + + tools::wallet2 w; + w.generate("", password1); + const std::string primary_address_1 = w.get_address_as_str(); + w.change_password("", password1, password2); + const std::string primary_address_2 = w.get_address_as_str(); + EXPECT_EQ(primary_address_1, primary_address_2); + + EXPECT_THROW(w.change_password("", password_wrong, password1), tools::error::invalid_password); +} + +TEST(wallet_storage, change_password_mem2file) +{ + const path target_wallet_file = unit_test::data_dir / "wallet_change_password_mem2file"; + + if (is_file_exist(target_wallet_file.string())) + remove(target_wallet_file); + if (is_file_exist(target_wallet_file.string() + ".keys")) + remove(target_wallet_file.string() + ".keys"); + ASSERT_FALSE(is_file_exist(target_wallet_file.string())); + ASSERT_FALSE(is_file_exist(target_wallet_file.string() + ".keys")); + + const epee::wipeable_string password1("https://safecurves.cr.yp.to/rigid.html"); + const epee::wipeable_string password2( + "https://csrc.nist.gov/csrc/media/projects/crypto-standards-development-process/documents/dualec_in_x982_and_sp800-90.pdf"); + + std::string primary_address_1, primary_address_2; + { + tools::wallet2 w; + w.generate("", password1); + primary_address_1 = w.get_address_as_str(); + w.change_password(target_wallet_file.string(), password1, password2); + } + + EXPECT_TRUE(is_file_exist(target_wallet_file.string())); + EXPECT_TRUE(is_file_exist(target_wallet_file.string() + ".keys")); + + { + tools::wallet2 w; + w.load(target_wallet_file.string(), password2); + primary_address_2 = w.get_address_as_str(); + } + + EXPECT_EQ(primary_address_1, primary_address_2); +} diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index d78cb732f..1c42f7f04 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -297,7 +297,7 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(query_key) - def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = '', autosave_current = True): + def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = '', autosave_current = True, enable_multisig_experimental = False): restore_deterministic_wallet = { 'method': 'restore_deterministic_wallet', 'params' : { @@ -308,6 +308,7 @@ class Wallet(object): 'password': password, 'language': language, 'autosave_current': autosave_current, + 'enable_multisig_experimental': enable_multisig_experimental }, 'jsonrpc': '2.0', 'id': '0' |