diff options
119 files changed, 4769 insertions, 1913 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ffdaeb55..f9d43aa55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,11 @@ set(RISCV 1) set(RISCV32 1) endif() +if(ARCH_ID STREQUAL "loongarch64") +set(LOONGARCH 1) +set(LOONGARCH64 1) +endif() + if(WIN32 OR ARM OR PPC64LE OR PPC64 OR PPC) set(OPT_FLAGS_RELEASE "-O2") else() @@ -747,7 +752,7 @@ else() message(STATUS "AES support explicitly disabled") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNO_AES") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNO_AES") - elseif(NOT ARM AND NOT PPC64LE AND NOT PPC64 AND NOT PPC AND NOT S390X AND NOT RISCV) + elseif(NOT ARM AND NOT PPC64LE AND NOT PPC64 AND NOT PPC AND NOT S390X AND NOT RISCV AND NOT LOONGARCH) message(STATUS "AES support enabled") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maes") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes") @@ -757,6 +762,8 @@ else() message(STATUS "AES support not available on s390x") elseif(RISCV) message(STATUS "AES support not available on RISC-V") + elseif(LOONGARCH) + message(STATUS "AES support not available on LOONGARCH") elseif(ARM6) message(STATUS "AES support not available on ARMv6") elseif(ARM7) @@ -48,7 +48,7 @@ all: release-all depends: cd contrib/depends && $(MAKE) HOST=$(target) && cd ../.. && mkdir -p build/$(target)/release - cd build/$(target)/release && cmake -DCMAKE_TOOLCHAIN_FILE=$(CURDIR)/contrib/depends/$(target)/share/toolchain.cmake ../../.. && $(MAKE) + cd build/$(target)/release && USE_DEVICE_TREZOR_MANDATORY=1 cmake -DCMAKE_TOOLCHAIN_FILE=$(CURDIR)/contrib/depends/$(target)/share/toolchain.cmake ../../.. && $(MAKE) cmake-debug: mkdir -p $(builddir)/debug @@ -102,9 +102,7 @@ The Bitcoin donation address is: Core development funding and/or some supporting services are also graciously provided by [sponsors](https://www.getmonero.org/community/sponsorships/): [<img width="150" src="https://www.getmonero.org/img/sponsors/tarilabs.png"/>](https://tarilabs.com/) -[<img width="150" src="https://www.getmonero.org/img/sponsors/globee.png"/>](https://globee.com/) [<img width="150" src="https://www.getmonero.org/img/sponsors/symas.png"/>](https://symas.com/) -[<img width="150" src="https://www.getmonero.org/img/sponsors/forked_logo.png"/>](http://www.forked.net/) [<img width="150" src="https://www.getmonero.org/img/sponsors/macstadium.png"/>](https://www.macstadium.com/) There are also several mining pools that kindly donate a portion of their fees, [a list of them can be found on our Bitcointalk post](https://bitcointalk.org/index.php?topic=583449.0). diff --git a/cmake/CheckTrezor.cmake b/cmake/CheckTrezor.cmake index 05b8ebd93..87582a713 100644 --- a/cmake/CheckTrezor.cmake +++ b/cmake/CheckTrezor.cmake @@ -75,7 +75,13 @@ if (USE_DEVICE_TREZOR) # Protobuf is required to build protobuf messages for Trezor include(FindProtobuf OPTIONAL) - FIND_PACKAGE(Protobuf CONFIG) + # PkgConfig works better with new Protobuf + find_package(PkgConfig QUIET) + pkg_check_modules(PROTOBUF protobuf) + + if (NOT Protobuf_FOUND) + FIND_PACKAGE(Protobuf CONFIG) + endif() if (NOT Protobuf_FOUND) FIND_PACKAGE(Protobuf) endif() @@ -83,11 +89,11 @@ if (USE_DEVICE_TREZOR) _trezor_protobuf_fix_vars() # 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) + if(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}") + elseif(${CMAKE_CXX_STANDARD} LESS 17 AND ${Protobuf_VERSION} GREATER 21) + trezor_fatal_msg("Trezor: Unsupported Protobuf version ${Protobuf_VERSION} with C++ ${CMAKE_CXX_STANDARD}. Please, use Protobuf v21.") + elseif(NOT 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}") @@ -150,9 +156,10 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) 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.") + if(PROTOBUF_LDFLAGS) + set(PROTOBUF_TRYCOMPILE_LINKER "${PROTOBUF_LDFLAGS}") + else() + set(PROTOBUF_TRYCOMPILE_LINKER "${Protobuf_LIBRARY}") endif() try_compile(Protobuf_COMPILE_TEST_PASSED @@ -164,7 +171,7 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) 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} + LINK_LIBRARIES "${PROTOBUF_TRYCOMPILE_LINKER}" ${CMAKE_TRY_COMPILE_LINK_LIBRARIES} OUTPUT_VARIABLE OUTPUT ) if(NOT Protobuf_COMPILE_TEST_PASSED) diff --git a/cmake/FindLibUSB.cmake b/cmake/FindLibUSB.cmake index f701b6398..647f3c656 100644 --- a/cmake/FindLibUSB.cmake +++ b/cmake/FindLibUSB.cmake @@ -119,10 +119,6 @@ if ( LibUSB_FOUND ) 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 AND NOT FREEBSD) OR (DEPENDS AND CMAKE_SYSTEM_NAME STREQUAL "Linux") OR ANDROID) find_library(LIBUDEV_LIBRARY udev) if(LIBUDEV_LIBRARY) @@ -132,6 +128,10 @@ if ( LibUSB_FOUND ) endif() endif() + 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 ) + # 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) @@ -141,7 +141,7 @@ if ( LibUSB_FOUND ) CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${LibUSB_INCLUDE_DIRS}" "-DLINK_DIRECTORIES=${LibUSB_LIBRARIES}" - LINK_LIBRARIES ${TEST_COMPILE_EXTRA_LIBRARIES} + LINK_LIBRARIES ${LibUSB_LIBRARIES} ${TEST_COMPILE_EXTRA_LIBRARIES} OUTPUT_VARIABLE OUTPUT) unset(TEST_COMPILE_EXTRA_LIBRARIES) message(STATUS "LibUSB Compilation test: ${LibUSB_COMPILE_TEST_PASSED}") diff --git a/contrib/depends/Makefile b/contrib/depends/Makefile index a1c93ae00..f612dbffb 100644 --- a/contrib/depends/Makefile +++ b/contrib/depends/Makefile @@ -1,5 +1,9 @@ .NOTPARALLEL : +# Pattern rule to print variables, e.g. make print-all_packages +print-%: FORCE + @echo '$($*)' + SOURCES_PATH ?= $(BASEDIR)/sources BASE_CACHE ?= $(BASEDIR)/built FALLBACK_DOWNLOAD_PATH ?= https://downloads.getmonero.org/depends-sources @@ -184,3 +188,4 @@ download: download-osx download-linux download-win download-freebsd download-and $(foreach package,$(all_packages),$(eval $(call ext_add_stages,$(package)))) .PHONY: install cached download-one download-osx download-linux download-win download-freebsd download-android download check-packages check-sources +.PHONY: FORCE diff --git a/contrib/depends/packages/native_cctools.mk b/contrib/depends/packages/native_cctools.mk index 0324f7acd..8c1ea4c62 100644 --- a/contrib/depends/packages/native_cctools.mk +++ b/contrib/depends/packages/native_cctools.mk @@ -6,6 +6,7 @@ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=70a7189418c2086d20c299c5d59250cf5940782c778892ccc899c66516ed240e $(package)_build_subdir=cctools $(package)_dependencies=native_clang native_libtapi +$(package)_patches=no-build-date.patch define $(package)_set_vars $(package)_config_opts=--target=$(host) --disable-lto-support --with-libtapi=$(host_prefix) @@ -14,6 +15,10 @@ $(package)_cc=$(host_prefix)/native/bin/clang $(package)_cxx=$(host_prefix)/native/bin/clang++ endef +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/no-build-date.patch +endef + define $(package)_config_cmds $($(package)_autoconf) endef diff --git a/contrib/depends/packages/native_libtapi.mk b/contrib/depends/packages/native_libtapi.mk index 56ee087cb..c5625501a 100644 --- a/contrib/depends/packages/native_libtapi.mk +++ b/contrib/depends/packages/native_libtapi.mk @@ -6,6 +6,11 @@ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=62e419c12d1c9fad67cc1cd523132bc00db050998337c734c15bc8d73cc02b61 $(package)_build_subdir=build $(package)_dependencies=native_clang +$(package)_patches=no_embed_git_rev.patch + +define $(package)_preprocess_cmds + patch -p1 -i $($(package)_patch_dir)/no_embed_git_rev.patch +endef define $(package)_config_cmds echo -n $(build_prefix) > INSTALLPREFIX; \ diff --git a/contrib/depends/packages/native_protobuf.mk b/contrib/depends/packages/native_protobuf.mk index 2dc11b23c..8d7649b67 100644 --- a/contrib/depends/packages/native_protobuf.mk +++ b/contrib/depends/packages/native_protobuf.mk @@ -1,4 +1,4 @@ -package=protobuf3 +package=native_protobuf $(package)_version=21.12 $(package)_version_protobuf_cpp=3.21.12 $(package)_download_path=https://github.com/protocolbuffers/protobuf/releases/download/v$($(package)_version)/ @@ -16,13 +16,13 @@ define $(package)_config_cmds endef define $(package)_build_cmds - $(MAKE) -C src + $(MAKE) -C src protoc endef define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -C src install + $(MAKE) DESTDIR=$($(package)_staging_dir) -C src install-binPROGRAMS install-nobase_dist_protoDATA endef define $(package)_postprocess_cmds - rm lib/libprotoc.a + rm -rf lib/ endef diff --git a/contrib/depends/packages/openssl.mk b/contrib/depends/packages/openssl.mk index a157762c7..8c9727fa7 100644 --- a/contrib/depends/packages/openssl.mk +++ b/contrib/depends/packages/openssl.mk @@ -4,9 +4,12 @@ $(package)_download_path=https://www.openssl.org/source $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=88525753f79d3bec27d2fa7c66aa0b92b3aa9498dafd93d7cfa4b3780cdae313 +# The bundled ranlib in Android NDK 18b inserts timestamps by default. +# To prevent reproducibility issues, we must enable [D]eterministic mode. + define $(package)_set_vars -$(package)_config_env=AR="$($(package)_ar)" ARFLAGS=$($(package)_arflags) RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" -$(package)_config_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB=ranlib +$(package)_config_env=AR="$($(package)_ar)" RANLIB="$($(package)_ranlib)" CC="$($(package)_cc)" +$(package)_config_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" PATH="$(host_prefix)/native/bin" CC=clang AR=ar RANLIB="ranlib -D" $(package)_build_env_android=ANDROID_NDK_ROOT="$(host_prefix)/native" $(package)_config_opts=--prefix=$(host_prefix) --openssldir=$(host_prefix)/etc/openssl --libdir=$(host_prefix)/lib $(package)_config_opts+=no-capieng @@ -38,6 +41,7 @@ $(package)_config_opts_arm_android=--static android-arm $(package)_config_opts_aarch64_android=--static android-arm64 $(package)_config_opts_aarch64_darwin=darwin64-arm64-cc $(package)_config_opts_riscv64_linux=linux-generic64 +$(package)_config_opts_loongarch64_linux=linux-generic64 $(package)_config_opts_mipsel_linux=linux-generic32 $(package)_config_opts_mips_linux=linux-generic32 $(package)_config_opts_powerpc_linux=linux-generic32 @@ -52,7 +56,7 @@ define $(package)_preprocess_cmds endef define $(package)_config_cmds - ./Configure $($(package)_config_opts) + ./Configure $($(package)_config_opts) ARFLAGS=$($(package)_arflags) endef define $(package)_build_cmds diff --git a/contrib/depends/packages/protobuf.mk b/contrib/depends/packages/protobuf.mk index 780357c90..9702506d0 100644 --- a/contrib/depends/packages/protobuf.mk +++ b/contrib/depends/packages/protobuf.mk @@ -21,12 +21,7 @@ define $(package)_build_cmds endef define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) -C src install-libLTLIBRARIES install-nobase_includeHEADERS &&\ - $(MAKE) DESTDIR=$($(package)_staging_dir) install-pkgconfigDATA + $(MAKE) DESTDIR=$($(package)_staging_dir) -C src install-nobase_includeHEADERS &&\ + $(MAKE) DESTDIR=$($(package)_staging_dir) install-pkgconfigDATA &&\ + cp src/.libs/libprotobuf.a $($(package)_staging_prefix_dir)/lib/ endef - -define $(package)_postprocess_cmds - rm lib/libprotoc.a &&\ - rm lib/*.la -endef - diff --git a/contrib/depends/packages/unbound.mk b/contrib/depends/packages/unbound.mk index d9ef0076a..254636bca 100644 --- a/contrib/depends/packages/unbound.mk +++ b/contrib/depends/packages/unbound.mk @@ -24,7 +24,7 @@ define $(package)_preprocess_cmds endef define $(package)_config_cmds - $($(package)_autoconf) ac_cv_func_getentropy=no + $($(package)_autoconf) ac_cv_func_getentropy=no AR_FLAGS=$($(package)_arflags) endef define $(package)_build_cmds diff --git a/contrib/depends/patches/native_cctools/no-build-date.patch b/contrib/depends/patches/native_cctools/no-build-date.patch new file mode 100644 index 000000000..8b7d1e1f7 --- /dev/null +++ b/contrib/depends/patches/native_cctools/no-build-date.patch @@ -0,0 +1,12 @@ +diff --git a/cctools/ld64/src/ld/Options.cpp b/cctools/ld64/src/ld/Options.cpp +index 3bb8324..033760d 100644 +--- a/cctools/ld64/src/ld/Options.cpp ++++ b/cctools/ld64/src/ld/Options.cpp +@@ -4279,7 +4279,6 @@ void Options::buildSearchPaths(int argc, const char* argv[]) + fVerbose = true; + extern const char ldVersionString[]; + fprintf(stderr, "%s", ldVersionString); +- fprintf(stderr, "BUILD " __TIME__ " " __DATE__"\n"); + fprintf(stderr, "configured to support archs: %s\n", ALL_SUPPORTED_ARCHS); + // if only -v specified, exit cleanly + if ( argc == 2 ) { diff --git a/contrib/depends/patches/native_libtapi/no_embed_git_rev.patch b/contrib/depends/patches/native_libtapi/no_embed_git_rev.patch new file mode 100644 index 000000000..b898ccb43 --- /dev/null +++ b/contrib/depends/patches/native_libtapi/no_embed_git_rev.patch @@ -0,0 +1,31 @@ +diff --git a/src/llvm/CMakeLists.txt b/src/llvm/CMakeLists.txt +index ab92717c8..4ad621ea3 100644 +--- a/src/llvm/CMakeLists.txt ++++ b/src/llvm/CMakeLists.txt +@@ -752,9 +752,10 @@ set(LLVM_SRPM_USER_BINARY_SPECFILE ${CMAKE_CURRENT_SOURCE_DIR}/llvm.spec.in + set(LLVM_SRPM_BINARY_SPECFILE ${CMAKE_CURRENT_BINARY_DIR}/llvm.spec) + set(LLVM_SRPM_DIR "${CMAKE_CURRENT_BINARY_DIR}/srpm") + +-# SVN_REVISION and GIT_COMMIT get set by the call to add_version_info_from_vcs. +-# DUMMY_VAR contains a version string which we don't care about. +-add_version_info_from_vcs(DUMMY_VAR) ++# A call to add_version_info_from_vcs() was removed, leaving SVN_REVISION ++# and GIT_COMMIT unset. Accordingly, LLVM_RPM_SPEC_REVISION is left empty. ++# This variable appears to be unused. Since it may be used in a future ++# update of native_libtapi this change serves as a precautionairy measure. + if ( SVN_REVISION ) + set(LLVM_RPM_SPEC_REVISION "r${SVN_REVISION}") + elseif ( GIT_COMMIT ) +diff --git a/src/llvm/cmake/modules/GenerateVersionFromCVS.cmake b/src/llvm/cmake/modules/GenerateVersionFromCVS.cmake +index 6b1c71983..e16326ed6 100644 +--- a/src/llvm/cmake/modules/GenerateVersionFromCVS.cmake ++++ b/src/llvm/cmake/modules/GenerateVersionFromCVS.cmake +@@ -24,7 +24,7 @@ include(VersionFromVCS) + set(ENV{TERM} "dumb") + + function(append_info name path) +- add_version_info_from_vcs(REVISION ${path}) ++ set(REVISION "git-0000000") + string(STRIP "${REVISION}" REVISION) + file(APPEND "${HEADER_FILE}.txt" + "#define ${name} \"${REVISION}\"\n") diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in index fc502c596..50eaaa593 100644 --- a/contrib/depends/toolchain.cmake.in +++ b/contrib/depends/toolchain.cmake.in @@ -147,6 +147,10 @@ if(ARCHITECTURE STREQUAL "riscv64") set(ARCH_ID "riscv64") set(ARCH "rv64gc") endif() +if(ARCHITECTURE STREQUAL "loongarch64") + set(ARCH_ID "loongarch64") + set(ARCH "loongarch") +endif() if(ARCHITECTURE STREQUAL "i686") SET(ARCH_ID "i386") diff --git a/contrib/epee/README.md b/contrib/epee/README.md index 8157d3e56..2937c1264 100644 --- a/contrib/epee/README.md +++ b/contrib/epee/README.md @@ -1 +1 @@ -epee - is a small library of helpers, wrappers, tools and and so on, used to make my life easier. +epee - is a small library of helpers, wrappers, tools and so on, used to make my life easier. diff --git a/contrib/epee/include/storages/portable_storage_val_converters.h b/contrib/epee/include/storages/portable_storage_val_converters.h index 96b0c024c..5eb9acffd 100644 --- a/contrib/epee/include/storages/portable_storage_val_converters.h +++ b/contrib/epee/include/storages/portable_storage_val_converters.h @@ -37,6 +37,7 @@ #include "misc_log_ex.h" #include <boost/lexical_cast.hpp> +#include <boost/numeric/conversion/bounds.hpp> #include <typeinfo> #include <iomanip> diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 7fcd02726..887f4aa40 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -31,6 +31,7 @@ #include "mlocker.h" #include <boost/utility/string_ref.hpp> +#include <boost/algorithm/string.hpp> #include <sstream> #include <string> #include <cstdint> @@ -69,23 +70,19 @@ namespace string_tools #ifdef _WIN32 std::string get_current_module_path(); #endif - bool set_module_name_and_folder(const std::string& path_to_process_); - bool trim_left(std::string& str); - bool trim_right(std::string& str); + void set_module_name_and_folder(const std::string& path_to_process_); + void trim_left(std::string& str); + void trim_right(std::string& str); //---------------------------------------------------------------------------- inline std::string& trim(std::string& str) { - trim_left(str); - trim_right(str); + boost::trim(str); return str; } //---------------------------------------------------------------------------- - inline std::string trim(const std::string& str_) + inline std::string trim(const std::string& str) { - std::string str = str_; - trim_left(str); - trim_right(str); - return str; + return boost::trim_copy(str); } std::string pad_string(std::string s, size_t n, char c = ' ', bool prepend = false); diff --git a/contrib/epee/src/readline_buffer.cpp b/contrib/epee/src/readline_buffer.cpp index ac68d1fdb..cefde158c 100644 --- a/contrib/epee/src/readline_buffer.cpp +++ b/contrib/epee/src/readline_buffer.cpp @@ -1,4 +1,5 @@ #include "readline_buffer.h" +#include "string_tools.h" #include <readline/readline.h> #include <readline/history.h> #include <iostream> @@ -173,7 +174,7 @@ static void handle_line(char* line) line_stat = rdln::full; the_line = line; std::string test_line = line; - boost::trim_right(test_line); + epee::string_tools::trim_right(test_line); if(!test_line.empty()) { if (!same_as_last_line(test_line)) diff --git a/contrib/epee/src/string_tools.cpp b/contrib/epee/src/string_tools.cpp index 3abb83c74..d8580c216 100644 --- a/contrib/epee/src/string_tools.cpp +++ b/contrib/epee/src/string_tools.cpp @@ -38,9 +38,12 @@ #include <cstdlib> #include <string> #include <type_traits> +#include <system_error> #include <boost/lexical_cast.hpp> +#include <boost/algorithm/string.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/utility/string_ref.hpp> +#include <boost/filesystem.hpp> #include "misc_log_ex.h" #include "storages/parserse_base_utils.h" #include "hex.h" @@ -147,46 +150,33 @@ namespace string_tools return pname; } #endif - - bool set_module_name_and_folder(const std::string& path_to_process_) - { - std::string path_to_process = path_to_process_; + + void set_module_name_and_folder(const std::string& path_to_process_) + { + boost::filesystem::path path_to_process = path_to_process_; + #ifdef _WIN32 path_to_process = get_current_module_path(); #endif - std::string::size_type a = path_to_process.rfind( '\\' ); - if(a == std::string::npos ) - { - a = path_to_process.rfind( '/' ); - } - if ( a != std::string::npos ) - { - get_current_module_name() = path_to_process.substr(a+1, path_to_process.size()); - get_current_module_folder() = path_to_process.substr(0, a); - return true; - }else - return false; - } + get_current_module_name() = path_to_process.filename().string(); + get_current_module_folder() = path_to_process.parent_path().string(); + } //---------------------------------------------------------------------------- - bool trim_left(std::string& str) - { - for(std::string::iterator it = str.begin(); it!= str.end() && isspace(static_cast<unsigned char>(*it));) - str.erase(str.begin()); - - return true; - } - //---------------------------------------------------------------------------- - bool trim_right(std::string& str) - { + void trim_left(std::string& str) + { + boost::trim_left(str); + return; + } - for(std::string::reverse_iterator it = str.rbegin(); it!= str.rend() && isspace(static_cast<unsigned char>(*it));) - str.erase( --((it++).base())); + //---------------------------------------------------------------------------- + void trim_right(std::string& str) + { + boost::trim_right(str); + return; + } - return true; - } - //---------------------------------------------------------------------------- std::string pad_string(std::string s, size_t n, char c, bool prepend) { if (s.size() < n) @@ -199,28 +189,17 @@ namespace string_tools return s; } - std::string get_extension(const std::string& str) - { - std::string res; - std::string::size_type pos = str.rfind('.'); - if(std::string::npos == pos) - return res; - - res = str.substr(pos+1, str.size()-pos); - return res; - } + std::string get_extension(const std::string& str) + { + return boost::filesystem::path(str).extension().string(); + } + //---------------------------------------------------------------------------- - std::string cut_off_extension(const std::string& str) - { - std::string res; - std::string::size_type pos = str.rfind('.'); - if(std::string::npos == pos) - return str; + std::string cut_off_extension(const std::string& str) + { + return boost::filesystem::path(str).stem().string(); + } - res = str.substr(0, pos); - return res; - } - //---------------------------------------------------------------------------- #ifdef _WIN32 std::wstring utf8_to_utf16(const std::string& str) { diff --git a/contrib/valgrind/monero.supp b/contrib/valgrind/monero.supp index 015b05a1c..8df2cc120 100644 --- a/contrib/valgrind/monero.supp +++ b/contrib/valgrind/monero.supp @@ -26,3 +26,34 @@ fun:_ZN2el4base7Storage7getELPPEv ... } + +{ + in boost multiprecision, we don't get a pointer to the actual code causing it but if so it's only in the test itself anyway + Memcheck:Overlap + fun:__memcpy_chk + fun:_ZL6MKHASHmm + fun:_ZN26difficulty_check_hash_Test8TestBodyEv + fun:_ZN7testing8internal38HandleSehExceptionsInMethodIfSupportedINS_4TestEvEET0_PT_MS4_FS3_vEPKc + fun:_ZN7testing8internal35HandleExceptionsInMethodIfSupportedINS_4TestEvEET0_PT_MS4_FS3_vEPKc + fun:_ZN7testing4Test3RunEv + fun:_ZN7testing8TestInfo3RunEv + fun:_ZN7testing8TestCase3RunEv + fun:_ZN7testing8internal12UnitTestImpl11RunAllTestsEv + fun:_ZN7testing8internal38HandleSehExceptionsInMethodIfSupportedINS0_12UnitTestImplEbEET0_PT_MS4_FS3_vEPKc + fun:_ZN7testing8internal35HandleExceptionsInMethodIfSupportedINS0_12UnitTestImplEbEET0_PT_MS4_FS3_vEPKc + fun:_ZN7testing8UnitTest3RunEv +} + +{ + valgrind seems to be confusing memmove with memcpy + Memcheck:Overlap + fun:__memcpy_chk + fun:memmove + fun:left_shift_byte<boost::multiprecision::backends::cpp_int_backend<512, 512, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void> > + fun:eval_left_shift<512, 512, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void> + fun:eval_left_shift<512, 512, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void> + fun:operator<<=<int> + fun:_ZN10cryptonote14check_hash_128ERKN6crypto4hashEN5boost14multiprecision6numberINS5_8backends15cpp_int_backendILj128ELj128ELNS5_16cpp_integer_typeE0ELNS5_18cpp_int_check_typeE0EvEELNS5_26expression_template_optionE0EEE + fun:_ZN10cryptonote10check_hashERKN6crypto4hashEN5boost14multiprecision6numberINS5_8backends15cpp_int_backendILj128ELj128ELNS5_16cpp_integer_typeE0ELNS5_18cpp_int_check_typeE0EvEELNS5_26expression_template_optionE0EEE + fun:_ZN26difficulty_check_hash_Test8TestBodyEv +} diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 3d52d5fb9..ad60c858d 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -449,579 +449,33 @@ void BlockchainDB::fixup() // There was a bug that would cause key images for transactions without // any outputs to not be added to the spent key image set. There are two - // instances of such transactions, in blocks 202612 and 685498. - // The key images below are those from the inputs in those transactions. + // instances of such transactions on mainnet, in blocks 202612 and 685498. // On testnet, there are no such transactions // See commit 533acc30eda7792c802ea8b6417917fa99b8bc2b for the fix + // Since its been 8 years since the fix was implemented, we can safely force + // nodes to re-sync if the bug is present in their chain. static const char * const mainnet_genesis_hex = "418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3"; crypto::hash mainnet_genesis_hash; epee::string_tools::hex_to_pod(mainnet_genesis_hex, mainnet_genesis_hash ); - set_batch_transactions(true); - batch_start(); if (get_block_hash_from_height(0) == mainnet_genesis_hash) { - // block 202612 (511 key images in 511 transactions) - static const char * const key_images_202612[] = - { - "51fc647fb27439fbb3672197d2068e4110391edf80d822f58607bd5757cba7f3", - "d8cf1c1bd41f13c4553186e130e6e2c1cd80135ddb418f350088926997a95ca9", - "95d2556c8acd1457dce7bfd9c83b1d82b821a55a9c9588b04b7b5cf562a65949", - "4b5d987fee1bb563a162d23e41741ad73560c003e26a09b6655f09496538daac", - "1d25ea86323d1578579d3894a54b99ea1c3e2dca547c6726c44aef67db958b02", - "92e46fb70be5d9df39ca83c4fc6ae26c594118314bb75502a9c9752a781d0b33", - "924d0cb9060d429be7e59d164a0f80a4dabc3607d44401b26fb93e7182ab435d", - "f63e4a23fec860fd4c3734623891330ac1ff5af251e83a0e6247287818b8a72f", - "5b14c5ef13738d015619b61dacefc2ade3660d25b35ef96330a8f4e2afc26526", - "d5016b012a2fb6ca23fd56ece544d847962264b4aee15efe1465805fd824a8fb", - "0a7f3da1d9dd341cd96829e484b07163099763ac7bd60603b7ee14f7dbcb278d", - "d716c03d7447d2b693f6f61b6ad36bd57344033fc1a11feaf60d569f40014530", - "23154a812e99ce226f6a87087e0812f419aed51289f1c0c359b0b61303b53a36", - "03940341e1a99d5b0c68eacfdf5a20df90d7d0a3d6089d39709cdd964490755c", - "ef09648814cfe071f5d8e9cfde57247ad09409265c4b6c84697bbb046809bd7e", - "8843ec52b0496ca4e895813cfe00bb18ea777d3618e9bd2e200287e888e2f5c7", - "8558bf39baf3df62b5d33cdf97163a365e6c44f4d6deef920730b4982b66449f", - "504d9380ce581de0af97d5800d5ca9e61d78df368907151ab1e567eb6445332a", - "673797763593c23b3ee07b43bd8760365e2c251a8a60a275528ff34a477110cc", - "25178c95e4d402c58d79c160d2c52dd3c45db2c78e6aaa8d24d35c64f19d4957", - "407c3a05dcc8bdcb0446b5d562cf05b4536fc7337344765215130d5f1e7ee906", - "4e7fa771a5455d8ee8295f01181a360cdc6467cc185c2834c7daf9fbf85b6f1d", - "6beb64cb024f9c5c988f942177fc9c1ec5ecfa85b7db0f13a17f9f98e8e46fe7", - "6406bfc4e486e64c889ea15577d66e5835c65c6a39ec081af8ac5acdc153b4b5", - "1b1da638709f9f85898af70ffaa5b88d5a4c9f2663ca92113c400ab25caf553c", - "da49407a9e1ed27abd28076a647177157c42517e2542e9b6a4921fdadf4e8742", - "3c3fdba2a792fddaeb033605163991a09933e8f05c6c934d718e50a613b64d69", - "82c60429171173739fa67c4807cab359620299e6ed2a9da80139b5b1e23c5456", - "0a19e5767e1381ac16f57cfa5aabd8463551d19f069f7f3465c11a8583253f3e", - "d0fae6ffdd4818399eae6224978f170f696150eaf699f2f218abc00c68530f96", - "0937889aeb3af5c64608d9a9f88229a068e53417448f00f9aa5e28c570cca8f8", - "d6072d269753020912524961ce8c6930cf35abe5c4b2bdc7fd678338d20a68fb", - "0e8bc9b06fcc842bdaa7df029bfd1f252d5defc014d58a006c10ab678ecf543f", - "9d42f90520f02c9258be53b123d69ddbce5f33f92a903d3fb4cf3358ff0e07b5", - "1cc05416b12cbe719617158773c3e6173435fc64e1ee44310dc696baecaeaa95", - "266b15222913c11ef6403ee15dc42c8c0e16bc5fa2f49110447802236e045797", - "791b123af3b71ac9497a010610f72207ff8ec642969b5cf0d2891b21e7eee562", - "946d4d7b084dc32495f22b35fc30394144c8e0ba04f3ad6e2c2bfb0173a2266d", - "2c015cb990c1583228d08b2d5de9227b950c3f57364fc1033bca5c0fbfd08c58", - "13fdc41862fd85507f579c69accb9cc6a40f5971bfa41e3caff598a3dcffd2fc", - "64b06d9874a83917c583c9439d1c736083377d67fda2961b623f7124663134c3", - "2fa49cd19e0aa02989991a4c3760f44be800fe8fb4d58b23aca382e10dc0d2d6", - "377628f265f799772e9fb6065be8b6eee200c329f729fe36c25ee179e4d20df9", - "ba94fa79134ce383b6a98b04dc6ad3d1b950e410d50a292bc770f9685e59fe91", - "875c924329f0733e31fe8d8aed70dc1906335b8a9984932b6368ea24edb39765", - "f31f4abb3f5ee42a5aae86d70b3bd9a9c1934933641893864dd333f89719d608", - "2bcd629e125514a780f568d3c2e8b12a2e7fbbee06e652bbeed3e7825508e31c", - "918b43581163ca1963de21bb9ac401756e75c3f00ac8dcfafc139f1ad5d7d998", - "5730dd57fa52749a0d6502b11c9d802ac495875542431310c674a65655b7c2a3", - "03f84b990683e569e2f6143bb963a2a8de411e7c4b7923117b94c7afcb4b43ea", - "b298c8510d35bd2be0ff0753ad7d98d480f4c6490bb67fb93cd4632ea726e8a7", - "0a771afbf9be104c01b89eaeb57073297d35ac8fbbcc0816820fdb9a29d26625", - "713d90d6ca1be1a4e05a5f8441dc528c699caa09eda49c09072f5f8291354c2e", - "988827f45c19330d9404309f63d536a447803cca7cb182ef005b074def09ab7d", - "9dcaa105b4def895f3faee704c250bdc924316f153cb972f3fb565beec0b7942", - "1c06c30afe65b59e9e22d6bb454e4209a03efe53cdbf27b3945d5d75b1b90427", - "49e08c13d1da209ec1aea7b7fbe0daa648e30febeb2aa5ffbaaabdd71a278ac2", - "e1c2e49ab7b829854c46a64772ded35459908e0f563edbcf5c612913b7767046", - "e08bb7d133490ca85a6325d46807170cd07618b6a5f6e1d069e44890cc366fab", - "5c73ca0691cde2f35b7933d6db33f0b642ec70d0bd3f2b0ebbd97754ca67e248", - "6404399151872a521dae767311712225dba65d810ba2feba209204221b5d772d", - "4a0c3aa6cef36f44edf08ad8fb1533d7e1186e317da8a3afb3d81af072043233", - "104b3e1af37cf10b663a7ec8452ea882082018c4d5be4cd49e7f532e2fea64e5", - "e723a46bf9684b4476c3005eb5c26511c58b7eb3c708ddf7470ee30a40834b32", - "18e6f0fa3aa779a73ceefabea27bff3202003fd2c558ec5f5d07920528947d57", - "c97e73eb593ff39e63307220796cc64974c0c8adac860a2559ab47c49bc0c860", - "13c363a962955b00db6d5a68b8307cd900ae9202d9b2deb357b8d433545244ac", - "76a488865151fab977d3639bac6cba4ba9b52aa17d28ac3580775ed0bff393e4", - "a14de587c9f4cd50bb470ecffd10026de97b9b5e327168a0a8906891d39d4319", - "b1d38ee1c4ca8ae2754a719706e6f71865e8c512310061b8d26438fedf78707e", - "772bb8a3f74be96fa84be5fa8f9a8ef355e2df54869c2e8ae6ad2bf54ed5057e", - "3083a7011da36be63e3f7cacd70ab52e364dd58783302f1cb07535a66b5735f5", - "2b1d892e3002aa3201deb4ffe28c0c43b75b8f30c96b5d43f6d5829049ecbd94", - "cb738aabe44c6fb17ade284bf27db0169e309bf8cf9c91c4e4e62856619a4c64", - "1707e04b792f4953f460f217b9bb94c84cef60736a749fb01277cfe0eaaa48c7", - "ab8b6bac9b8a4f00b78acb4bd50ed2758e0fa100964b6f298d2a943eb2af2b30", - "dd317193fef72490f3be01293b29e9c2f94eda10824a76ca74bf39dd7cb40ab2", - "4fb3d995b087af7517fcb79e71f43bac0c4fbda64d89417a40ca4a708f2e8bc1", - "549ba38c31bf926b2cb7e8f7f15d15df6388dce477a3aff0245caa44606849fc", - "7585c14ab9abbffb89d0fa9f00c78ecae9f7c9062e5d4f1fae8453b3951fc60b", - "953f855323f72461b7167e3df0f4fd746a06f5a7f98aa42acdce2eef822a0b2f", - "0931932d57dde94dcfb017179a5a0954b7d671422149738260a365ca44f50eb8", - "a3d179d16a4a275a3bb0f260cee9284db243abad637a9dbe92d02940f1c7ee8c", - "959843f1e76ff0785dafe312c2ea66380fdc32b9d6180920f05f874c74599a80", - "fbc36b3e1718fe6c338968b04caa01a7adb315d206abc63e56768d69e008a65d", - "f054de7eac5e2ea48072e7fb4db93594c5f5d2dfa0afe8266042b6adc80dfdca", - "39dfc68dc6ba8c457b2995562c867cef2f2cf994e8d6776a6b20869e25053f70", - "19ad7ca7629758c22ac83643605c8a32a6665bae8e35dbc9b4ad90343756ebb3", - "e89e80ea5c64cf7840f614f26e35a12c9c3091fa873e63b298835d9eda31a9ea", - "572c1b9a83c947f62331b83009cc2ec9e62eab7260b49929388e6500c45cd917", - "df0b21f679e6c0bf97f7b874e9f07c93c3467b092f3d9e1484e5646fda6eca5f", - "8f3b7c0f4b403af62fe83d3cfac3f1e2572af8afa4cea3f3e2e04291efe84cf6", - "aae8b8db243009d021d8c9897d52ee8125a17212f0a8b85f681ad8950ae45f0e", - "3d45a4957d27447dea83d9ae2ef392a3a86619bfcf8dda2db405a7b304997797", - "a5b0a619a8e3030b691bdba1ed951cd54e4bc2063602eae26d9791fb18e60301", - "14650df217dd64a2905cd058114e761502dff37d40e80789200bc53af29b265f", - "fd6a245ab5e4e6e18d7ba9b37478ce38248f0ab864e5511d2208ae3d25017e5f", - "fbe0be6dd42a11feb5db5ae56fcbbac41041ab04a35f1df075580e960c8eeab0", - "72f3f1213d9bec92ba9705b447d99cd0a6a446e37a3c1c50bb8ece1090bfe56e", - "20df836554e1534f62b2a6df9ce58e11c1b9b4746ce8ee3c462300a8c01f5e76", - "5c3d2a81b7331c86420ad32b6e9a212b73b1c3413724a0f91bf073eba18e2f1f", - "63264ddfb29cd36fc18f3ee6614c4101ba7229bc5ac375f912590d3f0df982f4", - "5ec4eb637761c1c9dbc6aa6649d4410508ef8d25d61ad6caa40c6ee3236d5515", - "270c70940536017915e1cdbc003de7279ec1c94cba1ef6130f4236f7e306e4f0", - "c1d1d57a7c03f6ddeeab5230a4910db8355e2143f473dea6e1d57c2f8d882b76", - "218c030a7fdc9917e9f87e2921e258d34d7740a68b5bee48a392b8a2acf1f347", - "ac47861c01c89ea64abee14cf6e1f317859ed56b69ae66377dc63e6575b7b1eb", - "23bf549c8a03f9870983c8098e974308ec362354b0dcf636c242a88f24fc2718", - "a3ce8b817e5212c851c6b95e693849a396c79b0d04b2a554de9b78933fbea2b7", - "7310120c1cc1961b0d3fce13743c8a7075ae426fe6cccaf83600f24cee106236", - "8fa0630f193777dcc4f5eccd1ad9ceacf80acdf65e52e4e01bf3a2b2fdd0dac6", - "4a5f5c87f67d573d0673f01abaebc26eaa62e6d04627588549cc9e6c142dc994", - "78971cccacc645116f9d380c167f955d54b386a22af112233f7de004fc0c8316", - "badc67216868e1de1bbe044bf0e6070e6ee0353d05c13fa0c43b1897db5219a2", - "c45b2a168bc51cbb615a79f97432cc4bb6b104da9cdc1fc640c930657452f71b", - "c17eda13541d14554c5db542155b08b6bf9cb403d425745b662ebc2b2b9b3a3b", - "313210cd9d2efc1603f07859bae7bd5fb5914f4a631b943f2f6ff5927a4e681a", - "6ee94ec8af4e6828f9b46c590ea55da640ef50e810a247d3e8cdf4b91c42d2c2", - "505b7a4d9f1ba6577aa2a941843f86c205b23b1ea21035925e587022e2f0aeed", - "98e6a7cd687e8192e300a8202999ec31ad57bc34f656f2ae90d148607ff6d29f", - "1be5db002c0a446cc2c1da363e5d08ae045cd8f5e76c8cccd65d5166393c0bdf", - "17c02ac6d390c5c735e1e873c40294220e89071fca08a5da396a131fa1ba8670", - "2540507c39ae6fdcd90de826077f0ca390da126166a25c15c048a60606a27367", - "5ab9328e525c7a017ef4f591d995ad4595d74cbf8ff4112af33b08c70661a304", - "9c105587a96c51d81422f64e46016564d22329760648c95dcac7218f3733f915", - "525afb1b94a75f1edc2b55c700071e14a2166acda003370047c30dba8ea80278", - "745d4a5d9f95ca4efa6261b6bcd4ecacd504b5b901a2ce1353c522a5c0c15dcc", - "5a5a568cd87ba34252ba254e6a320e1a7f52f13e7451bb887efb34ff881785f2", - "1ec50a80198cd830b51f4f7a0222015a268d9b40f04e7f838c7b8dc7abf63b01", - "68836b662d79349cb42f5cef54e6a066157d398cc87d3b13f29fc04e5cf364a5", - "658db317f355a9cbd86f673775cac0c308fe14967428fd283a36e300a6a53b2f", - "677d79a8c467dd9db38f0ef45c2787dd368f701a6b47bf7a5f06112c38da643e", - "2baa455d4066f5d628f9ecd315cb57deca71069db5d0d112ae0aa18a84b6f0d7", - "5e7b0889f351560081360ac2b1429b48b2f7d886227f144e3b198e2f1fa56ed9", - "c3d317fbf26e15add8b4f8f93df9de9b22297b8e4945ebab9ee249d4f72f4e45", - "3c0b705a5c1e31abc7e46d8ff3c148a033f6875454cfb67f8d2a2b9a57a5ba7e", - "a0ab74663561af2adc2d38be3569fbe7aa2454346416ac96e5eb26b1e01b1e2f", - "53526cffdb74327670566c1dacacffb7d30a43a7f1862ff8bab87737bfa5edb6", - "24c5d36ab98d88f87b2c71afb4ea8562e05c7aa0b50f3bc0f9ed50a4cd52989b", - "c3ce4de5f94dff65d11e33a865855a4404259cf45263914c884f79db4f35169d", - "f1009b6dcf30030cff872d636fb96ed233eb6ecb8ffed003c7da64e4f5a02f4c", - "e3729f58614d3b42450d1599d863983ab7e3e5c29fb57aad7958c8923a2627c4", - "31cf4792f7b5ce01b217ec80184edd2a7c49c0b21701f5494ee2c9bac67c28ca", - "b42a5c9c92a656c5bb2b759ce160fdfd245243aeb1786338faea63b62e9a60ce", - "a1efc8d5d0855933d5ac8fe5960c7acacb92fcb09bfbc929e5002f168251e648", - "c4322c7f3682ec94b0dcb42f13b498c36cf575d505aacc8ec8bf67a6a2abf4c9", - "684ee5aa3c98357aeaddcc30c6620939b52aeef729e24b4a46ccafc44f24d831", - "36180f2ae11d105e0efbfbddb94e6b45b08609a383e4e1a8fa3b06d7a8051de9", - "96c2d183eacc87581a0b17b8d07878bc10d436728510715109a7565d9972f8b5", - "3068c9d04d561c7e29e3f621280b61a61885de0e9ec35a66a3116ca7a9e09627", - "2eb94b9673ad6f8f88288fddfceae4baaeccb37bed88a35611d826ba06a5363b", - "fc8cd5fae8b81121001f7767dcd5f185c0fdcc88cce1fbb184ddbcfad697ba54", - "51521da1ecedea6d588d774eb155d936b32a14913c2f11d989bcc5116e65bf41", - "3d23542e597a83dd6307700d79058b920f281c65f832333734d8a0adec510495", - "11d2e01913ff0d4bd21970d709d88e63289492c0bbad7bff99c0d36858a841ca", - "de674f1eee3068d2bc8c2f2897d8556e5deb872869652f7d3a4e5dbc6f1063c8", - "e722d7f728526110c0921791b417afde4af1e87ae48ccc01911786197843104b", - "aaba3a4e2a7d20ab76edfbcccefc27acfd509db3638582c28230e73ffd71d340", - "1385a7209dafb9622dd4274179832e40c7fae19445383c34ef79adb0e4de0c98", - "108408531fca288d74de4a2c596eab8569e355d9ab2f8380f4d24073a6b3fa95", - "294476a86fcd39351ae452cdb8af9584067ec4501ec6182d0062bb154559fed3", - "e64b175e0284c5cb69c8db46344ed43b5ced8abfe3cbf0c197103cfd116944cd", - "cdd73a0f1fa7c14ed2177ae2163035333718847e49dd5cca6775bd20fc7553ad", - "d423d2a374bc66a4587a5e3affa327ca75b8116051320759a3b88a868a7b80d4", - "f13ad1e5b1315557d5497b58516eb3b0759d909725ddd0eb8a0dee439c6c0a48", - "3a600b547a6061186a54e491344fd50cc7d4f0566a386a40aba6545254773728", - "37a6f3f221fe17cc04a65aa544d5135e8297ecaf0853ba784dffacb74feb481b", - "0ca42d67d0f84b28861d63e919e6ce5ad527447fdc53a03d8497a0241bee9376", - "c7dbda42459e6fadb92c416eaef3e04674fc57915a93f3be4e656634c9654075", - "0e34d728ae4fe347a5afecdf886fbd4d48a65c5d0dfab807da6ae95b6b2d7a3a", - "f1bc69257ed510db5b2ed370010b037f452a29c49e36337264b3011ce2516216", - "33f98f6b8a8e202463955998fba3b790199daa893e5471554826cfd9daa5c02f", - "f8a0a37a2c9ebd7022d7cded1ee0318fd363020070b4cdaea800e44dcc1300d2", - "6862714daedb908a4d86a3a3f1e65ec2c29ae61501b4ddcaf184243dd095d71b", - "555cd19a6d21941c1174129b8bbcc70edcf0d6874262ce9e1e542351990d523d", - "2cd6b44326828f23a2aa33699754bfa072c4883f39d53616f6a6b74149b664b6", - "127f45d2eacb565c21c1030fe8054fd0a3a75705bc368924712aa145d414fa47", - "19225e2dae6e1166f21cdab1290194470ded09df9b66f3faad3c1cc9ebcf340f", - "b7b3f53f0539b2b4837b8bb9dae0ccbd200c8d36126d9f50199d68a4293a46d3", - "6b6323be01f27d6d759d9670825e8ebb9c4cd8016351702328df91cef36cfec8", - "020c31cfdfc5b22b10235745b89b311d271cf82f2ba16d03fdf7a8bc8538694b", - "62573218530182b79e40d0113b7d281dace6da33bfcd0f9318558de5e5c76f08", - "37d928416b15982f5bb8be40c5b62fae0b664e412c25891f8860c4242927e610", - "b07ad11134a5c0542d2b418ef3863e8ea8477de68d9310681818ddd40825fdb0", - "4af77cb76bab845b56470c95ce7b8cd84ce49a095984c1f3eed67b0ee344316e", - "e3fdd4668d8726ba6adc401ac662c0cf6b5c1872082c488ed7da966d425fb1c0", - "3dec71c81c7e78e879abc8da8b30e2446edbe98eeb8df9dafe9201ebb4c6a834", - "7105467d9c5e855f1362fbddf820ed5e757997862efc9000317d3830a2f60ef3", - "2821df94b021d3e77e8d9c0f3972340210f5ea2c0935cbf125cfc578d4d6722f", - "114e5807accc337a22598bded30ebf3e0cfd75877e239f10cb043f829c315ab5", - "d658a1c0354628cd7312593ab25d5b9083de8f0def6e8425f188101d256cd136", - "4818d3be9b2a38fcc8c85d6c46f69b502943f79cf2462dfb0b6499e761bcc836", - "92b8c943cb017b5f2d39264640c069f1ecced1d3ce9b3fd755d6df2fddb99458", - "6edbd0fdf064fcbccd4a9e7a8ea520b87cb7faf867f7fe8a5f53625beb575119", - "bf3b49c477dafb06af65bf09851c0fbef9dbc3152a7268d31b55a8e7a9a95860", - "0e234dbadfda1393be2f068182615dbb83736f84f87710b5c7965bdce9f4a26a", - "df5ceae34429e47b92bbd5505ba27666552e0eb619997f359c55699c3252b1ff", - "08c1c7d940d699a91a83249bd578772e8958ffe23179e6150f07b96f1b47ce1e", - "6f919a429270da0022d70062537bdc1b21c43a8abc552d8e366647e5b719d310", - "63c66e5fd5d27f6fda87912ce46fa91a5e5b3634ed147fa2986330fc2696d3fa", - "bde070b75296bca3aa494e7f549cd2bd1ff003776712bc98a3164b139b2054ab", - "66694196dac5b60cf5e0ae05db8f3894fe04d65910686806551f471a0a0472e9", - "0d2e97524b7ce4cf30b54e61b2689df036d099c53d42e2977b1671834bac39e7", - "e081af76e923455f408127862be5c9baf7de6b19b952aa2a1da997d4dfe594c0", - "121bf6ae1596983b703d62fecf60ea7dd3c3909acf1e0911652e7dadb420ed12", - "a25e7b17464df71cd84ad08b17c5268520923bc33fe78c21b756f17353ea39a0", - "e985f078fb44dbfdf3f4f34388f0f233a4e413e02297ee9a7dcc3fcceacd44f9", - "b9184cf45e6e6b112cd863b1719de1bcab2137eb957e8028edca3a204a9ebb96", - "157d177d5e4bcce0040eb4bddb681eacf9e2942e1c542a57ce851b4742a9cc4f", - "0823e06635c9a1a69fd8833d1e48df98d711c1c47c06f27bb384932db1bbe9ee", - "8beeec1fd1bcdecba235b449cc49abca69b6486ed1c0861a2bfb6a43c970b86f", - "349f61a1cfc9112e537522858a0edae732a2f8434cf4780d3d2ec1e96f581cca", - "587cdf72b5914d364f7e214a70481cf1131ee4a09e6b43e52428d2e56b000c13", - "a6aa0c179316534c7b9ffb5f42a2af98d1d3a166bfb413199579f259c7b5e6df", - "f9f3bb1ba8da5899b79186008ecfbd416b49f3b86d94045b91e34a40e41d5cff", - "0cdda65a60b7b4d94e794c9397e01f69fb29309ce4fac83e7392dbba6bc497f9", - "8fa5fce5ad09d43af7218ea5724cff2c4849a59ff73caf3bbca466e3c8538ba8", - "8874ef46008753fcc0b77eb7a4a8560e35966bf5a12bcad4203ad2b4c1f8bfbe", - "a8ee9a3aa2d0c08a951439ffb0e6d10315fc4776997b275de1ec19663e88c2c2", - "9c184cbbff464ab4d5f6bfa78c39bf0880fb93b1574139306a97acb940d415c9", - "5493a38c255c91ca49b958ed417f6c57e5bc444779d8f536f290596a31bc63d3", - "3e1e82b96cc599d9fc55ae74330692ccbfb538a4cc923975fd8876efe4b81552", - "16aaaf820c24c2726e349b0e49bbab72ca6eef7b3a2835de88d0cececa4da684", - "7fa52ba349f7203c3dbc2249f9881101a3318d21869dd59f17abf953d234db65", - "713d8018bb9ba3ab55c3a110120b9d7593514111075ef05f0fdb233ff2ceddc8", - "56063afb495759a0942f1c33f28a4fb8320c6d376cb3c9513249453e45f24a04", - "f9a6bacd9e055749b45174ecf3c3db18b78f3474761948a68adf601f54e59660", - "7ddd7c6d41572f93fe07c0300c34e455b6d1f4372204933bf45035241c8b060c", - "f81021b893a36b201de71330a2ea050b59dbf7560c62fa9cbea9261ab47a0ba2", - "a01fbe4114c18fd534ae1621404d25c08e3b6775a2631ff40279bafd8c9304f4", - "350fad8ebc938c6eb508a1483f385f577794a002bc1229db70a1b0131d174b9d", - "570cb8bce87f532c5051d8c4c864012408e672a7d492669e660251fb1e066bec", - "8cb6efbb129c84eba26d894c4293b476a6e9a1fe969c6ad18b554d2a57885f36", - "f384a98467bf7f084ca31bea121a4ec76e530f523d3225c21ed25a18544a9916", - "da127ab58ce557c2c95c20d6a291c2e5d880fff09dc28927b7bdfec97b995d72", - "a4d95b4f74366ec920d0a0c5d81265688cc18733ffc444cac9b01ae2431568aa", - "5ae2a71470570733422468bb733d53c85b1c8a6e7e6df5c05941556bcf342d1a", - "65a2d161ff0e095d3afe37584dbbe649f1e9cd440755b5a3c5b2a252d5c0b8bc", - "25ef70a8e41bb422ed7996a41160294e33238d6af17a532232f0a50b123431a2", - "f1f0f76ee901664a65b97104296babb9c7422370e99bb677ae07c2ee420e7f40", - "c3c66dda180b0330e75c8139b9f315a8c6b937f55d87d7be42e172bbac60d71e", - "5881786695a9e58e19d79f790c7d9243a847c0269c5770bdd01f5149c2a62a88", - "f2f816d3c8ebc7df06ab68d39223181aacc7be04364d1d4e69a56c33949bb983", - "80a1c3b6b2778d4846ad9fe0bb2dd5afd99aa897f8231bfaac45fde43d602d9f", - "72ad67cb043aa5df0c3dcc2464953a66893259d81c9cc7778c12bca3447fbd58", - "ad72420a7963b8d4536d8eba00b4b989992247cd8c01660e242a8e71edaf0e19", - "999d603d1cf6068e3bb6abe1bca976fa0ab84c4660b29ea8973de8b5cf5fd283", - "e137a5910f02a764c3a3d8f1579ac0c7e3cc34e58933216868efe010332c1e6e", - "10e0fa2362f59317626ae989bd1f962c583339d8d74d76c3e585243d152b91e8", - "1951c652704962f5c6e33a4d4aadfee5d53ce2253644d1ed542da3e278524a07", - "c938bccb7ba6c0217d8ba35ed91502aee051c8ae5bff05e88aab3b322aec936f", - "4d6386c689785edd5beb55911a3a9fc8914838c8192184199589beef8b6ddf9f", - "26f6f45a6468bc4b1f085fd28d63c264ee17564f9e247fc03ee179a0b579dcda", - "235b7bb82b72c61acd5979ca5f2ca740aee805a780ba22e11aae5cd34f6ec760", - "c027ffb585a1e4844b4907490b621b08c7e40a5e6f93e97bd4bb1b615bba9908", - "aa77fc8053d139b998577319de29457b78b1cc8b35a5f3526c0621eaa42ce6e8", - "afd0af9a11c5ae2a7c4a4571ce39ad57d8df70ef146ed83ad8eaff97b2387fb8", - "a1f8fee9f1da9a2b306489d00edf754187b55e97f4fe6f249432fe6c7f44d6be", - "4f12e8a123465a862060efb656299e6bef732e5954b02194308817b243e84d32", - "6a1ca62f7d6952ad2eba1c64035260319baf03deabf856ca860744fc886b3d3a", - "c72dd1fe890d6e4c1f7325a4f224e85aef6cdca8bf9441d228efaf126e02ba63", - "2f6ddcea18d891ef4252e657989de68adcc43c2175d49c0c059c5e49b9dd5aed", - "24efac0f240ed183c30398ee40307623f52113598f66c5864c90fc62643a2aec", - "6ba3ebc935e7cf7fbb446e7f5c12b19c4455e6894412b0eedee4fc945e429e9a", - "3519d6e5bc9649f97d07a07ef5471a553ffce35c7619f4f63e91a2ba38cbb729", - "65e073df352fa9917e5c2475167e6c523b68c1406e1b6e81109e2d4cc87c740d", - "d73bf816c3648a7d53d34be938c454e515efb0c974d5a225f33635540b2db90d", - "bce167790fc86a273db011757d09e2d1148521ce242c2ded58f26cc49993aacb", - "2d4286ed4039916f29602e86f47ea4c5b05998c0198509ca7799fcadfb337e8d", - "9837c495b1af4f76b09177514a0f3e1dceb509c52b91712f3c7d29dc1b91d09b", - "5c848b8291f39759903ce77f151acf40f3ab5afa2d4a45af62b320371c29a697", - "b92df5016ee947ce6a21365d3361977f7f2f6c14025a983c44e13d3e8cc72152", - "71d2f57222a39b1a7ed2df5e6fb23a964439b5a8e7d49b49d87e5cd5354baa75", - "88b44d0198fb15b0c20a97f87e021c744606bfd35eae2874f35c447aa4ac3cd4", - "29bb4c2557714119cd684da2867e689e37e3ca9c912db83ab84746816f6092ab", - "b1836d98a288752675b133b9018fa1edf174f311921d01926c1e1a5900c21029", - "a00645e090c7d96f3155ffbcfc41e526a763b0f53a98151ac4a7d4a5b14066b6", - "78aab09919d17773b0d62799b37bd2e2970f72f5d1eb6472489c367b6135374f", - "eb6123aeb28608f1c97b2bf62ef69f977cd0658a0ab491deebb1e972caa938c5", - "8dd7ef1650b1b30cdf7814ae4d61a237eb0acc3ec3ce0f72b1c25780329c2d7d", - "b1998419be3172858b990eea84fe10bb24b76c219cde277cb4305955fc7e0b65", - "1b10560016c4bc506eef9056dedc2943a17179081e6eaf85b48d37dc20eac3cc", - "1fb1d9d4d408a6734234910f554d272739a0d6fa401918d79b57be62c3f23ba2", - "dec878f54ce36788289b61d32de0d9539032aba22cd15522752f729659c7cc5c", - "fdbfd0773f5a66637b093dabf812197940d1134619a7e60a931b19915b7dab0a", - "21bd2c9aae052a1c187947d2964f2be4afa7b30248409c41a75522e88a4e7977", - "59326adab03416ec1d36699c18e5e4fa13ca1f2212d48c23bfdecb0be7263616", - "bcf263d39457c0aef8ef42fd98f8131198ec4fb203155dd5bcd759e499a9ca5c", - "f1ad083bcd8c7630eef93336d8a971ae8ae37166a6a00ac39684420c5f9afef8", - "d82ee2ac41b36e3c1787a01835bf054070486dc20f6565efedbbc37cd3bf5fa5", - "eba91a0dcbd3986299b0a3e66c116f77bd3421829291fd769522f01da26f107b", - "11016558b7e8c6386c6a3e862691dcba47e33e257f0e7df38901ea7c0eba894c", - "04f02795e34a0030e5186c8e700da8a46b1aa6bc0abed6b35c9c1cd6a73776b9", - "2628dc8ad7fb731d59456b2755a99c6701467125fa69816c21bfccabc31edf6b", - "9b7ca249ee5b45cd264492f30df09f708a7d9caed7beb9a5c6292f22e4c31f85", - "5c42e7caedf382092faaf392174792b3cf5f2fe29cb586387ee55611af0617c9", - "373f2fd5940a01feb79659c8b9099477f6d3e7b570ebb749c2ac336ea4be583d", - "fea22887147adc3a659a14902080b03e86b4b8b16985fdf0bbacaed00d812422", - "6a3e51a1443cff62af9fa12fafc8ea78ae74dac7792c9ae0f436f570ab33eb71", - "796be21e213d6d0cd6fbe2de1555fb73d1cf9edc041a9f1ff1ad583c4ca92460", - "03fcbcb31d3fd17f0eedb45ac5a51678c7c8b2b8498225d05f74e2892f017f72", - "d28da07c6c22daf9ae987b3033c33d3140e5a56fa1ffd7dc5c7853d55a45bcc7", - "fbb0ce02f50018741a12fc08eea80a18067d7bb0fcd96153d40bb8c808473aae", - "2bf7c05a0209b4ea31314f04bd754cd01c58102d7cde8c496c655b6494924354", - "1968a9e6e14ae86a1e02e6078fc4631851fce5dbac6aa34f860defd1ccfd0ded", - "d886181329c9e06462a1407f547d77b38ff2c868b86d8976aa478e1cbb3d66d4", - "0d465e02ff2f8eb0b5fb2fa9a38579c5d66337d4a16b58f8ed28d2d89fc02392", - "3196419015289807880ef24b6781734822d654dc260c0560d00bac65eacd5219", - "fa08390ddc333a2a12248d5ec3e51fff9b782227069fe5a0afbd8eba967ae8d1", - "49ae36a791cb84516688d59a1ed3e5112851d65f265078aa2d433b45fa984c8a", - "35daa428e12c59da6730760979aca3444d8b31149c6febd99fbfefa4b2372082", - "5ef1d697beba612ff31d1dc139817c313a4e2ad3977775943b635c141ef0f2a1", - "674256037ec00edb66b9355fb1d33a30a47a5d1f4bce2dd5475d89f1ea6502db", - "7b6f017bc550933af91eec32a79464f540c5e0c208703e175843ee4e9ffc0a50", - "bf0eb91da1d18dbb18fd9ff36c837387887ba142961176a44889718b2becb9dc", - "3e5ac43a05164b074a2ff6353e9882917c5a3dbe168c2695de895f7decf1a56c", - "35e8f004965347c2b18a000a83dd3a8084b8a4bf00522061ed1179aa1107c413", - "fccb0fff3a24e555ec96f702ec11d420338910d148fc7b039d67568ad3a0e032", - "5cab231048032dbf921b4fafa1951dd2da93bc3740667f31d5a1d5665b141261", - "ffedb24be73441fbcd069f7785ebb865870e0f3ed228190732e4ffd5962bb82d", - "a4fcfec18adf92f4ed822f64d2da9f5ae630885a1bfa626530f641db99aa7a30", - "f98bcee41b0e3deafa1efaa1863750dbfd9bd7430b82529b670867d852230b5d", - "8ab8d5fca047a52364a737c1af57bf656c9ad5049f08ef4c5aa252e61aa72123", - "91318b39ad94c1d58143586b6d90dd6092a9d7487e321f4976967b6ac445ff43", - "fabfbd4569ab018e12d5ffa9b1a40ca8eb2ca60a685817351d90eaa770d5eccb", - "bbc5ef34428d980e2401942ceecfe07cdf21bfb1acae0596ea1d43fccf504f69", - "26943e4201ea407a5667103fe07ca6e08ef76940f274349b0e2e776bcfb0acb6", - "e3b305ffe33e72841f8e2a8688cc5cc27d42aee7624b33b7b6399b42db392437", - "17c5a763dd57e6bcc7c4cf2db0eb5cf3e97116b67fe0dd519c97e4a4d55d5a62", - "bbd260216879ce86af8318ffcf73c9e063ca76dd8bc35d3b6be45b2b4184888b", - "41285591d0595bc42ab663051b410d51af39fe1720592e27acb1a8af72360a76", - "f29acd6068ce494d0c0fe294cad91bb8968e3fff3f595a113227ab545c3ca3e6", - "ec9013c6394528e7dd788ce7cc085ca79fcdfbb37565999d5b4b5a4e39452ecc", - "27829bd7f1a8fcddcad0cc34a3b3fc67d62a2f3e09f8e75d35035c2281e83afd", - "666bea9db4e15087204d076294d221d4cf5864f5d94de38f29132b1934a17ace", - "a3a30924cad3dbda3446e5a6324e0a1390c70f795d5ecfe17ee5c70b14f7d87d", - "19567fe5fdb10711d60aa4d9843e1c49c2a6d2fd1b5cf662e2105606bb7815d3", - "b139f1c3a2f15596b9320334e37e4184d5d584c4a81e72d821a7edcad3aa62d5", - "08f1531e0e3e8f8bae313b2c60a72d5601bf8b60d7a4d2f60e8481650340d250", - "c5895669e1ff182bf1dd6c00dc955265e08ded0952b8ca62a1c63ba11c99f4ca", - "84d1c28153f66c1a4eb5fa0df695e936d83294df31a08d8d8e2d4798d19d8ce0", - "b8699f6af853fdbe897848feb46a05d733363f304eac4c8c1932e6ea4bc062cb", - "10eb3f6c1d0661468d9ed83593e5e9c0b43c6feec6a5405a498194905ea6ed48", - "509e215a600d9cadcbf5d62632ba321d7314764218db00ce8c907e425fccc445", - "e62119b7be84c8eaad41ba7f4a35da403f4ed96b967a3134e89ee8b78f8792c2", - "f790754a95d59ea5ffe6b9b5cc386c600a9e19e8bec997c192764365f1d05376", - "990121b5aa4d6badfb7154db4cdbb4512124bc2f442bebac71ea90b5cc86f203", - "b6983dedaa891eb14c964d84461e5cd37ed27b61771c64978ba83e3ecea194fa", - "00fba1ceaa6aa1e378cd5b22a771d6070250ac37f4e17d7bf1a70b3139e9a860", - "429854e7738abf2ecf46909454039e2fc5a080eb9a3c0c5ea13b07426dac3ad9", - "ceb3e017944b0dd787be219d8629930b3f2e20e22b12dc88fd838488ebb896f3", - "eb9e5d14424c63e675fe771b73ca865f7d38cf334d65e2426e12a0b88c1a2236", - "556ee713449e6e59ac4b8b2e8310180c8f6280484e9db23456526cceb9216168", - "bc89c3aa889e0144ac526a1f861227430dde7e439cc6a7e9b25c9a049c3ca7b3", - "56d070c62ea99be66fff741a8e45fafda5f9ff783e84d5395b251f356ce4e16f", - "ace15859c399e5ecd13b1420d3c3703c6a88dfb4a084f7225e7ba40a4b444fc8", - "f03f1261ab6eb879fe9c5b0028cd400b3ffdfac4344e4c75f6cde3c05ded1f26", - "955b2fda8d0068226f89270028b316b5adac871f1c1c85435479aba14a381b0f", - "422509a98d7461a6b8ec87cbb173b2486577b59ea9b269e01c20567b38b3b3b2", - "007d4de62ad89a4f5985f0cd9b76a7293acf383b4e9e734e813b9df1d57f986f", - "13a04e32948225b7e22aa0137341ebbb811e0743032fac16be9d691a652db2eb", - "8244b11d880a52f9f9e1830a822e6eeeaf0b12fc759f8261bc2f490cb0794f3b", - "27d3415f8f8fd3048a1ee0d9487256dd1b0f9e31be663778efa5b3add63868ec", - "0053f888db916a8905320e253fe2f0666355e6fb6de36f897231920a3edfe39f", - "0bc5c0a2ea47fa3bb8be107e3b9d6b7226b1c8bd16ca1bab8f51b8e1de08aa8b", - "4ca13aaa161c79025b5cd6c9a8ac33554f5ceb479fe293d9a342c865cd9c9948", - "333afbe82e2a3df38bd1ef998f39c5feef2554697aa21b5410c0e95ef9156249", - "587c4fcabd18ff890064171fce3d5be0c4aa1bba46893fb6a827a85ab54d20f3", - "964328e4d51d67c4e2f1fd102a66b861d98199f81d18d660b1b4b52504cd772a", - "196aad5594651efd679d30b9feb0f0d172cf977b4f89aa670ec741a8bf21e669", - "9137bfd66bbf47bfa0bfcbb9f6e71b6eb3fd9776530e9fd30d3dab19e182f35d", - "8217392c4ed2313188f295d94148a027a7f43824a5f5fba33a0b8c1045d509b4", - "be9e12761519a4836e73015544163254877e1c4912fcea67a10e7026421dde75", - "7b5220421a520b876cc6cdba0d3895104d7fac914dca5b93f9fe8b226566b71e", - "5c83fccfeb4bf0eb8a94d43ebc84a81639a32f26c7ef77d0a2b626b7de7befdb", - "132fd6c92cf176f975efdb5ded53470b462a48a2815c6f54a93ce4f935784cc7", - "46a3dba364022d11aa616a2bc20e3be5c4390f38b9446edfa464d90d9af5d48f", - "34b3f3fd8a83905a37762060f51d0b116377b4820b031b8e668e16f46c5b0285", - "f0e397e033dabec859a4b9a9043c5f1fb0dba504764d6bcf2fe9bf2ffd318474", - "85ecf59c7dd3b24ad17f591bc4737f32f1384c370a7a6f2add06a811dc824b6c", - "4d03cdb1e6ad8e066a83654626d8c221433e8d4fd901c671300af37e000177f2", - "61cb9c651893e6401b25f2bdf79c9f3ddc9ffe69cf6c791c420462bd73e347e1", - "85f2686a42158cd5ad327781ecccd1bdcd346941dd4b4edc45f717de6a011800", - "92de2ab82cac528e6d4ccd61e5b7c79591dcad9909c8ad3c8276ece6d48c0f08", - "23a878a06bb94bff33083349149f3c860f2b00bc3fb28f04cbaf717c08af19a3", - "1b1cce18ff0323566b192885d7ced30f9a9531a2580240f2c593a7d5b8580974", - "08fcdec7ea1376d84f3b13a47a4b73c7781c9c7890bb28f712b58af4fd3f24fe", - "03cc08fc4ece807c6495272c412be23b045622cc6b786ed8d5c94156ae678a0e", - "c4d8a61dc3f5dcf4b83f27a90cbc37e816cf4754e12309626ec5679c99087c46", - "b29d00681e29001cdd63c4bc50e5e25715faef692aeebb678c8050e1c095e888", - "ac154617e93f2bb1afa232675f2135437a9cc9700c14c51c40084946596ba11a", - "ce9549de8e68ae89f424dd9e1cde8a4eea2069da667cfcfbe837691d37366668", - "426c45a98e2af35cc9708149f6c086ff5a3972e77d62c627d5e20de5d731cad8", - "7e21bfe240a3d9b77a129c734a1d428dbc890379fbaf862853f48b2f7470b2b0", - "fa090a71f77223a7210de6db18d9aa809e89fb15253aea28131df6c5a7639140", - "7094ad044c5ab025e088b43aa0b947601fabe58ed700a412fd96e4b917ced0c8", - "936d5cdc4f081b6fe36c356af4378d472cd7990303f2ea44da645afd7d5d7f9c", - "05342037d3b69349dce7b95529d4b2a63ceb9d9393217a68f7cc8c958a96c3ea", - "ff9e1c414ef27b1178b1de296526f50520b7ddb06286bf9c47792bfb449e40b6", - "2f2b7bedb34d2854b17ccb702cddd8bc0157e39721d58be0b2ad54ee291fc9f1", - "0d8db1f34140bbf7eb809137018a74af08cb3345b8a3e368cdda8521dab45791", - "b109e4bfabcfe4a1c38be1156d9ca851c75e6aa2e57c0869e40cd9056f571e07", - "5cb363547ca077c806fc69bc8c2006831ab89e72fd778ac1a48fa810934e350e", - "85ee928bb110fd64eae54a91fc8548883e7fc4c60a3c61b505c31cea2d295c86", - "1ec3df7d10ee6fd5f0532ad4fe771e6befc28b0bed0250bf523695d6d49a8246", - "de9db2fc07c866bd7b885fb41522b63d550d0ce2e8ac5e14464a41733c2319e6", - "9a27136422a8f56768db29ba172a7ba26c3c7aa910324e78e5ab3a3268ac3674", - "60213c315119bf9005cb533d1a5b403b4a13c59982fe7773d30fdd8f519f4205", - "40eb61ef1812eb8a4d389599bf449fc86653b2c4986061b952f46fc049de53f4", - "658ef0d8140162b5f04591be13b47456245f531208bbca3260b857ca09b803c5", - "02270fa66255048d724894e2206b4e773cc6a7b6d17ca090cdc25f317d5f53b9", - "2ec6a0147f419161f7198d05be5f93152d9ccc10672db0ea47ff1687c0f0dd15", - "4be1d8ceb96eb80ef7ce30079ded31163272aeccff5c18fe3aaa32ea2f5bed9d", - "04ecaf48f44de87243b17b4c71ebff00020738639336010fa57435a54b623798", - "e313a9feb7cfd1d56ec87b1f1062ff9a80da498f7b761af4bef0cecd1b4c385b", - "ede3748f971f22341f7f5844dc60fc03cdb30c7cc720ebe13ae588c17a78aa94", - "d90c0faa70e39b7c0a8c55457ed6e6478a4e4bf3707b08104326a1ee8377c3ab", - "c79ffd0bbc8d004cc542e212990df6498abddd3deb50fd00ed00a2ff690974d6", - "35c37d88cf73a89c4124b0ee537347c37fdb47156c8b0ecc509efc58236ed3f0", - "a99182f343ccf05e557ffa6df71f03688b2afcf314c59daa774fe78db6f47add", - "01115397a78af8a4ae2727ca7a01843235b626bd3db80888d3dfd0020d4135e2", - "4a55aced578470d2f7280096d7fe8095f294095fba4778d1977d6db9270472f0", - "4624adf8a5633f65b213b8ca46b55cb0ee36c41495f39b1ae70cbd545779b1a0", - "d72bcd5b57a9c47e7bd5e9a1103657d10beb7b6c6d41f2b2985bc3bd3cc74860", - "48baadb9a46293c92f29e7617846171356a42c3b5d18d49a05a7e173993785f7", - "3da927737af8cb0e1c77097e35c54158d18aabfb3051c45bcc7ddfe00b157b1f", - "b4a24bfdb2cb802c8d48a3a18fbfe18622a767fe7eddfca57d4555550ccd1643", - "c58f82ac7c49dcba1721a88358f07636c9df60d3fd383e5789b808dc57a1dc9d", - "5e1f756eff5155df073d30f4452bdafc4adaf4f35960771bf2c1e30137fd7a79", - "be4a332f289338d67bd4834eae3128c488a61d255e972da484b6252b67a46b89", - "d496e4a36238d03a83d8b45cf33d9388aa7568a279b034d1cdd87b457356cc5b", - "a1c5212730ccda34de393210e276bbd44720dda777bcfd602315a3eee582f7dc", - "08914ec63f6ef7fe1d678937dc0f6178883440b26b4aca29fce79068947e8397", - "49e2cd2bd9b974074d9814f93eed371620bd4ea5fbf97a625065704e8fb382d7", - "047c194111818b48ce93a4b006e4a09b9a2650757a87357111796e11e847bf23", - "5955b0baa8265341f35a6f24fdc79066ba3ca9c5354c69b6b37a9ef3a26be556", - "d7c962f3ea1938c5267cca4072548acf3afcce4d438ed62027caa211a5f98e8b", - "c8cfba67cb4ce7e291b35154a50476d9a5c6bbb5d6cbaaa5d2408547fea7b02b", - "de8a940d8a69a64ce264d2ab7320662aef2e391c587cb2aae22a86718d5dffbb", - "94176f1310b26e54d4de48f87b74aa0b60532f184a2268508dece86dd7f85d36", - "9ce6ee3fca56c9256a69df404782301300a6e5e7f5a25a1f6d68c0e9e42584c9", - "7c423c4a220c6ff43ab6432f92b166323c58ee77f8c096ba0b00d52d7bd507e8", - "0b62b9c1ae4d4988720e8d41d980b334458189de0a3dd01699338d7b07c3894e", - "64f45f6f75110624506c53716f2fc1d5fe5f88f82a5bc6a7459ce70eae56dbb3", - "12ffbeb8e52fed161af4d8a015d1a5c45dcb8240e5c8933ce3a88ba2c58f97e8", - "2ee6b7b96043c8ded9fb52f87cdd0d0580ca6f8cef183c8a656394a11c0aa393", - "aaef26b1f5726258bf9ce305a3e54bca65cf68779f90f9d24287245c27362e27", - "ef59588dce57c35d010bea4d209f44c62f0b7c7e65bc0226c0e4971934da9435", - "0e606c2f6f8dcd579faf4739312bd7327ac7796fa44a81780fe0d66fc7761fb9", - "2f307198afdbde5f95989a17e06ce1bb9ff36c441cf3b2248431534fe13bb9fe", - "51418e6df23d450aaacc74ef2df53a6b1693727b70bda9ebc43acfc23d8fb5ea", - "6e9e3ac46705ed80520695b924435b00d2b3079598bf7faca7fd1524be777e5e", - "1e96241e2876aad29ce64f5d7e7fbb8db7265449df816c0d30a96633778c5cb6", - "81788f00eb72696d811f946e65d2c96528c45590874a1defdd46651e9b79a3a1", - "d9aa5a9f1df50e933d7105a5d72b5fe96bfbb9fd4b5b0eaa5e80af12e72d497a", - "e1b6976a732d27fc5d6a96b6d3d0d1d5eaa6ec46bae4665f17e7a43aefc75280", - "9151c75edcec1cc90aa2d2c240ff657b0eef3f5f1ec37418c8854b2493114f1d", - "3e7c12d0132421f08ea0a390cfa325e422a6b35120fb2eb650f108a165237934", - "1ee0e85c7d8a91089c03f37318cdc9127026bf789e3ce4b75046eaa3eebd3458", - "4ae64a3ef66cad847409ce175bd5365c9097fd21647a05730ac6b45841add3c8", - "f0ea0f334cf1d64678a6dab08c07e2f94f339e8389bd17ebc882b5c8b736cbf2", - "da904db96060546ff69e28993ce8183766da9402ac10fae9fc1f1d67ebd83c90", - "db11820615f7b5e47778c45d2e083e77f49b608b587dc09ec26f077aba07a242", - "ff5c726a83bd785484de75bb03b421f9e8e382bf2740120a2fcf72326aa01c75", - "f8643a7efd6304980db323303ab73a6fd4f4ee1047520d39d571580395b97f21", - "8facf9737d07838aedf6030593bfb247d8c29fe8d9b18b2913408627a4424d7e", - "f0672964aa6e4c7dd4768e18827023787386927f4db89fd661444979afb43c18", - "6fd3649c8401f2704ed2be18518b870eb6bf2b9a6689d1b336f05bd8b49017f6", - "ed172dac7de827493e0c0fdd8d3299333acb678e72ed499e0224b389cc1e0fba", - "7f9a8a8cc8e34add11934a1a5882be5978a6d28405cb0a053ec5699a502b1cef", - "6ca829ebd2a0a40994f68c1db7978ec274b45c46e9b351df869a2bfe0630bbd4", - "0bbb017c437573a55db88258a9d9a01188bdd23bb6b26903b137814871661f47", - "9a6358d2541f46b6d05b80fe25a2cb025fbe9e4b227a6275908d5ee31c948569", - "a75d26c6d4ce944024f10e6d23e8b5b888d680120e15dc0e4fee8d8833ee0c6a", - "1d43d33556699b42c124b46e41243abf48727fe488428056fdd174a3861c1e3c", - "7b5bd3fecbaa093f005c4f806ea67846c0df6b04df7729925cb14724f6a8b582", - "6bdf2b54f2f5ab90191261d33dee80fede75896994016422b28db8ba62327d82", - "1a16181b250085a91ccecc118473fa2ab98515e894a7b63b347c24b5be560c7a", - "22d24891755910b48ad632358c26245bdcc375abc41f7e2c9fb3c7773dbf4e22", - "5b70c5d4a373d541619c944fcc3b61259550b0e9fba3eca16f0879e5845b43b9", - "b78f9098c9d76987b7409e63426a8d49972bb4e75289576c680cf96513d44b6d", - "916b53b8e85eb7e0a2a76d6fc8d2163430e7183ccb103d6705f54af4bb070907", - "dc3d78f43110d2aa9df83c5485ec33663ad5452b8cdeb1aeeac9d6b1487fb781", - "3975539ed5402cb9f5ab503584524dd141cc4296b666ec66d807f94f62b1c026", - "bd1f97fd89183643423073f22733880616456ca41960699f18e868cb9ac35508", - "90b468ff0f83460c3dd8cfe778c39d32c6bb1eeff9ac5de7804a4050d3b8073e", - "0e886d49d88b82c9f8dbdd2f38a535992f35ab16629724d746394db3898235fa", - "76c22e965242d1ca5614e829d9028dcad9c4b09393bcbdd318b0365557335fb9", - "59b168488ec8629f820a1efd8fc5a0c2adec4253e61d6a2945a68a9a43be9035", - "ff172b42854eaf2865caa985d2fb6283c5ab19574126623ffd615a761a5bce72", - "cb46ac9ccc024ee74c96e3cf1c13a6949a432e855dfa881b6a307c0e6daac59e", - "9971574924c0f413bf4c0f96bb9c2fbdfea8f475e33a8fb6f15fa40903b63444", - "1a95567deb0f45a8941e2248f33286485984a5e9d86d16c37d42169ad864dc36", - "205fc7f7ec7a83f0bc22d5269c91762cd00adc7428456d799be5a0cd76f08b0f", - "849dd41ef59a722901b7a0deb2c1fd3c110a91a726120a0a119cb7a15cf98438", - "a32880917c714612101af95e5c8d8eb5fb046fdcc68bae76c05b829b3fa73c2e", - "70b38d6d510d13b359dffa910329952c620a4bce4ee7a8552b9bb3a14572394d", - "ef257ab2f4226faa6ba288a6793f026609068effb866c18496a847e8b60b102d", - "e5196ab42ff53c8352288bde6b6b7312cd6f39f7d21b556b0db178d8470d5790", - "ca98f128bf085f2b718f2b3c12da7c4d98887cc94251a2b1705b637611bd83bb", - "79508f0b93a49ec19c5cb05906ca1ba3d3db8ed4f9c6884873d0d7e3e985ea51", - "9088be3f47f9debc63e928739f7163182b49eab044518b151f0b89f6b6aefdd0", - "46b2782fd669b6288a4d7348cf6671360277ba4864cc69bce3497369ac2ec31e", - "0fa5131557db67b430d516530be939ff25882adf68a076602f3dfad8c77c963a", - "3404302cc097d5457244453a4c9990804201ee8161188df811bcb32404998c71", - "856939710dbb90a8eeda875a31f9a52af759bd932b88e7b08df35414c54d4721", - "72569573b9b41d0ac5ce17764a139c6b8b36ef3ca6d92cec625dbcdae758ba22", - "9746da344e435a008d6acb4847211bb676376ecc76c825b5d44a28b89ceeb40e", - "3eafded1595516f032e33ec975f4c9c3a1055d13aa5575cf8a801d6103fdbeb4", - "e88a6d2daa863c0787cc523a2cab45c546fad788951b10d75e2b0954db24cca7", - "38f531e67f88f66de44d3357c8e8f2db456160ca31dd2024c9562f6afd260278", - }; - // block 685498 (13 key images in one transaction) - static const char * const key_images_685498[] = - { - "749b7277aa21c70c417f255fb181c3a30b44277edf657eaaebf28a2709dd2a90", - "5a9b3e1a87332d735cedaa2b5623a6a5e99d99f5a2887c8fc84293577e8bf25c", - "bea438768445eb3650cf619bf6758e94035abfe0ccda91d4a58c582df143d835", - "376e237ff4da5e5cbd6e1bba4b01412fa751f2959c0c57006589f382413df429", - "14ac2f2e044f8635a3a42ecb46c575424073c1f6e5ed5e905f516d57f63184b5", - "2d1d2ecb77b69a2901de00897d0509c1b7855a3b2f8eb1afe419008fc03cd15a", - "ea01658f0972b77ae9112d525ec073e3ec5c3b98d5ad912d95ab2636354b70b6", - "d3934864a46101d8c242415282e7fc9ee73ad16cd40355535d226ab45ecdb61a", - "ee379b05c5d02432330ebd4ea9c4f1c87d14c388568d526a0f8a22649a14e453", - "aeb7b842b410b13ca4af7a5ffd5ae6caddc8bfec653df1b945e478839a2e0057", - "451806929d9f5c3a7f365472703871abadc25b2a5a2d75472a45e86cd76c610b", - "272d9b9fcc9e253c08da9caf8233471150019582eaefef461c1f9ceff7e2c337", - "633cdedeb3b96ec4f234c670254c6f721e0b368d00b48c6b26759db7d62cf52d", - }; + // From tx 17ce4c8feeb82a6d6adaa8a89724b32bf4456f6909c7f84c8ce3ee9ebba19163 + static const char * const first_missing_key_image_202612 = + "121bf6ae1596983b703d62fecf60ea7dd3c3909acf1e0911652e7dadb420ed12"; if (height() > 202612) { - for (const auto &kis: key_images_202612) - { - crypto::key_image ki; - epee::string_tools::hex_to_pod(kis, ki); - if (!has_key_image(ki)) - { - LOG_PRINT_L1("Fixup: adding missing spent key " << ki); - add_spent_key(ki); - } - } - } - if (height() > 685498) - { - for (const auto &kis: key_images_685498) + crypto::key_image ki; + epee::string_tools::hex_to_pod(first_missing_key_image_202612, ki); + if (!has_key_image(ki)) { - crypto::key_image ki; - epee::string_tools::hex_to_pod(kis, ki); - if (!has_key_image(ki)) - { - LOG_PRINT_L1("Fixup: adding missing spent key " << ki); - add_spent_key(ki); - } + LOG_PRINT_L1("Fixup: detected missing key images from block 202612! Popping blocks..."); + while (height() > 202612) + pop_block(); } } } - batch_stop(); } bool BlockchainDB::txpool_tx_matches_category(const crypto::hash& tx_hash, relay_category category) diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 9628a5c4d..540e56f8d 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -718,41 +718,6 @@ public: */ virtual std::string get_db_name() const = 0; - - // FIXME: these are just for functionality mocking, need to implement - // RAII-friendly and multi-read one-write friendly locking mechanism - // - // acquire db lock - /** - * @brief acquires the BlockchainDB lock - * - * This function is a stub until such a time as locking is implemented at - * this level. - * - * The subclass implementation should return true unless implementing a - * locking scheme of some sort, in which case it should return true upon - * acquisition of the lock and block until then. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - * - * @return true, unless at a future time false makes sense (timeout, etc) - */ - virtual bool lock() = 0; - - // release db lock - /** - * @brief This function releases the BlockchainDB lock - * - * The subclass, should it have implemented lock(), will release any lock - * held by the calling thread. In the case of recursive locking, it should - * release one instance of a lock. - * - * If any of this cannot be done, the subclass should throw the corresponding - * subclass of DB_EXCEPTION - */ - virtual void unlock() = 0; - /** * @brief tells the BlockchainDB to start a new "batch" of blocks * diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 2c015faee..db8c68c45 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -1688,22 +1688,6 @@ std::string BlockchainLMDB::get_db_name() const return std::string("lmdb"); } -// TODO: this? -bool BlockchainLMDB::lock() -{ - LOG_PRINT_L3("BlockchainLMDB::" << __func__); - check_open(); - return false; -} - -// TODO: this? -void BlockchainLMDB::unlock() -{ - LOG_PRINT_L3("BlockchainLMDB::" << __func__); - check_open(); -} - - // The below two macros are for DB access within block add/remove, whether // regular batch txn is in use or not. m_write_txn is used as a batch txn, even // if it's only within block add/remove. diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 95e7b2aa4..dd0a5d30b 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -202,10 +202,6 @@ public: virtual std::string get_db_name() const; - virtual bool lock(); - - virtual void unlock(); - virtual bool block_exists(const crypto::hash& h, uint64_t *height = NULL) const; virtual uint64_t get_block_height(const crypto::hash& h) const; diff --git a/src/blockchain_db/testdb.h b/src/blockchain_db/testdb.h index a27183b2c..96b069772 100644 --- a/src/blockchain_db/testdb.h +++ b/src/blockchain_db/testdb.h @@ -50,8 +50,6 @@ public: virtual std::vector<std::string> get_filenames() const override { return std::vector<std::string>(); } virtual bool remove_data_file(const std::string& folder) const override { return true; } virtual std::string get_db_name() const override { return std::string(); } - virtual bool lock() override { return true; } - virtual void unlock() override { } virtual bool batch_start(uint64_t batch_num_blocks=0, uint64_t batch_bytes=0) override { return true; } virtual void batch_stop() override {} virtual void batch_abort() override {} diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex 2ed1d630f..2dc9ce3b3 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 70ae58cf0..63ec6ede9 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -240,6 +240,16 @@ namespace cryptonote ADD_CHECKPOINT2(2092500, "c4e00820c9c7989b49153d5e90ae095a18a11d990e82fcc3be54e6ed785472b5", "0xb4e585a31369cb"); ADD_CHECKPOINT2(2182500, "0d22b5f81982eff21d094af9e821dc2007e6342069e3b1a37b15d97646353124", "0xead4a874083492"); ADD_CHECKPOINT2(2661600, "41c9060e8426012238e8a26da26fcb90797436896cc70886a894c2c560bcccf2", "0x2e0d87526ff161f"); + ADD_CHECKPOINT2(2677000, "1b9fee6246eeb176bd17d637bf252e9af54a4218675f01b4449cc0901867f9eb", "0x2f165bc1a5163ba"); + ADD_CHECKPOINT2(2706000, "d8eb144c5e1fe6b329ecc900ec95e7792fccff84175fb23a25ed59d7299a511c", "0x310f7d89372f705"); + ADD_CHECKPOINT2(2720000, "b19fb41dff15bd1016afbee9f8469f05aab715c9e5d1b974466a11fd58ecbb86", "0x3216b5851ddbb61"); + ADD_CHECKPOINT2(2817000, "39726d19ccaac01d150bec827b877ffae710b516bd633503662036ef4422e577", "0x3900669561954c1"); + ADD_CHECKPOINT2(2844000, "28fc7b446dfef5b469f5778eb72ddf32a307a5f5a9823d1c394e772349e05d40", "0x3af384ec0e97d12"); + ADD_CHECKPOINT2(2851000, "5bf0e47fc782263191a33f63a67db6c711781dc2a3c442e17ed901ec401be5c9", "0x3b6cd8a8ed610e8"); + ADD_CHECKPOINT2(2971000, "3d4cac5ac515eeabd18769ab943af85f36db51d28720def0d0e6effc2c8f5ce3", "0x436e532738b8b5b"); + ADD_CHECKPOINT2(2985000, "08f5e6b7301c1b6ed88268a28f8677a06e8ff943b3f9e48d3080f71f9c134bfb", "0x444b7b42a633c96"); + ADD_CHECKPOINT2(3088000, "bddf8ca09110d33d6d497f13a113630c2b6af1c84d4f3a6f35cb1446f2604ade", "0x4aed3615c2f8c3e"); + ADD_CHECKPOINT2(3102800, "083f4a34f9490403b564286e7f13fd1ed45c52c86fa47195f151594e5bc87504", "0x4bbed52d4da5dfb"); return true; } diff --git a/src/common/timings.cc b/src/common/timings.cc index 612ac2cc6..aa719fa5e 100644 --- a/src/common/timings.cc +++ b/src/common/timings.cc @@ -12,10 +12,11 @@ TimingsDatabase::TimingsDatabase() { } -TimingsDatabase::TimingsDatabase(const std::string &filename): +TimingsDatabase::TimingsDatabase(const std::string &filename, const bool load_previous /*=false*/): filename(filename) { - load(); + if (load_previous) + load(); } TimingsDatabase::~TimingsDatabase() @@ -73,53 +74,96 @@ bool TimingsDatabase::load() { i.deciles.push_back(atoi(fields[idx++].c_str())); } - instances.insert(std::make_pair(name, i)); + instances.emplace_back(name, i); } fclose(f); return true; } -bool TimingsDatabase::save() +bool TimingsDatabase::save(const bool save_current_time /*=true*/) { - if (filename.empty()) + if (filename.empty() || instances.empty()) return true; - FILE *f = fopen(filename.c_str(), "w"); + FILE *f = fopen(filename.c_str(), "a"); // append if (!f) { MERROR("Failed to write to file " << filename << ": " << strerror(errno)); return false; } - for (const auto &i: instances) + + if (save_current_time) { - fprintf(f, "%s", i.first.c_str()); - fprintf(f, "\t%lu", (unsigned long)i.second.t); - fprintf(f, " %zu", i.second.npoints); - fprintf(f, " %f", i.second.min); - fprintf(f, " %f", i.second.max); - fprintf(f, " %f", i.second.mean); - fprintf(f, " %f", i.second.median); - fprintf(f, " %f", i.second.stddev); - fprintf(f, " %f", i.second.npskew); - for (uint64_t v: i.second.deciles) - fprintf(f, " %lu", (unsigned long)v); + // save current time in readable format (UTC) + std::time_t sys_time{std::time(nullptr)}; + std::tm *utc_time = std::gmtime(&sys_time); //GMT is equivalent to UTC + + // format: year-month-day : hour:minute:second + std::string current_time{}; + if (utc_time && sys_time != (std::time_t)(-1)) + { + char timeString[22]; //length = std::size("yyyy-mm-dd : hh:mm:ss") (constexpr std::size is C++17) + std::strftime(timeString, 22, "%F : %T", utc_time); + current_time += timeString; + } + else + { + current_time += "TIME_ERROR_"; + } + fputc('\n', f); // add an extra line before each 'print time' + fprintf(f, "%s", current_time.c_str()); fputc('\n', f); } + + for (const auto &i: instances) + { + fprintf(f, "%s,", i.first.c_str()); + + if (i.second.npoints > 0) + { + fprintf(f, "%lu,", (unsigned long)i.second.t); + fprintf(f, "%zu,", i.second.npoints); + fprintf(f, "%f,", i.second.min); + fprintf(f, "%f,", i.second.max); + fprintf(f, "%f,", i.second.mean); + fprintf(f, "%f,", i.second.median); + fprintf(f, "%f,", i.second.stddev); + fprintf(f, "%f,", i.second.npskew); + for (uint64_t v: i.second.deciles) + fprintf(f, "%lu,", (unsigned long)v); + + // note: only add a new line if there are points; assume that 'no points' means i.first is a message meant to be + // prepended to the next save operation + fputc('\n', f); + } + } fclose(f); + + // after saving, clear so next save does not append the same stuff over again + instances.clear(); + return true; } -std::vector<TimingsDatabase::instance> TimingsDatabase::get(const char *name) const +const TimingsDatabase::instance* TimingsDatabase::get_most_recent(const char *name) const { - std::vector<instance> ret; - auto range = instances.equal_range(name); - for (auto i = range.first; i != range.second; ++i) - ret.push_back(i->second); - std::sort(ret.begin(), ret.end(), [](const instance &e0, const instance &e1){ return e0.t < e1.t; }); - return ret; + time_t latest_time = 0; + const TimingsDatabase::instance *instance_ptr = nullptr; + + for (const auto &i: instances) + { + if (i.first != name) + continue; + if (i.second.t < latest_time) + continue; + + latest_time = i.second.t; + instance_ptr = &i.second; + } + return instance_ptr; } void TimingsDatabase::add(const char *name, const instance &i) { - instances.insert(std::make_pair(name, i)); + instances.emplace_back(name, i); } diff --git a/src/common/timings.h b/src/common/timings.h index fb905611f..6e7ff5659 100644 --- a/src/common/timings.h +++ b/src/common/timings.h @@ -2,8 +2,8 @@ #include <stdint.h> #include <string> +#include <utility> #include <vector> -#include <map> class TimingsDatabase { @@ -18,17 +18,17 @@ public: public: TimingsDatabase(); - TimingsDatabase(const std::string &filename); + TimingsDatabase(const std::string &filename, const bool load_previous = false); ~TimingsDatabase(); - std::vector<instance> get(const char *name) const; + const instance* get_most_recent(const char *name) const; void add(const char *name, const instance &data); + bool save(const bool print_current_time = true); private: bool load(); - bool save(); private: std::string filename; - std::multimap<std::string, instance> instances; + std::vector<std::pair<std::string, instance>> instances; }; diff --git a/src/common/util.cpp b/src/common/util.cpp index b4f3360ef..05ddfc359 100644 --- a/src/common/util.cpp +++ b/src/common/util.cpp @@ -114,6 +114,24 @@ static int flock_exnb(int fd) namespace tools { + + void copy_file(const std::string& from, const std::string& to) + { + using boost::filesystem::path; + #if BOOST_VERSION < 107400 + // Remove this preprocessor if/else when we are bumping the boost version. + boost::filesystem::copy_file( + path(from), + path(to), + boost::filesystem::copy_option::overwrite_if_exists); + #else + boost::filesystem::copy_file( + path(from), + path(to), + boost::filesystem::copy_options::overwrite_existing); + #endif + } + std::function<void(int)> signal_handler::m_handler; private_file::private_file() noexcept : m_handle(), m_filename() {} @@ -121,7 +139,7 @@ namespace tools private_file::private_file(std::FILE* handle, std::string&& filename) noexcept : m_handle(handle), m_filename(std::move(filename)) {} - private_file private_file::create(std::string name) + private_file private_file::create(std::string name, uint32_t extra_flags) { #ifdef WIN32 struct close_handle @@ -174,7 +192,7 @@ namespace tools name.c_str(), GENERIC_WRITE, FILE_SHARE_READ, std::addressof(attributes), - CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE), + CREATE_NEW, (FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | extra_flags), nullptr ) }; @@ -193,7 +211,7 @@ namespace tools } } #else - const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT), S_IRUSR); + const int fdr = open(name.c_str(), (O_RDONLY | O_CREAT | extra_flags), S_IRUSR); if (0 <= fdr) { struct stat rstats = {}; @@ -224,6 +242,23 @@ namespace tools return {}; } + private_file private_file::drop_and_recreate(std::string filename) + { + if (epee::file_io_utils::is_file_exist(filename)) { + boost::system::error_code ec{}; + boost::filesystem::remove(filename, ec); + if (ec) { + MERROR("Failed to remove " << filename << ": " << ec.message()); + return {}; + } + } +#ifdef WIN32 + return create(filename); +#else + return create(filename, O_EXCL); +#endif + } + private_file::~private_file() noexcept { try diff --git a/src/common/util.h b/src/common/util.h index 05748e020..ef591fc12 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -67,6 +67,8 @@ namespace tools } }; + void copy_file(const std::string& from, const std::string& to); + //! A file restricted to process owner AND process. Deletes file on destruction. class private_file { std::unique_ptr<std::FILE, close_file> m_handle; @@ -80,7 +82,11 @@ namespace tools /*! \return File only readable by owner and only used by this process OR `private_file{}` on error. */ - static private_file create(std::string filename); + static private_file create(std::string filename, uint32_t extra_flags = 0); + + /*! \return Drop and create file only readable by owner and only used + by this process OR `private_file{}` on error. */ + static private_file drop_and_recreate(std::string filename); private_file(private_file&&) = default; private_file& operator=(private_file&&) = default; diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt index 9ac376780..2559c754e 100644 --- a/src/crypto/CMakeLists.txt +++ b/src/crypto/CMakeLists.txt @@ -29,6 +29,7 @@ set(crypto_sources aesb.c blake256.c + blake2b.c chacha.c crypto-ops-data.c crypto-ops.c diff --git a/src/crypto/blake2b.c b/src/crypto/blake2b.c new file mode 100644 index 000000000..7e8be7890 --- /dev/null +++ b/src/crypto/blake2b.c @@ -0,0 +1,563 @@ +/* +Copyright (c) 2018-2019, tevador <tevador@gmail.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the 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. +*/ + +/* Original code from Argon2 reference source code package used under CC0 Licence + * https://github.com/P-H-C/phc-winner-argon2 + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves +*/ + +#include "blake2b.h" + +#include "memwipe.h" + +#include <stdint.h> +#include <string.h> +#include <stdio.h> + +/// BEGIN: endian.h + +#if defined(_MSC_VER) +#define FORCE_INLINE __inline +#elif defined(__GNUC__) || defined(__clang__) +#define FORCE_INLINE __inline__ +#else +#define FORCE_INLINE +#endif + + /* Argon2 Team - Begin Code */ + /* + Not an exhaustive list, but should cover the majority of modern platforms + Additionally, the code will always be correct---this is only a performance + tweak. + */ +#if (defined(__BYTE_ORDER__) && \ + (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) || \ + defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__MIPSEL__) || \ + defined(__AARCH64EL__) || defined(__amd64__) || defined(__i386__) || \ + defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || \ + defined(_M_ARM) +#define NATIVE_LITTLE_ENDIAN +#endif + /* Argon2 Team - End Code */ + +static FORCE_INLINE uint32_t load32(const void *src) { +#if defined(NATIVE_LITTLE_ENDIAN) + uint32_t w; + memcpy(&w, src, sizeof w); + return w; +#else + const uint8_t *p = (const uint8_t *)src; + uint32_t w = *p++; + w |= (uint32_t)(*p++) << 8; + w |= (uint32_t)(*p++) << 16; + w |= (uint32_t)(*p++) << 24; + return w; +#endif +} + +static FORCE_INLINE uint64_t load64_native(const void *src) { + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +} + +static FORCE_INLINE uint64_t load64(const void *src) { +#if defined(NATIVE_LITTLE_ENDIAN) + return load64_native(src); +#else + const uint8_t *p = (const uint8_t *)src; + uint64_t w = *p++; + w |= (uint64_t)(*p++) << 8; + w |= (uint64_t)(*p++) << 16; + w |= (uint64_t)(*p++) << 24; + w |= (uint64_t)(*p++) << 32; + w |= (uint64_t)(*p++) << 40; + w |= (uint64_t)(*p++) << 48; + w |= (uint64_t)(*p++) << 56; + return w; +#endif +} + +static FORCE_INLINE void store32(void *dst, uint32_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + memcpy(dst, &w, sizeof w); +#else + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +static FORCE_INLINE void store64_native(void *dst, uint64_t w) { + memcpy(dst, &w, sizeof w); +} + +static FORCE_INLINE void store64(void *dst, uint64_t w) { +#if defined(NATIVE_LITTLE_ENDIAN) + store64_native(dst, w); +#else + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +#endif +} + +/// END: endian.h + +/// BEGIN: blake2-impl.h + +static FORCE_INLINE uint64_t load48(const void *src) { + const uint8_t *p = (const uint8_t *)src; + uint64_t w = *p++; + w |= (uint64_t)(*p++) << 8; + w |= (uint64_t)(*p++) << 16; + w |= (uint64_t)(*p++) << 24; + w |= (uint64_t)(*p++) << 32; + w |= (uint64_t)(*p++) << 40; + return w; +} + +static FORCE_INLINE void store48(void *dst, uint64_t w) { + uint8_t *p = (uint8_t *)dst; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; + w >>= 8; + *p++ = (uint8_t)w; +} + +static FORCE_INLINE uint32_t rotr32(const uint32_t w, const unsigned c) { + return (w >> c) | (w << (32 - c)); +} + +static FORCE_INLINE uint64_t rotr64(const uint64_t w, const unsigned c) { + return (w >> c) | (w << (64 - c)); +} + +/// END: blake2-impl.h + +void clear_internal_memory(void *mem, const size_t length) { + memwipe(mem, length); +} + +/// BEGIN: blake2b.c + +static const uint64_t blake2b_IV[8] = { + UINT64_C(0x6a09e667f3bcc908), UINT64_C(0xbb67ae8584caa73b), + UINT64_C(0x3c6ef372fe94f82b), UINT64_C(0xa54ff53a5f1d36f1), + UINT64_C(0x510e527fade682d1), UINT64_C(0x9b05688c2b3e6c1f), + UINT64_C(0x1f83d9abfb41bd6b), UINT64_C(0x5be0cd19137e2179) }; + +static const unsigned int blake2b_sigma[12][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, + {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, + {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, + {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, + {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, +}; + +static FORCE_INLINE void blake2b_set_lastnode(blake2b_state *S) { + S->f[1] = (uint64_t)-1; +} + +static FORCE_INLINE void blake2b_set_lastblock(blake2b_state *S) { + if (S->last_node) { + blake2b_set_lastnode(S); + } + S->f[0] = (uint64_t)-1; +} + +static FORCE_INLINE void blake2b_increment_counter(blake2b_state *S, + uint64_t inc) { + S->t[0] += inc; + S->t[1] += (S->t[0] < inc); +} + +static FORCE_INLINE void blake2b_invalidate_state(blake2b_state *S) { + clear_internal_memory(S, sizeof(*S)); /* wipe */ + blake2b_set_lastblock(S); /* invalidate for further use */ +} + +static FORCE_INLINE void blake2b_init0(blake2b_state *S) { + memset(S, 0, sizeof(*S)); + memcpy(S->h, blake2b_IV, sizeof(S->h)); +} + +int blake2b_init_param(blake2b_state *S, const blake2b_param *P) { + const unsigned char *p = (const unsigned char *)P; + unsigned int i; + + if (NULL == P || NULL == S) { + return -1; + } + + blake2b_init0(S); + /* IV XOR Parameter Block */ + for (i = 0; i < 8; ++i) { + S->h[i] ^= load64(&p[i * sizeof(S->h[i])]); + } + S->outlen = P->digest_length; + return 0; +} + +/* Sequential blake2b initialization */ +int blake2b_init(blake2b_state *S, size_t outlen) { + blake2b_param P; + + if (S == NULL) { + return -1; + } + + if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + /* Setup Parameter Block for unkeyed BLAKE2 */ + P.digest_length = (uint8_t)outlen; + P.key_length = 0; + P.fanout = 1; + P.depth = 1; + P.leaf_length = 0; + P.node_offset = 0; + P.node_depth = 0; + P.inner_length = 0; + memset(P.reserved, 0, sizeof(P.reserved)); + memset(P.salt, 0, sizeof(P.salt)); + memset(P.personal, 0, sizeof(P.personal)); + + return blake2b_init_param(S, &P); +} + +int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, size_t keylen) { + blake2b_param P; + + if (S == NULL) { + return -1; + } + + if ((outlen == 0) || (outlen > BLAKE2B_OUTBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + if ((key == 0) || (keylen == 0) || (keylen > BLAKE2B_KEYBYTES)) { + blake2b_invalidate_state(S); + return -1; + } + + /* Setup Parameter Block for keyed BLAKE2 */ + P.digest_length = (uint8_t)outlen; + P.key_length = (uint8_t)keylen; + P.fanout = 1; + P.depth = 1; + P.leaf_length = 0; + P.node_offset = 0; + P.node_depth = 0; + P.inner_length = 0; + memset(P.reserved, 0, sizeof(P.reserved)); + memset(P.salt, 0, sizeof(P.salt)); + memset(P.personal, 0, sizeof(P.personal)); + + if (blake2b_init_param(S, &P) < 0) { + blake2b_invalidate_state(S); + return -1; + } + + { + uint8_t block[BLAKE2B_BLOCKBYTES]; + memset(block, 0, BLAKE2B_BLOCKBYTES); + memcpy(block, key, keylen); + blake2b_update(S, block, BLAKE2B_BLOCKBYTES); + /* Burn the key from stack */ + clear_internal_memory(block, BLAKE2B_BLOCKBYTES); + } + return 0; +} + +static void blake2b_compress(blake2b_state *S, const uint8_t *block) { + uint64_t m[16]; + uint64_t v[16]; + unsigned int i, r; + + for (i = 0; i < 16; ++i) { + m[i] = load64(block + i * sizeof(m[i])); + } + + for (i = 0; i < 8; ++i) { + v[i] = S->h[i]; + } + + v[8] = blake2b_IV[0]; + v[9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7] ^ S->f[1]; + +#define G(r, i, a, b, c, d) \ + do { \ + a = a + b + m[blake2b_sigma[r][2 * i + 0]]; \ + d = rotr64(d ^ a, 32); \ + c = c + d; \ + b = rotr64(b ^ c, 24); \ + a = a + b + m[blake2b_sigma[r][2 * i + 1]]; \ + d = rotr64(d ^ a, 16); \ + c = c + d; \ + b = rotr64(b ^ c, 63); \ + } while ((void)0, 0) + +#define ROUND(r) \ + do { \ + G(r, 0, v[0], v[4], v[8], v[12]); \ + G(r, 1, v[1], v[5], v[9], v[13]); \ + G(r, 2, v[2], v[6], v[10], v[14]); \ + G(r, 3, v[3], v[7], v[11], v[15]); \ + G(r, 4, v[0], v[5], v[10], v[15]); \ + G(r, 5, v[1], v[6], v[11], v[12]); \ + G(r, 6, v[2], v[7], v[8], v[13]); \ + G(r, 7, v[3], v[4], v[9], v[14]); \ + } while ((void)0, 0) + + for (r = 0; r < 12; ++r) { + ROUND(r); + } + + for (i = 0; i < 8; ++i) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } + +#undef G +#undef ROUND +} + +int blake2b_update(blake2b_state *S, const void *in, size_t inlen) { + const uint8_t *pin = (const uint8_t *)in; + + if (inlen == 0) { + return 0; + } + + /* Sanity check */ + if (S == NULL || in == NULL) { + return -1; + } + + /* Is this a reused state? */ + if (S->f[0] != 0) { + return -1; + } + + if (S->buflen + inlen > BLAKE2B_BLOCKBYTES) { + /* Complete current block */ + size_t left = S->buflen; + size_t fill = BLAKE2B_BLOCKBYTES - left; + memcpy(&S->buf[left], pin, fill); + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, S->buf); + S->buflen = 0; + inlen -= fill; + pin += fill; + /* Avoid buffer copies when possible */ + while (inlen > BLAKE2B_BLOCKBYTES) { + blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES); + blake2b_compress(S, pin); + inlen -= BLAKE2B_BLOCKBYTES; + pin += BLAKE2B_BLOCKBYTES; + } + } + memcpy(&S->buf[S->buflen], pin, inlen); + S->buflen += (unsigned int)inlen; + return 0; +} + +int blake2b_final(blake2b_state *S, void *out, size_t outlen) { + uint8_t buffer[BLAKE2B_OUTBYTES] = { 0 }; + unsigned int i; + + /* Sanity checks */ + if (S == NULL || out == NULL || outlen < S->outlen) { + return -1; + } + + /* Is this a reused state? */ + if (S->f[0] != 0) { + return -1; + } + + blake2b_increment_counter(S, S->buflen); + blake2b_set_lastblock(S); + memset(&S->buf[S->buflen], 0, BLAKE2B_BLOCKBYTES - S->buflen); /* Padding */ + blake2b_compress(S, S->buf); + + for (i = 0; i < 8; ++i) { /* Output full hash to temp buffer */ + store64(buffer + sizeof(S->h[i]) * i, S->h[i]); + } + + memcpy(out, buffer, S->outlen); + clear_internal_memory(buffer, sizeof(buffer)); + clear_internal_memory(S->buf, sizeof(S->buf)); + clear_internal_memory(S->h, sizeof(S->h)); + return 0; +} + +int blake2b(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen) { + blake2b_state S; + int ret = -1; + + /* Verify parameters */ + if (NULL == in && inlen > 0) { + goto fail; + } + + if (NULL == out || outlen == 0 || outlen > BLAKE2B_OUTBYTES) { + goto fail; + } + + if ((NULL == key && keylen > 0) || keylen > BLAKE2B_KEYBYTES) { + goto fail; + } + + if (keylen > 0) { + if (blake2b_init_key(&S, outlen, key, keylen) < 0) { + goto fail; + } + } + else { + if (blake2b_init(&S, outlen) < 0) { + goto fail; + } + } + + if (blake2b_update(&S, in, inlen) < 0) { + goto fail; + } + ret = blake2b_final(&S, out, outlen); + +fail: + clear_internal_memory(&S, sizeof(S)); + return ret; +} + +/* Argon2 Team - Begin Code */ +int blake2b_long(void *pout, size_t outlen, const void *in, size_t inlen) { + uint8_t *out = (uint8_t *)pout; + blake2b_state blake_state; + uint8_t outlen_bytes[sizeof(uint32_t)] = { 0 }; + int ret = -1; + + if (outlen > UINT32_MAX) { + goto fail; + } + + /* Ensure little-endian byte order! */ + store32(outlen_bytes, (uint32_t)outlen); + +#define TRY(statement) \ + do { \ + ret = statement; \ + if (ret < 0) { \ + goto fail; \ + } \ + } while ((void)0, 0) + + if (outlen <= BLAKE2B_OUTBYTES) { + TRY(blake2b_init(&blake_state, outlen)); + TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes))); + TRY(blake2b_update(&blake_state, in, inlen)); + TRY(blake2b_final(&blake_state, out, outlen)); + } + else { + uint32_t toproduce; + uint8_t out_buffer[BLAKE2B_OUTBYTES]; + uint8_t in_buffer[BLAKE2B_OUTBYTES]; + TRY(blake2b_init(&blake_state, BLAKE2B_OUTBYTES)); + TRY(blake2b_update(&blake_state, outlen_bytes, sizeof(outlen_bytes))); + TRY(blake2b_update(&blake_state, in, inlen)); + TRY(blake2b_final(&blake_state, out_buffer, BLAKE2B_OUTBYTES)); + memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2); + out += BLAKE2B_OUTBYTES / 2; + toproduce = (uint32_t)outlen - BLAKE2B_OUTBYTES / 2; + + while (toproduce > BLAKE2B_OUTBYTES) { + memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES); + TRY(blake2b(out_buffer, BLAKE2B_OUTBYTES, in_buffer, + BLAKE2B_OUTBYTES, NULL, 0)); + memcpy(out, out_buffer, BLAKE2B_OUTBYTES / 2); + out += BLAKE2B_OUTBYTES / 2; + toproduce -= BLAKE2B_OUTBYTES / 2; + } + + memcpy(in_buffer, out_buffer, BLAKE2B_OUTBYTES); + TRY(blake2b(out_buffer, toproduce, in_buffer, BLAKE2B_OUTBYTES, NULL, + 0)); + memcpy(out, out_buffer, toproduce); + } +fail: + clear_internal_memory(&blake_state, sizeof(blake_state)); + return ret; +#undef TRY +} +/* Argon2 Team - End Code */ + +/// END: blake2b.c diff --git a/src/crypto/blake2b.h b/src/crypto/blake2b.h new file mode 100644 index 000000000..ea00f381b --- /dev/null +++ b/src/crypto/blake2b.h @@ -0,0 +1,116 @@ +/* +Copyright (c) 2018-2019, tevador <tevador@gmail.com> + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the 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. +*/ + +/* Original code from Argon2 reference source code package used under CC0 Licence + * https://github.com/P-H-C/phc-winner-argon2 + * Copyright 2015 + * Daniel Dinu, Dmitry Khovratovich, Jean-Philippe Aumasson, and Samuel Neves +*/ + +/// NOTE: implementation lifted out of randomx package + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <limits.h> + +#if defined(__cplusplus) +extern "C" { +#endif + + enum blake2b_constant { + BLAKE2B_BLOCKBYTES = 128, + BLAKE2B_OUTBYTES = 64, + BLAKE2B_KEYBYTES = 64, + BLAKE2B_SALTBYTES = 16, + BLAKE2B_PERSONALBYTES = 16 + }; + +#pragma pack(push, 1) + typedef struct __blake2b_param { + uint8_t digest_length; /* 1 */ + uint8_t key_length; /* 2 */ + uint8_t fanout; /* 3 */ + uint8_t depth; /* 4 */ + uint32_t leaf_length; /* 8 */ + uint64_t node_offset; /* 16 */ + uint8_t node_depth; /* 17 */ + uint8_t inner_length; /* 18 */ + uint8_t reserved[14]; /* 32 */ + uint8_t salt[BLAKE2B_SALTBYTES]; /* 48 */ + uint8_t personal[BLAKE2B_PERSONALBYTES]; /* 64 */ + } blake2b_param; +#pragma pack(pop) + + typedef struct __blake2b_state { + uint64_t h[8]; + uint64_t t[2]; + uint64_t f[2]; + uint8_t buf[BLAKE2B_BLOCKBYTES]; + unsigned buflen; + unsigned outlen; + uint8_t last_node; + } blake2b_state; + + /* Ensure param structs have not been wrongly padded */ + /* Poor man's static_assert */ + enum { + blake2_size_check_0 = 1 / !!(CHAR_BIT == 8), + blake2_size_check_2 = + 1 / !!(sizeof(blake2b_param) == sizeof(uint64_t) * CHAR_BIT) + }; + + // crypto namespace (fixes naming collisions with libsodium) +#define blake2b_init crypto_blake2b_init +#define blake2b_init_key crypto_blake2b_init_key +#define blake2b_init_param crypto_blake2b_init_param +#define blake2b_update crypto_blake2b_update +#define blake2b_final crypto_blake2b_final +#define blake2b crypto_blake2b +#define blake2b_long crypto_blake2b_long + + /* Streaming API */ + int blake2b_init(blake2b_state *S, size_t outlen); + int blake2b_init_key(blake2b_state *S, size_t outlen, const void *key, + size_t keylen); + int blake2b_init_param(blake2b_state *S, const blake2b_param *P); + int blake2b_update(blake2b_state *S, const void *in, size_t inlen); + int blake2b_final(blake2b_state *S, void *out, size_t outlen); + + /* Simple API */ + int blake2b(void *out, size_t outlen, const void *in, size_t inlen, + const void *key, size_t keylen); + + /* Argon2 Team - Begin Code */ + int blake2b_long(void *out, size_t outlen, const void *in, size_t inlen); + /* Argon2 Team - End Code */ + +#if defined(__cplusplus) +} +#endif diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp index 9dc6e387d..c157c1fe1 100644 --- a/src/cryptonote_basic/account.cpp +++ b/src/cryptonote_basic/account.cpp @@ -152,6 +152,17 @@ DISABLE_VS_WARNINGS(4244 4345) m_keys.m_multisig_keys.clear(); } //----------------------------------------------------------------- + void account_base::set_spend_key(const crypto::secret_key& spend_secret_key) + { + // make sure derived spend public key matches saved public spend key + crypto::public_key spend_public_key; + crypto::secret_key_to_public_key(spend_secret_key, spend_public_key); + CHECK_AND_ASSERT_THROW_MES(m_keys.m_account_address.m_spend_public_key == spend_public_key, + "Unexpected derived public spend key"); + + m_keys.m_spend_secret_key = spend_secret_key; + } + //----------------------------------------------------------------- crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) { crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover); diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h index 7578aaf2a..8f484166a 100644 --- a/src/cryptonote_basic/account.h +++ b/src/cryptonote_basic/account.h @@ -95,6 +95,7 @@ namespace cryptonote bool store(const std::string& file_path); void forget_spend_key(); + void set_spend_key(const crypto::secret_key& spend_secret_key); const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; } void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); } diff --git a/src/cryptonote_basic/verification_context.h b/src/cryptonote_basic/verification_context.h index 11d54ae9f..8dcd55187 100644 --- a/src/cryptonote_basic/verification_context.h +++ b/src/cryptonote_basic/verification_context.h @@ -59,6 +59,7 @@ namespace cryptonote bool m_fee_too_low; bool m_too_few_outputs; bool m_tx_extra_too_big; + bool m_nonzero_unlock_time; }; struct block_verification_context diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index fec905928..578f2aa02 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -241,6 +241,8 @@ namespace config const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; const unsigned char HASH_KEY_WALLET = 0x8c; const unsigned char HASH_KEY_WALLET_CACHE = 0x8d; + const unsigned char HASH_KEY_BACKGROUND_CACHE = 0x8e; + const unsigned char HASH_KEY_BACKGROUND_KEYS_FILE = 0x8f; const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 3e1704048..3a7575119 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3691,18 +3691,16 @@ void Blockchain::check_ring_signature(const crypto::hash &tx_prefix_hash, const } //------------------------------------------------------------------ -uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version) +uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight) { - const uint64_t min_block_weight = get_min_block_weight(version); + constexpr uint64_t min_block_weight = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5; if (median_block_weight < min_block_weight) median_block_weight = min_block_weight; uint64_t hi, lo; - if (version >= HF_VERSION_PER_BYTE_FEE) { lo = mul128(block_reward, DYNAMIC_FEE_REFERENCE_TRANSACTION_WEIGHT, &hi); div128_64(hi, lo, median_block_weight, &hi, &lo, NULL, NULL); - if (version >= HF_VERSION_2021_SCALING) { // min_fee_per_byte = round_up( 0.95 * block_reward * ref_weight / (fee_median^2) ) // note: since hardfork HF_VERSION_2021_SCALING, fee_median (a.k.a. median_block_weight) equals effective long term median @@ -3711,29 +3709,7 @@ uint64_t Blockchain::get_dynamic_base_fee(uint64_t block_reward, size_t median_b lo -= lo / 20; return lo == 0 ? 1 : lo; } - else - { - // min_fee_per_byte = 0.2 * block_reward * ref_weight / (min_penalty_free_zone * fee_median) - div128_64(hi, lo, min_block_weight, &hi, &lo, NULL, NULL); - assert(hi == 0); - lo /= 5; - return lo; - } } - - const uint64_t fee_base = version >= 5 ? DYNAMIC_FEE_PER_KB_BASE_FEE_V5 : DYNAMIC_FEE_PER_KB_BASE_FEE; - - uint64_t unscaled_fee_base = (fee_base * min_block_weight / median_block_weight); - lo = mul128(unscaled_fee_base, block_reward, &hi); - div128_64(hi, lo, DYNAMIC_FEE_PER_KB_BASE_BLOCK_REWARD, &hi, &lo, NULL, NULL); - assert(hi == 0); - - // quantize fee up to 8 decimals - uint64_t mask = get_fee_quantization_mask(); - uint64_t qlo = (lo + mask - 1) / mask * mask; - MDEBUG("lo " << print_money(lo) << ", qlo " << print_money(qlo) << ", mask " << mask); - - return qlo; } //------------------------------------------------------------------ @@ -3744,7 +3720,6 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const uint64_t median = 0; uint64_t already_generated_coins = 0; uint64_t base_reward = 0; - if (version >= HF_VERSION_DYNAMIC_FEE) { median = m_current_block_cumul_weight_limit / 2; const uint64_t blockchain_height = m_db->height(); @@ -3754,33 +3729,15 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const } uint64_t needed_fee; - if (version >= HF_VERSION_PER_BYTE_FEE) { - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - uint64_t fee_per_byte = get_dynamic_base_fee(base_reward, use_long_term_median_in_fee ? std::min<uint64_t>(median, m_long_term_effective_median_block_weight) : median, version); + const uint64_t fee_per_byte = get_dynamic_base_fee(base_reward, + std::min<uint64_t>(median, m_long_term_effective_median_block_weight)); MDEBUG("Using " << print_money(fee_per_byte) << "/byte fee"); needed_fee = tx_weight * fee_per_byte; // quantize fee up to 8 decimals const uint64_t mask = get_fee_quantization_mask(); needed_fee = (needed_fee + mask - 1) / mask * mask; } - else - { - uint64_t fee_per_kb; - if (version < HF_VERSION_DYNAMIC_FEE) - { - fee_per_kb = FEE_PER_KB; - } - else - { - fee_per_kb = get_dynamic_base_fee(base_reward, median, version); - } - MDEBUG("Using " << print_money(fee_per_kb) << "/kB fee"); - - needed_fee = tx_weight / 1024; - needed_fee += (tx_weight % 1024) ? 1 : 0; - needed_fee *= fee_per_kb; - } if (fee < needed_fee - needed_fee / 50) // keep a little 2% buffer on acceptance - no integer overflow { @@ -3791,7 +3748,8 @@ bool Blockchain::check_fee(size_t tx_weight, uint64_t fee) const } //------------------------------------------------------------------ -void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const +void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t base_reward, uint64_t Mnw, + uint64_t Mlw, std::vector<uint64_t> &fees) { // variable names and calculations as per https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf // from (earlier than) this fork, the base fee is per byte @@ -3863,55 +3821,8 @@ void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_block base_reward = BLOCK_REWARD_OVERESTIMATE; } - get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, base_reward, Mnw, Mlw_penalty_free_zone_for_wallet, fees); -} - -//------------------------------------------------------------------ -uint64_t Blockchain::get_dynamic_base_fee_estimate(uint64_t grace_blocks) const -{ - const uint8_t version = get_current_hard_fork_version(); - const uint64_t db_height = m_db->height(); - - if (version < HF_VERSION_DYNAMIC_FEE) - return FEE_PER_KB; - - if (grace_blocks >= CRYPTONOTE_REWARD_BLOCKS_WINDOW) - grace_blocks = CRYPTONOTE_REWARD_BLOCKS_WINDOW - 1; - - if (version >= HF_VERSION_2021_SCALING) - { - std::vector<uint64_t> fees; - get_dynamic_base_fee_estimate_2021_scaling(grace_blocks, fees); - return fees[0]; - } - - const uint64_t min_block_weight = get_min_block_weight(version); - std::vector<uint64_t> weights; - get_last_n_blocks_weights(weights, CRYPTONOTE_REWARD_BLOCKS_WINDOW - grace_blocks); - weights.reserve(grace_blocks); - for (size_t i = 0; i < grace_blocks; ++i) - weights.push_back(min_block_weight); - - uint64_t median = epee::misc_utils::median(weights); - if(median <= min_block_weight) - median = min_block_weight; - - uint64_t already_generated_coins = db_height ? m_db->get_block_already_generated_coins(db_height - 1) : 0; - uint64_t base_reward; - if (!get_block_reward(m_current_block_cumul_weight_limit / 2, 1, already_generated_coins, base_reward, version)) - { - MERROR("Failed to determine block reward, using placeholder " << print_money(BLOCK_REWARD_OVERESTIMATE) << " as a high bound"); - base_reward = BLOCK_REWARD_OVERESTIMATE; - } - - const bool use_long_term_median_in_fee = version >= HF_VERSION_LONG_TERM_BLOCK_WEIGHT; - const uint64_t use_median_value = use_long_term_median_in_fee ? std::min<uint64_t>(median, m_long_term_effective_median_block_weight) : median; - const uint64_t fee = get_dynamic_base_fee(base_reward, use_median_value, version); - const bool per_byte = version < HF_VERSION_PER_BYTE_FEE; - MDEBUG("Estimating " << grace_blocks << "-block fee at " << print_money(fee) << "/" << (per_byte ? "byte" : "kB")); - return fee; + get_dynamic_base_fee_estimate_2021_scaling(base_reward, Mnw, Mlw_penalty_free_zone_for_wallet, fees); } - //------------------------------------------------------------------ // This function checks to see if a tx is unlocked. unlock_time is either // a block index or a unix time. @@ -5551,7 +5462,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "e9371004b9f6be59921b27bc81e28b4715845ade1c6d16891d5c455f72e21365"; +static const char expected_block_hashes_hash[] = "0046a0019beb6e697e27d834d6127851425f7ee09bfb8e9f8df7b1420131aca8"; void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints) { if (get_checkpoints == nullptr || !m_fast_sync) diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index be2848d09..2249d8cb2 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -638,27 +638,24 @@ namespace cryptonote * * @param block_reward the current block reward * @param median_block_weight the median block weight in the past window - * @param version hard fork version for rules and constants to use * * @return the fee */ - static uint64_t get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight, uint8_t version); + static uint64_t get_dynamic_base_fee(uint64_t block_reward, size_t median_block_weight); /** - * @brief get dynamic per kB or byte fee estimate for the next few blocks + * @brief get four levels of dynamic per byte fee estimate for the next few blocks * * The dynamic fee is based on the block weight in a past window, and - * the current block reward. It is expressed by kB before v8, and - * per byte from v8. - * This function calculates an estimate for a dynamic fee which will be - * valid for the next grace_blocks - * - * @param grace_blocks number of blocks we want the fee to be valid for + * the current block reward. It is expressed per byte, and is based on + * https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021-02.pdf * - * @return the fee estimate + * @param Mnw min(Msw, 50*Mlw) + * @param Mlw The median over the last 99990 and future 10 blocks of max(min(Mbw, 2*Ml), Zm, Ml/2) + * @param[out] fees fee estimate levels [Fl, Fn, Fm, Fh] */ - uint64_t get_dynamic_base_fee_estimate(uint64_t grace_blocks) const; - void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, uint64_t base_reward, uint64_t Mnw, uint64_t Mlw, std::vector<uint64_t> &fees) const; + static void get_dynamic_base_fee_estimate_2021_scaling(uint64_t base_reward, uint64_t Mnw, + uint64_t Mlw, std::vector<uint64_t> &fees); /** * @brief get four levels of dynamic per byte fee estimate for the next few blocks @@ -670,8 +667,7 @@ namespace cryptonote * valid for the next grace_blocks * * @param grace_blocks number of blocks we want the fee to be valid for - * - * @return the fee estimates (4 of them) + * @param[out] fees fee estimate levels [Fl, Fn, Fm, Fh] */ void get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_blocks, std::vector<uint64_t> &fees) const; diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp index 60b399bb5..d5bb8fee7 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.cpp +++ b/src/cryptonote_core/cryptonote_tx_utils.cpp @@ -203,7 +203,7 @@ namespace cryptonote return addr.m_view_public_key; } //--------------------------------------------------------------- - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags) + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool shuffle_outs, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); @@ -218,7 +218,7 @@ namespace cryptonote amount_keys.clear(); tx.version = rct ? 2 : 1; - tx.unlock_time = unlock_time; + tx.unlock_time = 0; tx.extra = extra; crypto::public_key txkey_pub; @@ -606,7 +606,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags) + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct, const rct::RCTConfig &rct_config, bool use_view_tags) { hw::device &hwdev = sender_account_keys.get_device(); hwdev.open_tx(tx_key); @@ -627,7 +627,7 @@ namespace cryptonote } bool shuffle_outs = true; - bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags); + bool r = construct_tx_with_tx_key(sender_account_keys, subaddresses, sources, destinations, change_addr, extra, tx, tx_key, additional_tx_keys, rct, rct_config, shuffle_outs, use_view_tags); hwdev.close_tx(); return r; } catch(...) { @@ -636,14 +636,14 @@ namespace cryptonote } } //--------------------------------------------------------------- - bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time) + bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx) { std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0}; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, tx_key, additional_tx_keys, false, { rct::RangeProofBorromean, 0}); } //--------------------------------------------------------------- bool generate_genesis_block( diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h index 2ee93fd8f..8f1073766 100644 --- a/src/cryptonote_core/cryptonote_tx_utils.h +++ b/src/cryptonote_core/cryptonote_tx_utils.h @@ -118,9 +118,9 @@ namespace cryptonote //--------------------------------------------------------------- crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const boost::optional<cryptonote::account_public_address>& change_addr); - bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time); - bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false); - bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false); + bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry> &sources, const std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx); + bool construct_tx_with_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool shuffle_outs = true, bool use_view_tags = false); + bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, std::vector<tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, const std::vector<uint8_t> &extra, transaction& tx, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false, const rct::RCTConfig &rct_config = { rct::RangeProofBorromean, 0 }, bool use_view_tags = false); bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key, const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index, const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys, diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 4f0501c1d..96b229177 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -235,6 +235,15 @@ namespace cryptonote return false; } + if (!kept_by_block && tx.unlock_time) + { + LOG_PRINT_L1("transaction unlock time is not zero: " << tx.unlock_time); + tvc.m_verifivation_failed = true; + tvc.m_nonzero_unlock_time = true; + tvc.m_no_drop_offense = true; + return false; + } + // if the transaction came from a block popped from the chain, // don't check if we have its key images as spent. // TODO: Investigate why not? diff --git a/src/cryptonote_protocol/levin_notify.cpp b/src/cryptonote_protocol/levin_notify.cpp index 5b420ec3f..1c3a2901c 100644 --- a/src/cryptonote_protocol/levin_notify.cpp +++ b/src/cryptonote_protocol/levin_notify.cpp @@ -741,9 +741,14 @@ namespace levin notify::status notify::get_status() const noexcept { if (!zone_) - return {false, false}; - - return {!zone_->noise.empty(), CRYPTONOTE_NOISE_CHANNELS <= zone_->connection_count}; + return {false, false, false}; + + // `connection_count` is only set when `!noise.empty()`. + const std::size_t connection_count = zone_->connection_count; + bool has_outgoing = connection_count; + if (zone_->noise.empty()) + has_outgoing = zone_->p2p->get_out_connections_count(); + return {!zone_->noise.empty(), CRYPTONOTE_NOISE_CHANNELS <= connection_count, has_outgoing}; } void notify::new_out_connection() diff --git a/src/cryptonote_protocol/levin_notify.h b/src/cryptonote_protocol/levin_notify.h index 52d36efb0..816170841 100644 --- a/src/cryptonote_protocol/levin_notify.h +++ b/src/cryptonote_protocol/levin_notify.h @@ -75,7 +75,8 @@ namespace levin struct status { bool has_noise; - bool connections_filled; + bool connections_filled; //!< True when has zone has `CRYPTONOTE_NOISE_CHANNELS` outgoing noise channels + bool has_outgoing; //!< True when zone has outgoing connections }; //! Construct an instance that cannot notify. diff --git a/src/device/log.hpp b/src/device/log.hpp index 92d174da1..21bd70149 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -48,7 +48,7 @@ namespace hw { * - All computation done by device are checked by default device. * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working * #define IODUMMYCRYPT_HWDEVICE 1 - * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value + * - It assumes sensitive data encryption is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value * #define IONOCRYPT_HWDEVICE 1 * - It assumes sensitive data encryption is off on device side. */ diff --git a/src/device_trezor/README.md b/src/device_trezor/README.md index ce08c0009..dede853ae 100644 --- a/src/device_trezor/README.md +++ b/src/device_trezor/README.md @@ -15,19 +15,29 @@ Please, refer to [monero readme](https://github.com/trezor/trezor-firmware/blob/ ## 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. +Trezor uses [Protobuf](https://protobuf.dev/) library. Monero is now compiled with C++ 17 by default. +Protobuf v21 is tested, older versions are not guaranteed to work. Note that Protobuf v23+ requires C++ 17. -Protobuf v21 is the latest compatible protobuf version. +If you are getting Trezor compilation errors, it may be caused by abseil (protobuf dependency) not being compiled with C++17. +To fix this try installing protobuf from sources: -If you want to compile Monero with Trezor support, please make sure the Protobuf v21 is installed. +```shell +git clone --recursive git@github.com:protocolbuffers/protobuf.git +cd protobuf +cmake -DABSL_PROPAGATE_CXX_STD=TRUE -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON -Dprotobuf_BUILD_TESTS=OFF . +cmake --build . +sudo make install +``` + +If Monero is compiled with C++14, Protobuf v21 is the latest compatible protobuf version for C++ 14. +If you want to compile Monero with Trezor support with C++14, 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: +To build with installed, but not linked Protobuf v21: ```bash CMAKE_PREFIX_PATH=$(find /opt/homebrew/Cellar/protobuf@21 -maxdepth 1 -type d -name "21.*" -print -quit) \ @@ -53,7 +63,7 @@ pacman --noconfirm -U mingw-w64-x86_64-protobuf-c-1.4.1-1-any.pkg.tar.zst mingw- ### Other systems -- install protobufv21 +- install Protobuf v21 - point `CMAKE_PREFIX_PATH` environment variable to Protobuf v21 installation. ## Troubleshooting diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 48cf818ef..fd82c66af 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -121,16 +121,15 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str } // exchange keys until the wallets are done - bool ready{false}; - wallets[0]->multisig(&ready); - while (!ready) + multisig::multisig_account_status ms_status{wallets[0]->get_multisig_status()}; + while (!ms_status.is_ready) { for (size_t n = 0; n < total; ++n) { kex_msgs_intermediate[n] = wallets[n]->exchange_multisig_keys(pwd_container->password(), kex_msgs_intermediate); } - wallets[0]->multisig(&ready); + ms_status = wallets[0]->get_multisig_status(); } std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->nettype()); diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h index 0d832f243..dc2fbabd8 100644 --- a/src/multisig/multisig_account.h +++ b/src/multisig/multisig_account.h @@ -40,6 +40,19 @@ namespace multisig { + struct multisig_account_status + { + // is the multisig account active/initialized? + bool multisig_is_active{false}; + // has the multisig account completed the main key exchange rounds? + bool kex_is_done{false}; + // is the multisig account ready to use? + bool is_ready{false}; + // multisig is: M-of-N + std::uint32_t threshold{0}; // M + std::uint32_t total{0}; // N + }; + /** * multisig account: * @@ -90,7 +103,8 @@ namespace multisig * * - prepares a kex msg for the first round of multisig key construction. * - the local account's kex msgs are signed with the base_privkey - * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's common_privkey + * - the first kex msg transmits the local base_common_privkey to other participants, for creating the group's + * common_privkey */ multisig_account(const crypto::secret_key &base_privkey, const crypto::secret_key &base_common_privkey); @@ -177,24 +191,48 @@ namespace multisig * - If force updating with maliciously-crafted messages, the resulting account will be invalid (either unable * to complete signatures, or a 'hostage' to the malicious signer [i.e. can't sign without his participation]). */ - void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, - const bool force_update_use_with_caution = false); + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, const bool force_update_use_with_caution = false); + /** + * brief: get_multisig_kex_round_booster - Create a multisig kex msg for the kex round that follows the kex round this + * account is currently working on, in order to 'boost' another participant's kex setup. + * - A booster message is for the round after the in-progress round because get_next_kex_round_msg() provides access + * to the in-progress round's message. + * - Useful for 'jumpstarting' the following kex round when you don't have messages from all other signers to complete + * the current round. + * - Sanitizes input messages and produces a new kex msg for round 'num_completed_rounds + 2'. + * + * - For example, in 2-of-3 escrowed purchasing, the [vendor, arbitrator] pair can boost the second round + * of key exchange by calling this function with the 'round 1' messages of each other. + * Then the [buyer] can use the resulting boost messages, in combination with [vender, arbitrator] round 1 messages, + * to complete the address in one step. In other words, call initialize_kex() on the round 1 messages, + * then call kex_update() on the round 2 booster messages to finish the multisig key. + * + * - Note: The 'threshold' and 'num_signers' are inputs here in case kex has not been initialized yet. + * param: threshold - threshold for multisig (M in M-of-N) + * param: num_signers - number of participants in multisig (N) + * param: expanded_msgs - set of multisig kex messages to process + * return: multisig kex message for next round + */ + multisig_kex_msg get_multisig_kex_round_booster(const std::uint32_t threshold, + const std::uint32_t num_signers, + const std::vector<multisig_kex_msg> &expanded_msgs) const; private: // implementation of kex_update() (non-transactional) void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, const bool incomplete_signer_set); /** - * brief: initialize_kex_update - Helper for kex_update_impl() - * - Collect the local signer's shared keys to ignore in incoming messages, build the aggregate ancillary key - * if appropriate. + * brief: get_kex_exclude_pubkeys - collect the local signer's shared keys to ignore in incoming messages + * return: keys held by the local account corresponding to the 'in-progress round' + * - If 'in-progress round' is the final round, these are the local account's shares of the final aggregate key. + */ + std::vector<crypto::public_key> get_kex_exclude_pubkeys() const; + /** + * brief: initialize_kex_update - initialize the multisig account for the first kex round * param: expanded_msgs - set of multisig kex messages to process * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) - * outparam: exclude_pubkeys_out - keys held by the local account corresponding to round 'current_round' - * - If 'current_round' is the final round, these are the local account's shares of the final aggregate key. */ void initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, - const std::uint32_t kex_rounds_required, - std::vector<crypto::public_key> &exclude_pubkeys_out); + const std::uint32_t kex_rounds_required); /** * brief: finalize_kex_update - Helper for kex_update_impl() * param: kex_rounds_required - number of rounds required for kex (not including post-kex verification round) diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp index ef0acf307..5c68dd5dc 100644 --- a/src/multisig/multisig_account_kex_impl.cpp +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -395,7 +395,7 @@ namespace multisig * - Sanitizes input msgs. * - Require uniqueness in: 'exclude_pubkeys'. * - Requires each input pubkey be recommended by 'num_recommendations = expected_round' msg signers. - * - For a final multisig key to be truly 'M-of-N', each of the the private key's components must be + * - For a final multisig key to be truly 'M-of-N', each of the private key's components must be * shared by (N - M + 1) signers. * - Requires that msgs are signed by only keys in 'signers'. * - Requires that each key in 'signers' recommends [num_signers - 2 CHOOSE (expected_round - 1)] pubkeys. @@ -567,7 +567,7 @@ namespace multisig // note: do NOT remove the local signer from the pubkey origins map, since the post-kex round can be force-updated with // just the local signer's post-kex message (if the local signer were removed, then the post-kex message's pubkeys - // would be completely lost) + // would be completely deleted) // evaluate pubkeys collected @@ -608,7 +608,7 @@ namespace multisig * INTERNAL * * brief: multisig_kex_process_round_msgs - Process kex messages for the active kex round. - * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_next_msg(). + * - A wrapper around evaluate_multisig_kex_round_msgs() -> multisig_kex_make_round_keys(). * - In other words, evaluate the input messages and try to make a message for the next round. * - Note: Must be called on the final round's msgs to evaluate the final key components * recommended by other participants. @@ -623,7 +623,7 @@ namespace multisig * outparam: keys_to_origins_map_out - map between round keys and identity keys * - If in the final round, these are key shares recommended by other signers for the final aggregate key. * - Otherwise, these are the local account's DH derivations for the next round. - * - See multisig_kex_make_next_msg() for an explanation. + * - See multisig_kex_make_round_keys() for an explanation. * return: multisig kex message for next round, or empty message if 'current_round' is the final round */ //---------------------------------------------------------------------------------------------------------------------- @@ -684,59 +684,67 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, - const std::uint32_t kex_rounds_required, - std::vector<crypto::public_key> &exclude_pubkeys_out) + std::vector<crypto::public_key> multisig_account::get_kex_exclude_pubkeys() const { + // exclude all keys the local account recommends + std::vector<crypto::public_key> exclude_pubkeys; + if (m_kex_rounds_complete == 0) { - // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys + // in the first round, only the local pubkey is recommended by the local signer + exclude_pubkeys.emplace_back(m_base_pubkey); + } + else + { + // in other rounds, kex msgs will contain participants' shared keys, so ignore shared keys the account helped + // create for this round + for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map) + exclude_pubkeys.emplace_back(shared_key_with_origins.first); + } - // collect participants' base common privkey shares - // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers - // will be blocked by duplicate-signer errors after this function is called - std::vector<crypto::secret_key> participant_base_common_privkeys; - participant_base_common_privkeys.reserve(expanded_msgs.size() + 1); + return exclude_pubkeys; + } + //---------------------------------------------------------------------------------------------------------------------- + // multisig_account: INTERNAL + //---------------------------------------------------------------------------------------------------------------------- + void multisig_account::initialize_kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const std::uint32_t kex_rounds_required) + { + // initialization is only needed during the first round + if (m_kex_rounds_complete > 0) + return; - // add local ancillary base privkey - participant_base_common_privkeys.emplace_back(m_base_common_privkey); + // the first round of kex msgs will contain each participant's base pubkeys and ancillary privkeys, so we prepare + // them here - // add other signers' base common privkeys - for (const auto &expanded_msg : expanded_msgs) - { - if (expanded_msg.get_signing_pubkey() != m_base_pubkey) - { - participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey()); - } - } + // collect participants' base common privkey shares + // note: duplicate privkeys are acceptable, and duplicates due to duplicate signers + // will be blocked by duplicate-signer errors after this function is called + std::vector<crypto::secret_key> participant_base_common_privkeys; + participant_base_common_privkeys.reserve(expanded_msgs.size() + 1); - // make common privkey - make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); + // add local ancillary base privkey + participant_base_common_privkeys.emplace_back(m_base_common_privkey); - // set common pubkey - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey), - "Failed to derive public key"); + // add other signers' base common privkeys + for (const multisig_kex_msg &expanded_msg : expanded_msgs) + { + if (expanded_msg.get_signing_pubkey() != m_base_pubkey) + participant_base_common_privkeys.emplace_back(expanded_msg.get_msg_privkey()); + } - // if N-of-N, then the base privkey will be used directly to make the account's share of the final key - if (kex_rounds_required == 1) - { - m_multisig_privkeys.clear(); - m_multisig_privkeys.emplace_back(m_base_privkey); - } + // make common privkey + make_multisig_common_privkey(std::move(participant_base_common_privkeys), m_common_privkey); - // exclude all keys the local account recommends - // - in the first round, only the local pubkey is recommended by the local signer - exclude_pubkeys_out.emplace_back(m_base_pubkey); - } - else - { - // in other rounds, kex msgs will contain participants' shared keys + // set common pubkey + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(m_common_privkey, m_common_pubkey), + "Failed to derive public key"); - // ignore shared keys the account helped create for this round - for (const auto &shared_key_with_origins : m_kex_keys_to_origins_map) - { - exclude_pubkeys_out.emplace_back(shared_key_with_origins.first); - } + // if N-of-N, then the base privkey will be used directly to make the account's share of the final key + if (kex_rounds_required == 1) + { + m_multisig_privkeys.clear(); + m_multisig_privkeys.emplace_back(m_base_privkey); } } //---------------------------------------------------------------------------------------------------------------------- @@ -771,9 +779,7 @@ namespace multisig result_keys.reserve(result_keys_to_origins_map.size()); for (const auto &result_key_and_origins : result_keys_to_origins_map) - { result_keys.emplace_back(result_key_and_origins.first); - } // compute final aggregate key, update local multisig privkeys with aggregation coefficients applied m_multisig_pubkey = generate_multisig_aggregate_key(std::move(result_keys), m_multisig_privkeys); @@ -811,7 +817,8 @@ namespace multisig // derived pubkey = multisig_key * G crypto::public_key_memsafe derived_pubkey; m_multisig_privkeys.push_back( - calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey)); + calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey) + ); // save the account's kex key mappings for this round [derived pubkey : other signers who will have the same key] m_kex_keys_to_origins_map[derived_pubkey] = std::move(derivation_and_origins.second); @@ -863,8 +870,7 @@ namespace multisig "Multisig kex has already completed all required rounds (including post-kex verification)."); // initialize account update - std::vector<crypto::public_key> exclude_pubkeys; - initialize_kex_update(expanded_msgs, kex_rounds_required, exclude_pubkeys); + this->initialize_kex_update(expanded_msgs, kex_rounds_required); // process messages into a [pubkey : {origins}] map multisig_keyset_map_memsafe_t result_keys_to_origins_map; @@ -875,12 +881,75 @@ namespace multisig m_threshold, m_signers, expanded_msgs, - exclude_pubkeys, + this->get_kex_exclude_pubkeys(), incomplete_signer_set, result_keys_to_origins_map); // finish account update - finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map)); + this->finalize_kex_update(kex_rounds_required, std::move(result_keys_to_origins_map)); + } + //----------------------------------------------------------------- + // multisig_account: EXTERNAL + //----------------------------------------------------------------- + multisig_kex_msg multisig_account::get_multisig_kex_round_booster(const std::uint32_t threshold, + const std::uint32_t num_signers, + const std::vector<multisig_kex_msg> &expanded_msgs) const + { + // the messages passed in should be required for the next kex round of this account (the round it is currently + // working on) + const std::uint32_t expected_msgs_round{m_kex_rounds_complete + 1}; + const std::uint32_t kex_rounds_required{multisig_kex_rounds_required(num_signers, threshold)}; + + CHECK_AND_ASSERT_THROW_MES(num_signers > 1, "Must be at least one other multisig signer."); + CHECK_AND_ASSERT_THROW_MES(num_signers <= config::MULTISIG_MAX_SIGNERS, "Too many multisig signers specified."); + CHECK_AND_ASSERT_THROW_MES(expected_msgs_round < kex_rounds_required, + "Multisig kex booster: this account has already completed all intermediate kex rounds so it can't make a kex " + "booster (there is no round available to boost)."); + CHECK_AND_ASSERT_THROW_MES(expanded_msgs.size() > 0, "At least one input kex message expected."); + + // sanitize pubkeys from input msgs + multisig_keyset_map_memsafe_t pubkey_origins_map; + const std::uint32_t msgs_round{ + multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, this->get_kex_exclude_pubkeys(), pubkey_origins_map) + }; + CHECK_AND_ASSERT_THROW_MES(msgs_round == expected_msgs_round, "Kex messages were not for expected round."); + + // remove the local signer from sanitized messages + remove_key_from_mapped_sets(m_base_pubkey, pubkey_origins_map); + + // make DH derivations for booster message + multisig_keyset_map_memsafe_t derivation_to_origins_map; + multisig_kex_make_round_keys(m_base_privkey, std::move(pubkey_origins_map), derivation_to_origins_map); + + // collect keys for booster message + std::vector<crypto::public_key> next_msg_keys; + next_msg_keys.reserve(derivation_to_origins_map.size()); + + if (msgs_round + 1 == kex_rounds_required) + { + // final kex round: send DH derivation pubkeys in the message + for (const auto &derivation_and_origins : derivation_to_origins_map) + { + // multisig_privkey = H(derivation) + // derived pubkey = multisig_key * G + crypto::public_key_memsafe derived_pubkey; + calculate_multisig_keypair_from_derivation(derivation_and_origins.first, derived_pubkey); + + // save keys that should be recommended to other signers + // - The keys multisig_key*G are sent to other participants in the message, so they can be used to produce the final + // multisig key via generate_multisig_spend_public_key(). + next_msg_keys.push_back(derived_pubkey); + } + } + else //(msgs_round + 1 < kex_rounds_required) + { + // intermediate kex round: send DH derivations directly in the message + for (const auto &derivation_and_origins : derivation_to_origins_map) + next_msg_keys.push_back(derivation_and_origins.first); + } + + // produce a kex message for the round after the round this account is currently working on + return multisig_kex_msg{msgs_round + 1, m_base_privkey, std::move(next_msg_keys)}.get_msg(); } //---------------------------------------------------------------------------------------------------------------------- } //namespace multisig diff --git a/src/multisig/multisig_tx_builder_ringct.cpp b/src/multisig/multisig_tx_builder_ringct.cpp index 8643a8af4..2653a70dd 100644 --- a/src/multisig/multisig_tx_builder_ringct.cpp +++ b/src/multisig/multisig_tx_builder_ringct.cpp @@ -820,7 +820,6 @@ tx_builder_ringct_t::~tx_builder_ringct_t() bool tx_builder_ringct_t::init( const cryptonote::account_keys& account_keys, const std::vector<std::uint8_t>& extra, - const std::uint64_t unlock_time, const std::uint32_t subaddr_account, const std::set<std::uint32_t>& subaddr_minor_indices, std::vector<cryptonote::tx_source_entry>& sources, @@ -854,7 +853,7 @@ bool tx_builder_ringct_t::init( // misc. fields unsigned_tx.version = 2; //rct = 2 - unsigned_tx.unlock_time = unlock_time; + unsigned_tx.unlock_time = 0; // sort inputs sort_sources(sources); diff --git a/src/multisig/multisig_tx_builder_ringct.h b/src/multisig/multisig_tx_builder_ringct.h index f1bd24e73..a1b72b177 100644 --- a/src/multisig/multisig_tx_builder_ringct.h +++ b/src/multisig/multisig_tx_builder_ringct.h @@ -71,7 +71,6 @@ public: bool init( const cryptonote::account_keys& account_keys, const std::vector<std::uint8_t>& extra, - const std::uint64_t unlock_time, const std::uint32_t subaddr_account, const std::set<std::uint32_t>& subaddr_minor_indices, std::vector<cryptonote::tx_source_entry>& sources, diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 815c1b354..505ba026e 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -702,18 +702,18 @@ namespace nodetool if (m_nettype == cryptonote::TESTNET) { full_addrs.insert("176.9.0.187:28080"); - full_addrs.insert("88.99.173.38:28080"); full_addrs.insert("51.79.173.165:28080"); full_addrs.insert("192.99.8.110:28080"); full_addrs.insert("37.187.74.171:28080"); + full_addrs.insert("77.172.183.193:28080"); } else if (m_nettype == cryptonote::STAGENET) { full_addrs.insert("176.9.0.187:38080"); - full_addrs.insert("88.99.173.38:38080"); full_addrs.insert("51.79.173.165:38080"); full_addrs.insert("192.99.8.110:38080"); full_addrs.insert("37.187.74.171:38080"); + full_addrs.insert("77.172.183.193:38080"); } else if (m_nettype == cryptonote::FAKECHAIN) { @@ -723,10 +723,10 @@ namespace nodetool full_addrs.insert("176.9.0.187:18080"); full_addrs.insert("88.198.163.90:18080"); full_addrs.insert("66.85.74.134:18080"); - full_addrs.insert("88.99.173.38:18080"); full_addrs.insert("51.79.173.165:18080"); full_addrs.insert("192.99.8.110:18080"); full_addrs.insert("37.187.74.171:18080"); + full_addrs.insert("77.172.183.193:18080"); } return full_addrs; } @@ -857,12 +857,12 @@ namespace nodetool if (m_nettype == cryptonote::MAINNET) { return { - "xwvz3ekocr3dkyxfkmgm2hvbpzx2ysqmaxgter7znnqrhoicygkfswid.onion:18083", - "4pixvbejrvihnkxmduo2agsnmc3rrulrqc7s3cbwwrep6h6hrzsibeqd.onion:18083", "zbjkbsxc5munw3qusl7j2hpcmikhqocdf4pqhnhtpzw5nt5jrmofptid.onion:18083", "qz43zul2x56jexzoqgkx2trzwcfnr6l3hbtfcfx54g4r3eahy3bssjyd.onion:18083", "plowsof3t5hogddwabaeiyrno25efmzfxyro2vligremt7sxpsclfaid.onion:18083", "plowsoffjexmxalw73tkjmf422gq6575fc7vicuu4javzn2ynnte6tyd.onion:18083", + "plowsofe6cleftfmk2raiw5h2x66atrik3nja4bfd3zrfa2hdlgworad.onion:18083", + "aclc4e2jhhtr44guufbnwk5bzwhaecinax4yip4wr4tjn27sjsfg6zqd.onion:18083", }; } return {}; @@ -870,10 +870,9 @@ namespace nodetool if (m_nettype == cryptonote::MAINNET) { return { - "s3l6ke4ed3df466khuebb4poienoingwof7oxtbo6j4n56sghe3a.b32.i2p:18080", - "sel36x6fibfzujwvt4hf5gxolz6kd3jpvbjqg6o3ud2xtionyl2q.b32.i2p:18080", - "uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p:18080", - "vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p:18080", + "uqj3aphckqtjsitz7kxx5flqpwjlq5ppr3chazfued7xucv3nheq.b32.i2p", + "vdmnehdjkpkg57nthgnjfuaqgku673r5bpbqg56ix6fyqoywgqrq.b32.i2p", + "ugnlcdciyhghh2zert7c3kl4biwkirc43ke33jiy5slnd3mv2trq.b32.i2p", }; } return {}; @@ -2296,11 +2295,12 @@ namespace nodetool if (enet::zone::tor < network->first) break; // unknown network - if (network->second.m_connect) + const auto status = network->second.m_notifier.get_status(); + if (network->second.m_connect && status.has_outgoing) return send(*network); } - // configuration should not allow this scenario + MWARNING("Unable to send " << txs.size() << " transaction(s): anonymity networks had no outgoing connections"); return enet::zone::invalid; } //----------------------------------------------------------------------------------- diff --git a/src/p2p/net_peerlist.cpp b/src/p2p/net_peerlist.cpp index 11cf235a5..78cf13785 100644 --- a/src/p2p/net_peerlist.cpp +++ b/src/p2p/net_peerlist.cpp @@ -42,6 +42,7 @@ #include <boost/serialization/version.hpp> #include "net_peerlist_boost_serialization.h" +#include "common/util.h" namespace nodetool @@ -200,7 +201,7 @@ namespace nodetool if (!out) { // if failed, try reading in unportable mode - boost::filesystem::copy_file(path, path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + tools::copy_file(path, path + ".unportable"); src_file.close(); src_file.open( path , std::ios_base::binary | std::ios_base::in); if(src_file.fail()) diff --git a/src/ringct/rctTypes.h b/src/ringct/rctTypes.h index 299798e17..dea95ace0 100644 --- a/src/ringct/rctTypes.h +++ b/src/ringct/rctTypes.h @@ -771,7 +771,7 @@ namespace std BLOB_SERIALIZER(rct::key); BLOB_SERIALIZER(rct::key64); BLOB_SERIALIZER(rct::ctkey); -BLOB_SERIALIZER(rct::multisig_kLRki); +BLOB_SERIALIZER_FORCED(rct::multisig_kLRki); BLOB_SERIALIZER(rct::boroSig); VARIANT_TAG(debug_archive, rct::key, "rct::key"); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 9a0b02f70..16d66e79d 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1378,6 +1378,8 @@ namespace cryptonote add_reason(reason, "too few outputs"); if ((res.tx_extra_too_big = tvc.m_tx_extra_too_big)) add_reason(reason, "tx-extra too big"); + if ((res.nonzero_unlock_time = tvc.m_nonzero_unlock_time)) + add_reason(reason, "tx unlock time is not zero"); const std::string punctuation = reason.empty() ? "" : ": "; if (tvc.m_verifivation_failed) { @@ -3019,16 +3021,10 @@ namespace cryptonote CHECK_PAYMENT(req, res, COST_PER_FEE_ESTIMATE); - const uint8_t version = m_core.get_blockchain_storage().get_current_hard_fork_version(); - if (version >= HF_VERSION_2021_SCALING) { m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.grace_blocks, res.fees); res.fee = res.fees[0]; } - else - { - res.fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.grace_blocks); - } res.quantization_mask = Blockchain::get_fee_quantization_mask(); 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 2a0a6201d..c634b8957 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 13 +#define CORE_RPC_VERSION_MINOR 14 #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) @@ -640,6 +640,7 @@ namespace cryptonote bool too_few_outputs; bool sanity_check_failed; bool tx_extra_too_big; + bool nonzero_unlock_time; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_access_response_base) @@ -655,6 +656,7 @@ namespace cryptonote KV_SERIALIZE(too_few_outputs) KV_SERIALIZE(sanity_check_failed) KV_SERIALIZE(tx_extra_too_big) + KV_SERIALIZE(nonzero_unlock_time) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 52067bd4d..4712de134 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -422,6 +422,16 @@ namespace rpc if (!res.error_details.empty()) res.error_details += " and "; res.error_details += "too few outputs"; } + if (tvc.m_tx_extra_too_big) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details += "tx_extra too long"; + } + if (tvc.m_nonzero_unlock_time) + { + if (!res.error_details.empty()) res.error_details += " and "; + res.error_details += "non-zero unlock time"; + } if (res.error_details.empty()) { res.error_details = "an unknown issue was found with the transaction"; @@ -831,14 +841,11 @@ namespace rpc void DaemonHandler::handle(const GetFeeEstimate::Request& req, GetFeeEstimate::Response& res) { res.hard_fork_version = m_core.get_blockchain_storage().get_current_hard_fork_version(); - res.estimated_base_fee = m_core.get_blockchain_storage().get_dynamic_base_fee_estimate(req.num_grace_blocks); - if (res.hard_fork_version < HF_VERSION_PER_BYTE_FEE) - { - res.size_scale = 1024; // per KiB fee - res.fee_mask = 1; - } - else + std::vector<uint64_t> fees; + m_core.get_blockchain_storage().get_dynamic_base_fee_estimate_2021_scaling(req.num_grace_blocks, fees); + res.estimated_base_fee = fees[0]; + { res.size_scale = 1; // per byte fee res.fee_mask = Blockchain::get_fee_quantization_mask(); diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h index 896d00583..2da26abe7 100644 --- a/src/serialization/crypto.h +++ b/src/serialization/crypto.h @@ -81,7 +81,7 @@ BLOB_SERIALIZER(crypto::chacha_iv); BLOB_SERIALIZER(crypto::hash); BLOB_SERIALIZER(crypto::hash8); BLOB_SERIALIZER(crypto::public_key); -BLOB_SERIALIZER(crypto::secret_key); +BLOB_SERIALIZER_FORCED(crypto::secret_key); BLOB_SERIALIZER(crypto::key_derivation); BLOB_SERIALIZER(crypto::key_image); BLOB_SERIALIZER(crypto::signature); diff --git a/src/serialization/serialization.h b/src/serialization/serialization.h index d1f97e324..33edf3f62 100644 --- a/src/serialization/serialization.h +++ b/src/serialization/serialization.h @@ -50,13 +50,16 @@ #include <boost/type_traits/integral_constant.hpp> #include <boost/mpl/bool.hpp> -/*! \struct is_blob_type +/*! \struct is_blob_type / is_blob_forced * - * \brief a descriptor for dispatching serialize + * \brief descriptors for dispatching serialize: whether to take byte-wise copy/store to type */ template <class T> struct is_blob_type { typedef boost::false_type type; }; +template <class T> +struct is_blob_forced: std::false_type {}; + /*! \fn do_serialize(Archive &ar, T &v) * * \brief main function for dispatching serialization for a given pair of archive and value types @@ -68,6 +71,8 @@ struct is_blob_type { typedef boost::false_type type; }; template <class Archive, class T> inline std::enable_if_t<is_blob_type<T>::type::value, bool> do_serialize(Archive &ar, T &v) { + static_assert(std::is_trivially_copyable<T>() || is_blob_forced<T>(), + "sanity check: types that can't be trivially copied shouldn't be using the blob serializer"); ar.serialize_blob(&v, sizeof(v)); return true; } @@ -94,6 +99,9 @@ inline bool do_serialize(Archive &ar, bool &v) /*! \macro BLOB_SERIALIZER * * \brief makes the type have a blob serializer trait defined + * + * In case your type is not a good candidate to be blob serialized, a static assertion may be thrown + * at compile-time. */ #define BLOB_SERIALIZER(T) \ template<> \ @@ -101,6 +109,20 @@ inline bool do_serialize(Archive &ar, bool &v) typedef boost::true_type type; \ } +/*! \macro BLOB_SERIALIZER_FORCED + * + * \brief makes the type have a blob serializer trait defined, even if it isn't trivially copyable + * + * Caution: do NOT use this macro for your type <T>, unless you are absolutely sure that moving raw + * bytes in/out of this type will not cause undefined behavior. Any types with managed memory + * (e.g. vector, string, etc) will segfault and/or cause memory errors if you use this macro with + * that type. + */ +#define BLOB_SERIALIZER_FORCED(T) \ + BLOB_SERIALIZER(T); \ + template<> \ + struct is_blob_forced<T>: std::true_type {}; + /*! \macro VARIANT_TAG * * \brief Adds the tag \tag to the \a Archive of \a Type diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h index d9592bc96..b1ef94097 100644 --- a/src/serialization/tuple.h +++ b/src/serialization/tuple.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2023, The Monero Project +// Copyright (c) 2014-2024, The Monero Project // // All rights reserved. // @@ -29,7 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #pragma once -#include <memory> +#include <tuple> #include "serialization.h" namespace serialization @@ -51,119 +51,71 @@ namespace serialization } } -template <template <bool> class Archive, class E0, class E1, class E2> -inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2>& p) +template <size_t I, bool BackwardsCompat, bool W, template <bool> class Archive, typename... Ts> +bool do_serialize_tuple_nth(Archive<W>& ar, std::tuple<Ts...>& v) { - size_t cnt; - ar.begin_array(cnt); - if (!ar.good()) - return false; - if (cnt != 3) - return false; + static constexpr const size_t tuple_size = std::tuple_size<std::tuple<Ts...>>(); + static_assert(I <= tuple_size, "bad call"); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) - return false; - if (!ar.good()) - return false; + if constexpr (I == 0) + { + // If BackwardsCompat is true, we serialize the size of 3-tuples and 4-tuples + if constexpr (BackwardsCompat && (tuple_size == 3 || tuple_size == 4)) + { + size_t cnt = tuple_size; + ar.begin_array(cnt); + if (cnt != tuple_size) + return false; + } + else + { + ar.begin_array(); + } + } + else if constexpr (I < tuple_size) + { + ar.delimit_array(); + } - ar.end_array(); - return true; + if constexpr (I == tuple_size) + { + ar.end_array(); + return ar.good(); + } + else + { + if (!::serialization::detail::serialize_tuple_element(ar, std::get<I>(v)) + || !ar.good()) + return false; + + return do_serialize_tuple_nth<I + 1, BackwardsCompat>(ar, v); + } } -template <template <bool> class Archive, class E0, class E1, class E2> -inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2>& p) +template <bool BackwardsCompat, bool W, template <bool> class Archive, typename... Ts> +bool do_serialize_tuple(Archive<W>& ar, std::tuple<Ts...>& v) { - ar.begin_array(3); - if (!ar.good()) - return false; - if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) - return false; - if (!ar.good()) - return false; - ar.end_array(); - return true; + return do_serialize_tuple_nth<0, BackwardsCompat>(ar, v); } -template <template <bool> class Archive, class E0, class E1, class E2, class E3> -inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2,E3>& p) +template <bool W, template <bool> class Archive, typename... Ts> +bool do_serialize(Archive<W>& ar, std::tuple<Ts...>& v) { - size_t cnt; - ar.begin_array(cnt); - if (!ar.good()) - return false; - if (cnt != 4) - return false; + return do_serialize_tuple<true>(ar, v); +} - if (!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if (!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) - return false; - if (!ar.good()) - return false; +#define TUPLE_COMPACT_FIELDS(v) \ + do { \ + if (!do_serialize_tuple<false>(ar, v) || !ar.good()) \ + return false; \ + } while (0); - ar.end_array(); - return true; -} +#define TUPLE_COMPACT_FIELD_N(t, v) \ + do { \ + ar.tag(t); \ + TUPLE_COMPACT_FIELDS(v); \ + } while (0); -template <template <bool> class Archive, class E0, class E1, class E2, class E3> -inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2,E3>& p) -{ - ar.begin_array(4); - if (!ar.good()) - return false; - if(!::serialization::detail::serialize_tuple_element(ar, std::get<0>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if(!::serialization::detail::serialize_tuple_element(ar, std::get<1>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if(!::serialization::detail::serialize_tuple_element(ar, std::get<2>(p))) - return false; - if (!ar.good()) - return false; - ar.delimit_array(); - if(!::serialization::detail::serialize_tuple_element(ar, std::get<3>(p))) - return false; - if (!ar.good()) - return false; - ar.end_array(); - return true; -} +#define TUPLE_COMPACT_FIELD(f) TUPLE_COMPACT_FIELD_N(#f, f) +#define TUPLE_COMPACT_FIELD_F(f) TUPLE_COMPACT_FIELD_N(#f, v.f) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 5a9d790cb..4f76834e3 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -148,10 +148,16 @@ typedef cryptonote::simple_wallet sw; } \ } while(0) -enum TransferType { - Transfer, - TransferLocked, -}; +#define CHECK_IF_BACKGROUND_SYNCING(msg) \ + do \ + { \ + if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) \ + { \ + std::string type = m_wallet->is_background_wallet() ? "background wallet" : "background syncing wallet"; \ + fail_msg_writer() << boost::format(tr("%s %s")) % type % msg; \ + return false; \ + } \ + } while (0) static std::string get_human_readable_timespan(std::chrono::seconds seconds); static std::string get_human_readable_timespan(uint64_t seconds); @@ -189,8 +195,6 @@ namespace const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]"); const char* USAGE_PAYMENT_ID("payment_id"); const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [subtractfeefrom=<D0>[,<D1>,all,...]] [<payment_id>]"); - const char* USAGE_LOCKED_TRANSFER("locked_transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <addr> <amount>) <lockblocks> [<payment_id (obsolete)>]"); - const char* USAGE_LOCKED_SWEEP_ALL("locked_sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] <address> <lockblocks> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_ALL("sweep_all [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_ACCOUNT("sweep_account <account> [index=<N1>[,<N2>,...] | index=all] [<priority>] [<ring_size>] [outputs=<N>] <address> [<payment_id (obsolete)>]"); const char* USAGE_SWEEP_BELOW("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id (obsolete)>]"); @@ -321,7 +325,7 @@ namespace auto pwd_container = tools::password_container::prompt(verify, prompt); if (!pwd_container) { - tools::fail_msg_writer() << sw::tr("failed to read wallet password"); + tools::fail_msg_writer() << sw::tr("failed to read password"); } return pwd_container; } @@ -331,6 +335,11 @@ namespace return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify); } + boost::optional<tools::password_container> background_sync_cache_password_prompter(bool verify) + { + return password_prompter(verify ? sw::tr("Enter a custom password for the background sync cache") : sw::tr("Background sync cache password"), verify); + } + inline std::string interpret_rpc_response(bool ok, const std::string& status) { std::string err; @@ -448,6 +457,41 @@ namespace return "invalid"; } + const struct + { + const char *name; + tools::wallet2::BackgroundSyncType background_sync_type; + } background_sync_type_names[] = + { + { "off", tools::wallet2::BackgroundSyncOff }, + { "reuse-wallet-password", tools::wallet2::BackgroundSyncReusePassword }, + { "custom-background-password", tools::wallet2::BackgroundSyncCustomPassword }, + }; + + bool parse_background_sync_type(const std::string &s, tools::wallet2::BackgroundSyncType &background_sync_type) + { + for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) + { + if (s == background_sync_type_names[n].name) + { + background_sync_type = background_sync_type_names[n].background_sync_type; + return true; + } + } + fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse background sync type"); + return false; + } + + std::string get_background_sync_type_name(tools::wallet2::BackgroundSyncType type) + { + for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) + { + if (type == background_sync_type_names[n].background_sync_type) + return background_sync_type_names[n].name; + } + return "invalid"; + } + std::string get_version_string(uint32_t version) { return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff); @@ -800,6 +844,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto fail_msg_writer() << tr("wallet is watch-only and has no spend key"); return true; } + CHECK_IF_BACKGROUND_SYNCING("has no spend key"); // don't log PAUSE_READLINE(); if (m_wallet->key_on_device()) { @@ -819,7 +864,6 @@ bool simple_wallet::print_seed(bool encrypted) { bool success = false; epee::wipeable_string seed; - bool ready, multisig; if (m_wallet->key_on_device()) { @@ -831,11 +875,12 @@ bool simple_wallet::print_seed(bool encrypted) fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } + CHECK_IF_BACKGROUND_SYNCING("has no seed"); - multisig = m_wallet->multisig(&ready); - if (multisig) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (ms_status.multisig_is_active) { - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("wallet is multisig but not yet finalized"); return true; @@ -844,7 +889,7 @@ bool simple_wallet::print_seed(bool encrypted) SCOPED_WALLET_UNLOCK(); - if (!multisig && !m_wallet->is_deterministic()) + if (!ms_status.multisig_is_active && !m_wallet->is_deterministic()) { fail_msg_writer() << tr("wallet is non-deterministic and has no seed"); return true; @@ -859,7 +904,7 @@ bool simple_wallet::print_seed(bool encrypted) seed_pass = pwd_container->password(); } - if (multisig) + if (ms_status.multisig_is_active) success = m_wallet->get_multisig_seed(seed, seed_pass); else if (m_wallet->is_deterministic()) success = m_wallet->get_seed(seed, seed_pass); @@ -898,7 +943,7 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("wallet is multisig and has no seed"); return true; @@ -908,6 +953,7 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s fail_msg_writer() << tr("wallet is watch-only and has no seed"); return true; } + CHECK_IF_BACKGROUND_SYNCING("has no seed"); epee::wipeable_string password; { @@ -1044,7 +1090,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("This wallet is already multisig"); return false; @@ -1054,6 +1100,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); return false; } + CHECK_IF_BACKGROUND_SYNCING("cannot be made multisig"); if(m_wallet->get_num_transfer_details()) { @@ -1091,7 +1138,7 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("This wallet is already multisig"); return false; @@ -1136,9 +1183,7 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo auto local_args = args; local_args.erase(local_args.begin()); std::string multisig_extra_info = m_wallet->make_multisig(orig_pwd_container->password(), local_args, threshold); - bool ready; - m_wallet->multisig(&ready); - if (!ready) + if (!m_wallet->get_multisig_status().is_ready) { success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; @@ -1156,13 +1201,13 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo return false; } - uint32_t total; - if (!m_wallet->multisig(NULL, &threshold, &total)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("Error creating multisig: new wallet is not multisig"); return false; } - success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") + success_msg_writer() << std::to_string(ms_status.threshold) << "/" << ms_status.total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); return true; @@ -1188,18 +1233,18 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & const bool force_update_use_with_caution, const bool called_by_mms) { CHECK_MULTISIG_ENABLED(); - bool ready; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (!m_wallet->multisig(&ready)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This wallet is not multisig"); return false; } - if (ready) + if (ms_status.is_ready) { fail_msg_writer() << tr("This wallet is already finalized"); return false; @@ -1215,9 +1260,7 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & try { std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args, force_update_use_with_caution); - bool ready; - m_wallet->multisig(&ready); - if (!ready) + if (!m_wallet->get_multisig_status().is_ready) { message_writer() << tr("Another step is needed"); message_writer() << multisig_extra_info; @@ -1228,9 +1271,8 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & } return true; } else { - uint32_t threshold, total; - m_wallet->multisig(NULL, &threshold, &total); - success_msg_writer() << tr("Multisig wallet has been successfully created. Current wallet type: ") << threshold << "/" << total; + const multisig::multisig_account_status ms_status_new{m_wallet->get_multisig_status()}; + success_msg_writer() << tr("Multisig wallet has been successfully created. Current wallet type: ") << ms_status_new.threshold << "/" << ms_status_new.total; success_msg_writer() << tr("Multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } } @@ -1253,18 +1295,18 @@ bool simple_wallet::export_multisig(const std::vector<std::string> &args) bool simple_wallet::export_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { CHECK_MULTISIG_ENABLED(); - bool ready; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (!m_wallet->multisig(&ready)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This wallet is not multisig"); return false; } - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return false; @@ -1320,24 +1362,24 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) bool simple_wallet::import_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { CHECK_MULTISIG_ENABLED(); - bool ready; - uint32_t threshold, total; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (!m_wallet->multisig(&ready, &threshold, &total)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This wallet is not multisig"); return false; } - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return false; } - if (args.size() < threshold - 1) + if (args.size() + 1 < ms_status.threshold) { PRINT_USAGE(USAGE_IMPORT_MULTISIG_INFO); return false; @@ -1417,18 +1459,19 @@ bool simple_wallet::sign_multisig(const std::vector<std::string> &args) bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { CHECK_MULTISIG_ENABLED(); - bool ready; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()};\ + if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if(!m_wallet->multisig(&ready)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This is not a multisig wallet"); return false; } - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return false; @@ -1502,9 +1545,7 @@ bool simple_wallet::sign_multisig_main(const std::vector<std::string> &args, boo if (txids.empty()) { - uint32_t threshold; - m_wallet->multisig(NULL, &threshold); - uint32_t signers_needed = threshold - signers - 1; + uint32_t signers_needed = ms_status.threshold - signers - 1; success_msg_writer(true) << tr("Transaction successfully signed to file ") << filename << ", " << signers_needed << " more signer(s) needed"; return true; @@ -1534,19 +1575,19 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, bool called_by_mms) { CHECK_MULTISIG_ENABLED(); - bool ready; - uint32_t threshold; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return false; } - if (!m_wallet->multisig(&ready, &threshold)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This is not a multisig wallet"); return false; } - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return false; @@ -1584,10 +1625,10 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b return false; } } - if (txs.m_signers.size() < threshold) + if (txs.m_signers.size() < ms_status.threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) - % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + % txs.m_signers.size() % (ms_status.threshold - txs.m_signers.size())).str(); return false; } @@ -1616,19 +1657,19 @@ bool simple_wallet::submit_multisig_main(const std::vector<std::string> &args, b bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) { CHECK_MULTISIG_ENABLED(); - bool ready; - uint32_t threshold; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (!m_wallet->multisig(&ready, &threshold)) + if (!ms_status.multisig_is_active) { fail_msg_writer() << tr("This is not a multisig wallet"); return true; } - if (!ready) + if (!ms_status.is_ready) { fail_msg_writer() << tr("This multisig wallet is not yet finalized"); return true; @@ -1654,10 +1695,10 @@ bool simple_wallet::export_raw_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("Failed to load multisig transaction from file"); return true; } - if (txs.m_signers.size() < threshold) + if (txs.m_signers.size() < ms_status.threshold) { fail_msg_writer() << (boost::format(tr("Multisig transaction signed by only %u signers, needs %u more signatures")) - % txs.m_signers.size() % (threshold - txs.m_signers.size())).str(); + % txs.m_signers.size() % (ms_status.threshold - txs.m_signers.size())).str(); return true; } @@ -2119,6 +2160,7 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args) bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze) { + CHECK_IF_BACKGROUND_SYNCING("cannot freeze/thaw"); if (args.empty()) { fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw"); @@ -2158,6 +2200,7 @@ bool simple_wallet::thaw(const std::vector<std::string> &args) bool simple_wallet::frozen(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot see frozen key images"); if (args.empty()) { size_t ntd = m_wallet->get_num_transfer_details(); @@ -2808,6 +2851,57 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: return true; } +bool simple_wallet::setup_background_sync(const std::vector<std::string> &args/* = std::vector<std::string>()*/) +{ + if (m_wallet->get_multisig_status().multisig_is_active) + { + fail_msg_writer() << tr("background sync not implemented for multisig wallet"); + return true; + } + if (m_wallet->watch_only()) + { + fail_msg_writer() << tr("background sync not implemented for watch only wallet"); + return true; + } + if (m_wallet->key_on_device()) + { + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } + + tools::wallet2::BackgroundSyncType background_sync_type; + if (!parse_background_sync_type(args[1], background_sync_type)) + { + fail_msg_writer() << tr("invalid option"); + return true; + } + + const auto pwd_container = get_and_verify_password(); + if (!pwd_container) + return true; + + try + { + boost::optional<epee::wipeable_string> background_cache_password = boost::none; + if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) + { + const auto background_pwd_container = background_sync_cache_password_prompter(true); + if (!background_pwd_container) + return true; + background_cache_password = background_pwd_container->password(); + } + + LOCK_IDLE_SCOPE(); + m_wallet->setup_background_sync(background_sync_type, pwd_container->password(), background_cache_password); + } + catch (const std::exception &e) + { + fail_msg_writer() << tr("Error setting background sync type: ") << e.what(); + } + + return true; +} + bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { const auto pwd_container = get_and_verify_password(); @@ -3040,6 +3134,7 @@ bool simple_wallet::apropos(const std::vector<std::string> &args) bool simple_wallet::scan_tx(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot scan tx"); if (args.empty()) { PRINT_USAGE(USAGE_SCAN_TX); @@ -3126,14 +3221,6 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::on_command, this, &simple_wallet::transfer, _1), tr(USAGE_TRANSFER), tr("Transfer <amount> to <address>. If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included). The \"subtractfeefrom=\" list allows you to choose which destinations to fund the tx fee from instead of the change output. The fee will be split across the chosen destinations proportionally equally. For example, to make 3 transfers where the fee is taken from the first and third destinations, one could do: \"transfer <addr1> 3 <addr2> 0.5 <addr3> 1 subtractfeefrom=0,2\". Let's say the tx fee is 0.1. The balance would drop by exactly 4.5 XMR including fees, and addr1 & addr3 would receive 2.925 & 0.975 XMR, respectively. Use \"subtractfeefrom=all\" to spread the fee across all destinations.")); - m_cmd_binder.set_handler("locked_transfer", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_transfer,_1), - tr(USAGE_LOCKED_TRANSFER), - tr("Transfer <amount> to <address> and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" is specified, the wallet uses outputs received by addresses of those indices. If omitted, the wallet randomly chooses address indices to be used. In any case, it tries its best not to combine outputs across multiple addresses. <priority> is the priority of the transaction. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability. Multiple payments can be made at once by adding URI_2 or <address_2> <amount_2> etcetera (before the payment ID, if it's included)")); - m_cmd_binder.set_handler("locked_sweep_all", - boost::bind(&simple_wallet::on_command, this, &simple_wallet::locked_sweep_all,_1), - tr(USAGE_LOCKED_SWEEP_ALL), - tr("Send all unlocked balance to an address and lock it for <lockblocks> (max. 1000000). If the parameter \"index=<N1>[,<N2>,...]\" or \"index=all\" is specified, the wallet sweeps outputs received by those or all address indices, respectively. If omitted, the wallet randomly chooses an address index to be used. <priority> is the priority of the sweep. The higher the priority, the higher the transaction fee. Valid values in priority order (from lowest to highest) are: unimportant, normal, elevated, priority. If omitted, the default value (see the command \"set priority\") is used. <ring_size> is the number of inputs to include for untraceability.")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::on_command, this, &simple_wallet::sweep_unmixable, _1), tr("Send all unmixable outputs to yourself with ring_size 1")); @@ -3265,6 +3352,8 @@ simple_wallet::simple_wallet() " Ignore outputs of amount below this threshold when spending.\n " "track-uses <1|0>\n " " Whether to keep track of owned outputs uses.\n " + "background-sync <off|reuse-wallet-password|custom-background-password>\n " + " Set this to enable scanning in the background with just the view key while the wallet is locked.\n " "setup-background-mining <1|0>\n " " Whether to enable background mining. Set this to support the network and to get a chance to receive new monero.\n " "device-name <device_name[:device_spec]>\n " @@ -3667,6 +3756,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above()); success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below()); success_msg_writer() << "track-uses = " << m_wallet->track_uses(); + success_msg_writer() << "background-sync = " << get_background_sync_type_name(m_wallet->background_sync_type()); success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; success_msg_writer() << "device-name = " << m_wallet->device_name(); success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); @@ -3682,6 +3772,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) } else { + CHECK_IF_BACKGROUND_SYNCING("cannot change wallet settings"); #define CHECK_SIMPLE_VARIABLE(name, f, help) do \ if (args[0] == name) { \ @@ -3735,6 +3826,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); + CHECK_SIMPLE_VARIABLE("background-sync", setup_background_sync, tr("off (default); reuse-wallet-password (reuse the wallet password to encrypt the background cache); custom-background-password (use a custom background password to encrypt the background cache)")); CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); @@ -3838,7 +3930,7 @@ bool simple_wallet::ask_wallet_create_if_needed() bool ok = true; if (!m_restoring) { - message_writer() << tr("Looking for filename: ") << boost::filesystem::complete(wallet_path); + message_writer() << tr("Looking for filename: ") << boost::filesystem::absolute(wallet_path); message_writer() << tr("No wallet found with that name. Confirm creation of new wallet named: ") << wallet_path; confirm_creation = input_line("", true); if(std::cin.eof()) @@ -3870,7 +3962,7 @@ void simple_wallet::print_seed(const epee::wipeable_string &seed) { success_msg_writer(true) << "\n" << boost::format(tr("NOTE: the following %s can be used to recover access to your wallet. " "Write them down and store them somewhere safe and secure. Please do not store them in " - "your email or on file storage services outside of your immediate control.\n")) % (m_wallet->multisig() ? tr("string") : tr("25 words")); + "your email or on file storage services outside of your immediate control.\n")) % (m_wallet->get_multisig_status().multisig_is_active ? tr("string") : tr("25 words")); // don't log int space_index = 0; size_t len = seed.size(); @@ -4675,7 +4767,10 @@ std::string simple_wallet::get_mnemonic_language() //---------------------------------------------------------------------------------------------------- boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const { - auto pwd_container = default_password_prompter(m_wallet_file.empty()); + const bool verify = m_wallet_file.empty(); + auto pwd_container = (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) + ? background_sync_cache_password_prompter(verify) + : default_password_prompter(verify); if (!pwd_container) return boost::none; @@ -4774,7 +4869,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr "your current session's state. Otherwise, you might need to synchronize \n" "your wallet again (your wallet keys are NOT at risk in any case).\n") ; - success_msg_writer() << tr("Filename: ") << boost::filesystem::complete(m_wallet->get_keys_file()); + success_msg_writer() << tr("Filename: ") << boost::filesystem::absolute(m_wallet->get_keys_file()); if (!two_random) { @@ -4922,14 +5017,14 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr const epee::wipeable_string &msig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true); m_wallet->generate(m_wallet_file, std::move(rc.second).password(), msig_keys, create_address_file); } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total) || !ready) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active || !ms_status.is_ready) { fail_msg_writer() << tr("failed to generate new mutlisig wallet"); return {}; } - message_writer(console_color_white, true) << boost::format(tr("Generated new %u/%u multisig wallet: ")) % threshold % total + message_writer(console_color_white, true) << boost::format(tr("Generated new %u/%u multisig wallet: ")) % ms_status.threshold % ms_status.total << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } catch (const std::exception& e) @@ -4973,12 +5068,13 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p m_wallet->callback(this); m_wallet->load(m_wallet_file, password); std::string prefix; - bool ready; - uint32_t threshold, total; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; if (m_wallet->watch_only()) prefix = tr("Opened watch-only wallet"); - else if (m_wallet->multisig(&ready, &threshold, &total)) - prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else if (ms_status.multisig_is_active) + prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % ms_status.threshold % ms_status.total % (ms_status.is_ready ? "" : " (not yet finalized)")).str(); + else if (m_wallet->is_background_wallet()) + prefix = tr("Opened background wallet"); else prefix = tr("Opened wallet"); message_writer(console_color_white, true) << @@ -5096,7 +5192,7 @@ bool simple_wallet::save(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("wallet is multisig and cannot save a watch-only version"); return true; @@ -5186,6 +5282,10 @@ void simple_wallet::stop_background_mining() //---------------------------------------------------------------------------------------------------- void simple_wallet::check_background_mining(const epee::wipeable_string &password) { + // Background mining can be toggled from the main wallet + if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) + return; + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); if (setup == tools::wallet2::BackgroundMiningNo) { @@ -6001,6 +6101,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot rescan spent"); if (!m_wallet->is_trusted_daemon()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); @@ -6256,10 +6357,27 @@ void simple_wallet::check_for_inactivity_lock(bool user) " || ||" << std::endl << "" << std::endl; } + + bool started_background_sync = false; + if (!m_wallet->is_background_wallet() && + m_wallet->background_sync_type() != tools::wallet2::BackgroundSyncOff) + { + LOCK_IDLE_SCOPE(); + m_wallet->start_background_sync(); + started_background_sync = true; + } + while (1) { const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); - tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); + tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << ( + (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) + ? tr("The background password is required to unlock the console.") + : tr("The wallet password is required to unlock the console.") + ); + + if (m_wallet->is_background_syncing()) + tools::msg_writer() << tr("\nSyncing in the background while locked...") << std::endl; const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); if (show_wallet_name) @@ -6272,8 +6390,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) } try { - if (get_and_verify_password()) + const auto pwd_container = get_and_verify_password(); + if (pwd_container) + { + if (started_background_sync) + { + LOCK_IDLE_SCOPE(); + m_wallet->stop_background_sync(pwd_container->password()); + } break; + } } catch (...) { /* do nothing, just let the loop loop */ } } @@ -6297,9 +6423,10 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std: return (this->*cmd)(args); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms) +bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool called_by_mms) { // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" + CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); if (!try_connect_to_daemon()) return false; @@ -6349,7 +6476,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return false; } - const size_t min_args = (transfer_type == TransferLocked) ? 2 : 1; + const size_t min_args = 1; if(local_args.size() < min_args) { fail_msg_writer() << tr("wrong number of arguments"); @@ -6374,26 +6501,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } } - uint64_t locked_blocks = 0; - if (transfer_type == TransferLocked) - { - try - { - locked_blocks = boost::lexical_cast<uint64_t>(local_args.back()); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("bad locked_blocks parameter:") << " " << local_args.back(); - return false; - } - if (locked_blocks > 1000000) - { - fail_msg_writer() << tr("Locked blocks too high, max 1000000 (Ëœ4 yrs)"); - return false; - } - local_args.pop_back(); - } - // Parse subtractfeefrom destination list tools::wallet2::unique_index_container subtract_fee_from_outputs; bool subtract_fee_from_all = false; @@ -6523,28 +6630,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri try { // figure out what tx will be necessary - std::vector<tools::wallet2::pending_tx> ptx_vector; - uint64_t bc_height, unlock_block = 0; - std::string err; - switch (transfer_type) - { - case TransferLocked: - bc_height = get_daemon_blockchain_height(err); - if (!err.empty()) - { - fail_msg_writer() << tr("failed to get blockchain height: ") << err; - return false; - } - unlock_block = bc_height + locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); - break; - default: - LOG_ERROR("Unknown transfer method, using default"); - /* FALLTHRU */ - case Transfer: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); - break; - } + auto ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, priority, extra, + m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); if (ptx_vector.empty()) { @@ -6654,11 +6741,6 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri if (dust_in_fee != 0) prompt << boost::format(tr(", of which %s is dust from change")) % print_money(dust_in_fee); if (dust_not_in_fee != 0) prompt << tr(".") << ENDL << boost::format(tr("A total of %s from dust change will be sent to dust address")) % print_money(dust_not_in_fee); - if (transfer_type == TransferLocked) - { - float days = locked_blocks / 720.0f; - prompt << boost::format(tr(".\nThis transaction (including %s change) will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % cryptonote::print_money(change) % ((unsigned long long)unlock_block) % days; - } if (!process_ring_members(ptx_vector, prompt, m_wallet->print_ring_members())) return false; @@ -6676,7 +6758,8 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } // actually commit the transactions - if (m_wallet->multisig() && called_by_mms) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (ms_status.multisig_is_active && called_by_mms) { std::string ciphertext = m_wallet->save_multisig_tx(ptx_vector); if (!ciphertext.empty()) @@ -6685,7 +6768,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to MMS"); } } - else if (m_wallet->multisig()) + else if (ms_status.multisig_is_active) { bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); if (!r) @@ -6757,40 +6840,20 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri //---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); if (args_.size() < 1) { PRINT_USAGE(USAGE_TRANSFER); return true; } - transfer_main(Transfer, args_, false); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) -{ - if (args_.size() < 1) - { - PRINT_USAGE(USAGE_LOCKED_TRANSFER); - return true; - } - transfer_main(TransferLocked, args_, false); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) -{ - if (args_.size() < 1) - { - PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL); - return true; - } - sweep_main(m_current_subaddress_account, 0, true, args_); + transfer_main(args_, false); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); if (!try_connect_to_daemon()) return true; @@ -6839,7 +6902,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) } // actually commit the transactions - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); @@ -6896,8 +6959,9 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, const std::vector<std::string> &args_) +bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); auto print_usage = [this, account, below]() { if (below) @@ -6976,41 +7040,6 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co return true; } - uint64_t unlock_block = 0; - if (locked) { - uint64_t locked_blocks = 0; - - if (local_args.size() < 2) { - fail_msg_writer() << tr("missing lockedblocks parameter"); - return true; - } - - try - { - locked_blocks = boost::lexical_cast<uint64_t>(local_args[1]); - } - catch (const std::exception &e) - { - fail_msg_writer() << tr("bad locked_blocks parameter"); - return true; - } - if (locked_blocks > 1000000) - { - fail_msg_writer() << tr("Locked blocks too high, max 1000000 (Ëœ4 yrs)"); - return true; - } - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (!err.empty()) - { - fail_msg_writer() << tr("failed to get blockchain height: ") << err; - return true; - } - unlock_block = bc_height + locked_blocks; - - local_args.erase(local_args.begin() + 1); - } - size_t outputs = 1; if (local_args.size() > 0 && local_args[0].substr(0, 8) == "outputs=") { @@ -7085,7 +7114,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, fake_outs_count, unlock_block /* unlock_time */, priority, extra, account, subaddr_indices); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, fake_outs_count, priority, extra, account, subaddr_indices); if (ptx_vector.empty()) { @@ -7144,7 +7173,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co } // actually commit the transactions - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); @@ -7214,6 +7243,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_single(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); if (!try_connect_to_daemon()) return true; @@ -7342,7 +7372,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, fake_outs_count, 0 /* unlock_time */, priority, extra); + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, outputs, fake_outs_count, priority, extra); if (ptx_vector.empty()) { @@ -7379,7 +7409,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) } // actually commit the transactions - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { CHECK_MULTISIG_ENABLED(); bool r = m_wallet->save_multisig_tx(ptx_vector, "multisig_monero_tx"); @@ -7452,12 +7482,14 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_all(const std::vector<std::string> &args_) { - sweep_main(m_current_subaddress_account, 0, false, args_); + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + sweep_main(m_current_subaddress_account, 0, args_); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_account(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); auto local_args = args_; if (local_args.empty()) { @@ -7472,12 +7504,13 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_) } local_args.erase(local_args.begin()); - sweep_main(account, 0, false, local_args); + sweep_main(account, 0, local_args); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_below(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); uint64_t below = 0; if (args_.size() < 1) { @@ -7490,12 +7523,13 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) fail_msg_writer() << tr("invalid amount threshold"); return true; } - sweep_main(m_current_subaddress_account, below, false, std::vector<std::string>(++args_.begin(), args_.end())); + sweep_main(m_current_subaddress_account, below, std::vector<std::string>(++args_.begin(), args_.end())); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::donate(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot donate"); std::vector<std::string> local_args = args_; if(local_args.empty() || local_args.size() > 5) { @@ -7557,6 +7591,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message) { + CHECK_IF_BACKGROUND_SYNCING("cannot load tx"); // gather info to ask the user uint64_t amount = 0, amount_to_dests = 0, change = 0; size_t min_ring_size = ~0; @@ -7727,7 +7762,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if(m_wallet->multisig()) + if(m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("This is a multisig wallet, it can only sign with sign_multisig"); return true; @@ -7737,6 +7772,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) fail_msg_writer() << tr("This is a watch only wallet"); return true; } + CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer"); bool export_raw = false; std::string unsigned_filename = "unsigned_monero_tx"; @@ -7844,6 +7880,8 @@ std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::sec bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot get tx key"); + std::vector<std::string> local_args = args_; if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR) @@ -7884,6 +7922,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot set tx key"); + std::vector<std::string> local_args = args_; if(local_args.size() != 2 && local_args.size() != 3) { @@ -7960,6 +8000,8 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot get tx proof"); + if (args.size() != 2 && args.size() != 3) { PRINT_USAGE(USAGE_GET_TX_PROOF); @@ -8166,6 +8208,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot get spend proof"); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -8250,6 +8293,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot get reserve proof"); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -8260,7 +8304,7 @@ bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) return true; } - if (m_wallet->watch_only() || m_wallet->multisig()) + if (m_wallet->watch_only() || m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("The reserve proof can be generated only by a full wallet"); return true; @@ -8845,6 +8889,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) uint64_t max_height = 0; uint64_t found_min_amount = std::numeric_limits<uint64_t>::max(); uint64_t found_max_amount = 0; + uint64_t found_sum_amount = 0; uint64_t count = 0; for (const auto& td : transfers) { @@ -8856,6 +8901,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) if (max_height < td.m_block_height) max_height = td.m_block_height; if (found_min_amount > amount) found_min_amount = amount; if (found_max_amount < amount) found_max_amount = amount; + found_sum_amount += amount; ++count; } if (amount_to_tds.empty()) @@ -8880,6 +8926,7 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) << tr("\nMax block height: ") << max_height << tr("\nMin amount found: ") << print_money(found_min_amount) << tr("\nMax amount found: ") << print_money(found_max_amount) + << tr("\nSum amount found: ") << print_money(found_sum_amount) << tr("\nTotal count: ") << count; const size_t histogram_height = 10; const size_t histogram_width = 50; @@ -8936,6 +8983,8 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) { + CHECK_IF_BACKGROUND_SYNCING("cannot rescan"); + uint64_t start_height = 0; ResetType reset_type = ResetSoft; @@ -9160,6 +9209,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector if (command == "new") { // create a new account and switch to it + CHECK_IF_BACKGROUND_SYNCING("cannot create new account"); std::string label = boost::join(local_args, " "); if (label.empty()) label = tr("(Untitled account)"); @@ -9190,6 +9240,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector else if (command == "label" && local_args.size() >= 1) { // set label of the specified account + CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); uint32_t index_major; if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0])) { @@ -9211,6 +9262,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector } else if (command == "tag" && local_args.size() >= 2) { + CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); const std::string tag = local_args[0]; std::set<uint32_t> account_indices; for (size_t i = 1; i < local_args.size(); ++i) @@ -9235,6 +9287,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector } else if (command == "untag" && local_args.size() >= 1) { + CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); std::set<uint32_t> account_indices; for (size_t i = 0; i < local_args.size(); ++i) { @@ -9258,6 +9311,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector } else if (command == "tag_description" && local_args.size() >= 1) { + CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); const std::string tag = local_args[0]; std::string description; if (local_args.size() > 1) @@ -9375,6 +9429,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else if (local_args[0] == "new") { + CHECK_IF_BACKGROUND_SYNCING("cannot add address"); local_args.erase(local_args.begin()); std::string label; if (local_args.size() > 0) @@ -9387,6 +9442,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else if (local_args[0] == "mnew") { + CHECK_IF_BACKGROUND_SYNCING("cannot add addresses"); local_args.erase(local_args.begin()); if (local_args.size() != 1) { @@ -9412,6 +9468,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else if (local_args[0] == "one-off") { + CHECK_IF_BACKGROUND_SYNCING("cannot add address"); local_args.erase(local_args.begin()); std::string label; if (local_args.size() != 2) @@ -9430,6 +9487,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: } else if (local_args.size() >= 2 && local_args[0] == "label") { + CHECK_IF_BACKGROUND_SYNCING("cannot modify address"); if (!epee::string_tools::get_xtype_from_string(index, local_args[1])) { fail_msg_writer() << tr("failed to parse index: ") << local_args[1]; @@ -9576,6 +9634,8 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg //---------------------------------------------------------------------------------------------------- bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { + CHECK_IF_BACKGROUND_SYNCING("cannot get address book"); + if (args.size() == 0) { } @@ -9636,6 +9696,8 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_tx_note(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot set tx note"); + if (args.size() == 0) { PRINT_USAGE(USAGE_SET_TX_NOTE); @@ -9664,6 +9726,8 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_tx_note(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot get tx note"); + if (args.size() != 1) { PRINT_USAGE(USAGE_GET_TX_NOTE); @@ -9689,6 +9753,8 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::set_description(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot set description"); + // 0 arguments allowed, for setting the description to empty string std::string description = ""; @@ -9705,6 +9771,8 @@ bool simple_wallet::set_description(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::get_description(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot get description"); + if (args.size() != 0) { PRINT_USAGE(USAGE_GET_DESCRIPTION); @@ -9748,8 +9816,8 @@ bool simple_wallet::status(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::wallet_info(const std::vector<std::string> &args) { - bool ready; - uint32_t threshold, total; + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + std::string description = m_wallet->get_description(); if (description.empty()) { @@ -9761,8 +9829,10 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args) std::string type; if (m_wallet->watch_only()) type = tr("Watch only"); - else if (m_wallet->multisig(&ready, &threshold, &total)) - type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); + else if (ms_status.multisig_is_active) + type = (boost::format(tr("%u/%u multisig%s")) % ms_status.threshold % ms_status.total % (ms_status.is_ready ? "" : " (not yet finalized)")).str(); + else if (m_wallet->is_background_wallet()) + type = tr("Background wallet"); else type = tr("Normal"); message_writer() << tr("Type: ") << type; @@ -9774,6 +9844,7 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::sign(const std::vector<std::string> &args) { + CHECK_IF_BACKGROUND_SYNCING("cannot sign"); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -9789,7 +9860,7 @@ bool simple_wallet::sign(const std::vector<std::string> &args) fail_msg_writer() << tr("wallet is watch-only and cannot sign"); return true; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { fail_msg_writer() << tr("This wallet is multisig and cannot sign"); return true; @@ -9881,6 +9952,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args_) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } + CHECK_IF_BACKGROUND_SYNCING("cannot export key images"); auto args = args_; if (m_wallet->watch_only()) @@ -9934,6 +10006,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } + CHECK_IF_BACKGROUND_SYNCING("cannot import key images"); if (!m_wallet->is_trusted_daemon()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); @@ -10042,6 +10115,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args_) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } + CHECK_IF_BACKGROUND_SYNCING("cannot export outputs"); auto args = args_; bool all = false; @@ -10091,6 +10165,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } + CHECK_IF_BACKGROUND_SYNCING("cannot import outputs"); if (args.size() != 1) { PRINT_USAGE(USAGE_IMPORT_OUTPUTS); @@ -11018,7 +11093,7 @@ void simple_wallet::mms_sync(const std::vector<std::string> &args) void simple_wallet::mms_transfer(const std::vector<std::string> &args) { // It's too complicated to check any arguments here, just let 'transfer_main' do the whole job - transfer_main(Transfer, args, true); + transfer_main(args, true); } void simple_wallet::mms_delete(const std::vector<std::string> &args) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 2a5d7f2b6..0e00c3490 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -147,6 +147,7 @@ namespace cryptonote bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); + bool setup_background_sync(const std::vector<std::string> &args = std::vector<std::string>()); bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>()); bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); @@ -167,11 +168,9 @@ namespace cryptonote bool show_incoming_transfers(const std::vector<std::string> &args); bool show_payments(const std::vector<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); - bool transfer_main(int transfer_type, const std::vector<std::string> &args, bool called_by_mms); + bool transfer_main(const std::vector<std::string> &args, bool called_by_mms); bool transfer(const std::vector<std::string> &args); - bool locked_transfer(const std::vector<std::string> &args); - bool locked_sweep_all(const std::vector<std::string> &args); - bool sweep_main(uint32_t account, uint64_t below, bool locked, const std::vector<std::string> &args); + bool sweep_main(uint32_t account, uint64_t below, const std::vector<std::string> &args); bool sweep_all(const std::vector<std::string> &args); bool sweep_account(const std::vector<std::string> &args); bool sweep_below(const std::vector<std::string> &args); diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 7f4dbbc79..40481fc4b 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -38,6 +38,7 @@ #include "subaddress_account.h" #include "common_defines.h" #include "common/util.h" +#include "multisig/multisig_account.h" #include "mnemonics/electrum-words.h" #include "mnemonics/english.h" @@ -57,6 +58,40 @@ using namespace cryptonote; #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" +#define LOCK_REFRESH() \ + bool refresh_enabled = m_refreshEnabled; \ + m_refreshEnabled = false; \ + m_wallet->stop(); \ + m_refreshCV.notify_one(); \ + boost::mutex::scoped_lock lock(m_refreshMutex); \ + boost::mutex::scoped_lock lock2(m_refreshMutex2); \ + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ + /* m_refreshMutex's still locked here */ \ + if (refresh_enabled) \ + startRefresh(); \ + }) + +#define PRE_VALIDATE_BACKGROUND_SYNC() \ + do \ + { \ + clearStatus(); \ + if (m_wallet->key_on_device()) \ + { \ + setStatusError(tr("HW wallet cannot use background sync")); \ + return false; \ + } \ + if (m_wallet->watch_only()) \ + { \ + setStatusError(tr("View only wallet cannot use background sync")); \ + return false; \ + } \ + if (m_wallet->get_multisig_status().multisig_is_active) \ + { \ + setStatusError(tr("Multisig wallet cannot use background sync")); \ + return false; \ + } \ + } while (0) + namespace Monero { namespace { @@ -87,12 +122,13 @@ namespace { throw runtime_error("Wallet is not initialized yet"); } - bool ready; - if (!wallet->multisig(&ready)) { + const multisig::multisig_account_status ms_status{wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { throw runtime_error("Wallet is not multisig"); } - if (!ready) { + if (!ms_status.is_ready) { throw runtime_error("Multisig wallet is not finalized yet"); } } @@ -105,12 +141,13 @@ namespace { throw runtime_error("Wallet is not initialized yet"); } - bool ready; - if (!wallet->multisig(&ready)) { + const multisig::multisig_account_status ms_status{wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { throw runtime_error("Wallet is not multisig"); } - if (ready) { + if (ms_status.is_ready) { throw runtime_error("Multisig wallet is already finalized"); } } @@ -763,6 +800,8 @@ bool WalletImpl::close(bool store) std::string WalletImpl::seed(const std::string& seed_offset) const { + if (checkBackgroundSync("cannot get seed")) + return std::string(); epee::wipeable_string seed; if (m_wallet) m_wallet->get_seed(seed, seed_offset); @@ -776,6 +815,8 @@ std::string WalletImpl::getSeedLanguage() const void WalletImpl::setSeedLanguage(const std::string &arg) { + if (checkBackgroundSync("cannot set seed language")) + return; m_wallet->set_seed_language(arg); } @@ -799,6 +840,8 @@ void WalletImpl::statusWithErrorString(int& status, std::string& errorString) co bool WalletImpl::setPassword(const std::string &password) { + if (checkBackgroundSync("cannot change password")) + return false; clearStatus(); try { m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password); @@ -928,6 +971,8 @@ bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transact void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) { + if (checkBackgroundSync("cannot change refresh height")) + return; m_wallet->set_refresh_from_block_height(refresh_from_block_height); } @@ -1036,6 +1081,8 @@ void WalletImpl::refreshAsync() bool WalletImpl::rescanBlockchain() { + if (checkBackgroundSync("cannot rescan blockchain")) + return false; clearStatus(); m_refreshShouldRescan = true; doRefresh(); @@ -1044,6 +1091,8 @@ bool WalletImpl::rescanBlockchain() void WalletImpl::rescanBlockchainAsync() { + if (checkBackgroundSync("cannot rescan blockchain")) + return; m_refreshShouldRescan = true; refreshAsync(); } @@ -1067,7 +1116,7 @@ int WalletImpl::autoRefreshInterval() const UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) { clearStatus(); UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); - if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ + if (checkBackgroundSync("cannot load tx") || !m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ setStatusError(tr("Failed to load unsigned transactions")); transaction->m_status = UnsignedTransaction::Status::Status_Error; transaction->m_errorString = errorString(); @@ -1087,6 +1136,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); + if (checkBackgroundSync("cannot submit tx")) + return false; std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this)); bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); @@ -1110,6 +1161,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) setStatusError(tr("Wallet is view only")); return false; } + if (checkBackgroundSync("cannot export key images")) + return false; try { @@ -1130,6 +1183,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) bool WalletImpl::importKeyImages(const string &filename) { + if (checkBackgroundSync("cannot import key images")) + return false; if (!trustedDaemon()) { setStatusError(tr("Key images can only be imported with a trusted daemon")); return false; @@ -1153,6 +1208,8 @@ bool WalletImpl::importKeyImages(const string &filename) bool WalletImpl::exportOutputs(const string &filename, bool all) { + if (checkBackgroundSync("cannot export outputs")) + return false; if (m_wallet->key_on_device()) { setStatusError(string(tr("Not supported on HW wallets.")) + filename); @@ -1183,6 +1240,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) bool WalletImpl::importOutputs(const string &filename) { + if (checkBackgroundSync("cannot import outputs")) + return false; if (m_wallet->key_on_device()) { setStatusError(string(tr("Not supported on HW wallets.")) + filename); @@ -1215,6 +1274,8 @@ bool WalletImpl::importOutputs(const string &filename) bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) { + if (checkBackgroundSync("cannot scan transactions")) + return false; if (txids.empty()) { setStatusError(string(tr("Failed to scan transactions: no transaction ids provided."))); @@ -1253,8 +1314,86 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) return true; } +bool WalletImpl::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) +{ + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + + tools::wallet2::BackgroundSyncType bgs_type; + switch (background_sync_type) + { + case Wallet::BackgroundSync_Off: bgs_type = tools::wallet2::BackgroundSyncOff; break; + case Wallet::BackgroundSync_ReusePassword: bgs_type = tools::wallet2::BackgroundSyncReusePassword; break; + case Wallet::BackgroundSync_CustomPassword: bgs_type = tools::wallet2::BackgroundSyncCustomPassword; break; + default: setStatusError(tr("Unknown background sync type")); return false; + } + + boost::optional<epee::wipeable_string> bgc_password = background_cache_password + ? boost::optional<epee::wipeable_string>(*background_cache_password) + : boost::none; + + LOCK_REFRESH(); + m_wallet->setup_background_sync(bgs_type, wallet_password, bgc_password); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to setup background sync: " << e.what()); + setStatusError(string(tr("Failed to setup background sync: ")) + e.what()); + return false; + } + return true; +} + +Wallet::BackgroundSyncType WalletImpl::getBackgroundSyncType() const +{ + switch (m_wallet->background_sync_type()) + { + case tools::wallet2::BackgroundSyncOff: return Wallet::BackgroundSync_Off; + case tools::wallet2::BackgroundSyncReusePassword: return Wallet::BackgroundSync_ReusePassword; + case tools::wallet2::BackgroundSyncCustomPassword: return Wallet::BackgroundSync_CustomPassword; + default: setStatusError(tr("Unknown background sync type")); return Wallet::BackgroundSync_Off; + } +} + +bool WalletImpl::startBackgroundSync() +{ + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + LOCK_REFRESH(); + m_wallet->start_background_sync(); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to start background sync: " << e.what()); + setStatusError(string(tr("Failed to start background sync: ")) + e.what()); + return false; + } + return true; +} + +bool WalletImpl::stopBackgroundSync(const std::string &wallet_password) +{ + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + LOCK_REFRESH(); + m_wallet->stop_background_sync(epee::wipeable_string(wallet_password)); + } + catch (const std::exception &e) + { + LOG_ERROR("Failed to stop background sync: " << e.what()); + setStatusError(string(tr("Failed to stop background sync: ")) + e.what()); + return false; + } + return true; +} + void WalletImpl::addSubaddressAccount(const std::string& label) { + if (checkBackgroundSync("cannot add account")) + return; m_wallet->add_subaddress_account(label); } size_t WalletImpl::numSubaddressAccounts() const @@ -1267,10 +1406,14 @@ size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const } void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label) { + if (checkBackgroundSync("cannot add subbaddress")) + return; m_wallet->add_subaddress(accountIndex, label); } std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const { + if (checkBackgroundSync("cannot get subbaddress label")) + return ""; try { return m_wallet->get_subaddress_label({accountIndex, addressIndex}); @@ -1284,6 +1427,8 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre } void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) { + if (checkBackgroundSync("cannot set subbaddress label")) + return; try { return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label); @@ -1297,12 +1442,23 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex MultisigState WalletImpl::multisig() const { MultisigState state; - state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); + if (checkBackgroundSync("cannot use multisig")) + return state; + + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + state.isMultisig = ms_status.multisig_is_active; + state.kexIsDone = ms_status.kex_is_done; + state.isReady = ms_status.is_ready; + state.threshold = ms_status.threshold; + state.total = ms_status.total; return state; } string WalletImpl::getMultisigInfo() const { + if (checkBackgroundSync("cannot use multisig")) + return string(); try { clearStatus(); return m_wallet->get_multisig_first_kex_msg(); @@ -1315,10 +1471,12 @@ string WalletImpl::getMultisigInfo() const { } string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) { + if (checkBackgroundSync("cannot make multisig")) + return string(); try { clearStatus(); - if (m_wallet->multisig()) { + if (m_wallet->get_multisig_status().multisig_is_active) { throw runtime_error("Wallet is already multisig"); } @@ -1345,6 +1503,21 @@ std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &inf return string(); } +std::string WalletImpl::getMultisigKeyExchangeBooster(const std::vector<std::string> &info, + const std::uint32_t threshold, + const std::uint32_t num_signers) { + try { + clearStatus(); + + return m_wallet->get_multisig_key_exchange_booster(epee::wipeable_string(m_password), info, threshold, num_signers); + } catch (const exception& e) { + LOG_ERROR("Error on boosting multisig key exchange: " << e.what()); + setStatusError(string(tr("Failed to boost multisig key exchange: ")) + e.what()); + } + + return string(); +} + bool WalletImpl::exportMultisigImages(string& images) { try { clearStatus(); @@ -1455,6 +1628,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { + if (checkBackgroundSync("cannot create transactions")) + break; + std::vector<uint8_t> extra; std::string extra_nonce; vector<cryptonote::tx_destination_entry> dsts; @@ -1519,11 +1695,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri fake_outs_count = m_wallet->adjust_mixin(mixin_count); if (amount) { - transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, adjusted_priority, extra, subaddr_account, subaddr_indices); } else { - transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, adjusted_priority, extra, subaddr_account, subaddr_indices); } @@ -1621,6 +1797,9 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { + if (checkBackgroundSync("cannot sweep")) + break; + try { transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(); pendingTxPostProcess(transaction); @@ -1754,11 +1933,15 @@ uint32_t WalletImpl::defaultMixin() const void WalletImpl::setDefaultMixin(uint32_t arg) { + if (checkBackgroundSync("cannot set default mixin")) + return; m_wallet->default_mixin(arg); } bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val) { + if (checkBackgroundSync("cannot set cache attribute")) + return false; m_wallet->set_attribute(key, val); return true; } @@ -1772,6 +1955,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) { + if (checkBackgroundSync("cannot set user note")) + return false; cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) return false; @@ -1783,6 +1968,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) std::string WalletImpl::getUserNote(const std::string &txid) const { + if (checkBackgroundSync("cannot get user note")) + return ""; cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) return ""; @@ -1793,6 +1980,9 @@ std::string WalletImpl::getUserNote(const std::string &txid) const std::string WalletImpl::getTxKey(const std::string &txid_str) const { + if (checkBackgroundSync("cannot get tx key")) + return ""; + crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { @@ -1877,6 +2067,9 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const { + if (checkBackgroundSync("cannot get tx proof")) + return ""; + crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { @@ -1933,6 +2126,9 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad } std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { + if (checkBackgroundSync("cannot get spend proof")) + return ""; + crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { @@ -1975,6 +2171,9 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string } std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { + if (checkBackgroundSync("cannot get reserve proof")) + return ""; + try { clearStatus(); @@ -2021,6 +2220,9 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string std::string WalletImpl::signMessage(const std::string &message, const std::string &address) { + if (checkBackgroundSync("cannot sign message")) + return ""; + if (address.empty()) { return m_wallet->sign(message, tools::wallet2::sign_with_spend_key); } @@ -2053,8 +2255,8 @@ std::string WalletImpl::signMultisigParticipant(const std::string &message) cons { clearStatus(); - bool ready = false; - if (!m_wallet->multisig(&ready) || !ready) { + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + if (!ms_status.multisig_is_active || !ms_status.is_ready) { m_status = Status_Error; m_errorString = tr("The wallet must be in multisig ready state"); return {}; @@ -2147,6 +2349,16 @@ bool WalletImpl::isDeterministic() const return m_wallet->is_deterministic(); } +bool WalletImpl::isBackgroundSyncing() const +{ + return m_wallet->is_background_syncing(); +} + +bool WalletImpl::isBackgroundWallet() const +{ + return m_wallet->is_background_wallet(); +} + void WalletImpl::clearStatus() const { boost::lock_guard<boost::mutex> l(m_statusMutex); @@ -2215,9 +2427,7 @@ void WalletImpl::doRefresh() if(rescan) m_wallet->rescan_blockchain(false); m_wallet->refresh(trustedDaemon()); - if (!m_synchronized) { - m_synchronized = true; - } + m_synchronized = m_wallet->is_synced(); // assuming if we have empty history, it wasn't initialized yet // for further history changes client need to update history in // "on_money_received" and "on_money_sent" callbacks @@ -2320,6 +2530,24 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a return true; } +bool WalletImpl::checkBackgroundSync(const std::string &message) const +{ + clearStatus(); + if (m_wallet->is_background_wallet()) + { + LOG_ERROR("Background wallets " + message); + setStatusError(tr("Background wallets ") + message); + return true; + } + if (m_wallet->is_background_syncing()) + { + LOG_ERROR(message + " while background syncing"); + setStatusError(message + tr(" while background syncing. Stop background syncing first.")); + return true; + } + return false; +} + bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) { return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); @@ -2338,6 +2566,8 @@ std::string WalletImpl::getDefaultDataDir() const bool WalletImpl::rescanSpent() { clearStatus(); + if (checkBackgroundSync("cannot rescan spent")) + return false; if (!trustedDaemon()) { setStatusError(tr("Rescan spent can only be used with a trusted daemon")); return false; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index d1bf4f759..1901efbca 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -148,6 +148,7 @@ public: std::string getMultisigInfo() const override; std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override; + std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; @@ -172,6 +173,13 @@ public: bool importOutputs(const std::string &filename) override; bool scanTransactions(const std::vector<std::string> &txids) override; + bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password = optional<std::string>()) override; + BackgroundSyncType getBackgroundSyncType() const override; + bool startBackgroundSync() override; + bool stopBackgroundSync(const std::string &wallet_password) override; + bool isBackgroundSyncing() const override; + bool isBackgroundWallet() const override; + virtual void disposeTransaction(PendingTransaction * t) override; virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, PendingTransaction::Priority priority) const override; @@ -238,6 +246,7 @@ private: bool isNewWallet() const; void pendingTxPostProcess(PendingTransactionImpl * pending); bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false); + bool checkBackgroundSync(const std::string &message) const; private: friend class PendingTransactionImpl; @@ -253,6 +262,10 @@ private: mutable boost::mutex m_statusMutex; mutable int m_status; mutable std::string m_errorString; + // TODO: harden password handling in the wallet API, see relevant discussion + // https://github.com/monero-project/monero-gui/issues/1537 + // https://github.com/feather-wallet/feather/issues/72#issuecomment-1405602142 + // https://github.com/monero-project/monero/pull/8619#issuecomment-1632951461 std::string m_password; std::unique_ptr<TransactionHistoryImpl> m_history; std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index df86da847..4eaf80e70 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -322,9 +322,10 @@ struct SubaddressAccount }; struct MultisigState { - MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {} + MultisigState() : isMultisig(false), kexIsDone(false), isReady(false), threshold(0), total(0) {} bool isMultisig; + bool kexIsDone; bool isReady; uint32_t threshold; uint32_t total; @@ -445,6 +446,12 @@ struct Wallet ConnectionStatus_WrongVersion }; + enum BackgroundSyncType { + BackgroundSync_Off = 0, + BackgroundSync_ReusePassword = 1, + BackgroundSync_CustomPassword = 2 + }; + virtual ~Wallet() = 0; virtual std::string seed(const std::string& seed_offset = "") const = 0; virtual std::string getSeedLanguage() const = 0; @@ -802,6 +809,15 @@ struct Wallet */ virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0; /** + * @brief getMultisigKeyExchangeBooster - obtain partial information for the key exchange round after the in-progress round, + * to speed up another signer's key exchange process + * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call + * @param threshold - number of required signers to make valid transaction. Must be <= number of participants. + * @param num_signers - total number of multisig participants. + * @return new info string if more rounds required or exception if no more rounds (i.e. no rounds to boost) + */ + virtual std::string getMultisigKeyExchangeBooster(const std::vector<std::string> &info, const uint32_t threshold, const uint32_t num_signers) = 0; + /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images * @return true if success @@ -936,6 +952,42 @@ struct Wallet */ virtual bool scanTransactions(const std::vector<std::string> &txids) = 0; + /*! + * \brief setupBackgroundSync - setup background sync mode with just a view key + * \param background_sync_type - the mode the wallet background syncs in + * \param wallet_password + * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type + * \return - true on success + */ + virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) = 0; + + /*! + * \brief getBackgroundSyncType - get mode the wallet background syncs in + * \return - the type, or off if type is unknown + */ + virtual BackgroundSyncType getBackgroundSyncType() const = 0; + + /** + * @brief startBackgroundSync - sync the chain in the background with just view key + */ + virtual bool startBackgroundSync() = 0; + + /** + * @brief stopBackgroundSync - bring back spend key and process background synced txs + * \param wallet_password + */ + virtual bool stopBackgroundSync(const std::string &wallet_password) = 0; + + /** + * @brief isBackgroundSyncing - returns true if the wallet is background syncing + */ + virtual bool isBackgroundSyncing() const = 0; + + /** + * @brief isBackgroundWallet - returns true if the wallet is a background wallet + */ + virtual bool isBackgroundWallet() const = 0; + virtual TransactionHistory * history() = 0; virtual AddressBook * addressBook() = 0; virtual Subaddress * subaddress() = 0; diff --git a/src/wallet/message_store.h b/src/wallet/message_store.h index 202d77be6..c0afa2afa 100644 --- a/src/wallet/message_store.h +++ b/src/wallet/message_store.h @@ -245,18 +245,23 @@ namespace mms crypto::secret_key view_secret_key; bool multisig; bool multisig_is_ready; + bool multisig_kex_is_done; bool has_multisig_partial_key_images; uint32_t multisig_rounds_passed; size_t num_transfer_details; std::string mms_file; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(address) VARINT_FIELD(nettype) FIELD(view_secret_key) FIELD(multisig) FIELD(multisig_is_ready) + if (version > 0) + FIELD(multisig_kex_is_done) + else + multisig_kex_is_done = multisig_is_ready; FIELD(has_multisig_partial_key_images) VARINT_FIELD(multisig_rounds_passed) VARINT_FIELD(num_transfer_details) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a9ad67f04..f5d4583c3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -30,6 +30,7 @@ #include <algorithm> #include <numeric> +#include <string> #include <tuple> #include <queue> #include <boost/format.hpp> @@ -155,6 +156,8 @@ static const std::string MULTISIG_SIGNATURE_MAGIC = "SigMultisigPkV1"; static const std::string ASCII_OUTPUT_MAGIC = "MoneroAsciiDataV1"; +static const std::string BACKGROUND_WALLET_SUFFIX = ".background"; + boost::mutex tools::wallet2::default_daemon_address_lock; std::string tools::wallet2::default_daemon_address = ""; @@ -1007,14 +1010,14 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) * @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) +crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key, const unsigned char domain_separator) { 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; + cache_key_data[HASH_SIZE] = domain_separator; cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); return cache_key; @@ -1102,7 +1105,7 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<too boost::lock_guard<boost::mutex> lock(lockers_lock); if (lockers++ > 0) locked = false; - if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only()) + if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only() || w.is_background_syncing()) { locked = false; return; @@ -1218,6 +1221,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_ignore_outputs_above(MONEY_SUPPLY), m_ignore_outputs_below(0), m_track_uses(false), + m_is_background_wallet(false), + m_background_sync_type(BackgroundSyncOff), + m_background_syncing(false), + m_processing_background_cache(false), + m_custom_background_key(boost::none), m_show_wallet_name_when_locked(false), m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), m_setup_background_mining(BackgroundMiningMaybe), @@ -1360,6 +1368,10 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u { boost::lock_guard<boost::recursive_mutex> lock(m_daemon_rpc_mutex); + if(daemon_address.empty()) { + daemon_address.append("http://localhost:" + std::to_string(get_config(m_nettype).RPC_DEFAULT_PORT)); + } + if(m_http_client->is_connected()) m_http_client->disconnect(); CHECK_AND_ASSERT_MES2(m_proxy.empty() || proxy.empty() , "It is not possible to set global proxy (--proxy) and daemon specific proxy together."); @@ -1438,20 +1450,20 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab //---------------------------------------------------------------------------------------------------- bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase) const { - bool ready; - uint32_t threshold, total; - if (!multisig(&ready, &threshold, &total)) + const multisig::multisig_account_status ms_status{get_multisig_status()}; + + if (!ms_status.multisig_is_active) { std::cout << "This is not a multisig wallet" << std::endl; return false; } - if (!ready) + if (!ms_status.is_ready) { std::cout << "This multisig wallet is not yet finalized" << std::endl; return false; } - const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(threshold, total); + const uint64_t num_expected_ms_keys = num_priv_multisig_keys_post_setup(ms_status.threshold, ms_status.total); crypto::secret_key skey; crypto::public_key pkey; @@ -1459,8 +1471,8 @@ bool wallet2::get_multisig_seed(epee::wipeable_string& seed, const epee::wipeabl 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)); + data.append((const char*)&ms_status.threshold, sizeof(uint32_t)); + data.append((const char*)&ms_status.total, sizeof(uint32_t)); skey = keys.m_spend_secret_key; data.append((const char*)&skey, sizeof(skey)); pkey = keys.m_account_address.m_spend_public_key; @@ -1849,6 +1861,9 @@ bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std //---------------------------------------------------------------------------------------------------- void wallet2::scan_tx(const std::unordered_set<crypto::hash> &txids) { + THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, + "cannot scan tx from background wallet"); + // Get the transactions from daemon in batches sorted lowest height to highest tx_entry_data txs_to_scan = get_tx_entries(txids); if (txs_to_scan.tx_entries.empty()) @@ -2156,11 +2171,11 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); // if keys are encrypted, ask for password - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k) + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k && !m_background_syncing) { static critical_section password_lock; CRITICAL_REGION_LOCAL(password_lock); - if (!m_encrypt_keys_after_refresh) + if (!m_encrypt_keys_after_refresh && !m_processing_background_cache) { boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received"); THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero")); @@ -2172,7 +2187,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons crypto::public_key output_public_key; THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key"); - if (m_multisig) + if (m_multisig || m_background_syncing/*no spend key*/) { tx_scan_info.in_ephemeral.pub = output_public_key; tx_scan_info.in_ephemeral.sec = crypto::null_skey; @@ -2429,6 +2444,22 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error, "transactions outputs size=" + std::to_string(tx.vout.size()) + " not match with daemon response size=" + std::to_string(o_indices.size())); + + // we're going to re-process this receive when background sync is disabled + if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) + { + size_t bgs_idx = m_background_sync_data.txs.size(); + background_synced_tx_t bgs_tx = { + .index_in_background_sync_data = bgs_idx, + .tx = tx, + .output_indices = o_indices, + .height = height, + .block_timestamp = ts, + .double_spend_seen = double_spend_seen + }; + LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); + m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); + } } for(size_t o: outs) @@ -2454,7 +2485,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote td.m_tx = (const cryptonote::transaction_prefix&)tx; td.m_txid = txid; td.m_key_image = tx_scan_info[o].ki; - td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_known = !m_watch_only && !m_multisig && !m_background_syncing; if (!td.m_key_image_known) { // we might have cold signed, and have a mapping to key images @@ -2644,10 +2675,25 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote set_spent(it->second, height); if (!ignore_callbacks && 0 != m_callback) m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); + + if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) + { + size_t bgs_idx = m_background_sync_data.txs.size(); + background_synced_tx_t bgs_tx = { + .index_in_background_sync_data = bgs_idx, + .tx = tx, + .output_indices = o_indices, + .height = height, + .block_timestamp = ts, + .double_spend_seen = double_spend_seen + }; + LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); + m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); + } } } - if (!pool && m_track_uses) + if (!pool && (m_track_uses || (m_background_syncing && it == m_key_images.end()))) { PERF_TIMER(track_uses); const uint64_t amount = in_to_key.amount; @@ -2661,7 +2707,27 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote { size_t idx = i->second; THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range"); - m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); + + if (m_track_uses) + m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); + + // We'll re-process all txs which *might* be spends when we disable + // background sync and retrieve the spend key. We don't know if an + // output is a spend in this tx if we don't know its key image. + if (m_background_syncing && !m_transfers[idx].m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) + { + size_t bgs_idx = m_background_sync_data.txs.size(); + background_synced_tx_t bgs_tx = { + .index_in_background_sync_data = bgs_idx, + .tx = tx, + .output_indices = o_indices, + .height = height, + .block_timestamp = ts, + .double_spend_seen = double_spend_seen + }; + LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); + m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); + } } } } @@ -2671,7 +2737,24 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote continue; for (uint64_t offset: offsets) if (offset == td.m_global_output_index) - td.m_uses.push_back(std::make_pair(height, txid)); + { + if (m_track_uses) + td.m_uses.push_back(std::make_pair(height, txid)); + if (m_background_syncing && !td.m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) + { + size_t bgs_idx = m_background_sync_data.txs.size(); + background_synced_tx_t bgs_tx = { + .index_in_background_sync_data = bgs_idx, + .tx = tx, + .output_indices = o_indices, + .height = height, + .block_timestamp = ts, + .double_spend_seen = double_spend_seen + }; + LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); + m_background_sync_data.txs.insert({txid, std::move(bgs_tx)}); + } + } } } } @@ -3044,8 +3127,8 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh req.start_height = start_height; req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; - req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; - if (try_incremental) + req.requested_info = (first && !m_background_syncing) ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; + if (try_incremental && !m_background_syncing) req.pool_info_since = m_pool_info_query_time; { @@ -3068,7 +3151,7 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height << ", pool info " << static_cast<unsigned int>(res.pool_info_extent)); - if (first) + if (first && !m_background_syncing) { if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) { @@ -3582,6 +3665,9 @@ void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash // incremental update anymore, because with that we might miss some txs altogether. void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental) { + process_txs.clear(); + if (m_background_syncing) + return; bool updated = false; if (m_pool_info_query_time != 0 && try_incremental) { @@ -4116,6 +4202,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo } m_first_refresh_done = true; + if (m_background_syncing || m_is_background_wallet) + m_background_sync_data.first_refresh_done = true; LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false))); } @@ -4198,6 +4286,14 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st td.m_uses.pop_back(); } + for (auto it = m_background_sync_data.txs.begin(); it != m_background_sync_data.txs.end(); ) + { + if(height <= it->second.height) + it = m_background_sync_data.txs.erase(it); + else + ++it; + } + if (output_tracker_cache) output_tracker_cache->clear(); @@ -4272,8 +4368,12 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_ // C THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(), error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); + detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache); + if (m_background_syncing && height < m_background_sync_data.start_height) + m_background_sync_data.start_height = height; + if (m_callback) m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size()); } @@ -4283,6 +4383,7 @@ bool wallet2::deinit() if(m_is_initialized) { m_is_initialized = false; unlock_keys_file(); + unlock_background_keys_file(); m_account.deinit(); } return true; @@ -4309,6 +4410,7 @@ bool wallet2::clear() m_device_last_key_image_sync = 0; m_pool_info_query_time = 0; m_skip_to_height = 0; + m_background_sync_data = background_sync_data_t{}; return true; } //---------------------------------------------------------------------------------------------------- @@ -4327,13 +4429,30 @@ void wallet2::clear_soft(bool keep_key_images) m_scanned_pool_txs[1].clear(); m_pool_info_query_time = 0; m_skip_to_height = 0; + m_background_sync_data = background_sync_data_t{}; cryptonote::block b; generate_genesis(b); m_blockchain.push_back(get_block_hash(b)); m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); } - +//---------------------------------------------------------------------------------------------------- +void wallet2::clear_user_data() +{ + for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) + i->second.m_dests.clear(); + for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) + i->second.m_dests.clear(); + for (auto i = m_transfers.begin(); i != m_transfers.end(); ++i) + i->m_frozen = false; + m_tx_keys.clear(); + m_tx_notes.clear(); + m_address_book.clear(); + m_subaddress_labels.clear(); + m_attributes.clear(); + m_account_tags = std::pair<std::map<std::string, std::string>, std::vector<std::string>>(); +} +//---------------------------------------------------------------------------------------------------- /*! * \brief Stores wallet information to wallet file. * \param keys_file_name Name of wallet file @@ -4345,16 +4464,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable { boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(password, watch_only); CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); - + return store_keys_file_data(keys_file_name, keys_file_data.get()); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only, bool background_keys_file) +{ + boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(key, watch_only, background_keys_file); + CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); + return store_keys_file_data(keys_file_name, keys_file_data.get(), background_keys_file); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file) +{ std::string tmp_file_name = keys_file_name + ".new"; std::string buf; - bool r = ::serialization::dump_binary(keys_file_data.get(), buf); + bool r = ::serialization::dump_binary(keys_file_data, buf); r = r && save_to_file(tmp_file_name, buf); CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); - unlock_keys_file(); + if (!background_keys_file) + unlock_keys_file(); + else + unlock_background_keys_file(); + std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); - lock_keys_file(); + + if (!background_keys_file) + lock_keys_file(); + else + lock_background_keys_file(keys_file_name); if (e) { boost::filesystem::remove(tmp_file_name); @@ -4367,25 +4505,26 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable //---------------------------------------------------------------------------------------------------- boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) { + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + verify_password_with_cached_key(key); + return get_keys_file_data(key, watch_only); +} +//---------------------------------------------------------------------------------------------------- +boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypto::chacha_key& key, bool watch_only, bool background_keys_file) +{ epee::byte_slice account_data; std::string multisig_signers; std::string multisig_derivations; cryptonote::account_base account = m_account; - 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); account.decrypt_keys(key); } - if (watch_only) + if (watch_only || background_keys_file) account.forget_spend_key(); account.encrypt_keys(key); @@ -4520,6 +4659,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_track_uses ? 1 : 0); json.AddMember("track_uses", value2, json.GetAllocator()); + value2.SetInt(m_background_sync_type); + json.AddMember("background_sync_type", value2, json.GetAllocator()); + value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); @@ -4580,6 +4722,12 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: value2.SetInt(m_enable_multisig ? 1 : 0); json.AddMember("enable_multisig", value2, json.GetAllocator()); + if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key) + { + value.SetString(reinterpret_cast<const char*>(m_custom_background_key.get().data()), m_custom_background_key.get().size()); + json.AddMember("custom_background_key", value, json.GetAllocator()); + } + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -4606,13 +4754,81 @@ void wallet2::setup_keys(const epee::wipeable_string &password) m_account.decrypt_viewkey(key); } - m_cache_key = derive_cache_key(key); + m_cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE); get_ringdb_key(); } //---------------------------------------------------------------------------------------------------- +void validate_background_cache_password_usage(const tools::wallet2::BackgroundSyncType background_sync_type, const boost::optional<epee::wipeable_string> &background_cache_password, const bool multisig, const bool watch_only, const bool key_on_device) +{ + THROW_WALLET_EXCEPTION_IF(multisig || watch_only || key_on_device, error::wallet_internal_error, multisig + ? "Background sync not implemented for multisig wallets" : watch_only + ? "Background sync not implemented for view only wallets" + : "Background sync not implemented for HW wallets"); + + switch (background_sync_type) + { + case tools::wallet2::BackgroundSyncOff: + { + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "background sync is not enabled"); + break; + } + case tools::wallet2::BackgroundSyncReusePassword: + { + THROW_WALLET_EXCEPTION_IF(background_cache_password, error::wallet_internal_error, + "unexpected custom background cache password"); + break; + } + case tools::wallet2::BackgroundSyncCustomPassword: + { + THROW_WALLET_EXCEPTION_IF(!background_cache_password, error::wallet_internal_error, + "expected custom background cache password"); + break; + } + default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); + } +} +//---------------------------------------------------------------------------------------------------- +void get_custom_background_key(const epee::wipeable_string &password, crypto::chacha_key &custom_background_key, const uint64_t kdf_rounds) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); + custom_background_key = derive_cache_key(key, config::HASH_KEY_BACKGROUND_KEYS_FILE); +} +//---------------------------------------------------------------------------------------------------- +const crypto::chacha_key wallet2::get_cache_key() +{ + if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing) + { + THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); + // Domain separate keys used to encrypt background keys file and cache + return derive_cache_key(m_custom_background_key.get(), config::HASH_KEY_BACKGROUND_CACHE); + } + else + { + return m_cache_key; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::verify_password_with_cached_key(const epee::wipeable_string &password) +{ + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); + verify_password_with_cached_key(key); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::verify_password_with_cached_key(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, config::HASH_KEY_WALLET_CACHE); + THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); +} +//---------------------------------------------------------------------------------------------------- void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password) { + THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, + "cannot change password from background wallet"); + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) decrypt_keys(original_password); setup_keys(new_password); @@ -4671,8 +4887,24 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st std::string account_data; account_data.resize(keys_file_data.account_data.size()); crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); + if (try_v0_format) crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + + // Check if it's a background keys file if both of the above formats fail + { + m_is_background_wallet = false; + m_background_syncing = false; + cryptonote::account_base account_data_check; + if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) + { + get_custom_background_key(password, key, m_kdf_rounds); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + m_is_background_wallet = !json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); + m_background_syncing = m_is_background_wallet; // start a background wallet background syncing + } + } + // The contents should be JSON if the wallet follows the new format. if (json.Parse(account_data.c_str()).HasParseError()) { @@ -4709,6 +4941,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_ignore_outputs_above = MONEY_SUPPLY; m_ignore_outputs_below = 0; m_track_uses = false; + m_background_sync_type = BackgroundSyncOff; m_show_wallet_name_when_locked = false; m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; m_setup_background_mining = BackgroundMiningMaybe; @@ -4723,6 +4956,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st encrypted_secret_keys = false; m_enable_multisig = false; m_allow_mismatched_daemon_version = false; + m_custom_background_key = boost::none; } else if(json.IsObject()) { @@ -4950,6 +5184,39 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); m_enable_multisig = field_enable_multisig; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff); + m_background_sync_type = field_background_sync_type; + + // Load encryption key used to encrypt background cache + crypto::chacha_key custom_background_key; + m_custom_background_key = boost::none; + if (m_background_sync_type == BackgroundSyncCustomPassword && !m_is_background_wallet) + { + if (!json.HasMember("custom_background_key")) + { + LOG_ERROR("Field custom_background_key not found in JSON"); + return false; + } + else if (!json["custom_background_key"].IsString()) + { + LOG_ERROR("Field custom_background_key found in JSON, but not String"); + return false; + } + else if (json["custom_background_key"].GetStringLength() != sizeof(crypto::chacha_key)) + { + LOG_ERROR("Field custom_background_key found in JSON, but not correct length"); + return false; + } + const char *field_custom_background_key = json["custom_background_key"].GetString(); + memcpy(custom_background_key.data(), field_custom_background_key, sizeof(crypto::chacha_key)); + m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); + LOG_PRINT_L1("Loaded custom background key derived from custom password"); + } + else if (json.HasMember("custom_background_key")) + { + LOG_ERROR("Unexpected field custom_background_key found in JSON"); + } } else { @@ -5013,12 +5280,17 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st const cryptonote::account_keys& keys = m_account.get_keys(); hw::device &hwdev = m_account.get_device(); r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) + if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); if (r) - setup_keys(password); + { + if (!m_is_background_wallet) + setup_keys(password); + else + m_custom_background_key = boost::optional<crypto::chacha_key>(key); + } return true; } @@ -5033,11 +5305,12 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const epee::wipeable_string& password) +bool wallet2::verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out) { // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). unlock_keys_file(); - bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds); + const bool no_spend_key = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; + bool r = verify_password(m_keys_file, password, no_spend_key, m_account.get_device(), m_kdf_rounds, spend_key_out); lock_keys_file(); return r; } @@ -5055,7 +5328,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password * */ -bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) +bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out) { rapidjson::Document json; wallet2::keys_file_data keys_file_data; @@ -5072,9 +5345,22 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip std::string account_data; account_data.resize(keys_file_data.account_data.size()); crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); - if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); + if (try_v0_format) crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + // Check if it's a background keys file if both of the above formats fail + { + cryptonote::account_base account_data_check; + if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) + { + get_custom_background_key(password, key, kdf_rounds); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + const bool is_background_wallet = json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); + no_spend_key = no_spend_key || is_background_wallet; + } + } + // The contents should be JSON if the wallet follows the new format. if (json.Parse(account_data.c_str()).HasParseError()) { @@ -5099,6 +5385,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); if(!no_spend_key) r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey; return r; } @@ -5110,9 +5397,7 @@ 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); + verify_password_with_cached_key(key); m_account.encrypt_viewkey(key); m_account.decrypt_keys(key); @@ -5504,32 +5789,77 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p } } //---------------------------------------------------------------------------------------------------- -std::string wallet2::make_multisig(const epee::wipeable_string &password, - const std::vector<std::string> &initial_kex_msgs, - const std::uint32_t threshold) +epee::misc_utils::auto_scope_leave_caller wallet2::decrypt_account_for_multisig(const epee::wipeable_string &password) { // decrypt account keys + // note: this conditional's clauses are old and undocumented epee::misc_utils::auto_scope_leave_caller keys_reencryptor; if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) { crypto::chacha_key chacha_key; crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); - m_account.encrypt_viewkey(chacha_key); - m_account.decrypt_keys(chacha_key); + this->decrypt_keys(chacha_key); keys_reencryptor = epee::misc_utils::create_scope_leave_handler( - [&, this, chacha_key]() + [this, chacha_key]() { - m_account.encrypt_keys(chacha_key); - m_account.decrypt_viewkey(chacha_key); + this->encrypt_keys(chacha_key); } ); } - // create multisig account - multisig::multisig_account multisig_account{ - multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), - multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) + return keys_reencryptor; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_uninitialized_multisig_account(multisig::multisig_account &account_out) const +{ + // create uninitialized multisig account + account_out = multisig::multisig_account{ + // k_base = H(normal private spend key) + multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_spend_secret_key), + // k_view = H(normal private view key) + multisig::get_multisig_blinded_secret_key(this->get_account().get_keys().m_view_secret_key) }; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_reconstructed_multisig_account(multisig::multisig_account &account_out) const +{ + const multisig::multisig_account_status ms_status{this->get_multisig_status()}; + CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active, + "The wallet is not multisig, so the multisig account couldn't be reconstructed"); + + // reconstruct multisig account + crypto::public_key common_pubkey; + crypto::secret_key_to_public_key(this->get_account().get_keys().m_view_secret_key, common_pubkey); + + multisig::multisig_keyset_map_memsafe_t kex_origins_map; + for (const auto &derivation : m_multisig_derivations) + kex_origins_map[derivation]; + + account_out = multisig::multisig_account{ + m_multisig_threshold, + m_multisig_signers, + this->get_account().get_keys().m_spend_secret_key, + this->get_account().get_keys().m_view_secret_key, + this->get_account().get_keys().m_multisig_keys, + this->get_account().get_keys().m_view_secret_key, + m_account_public_address.m_spend_public_key, + common_pubkey, + m_multisig_rounds_passed, + std::move(kex_origins_map), + "" + }; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::make_multisig(const epee::wipeable_string &password, + const std::vector<std::string> &initial_kex_msgs, + const std::uint32_t threshold) +{ + // decrypt account keys + epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)}; + + // create multisig account + multisig::multisig_account multisig_account; + this->get_uninitialized_multisig_account(multisig_account); // open initial kex messages, validate them, extract signers std::vector<multisig::multisig_kex_msg> expanded_msgs; @@ -5537,7 +5867,7 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, expanded_msgs.reserve(initial_kex_msgs.size()); signers.reserve(initial_kex_msgs.size() + 1); - for (const auto &msg : initial_kex_msgs) + for (const std::string &msg : initial_kex_msgs) { expanded_msgs.emplace_back(msg); @@ -5546,17 +5876,19 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, CHECK_AND_ASSERT_THROW_MES(expanded_msgs.back().get_round() == 1, "Trying to make multisig with message that has invalid multisig kex round (should be '1')."); - // 2. duplicate signers not allowed + // 2. duplicate signers not allowed (the number of signers is implied by the number of initial kex messages passed + // in, so we can't just ignore duplicates here) CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), expanded_msgs.back().get_signing_pubkey()) == signers.end(), "Duplicate signers not allowed when converting a wallet to multisig."); - // add signer (skip self for now) - if (expanded_msgs.back().get_signing_pubkey() != multisig_account.get_base_pubkey()) - signers.push_back(expanded_msgs.back().get_signing_pubkey()); + // add signer + signers.push_back(expanded_msgs.back().get_signing_pubkey()); } - // add self to signers - signers.push_back(multisig_account.get_base_pubkey()); + // expect that self is in the input list (this guarantees that the input list size always equals the number of intended + // signers for the account [when combined with duplicate checking]) + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), multisig_account.get_base_pubkey()) != signers.end(), + "The local account's signer key was not found in initial multisig kex messages when converting a wallet to multisig."); // intialize key exchange multisig_account.initialize_kex(threshold, signers, expanded_msgs); @@ -5567,12 +5899,13 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, { // Save the original i.e. non-multisig keys so the MMS can continue to use them to encrypt and decrypt messages // (making a wallet multisig overwrites those keys, see account_base::make_multisig) - m_original_address = get_account().get_keys().m_account_address; - m_original_view_secret_key = get_account().get_keys().m_view_secret_key; + m_original_address = this->get_account().get_keys().m_account_address; + m_original_view_secret_key = this->get_account().get_keys().m_view_secret_key; m_original_keys_available = true; } - clear(); + // clear wallet caches + this->clear(); // account base MINFO("Creating multisig address..."); @@ -5582,14 +5915,14 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, multisig_account.get_multisig_privkeys()), "Failed to create multisig wallet account due to bad keys"); - init_type(hw::device::device_type::SOFTWARE); + this->init_type(hw::device::device_type::SOFTWARE); m_original_keys_available = true; m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = signers; m_multisig_rounds_passed = 1; - // derivations stored (should be empty in last round) + // derivations stored (note: should be empty in last kex round) m_multisig_derivations.clear(); m_multisig_derivations.reserve(multisig_account.get_kex_keys_to_origins_map().size()); @@ -5603,12 +5936,12 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, keys_reencryptor = epee::misc_utils::auto_scope_leave_caller(); if (!m_wallet_file.empty()) - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); - setup_new_blockchain(); + this->setup_new_blockchain(); if (!m_wallet_file.empty()) - store(); + this->store(); return multisig_account.get_next_kex_round_msg(); } @@ -5617,45 +5950,15 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor const std::vector<std::string> &kex_messages, const bool force_update_use_with_caution /*= false*/) { - bool ready{false}; - CHECK_AND_ASSERT_THROW_MES(multisig(&ready), "The wallet is not multisig"); + const multisig::multisig_account_status ms_status{this->get_multisig_status()}; + CHECK_AND_ASSERT_THROW_MES(ms_status.multisig_is_active, "The wallet is not multisig"); // decrypt account keys - epee::misc_utils::auto_scope_leave_caller keys_reencryptor; - if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) - { - crypto::chacha_key chacha_key; - crypto::generate_chacha_key(password.data(), password.size(), chacha_key, m_kdf_rounds); - m_account.encrypt_viewkey(chacha_key); - m_account.decrypt_keys(chacha_key); - keys_reencryptor = epee::misc_utils::create_scope_leave_handler( - [&, this, chacha_key]() - { - m_account.encrypt_keys(chacha_key); - m_account.decrypt_viewkey(chacha_key); - } - ); - } + epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)}; // reconstruct multisig account - multisig::multisig_keyset_map_memsafe_t kex_origins_map; - - for (const auto &derivation : m_multisig_derivations) - kex_origins_map[derivation]; - - multisig::multisig_account multisig_account{ - m_multisig_threshold, - m_multisig_signers, - get_account().get_keys().m_spend_secret_key, - crypto::null_skey, //base common privkey: not used - get_account().get_keys().m_multisig_keys, - get_account().get_keys().m_view_secret_key, - m_account_public_address.m_spend_public_key, - m_account_public_address.m_view_public_key, - m_multisig_rounds_passed, - std::move(kex_origins_map), - "" - }; + multisig::multisig_account multisig_account; + this->get_reconstructed_multisig_account(multisig_account); // KLUDGE: early return if there are no kex messages and main kex is complete (will return the post-kex verification round // message) (it's a kludge because this behavior would be more appropriate for a standalone wallet method) @@ -5671,7 +5974,7 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor std::vector<multisig::multisig_kex_msg> expanded_msgs; expanded_msgs.reserve(kex_messages.size()); - for (const auto &msg : kex_messages) + for (const std::string &msg : kex_messages) expanded_msgs.emplace_back(msg); // update multisig kex @@ -5707,27 +6010,27 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor if (!m_wallet_file.empty()) { - bool r = store_keys(m_keys_file, password, false); + bool r = this->store_keys(m_keys_file, password, false); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); if (boost::filesystem::exists(m_wallet_file + ".address.txt")) { - r = save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true); + r = this->save_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_nettype), true); if(!r) MERROR("String with address text not saved"); } } m_subaddresses.clear(); m_subaddress_labels.clear(); - add_subaddress_account(tr("Primary account")); + this->add_subaddress_account(tr("Primary account")); if (!m_wallet_file.empty()) - store(); + this->store(); } // wallet/file relationship if (!m_wallet_file.empty()) - create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); + this->create_keys_file(m_wallet_file, false, password, boost::filesystem::exists(m_wallet_file + ".address.txt")); return multisig_account.get_next_kex_round_msg(); } @@ -5735,30 +6038,87 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor std::string wallet2::get_multisig_first_kex_msg() const { // create multisig account - multisig::multisig_account multisig_account{ - // k_base = H(normal private spend key) - multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_spend_secret_key), - // k_view = H(normal private view key) - multisig::get_multisig_blinded_secret_key(get_account().get_keys().m_view_secret_key) - }; + multisig::multisig_account multisig_account; + this->get_uninitialized_multisig_account(multisig_account); return multisig_account.get_next_kex_round_msg(); } //---------------------------------------------------------------------------------------------------- -bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const +std::string wallet2::get_multisig_key_exchange_booster(const epee::wipeable_string &password, + const std::vector<std::string> &kex_messages, + const std::uint32_t threshold, + const std::uint32_t num_signers) { - if (!m_multisig) - return false; - if (threshold) - *threshold = m_multisig_threshold; - if (total) - *total = m_multisig_signers.size(); - if (ready) + CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); + + // decrypt account keys + epee::misc_utils::auto_scope_leave_caller keys_reencryptor{this->decrypt_account_for_multisig(password)}; + + // prepare multisig account + multisig::multisig_account multisig_account; + + const multisig::multisig_account_status ms_status{this->get_multisig_status()}; + CHECK_AND_ASSERT_THROW_MES(!ms_status.is_ready, "Multisig wallet creation process has already been finished."); + + if (ms_status.multisig_is_active) + { + // case: this wallet is in the middle of multisig key exchange + // - boost the round that comes after the in-progress round + + CHECK_AND_ASSERT_THROW_MES(threshold == m_multisig_threshold, + "Expected threshold does not match multisig wallet setting."); + CHECK_AND_ASSERT_THROW_MES(num_signers == m_multisig_signers.size(), + "Expected number of signers does not match multisig wallet setting."); + + // reconstruct multisig account + this->get_reconstructed_multisig_account(multisig_account); + } + else + { + // case: make_multisig() has not been called + // DANGER: If 'num_signers - threshold > 1', but this wallet's future multisig settings + // will be 'num_signers - threshold == 1', then the booster message WILL leak the + // future multisig wallet's private keys in this case where the wallet2 multisig wallet is uninitialized. + + this->get_uninitialized_multisig_account(multisig_account); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const std::string &msg : kex_messages) + expanded_msgs.emplace_back(msg); + + // get kex booster message + // note: booster does not change wallet state other than decrypting/reencrypting account keys + return multisig_account.get_multisig_kex_round_booster(threshold, num_signers, expanded_msgs).get_msg(); +} +//---------------------------------------------------------------------------------------------------- +multisig::multisig_account_status wallet2::get_multisig_status() const +{ + multisig::multisig_account_status ret; + + if (m_multisig) { - *ready = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) && + ret.multisig_is_active = true; + ret.threshold = m_multisig_threshold; + ret.total = m_multisig_signers.size(); + ret.kex_is_done = !(get_account().get_keys().m_account_address.m_spend_public_key == rct::rct2pk(rct::identity())) && + (m_multisig_rounds_passed >= multisig::multisig_kex_rounds_required(m_multisig_signers.size(), m_multisig_threshold)); + ret.is_ready = ret.kex_is_done && (m_multisig_rounds_passed == multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold)); } - return true; + else + { + ret.multisig_is_active = false; + ret.threshold = 0; + ret.total = 0; + ret.kex_is_done = false; + ret.is_ready = false; + } + + return ret; } //---------------------------------------------------------------------------------------------------- bool wallet2::has_multisig_partial_key_images() const @@ -5788,11 +6148,30 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin { if (wallet_name.empty()) return; + THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, + "cannot change wallet settings from background wallet"); prepare_file_names(wallet_name); boost::system::error_code ignored_ec; THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); bool r = store_keys(m_keys_file, password, m_watch_only); THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + // Update the background keys file when we rewrite the main wallet keys file + if (m_background_sync_type == BackgroundSyncCustomPassword && m_custom_background_key) + { + const std::string background_keys_filename = make_background_keys_file_name(wallet_name); + if (!lock_background_keys_file(background_keys_filename)) + { + LOG_ERROR("Background keys file " << background_keys_filename << " is opened by another wallet program and cannot be rewritten"); + return; // not fatal, background keys file will just have different wallet settings + } + store_background_keys(m_custom_background_key.get()); + store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); + } + else if (m_background_sync_type == BackgroundSyncReusePassword) + { + reset_background_sync_data(m_background_sync_data); + } } /*! * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) @@ -5826,6 +6205,16 @@ bool wallet2::wallet_valid_path_format(const std::string& file_path) return !file_path.empty(); } //---------------------------------------------------------------------------------------------------- +std::string wallet2::make_background_wallet_file_name(const std::string &wallet_file) +{ + return wallet_file + BACKGROUND_WALLET_SUFFIX; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::make_background_keys_file_name(const std::string &wallet_file) +{ + return make_background_wallet_file_name(wallet_file) + ".keys"; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id) { cryptonote::blobdata payment_id_data; @@ -6051,10 +6440,78 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer"); } - wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); + wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_is_background_wallet, password); //keys loaded ok! //try to load wallet cache. but even if we failed, it is not big problem + load_wallet_cache(use_fs, cache_buf); + + // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. + // Here we erase these multisig keys if they're zero'd out to free up space. + for (auto &td : m_transfers) + { + auto mk_it = td.m_multisig_k.begin(); + while (mk_it != td.m_multisig_k.end()) + { + if (*mk_it == rct::zero()) + mk_it = td.m_multisig_k.erase(mk_it); + else + ++mk_it; + } + } + + cryptonote::block genesis; + generate_genesis(genesis); + crypto::hash genesis_hash = get_block_hash(genesis); + + if (m_blockchain.empty()) + { + m_blockchain.push_back(genesis_hash); + m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); + } + else + { + check_genesis(genesis_hash); + } + + trim_hashchain(); + + if (get_num_subaddress_accounts() == 0) + add_subaddress_account(tr("Primary account")); + + try + { + find_and_save_rings(false); + } + catch (const std::exception &e) + { + MERROR("Failed to save rings, will try again next time"); + } + + try + { + if (use_fs) + m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); + } + catch (const std::exception &e) + { + MERROR("Failed to initialize MMS, it will be unusable"); + } + + try + { + if (use_fs) + process_background_cache_on_open(); + } + catch (const std::exception &e) + { + MERROR("Failed to process background cache on open: " << e.what()); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf) +{ + boost::system::error_code e; bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty(); if (cache_missing) { @@ -6068,7 +6525,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass bool r = true; if (use_fs) { - load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); + r = load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); } @@ -6081,7 +6538,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); - crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), get_cache_key(), cache_file_data.iv, &cache_data[0]); try { bool loaded = false; @@ -6136,7 +6593,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) tools::copy_file(m_wallet_file, m_wallet_file + ".unportable"); std::stringstream iss; iss.str(""); iss << cache_data; @@ -6158,7 +6615,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass catch (...) { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - if (use_fs) boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + if (use_fs) tools::copy_file(m_wallet_file, m_wallet_file + ".unportable"); std::stringstream iss; iss.str(""); iss << cache_file_buf; @@ -6171,57 +6628,76 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_background_cache_on_open() +{ + if (m_wallet_file.empty()) + return; + if (m_background_syncing || m_is_background_wallet) + return; + if (m_background_sync_type == BackgroundSyncOff) + return; - // Wallets used to wipe, but not erase, old unused multisig key info, which lead to huge memory leaks. - // Here we erase these multisig keys if they're zero'd out to free up space. - for (auto &td : m_transfers) + if (m_background_sync_type == BackgroundSyncReusePassword) { - auto mk_it = td.m_multisig_k.begin(); - while (mk_it != td.m_multisig_k.end()) + const background_sync_data_t background_sync_data = m_background_sync_data; + const hashchain blockchain = m_blockchain; + process_background_cache(background_sync_data, blockchain, m_last_block_reward); + + // Reset the background cache after processing + reset_background_sync_data(m_background_sync_data); + } + else if (m_background_sync_type == BackgroundSyncCustomPassword) + { + // If the background wallet files don't exist, recreate them + const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); + const std::string background_wallet_file = make_background_wallet_file_name(m_wallet_file); + const bool background_keys_file_exists = boost::filesystem::exists(background_keys_file); + const bool background_wallet_exists = boost::filesystem::exists(background_wallet_file); + + THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_keys_file), error::background_wallet_already_open, background_wallet_file); + THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); + + if (!background_keys_file_exists) { - if (*mk_it == rct::zero()) - mk_it = td.m_multisig_k.erase(mk_it); - else - ++mk_it; + MDEBUG("Background keys file not found, restoring"); + store_background_keys(m_custom_background_key.get()); } - } - cryptonote::block genesis; - generate_genesis(genesis); - crypto::hash genesis_hash = get_block_hash(genesis); + if (!background_wallet_exists) + { + MDEBUG("Background cache not found, restoring"); + store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); + return; + } - if (m_blockchain.empty()) - { - m_blockchain.push_back(genesis_hash); - m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); - } - else - { - check_genesis(genesis_hash); - } + MDEBUG("Loading background cache"); - trim_hashchain(); + // Set up a minimal background wallet2 instance + std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype)); + background_w2->m_is_background_wallet = true; + background_w2->m_background_syncing = true; + background_w2->m_background_sync_type = m_background_sync_type; + background_w2->m_custom_background_key = m_custom_background_key; - if (get_num_subaddress_accounts() == 0) - add_subaddress_account(tr("Primary account")); + cryptonote::account_base account = m_account; + account.forget_spend_key(); + background_w2->m_account = account; - try - { - find_and_save_rings(false); - } - catch (const std::exception &e) - { - MERROR("Failed to save rings, will try again next time"); - } - - try - { - if (use_fs) - m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); + // Load background cache from file + background_w2->clear(); + background_w2->prepare_file_names(background_wallet_file); + background_w2->load_wallet_cache(true/*use_fs*/); + + process_background_cache(background_w2->m_background_sync_data, background_w2->m_blockchain, background_w2->m_last_block_reward); + + // Reset the background cache after processing + store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); } - catch (const std::exception &e) + else { - MERROR("Failed to initialize MMS, it will be unusable"); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); } } //---------------------------------------------------------------------------------------------------- @@ -6303,6 +6779,8 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas same_file = canonical_old_path == canonical_new_path; } + THROW_WALLET_EXCEPTION_IF(m_is_background_wallet && !same_file, error::wallet_internal_error, + "Cannot save background wallet files to a different location"); if (!same_file) { @@ -6319,6 +6797,21 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas } } } + else if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing && !m_is_background_wallet) + { + // We're background syncing, so store the wallet cache as a background cache + // keeping the background sync data + try + { + THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); + store_background_cache(m_custom_background_key.get(), false/*do_reset_background_sync_data*/); + } + catch (const std::exception &e) + { + MERROR("Failed to store background cache while background syncing: " << e.what()); + } + return; + } // get wallet cache data boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); @@ -6412,6 +6905,22 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas // store should only exist if the MMS is really active m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file); } + + if (m_background_sync_type == BackgroundSyncCustomPassword && !m_background_syncing && !m_is_background_wallet) + { + // Update the background wallet cache when we store the main wallet cache + // Note: if background syncing when this is called, it means the background + // wallet is open and was already stored above + try + { + THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); + store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); + } + catch (const std::exception &e) + { + MERROR("Failed to update background cache: " << e.what()); + } + } } //---------------------------------------------------------------------------------------------------- boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() @@ -6429,7 +6938,7 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() std::string cipher; cipher.resize(cache_file_data.get().cache_data.size()); cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); - crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]); + crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), get_cache_key(), cache_file_data.get().iv, &cipher[0]); cache_file_data.get().cache_data = cipher; return cache_file_data; } @@ -7252,14 +7761,15 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin { tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; THROW_WALLET_EXCEPTION_IF(sd.sources.empty(), error::wallet_internal_error, "Empty sources"); + THROW_WALLET_EXCEPTION_IF(sd.unlock_time, error::nonzero_unlock_time); LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size()); signed_txes.ptx.push_back(pending_tx()); tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); rct::RCTConfig rct_config = sd.rct_config; crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, sd.unlock_time, tx_key, additional_tx_keys, sd.use_rct, rct_config, sd.use_view_tags); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sd.sources, sd.splitted_dsts, sd.change_dts.addr, sd.extra, ptx.tx, tx_key, additional_tx_keys, sd.use_rct, rct_config, sd.use_view_tags); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, m_nettype); // we don't test tx size, because we don't know the current limit, due to not having a blockchain, // and it's a bit pointless to fail there anyway, since it'd be a (good) guess only. We sign anyway, // and if we really go over limit, the daemon will reject when it gets submitted. Chances are it's @@ -7787,7 +8297,6 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto not multisig_tx_builder.init( m_account.get_keys(), ptx.construction_data.extra, - ptx.construction_data.unlock_time, ptx.construction_data.subaddr_account, ptx.construction_data.subaddr_indices, ptx.construction_data.sources, @@ -8480,6 +8989,34 @@ bool wallet2::is_keys_file_locked() const return m_keys_file_locker->locked(); } +bool wallet2::lock_background_keys_file(const std::string &background_keys_file) +{ + if (background_keys_file.empty() || !boost::filesystem::exists(background_keys_file)) + return true; + if (m_background_keys_file_locker && m_background_keys_file_locker->locked()) + return true; + m_background_keys_file_locker.reset(new tools::file_locker(background_keys_file)); + return m_background_keys_file_locker->locked(); +} + +bool wallet2::unlock_background_keys_file() +{ + if (!m_background_keys_file_locker) + { + MDEBUG("background keys file locker is not set"); + return false; + } + m_background_keys_file_locker.reset(); + return true; +} + +bool wallet2::is_background_keys_file_locked() const +{ + if (!m_background_keys_file_locker) + return false; + return m_background_keys_file_locker->locked(); +} + bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const { if (!unlocked) // don't add locked outs @@ -9199,7 +9736,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> template<typename T> void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, + uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, bool use_view_tags) { using namespace cryptonote; @@ -9310,9 +9847,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; LOG_PRINT_L2("constructing tx"); - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, false, {}, use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, tx_key, additional_tx_keys, false, {}, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); std::string key_images; @@ -9345,7 +9882,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent ptx.construction_data.splitted_dsts = splitted_dsts; ptx.construction_data.selected_transfers = selected_transfers; ptx.construction_data.extra = tx.extra; - ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.unlock_time = 0; ptx.construction_data.use_rct = false; ptx.construction_data.rct_config = { rct::RangeProofBorromean, 0 }; ptx.construction_data.use_view_tags = use_view_tags; @@ -9360,7 +9897,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags) + uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, bool use_view_tags) { using namespace cryptonote; // throw if attempting a transaction with no destinations @@ -9556,7 +10093,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry THROW_WALLET_EXCEPTION_IF( not multisig_tx_builder.init(m_account.get_keys(), extra, - unlock_time, subaddr_account, subaddr_minor_indices, sources, @@ -9576,9 +10112,9 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry } else { // make a normal tx - bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, unlock_time, tx_key, additional_tx_keys, true, rct_config, use_view_tags); + bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), m_subaddresses, sources, splitted_dsts, change_dts.addr, extra, tx, tx_key, additional_tx_keys, true, rct_config, use_view_tags); LOG_PRINT_L2("constructed tx, r="<<r); - THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_nettype); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, m_nettype); } THROW_WALLET_EXCEPTION_IF(upper_transaction_weight_limit <= get_transaction_weight(tx), error::tx_too_big, tx, upper_transaction_weight_limit); @@ -9700,7 +10236,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.construction_data.splitted_dsts = splitted_dsts; ptx.construction_data.selected_transfers = ptx.selected_transfers; ptx.construction_data.extra = tx.extra; - ptx.construction_data.unlock_time = unlock_time; + ptx.construction_data.unlock_time = 0; ptx.construction_data.use_rct = true; ptx.construction_data.rct_config = { rct::RangeProofPaddedBulletproof, @@ -9860,7 +10396,7 @@ static uint32_t get_count_above(const std::vector<wallet2::transfer_details> &tr // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs) +std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -10383,10 +10919,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp tx.selected_transfers.size() << " inputs"); auto tx_dsts = tx.get_adjusted_dsts(needed_fee); if (use_rct) - transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); @@ -10422,21 +10958,21 @@ 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)); - size_t fee_tries; - for (fee_tries = 0; fee_tries < 10 && needed_fee > test_ptx.fee; ++fee_tries) { + size_t fee_tries = 0; + do { tx_dsts = tx.get_adjusted_dsts(needed_fee); if (use_rct) - transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx_dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); 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 && ++fee_tries < 10); THROW_WALLET_EXCEPTION_IF(fee_tries == 10, error::wallet_internal_error, "Too many attempts to raise pending tx fee to level of needed fee"); @@ -10503,7 +11039,6 @@ skip_tx: fake_outs_count, /* CONST size_t fake_outputs_count, */ tx.outs, /* MOD std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, */ valid_public_keys_cache, - unlock_time, /* CONST uint64_t unlock_time, */ tx.needed_fee, /* CONST uint64_t fee, */ extra, /* const std::vector<uint8_t>& extra, */ test_tx, /* OUT cryptonote::transaction& tx, */ @@ -10516,7 +11051,6 @@ skip_tx: fake_outs_count, tx.outs, valid_public_keys_cache, - unlock_time, tx.needed_fee, extra, detail::digit_split_strategy, @@ -10630,7 +11164,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, c return true; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) +std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -10700,10 +11234,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below } } - return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra); + return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, priority, extra); } -std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) +std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -10721,10 +11255,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt break; } } - return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra); + return create_transactions_from(address, is_subaddress, outputs, unused_transfers_indices, unused_dust_indices, fake_outs_count, priority, extra); } -std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra) +std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -10833,10 +11367,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); @@ -10870,10 +11404,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton dt.amount = dt_amount + dt_residue; } if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, unlock_time, needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, valid_public_keys_cache, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(use_per_byte_fee, test_ptx.tx, txBlob.size(), base_fee, fee_quantization_mask); @@ -10909,10 +11443,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton cryptonote::transaction test_tx; pending_tx test_ptx; if (use_rct) { - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, tx.needed_fee, extra, test_tx, test_ptx, rct_config, use_view_tags); } else { - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, unlock_time, tx.needed_fee, extra, + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, tx.outs, valid_public_keys_cache, tx.needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx, use_view_tags); } auto txBlob = t_serializable_object_to_blob(test_ptx.tx); @@ -11214,7 +11748,7 @@ std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions() unmixable_transfer_outputs.push_back(n); } - return create_transactions_from(m_account_public_address, false, 1, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 0 /* unlock_time */, 1 /*priority */, std::vector<uint8_t>()); + return create_transactions_from(m_account_public_address, false, 1, unmixable_transfer_outputs, unmixable_dust_outputs, 0 /*fake_outs_count */, 1 /*priority */, std::vector<uint8_t>()); } //---------------------------------------------------------------------------------------------------- void wallet2::discard_unmixable_outputs() @@ -12401,7 +12935,7 @@ uint64_t wallet2::get_approximate_blockchain_height() const // Calculated blockchain height uint64_t approx_blockchain_height = fork_block + (time(NULL) - fork_time)/seconds_per_block; // testnet and stagenet got some huge rollbacks, so the estimation is way off - static const uint64_t approximate_rolled_back_blocks = m_nettype == TESTNET ? 342100 : 30000; + static const uint64_t approximate_rolled_back_blocks = m_nettype == TESTNET ? 342100 : m_nettype == STAGENET ? 60000 : 30000; if ((m_nettype == TESTNET || m_nettype == STAGENET) && approx_blockchain_height > approximate_rolled_back_blocks) approx_blockchain_height -= approximate_rolled_back_blocks; LOG_PRINT_L2("Calculated blockchain height: " << approx_blockchain_height); @@ -13164,6 +13698,413 @@ bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool o return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none); } +/* + In background sync mode, we use just the view key when the wallet is scanning + to identify all txs where: + + 1. We received an output. + 2. We spent an output. + 3. We *may* have spent a received output but we didn't know for sure because + the spend key was not loaded while background sync was enabled. + + When the user is ready to use the spend key again, we call this function to + process all those background synced transactions with the spend key loaded, + so that we can properly generate key images for the transactions which we + we were not able to do so for while background sync was enabled. This allows + us to determine *all* receives and spends the user completed while the wallet + had background sync enabled. Once this function completes, we can continue + scanning from where the background sync left off. + + Txs of type 3 (txs which we *may* have spent received output(s)) are txs where + 1+ rings contain an output that the user received and the wallet does not know + the associated key image for that output. We don't know if the user spent in + this type of tx or not. This function will generate key images for all outputs + we don't know key images for, and then check if those outputs were spent in + the txs of type 3. + + By storing this type of "plausible spend tx" when scanning in background sync + mode, we avoid the need to query the daemon with key images when background + sync mode is disabled to see if those key images were spent. This would + reveal key images to 3rd party nodes for users who don't run their own. + Although this is not a perfect solution to avoid revealing key images to a 3rd + party node (since tx submission trivially reveals key images to a node), it's + probably better than revealing *unused* key images to a 3rd party node, which + would enable the 3rd party to deduce that a tx is spending an output at least + X old when the key image is included in the chain. +*/ +void wallet2::process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_synced_chain, uint64_t last_block_reward) +{ + // We expect the spend key to be in a decrypted state while + // m_processing_background_cache is true + m_processing_background_cache = true; + auto done_processing = epee::misc_utils::create_scope_leave_handler([&, this]() { + m_processing_background_cache = false; + }); + + if (m_background_syncing || m_multisig || m_watch_only || key_on_device()) + return; + + if (!background_sync_data.first_refresh_done) + { + MDEBUG("Skipping processing background cache, background cache has not synced yet"); + return; + } + + // Skip processing if wallet cache is synced higher than background cache + const uint64_t current_height = m_blockchain.size(); + const uint64_t background_height = background_synced_chain.size(); + MDEBUG("Background cache height " << background_height << " , wallet height " << current_height); + if (current_height > background_height) + { + MWARNING("Skipping processing background cache, synced height is higher than background cache"); + return; + } + + if (m_refresh_from_block_height < background_sync_data.wallet_refresh_from_block_height || + m_subaddress_lookahead_major > background_sync_data.subaddress_lookahead_major || + m_subaddress_lookahead_minor > background_sync_data.subaddress_lookahead_minor || + m_refresh_type < background_sync_data.wallet_refresh_type) + { + MWARNING("Skipping processing background cache, background wallet sync settings did not match main wallet's"); + MDEBUG("Wallet settings: " << + ", m_refresh_from_block_height: " << m_refresh_from_block_height << " vs " << background_sync_data.wallet_refresh_from_block_height << + ", m_subaddress_lookahead_major: " << m_subaddress_lookahead_major << " vs " << background_sync_data.subaddress_lookahead_major << + ", m_subaddress_lookahead_minor: " << m_subaddress_lookahead_minor << " vs " << background_sync_data.subaddress_lookahead_minor << + ", m_refresh_type: " << m_refresh_type << " vs " << background_sync_data.wallet_refresh_type); + return; + } + + // Sort background synced txs in the order they appeared in the cache so that + // we process them in the order they appeared in the chain. Thus if tx2 spends + // from tx1, we will know because tx1 is processed before tx2. + std::vector<std::pair<crypto::hash, background_synced_tx_t>> sorted_bgs_cache(background_sync_data.txs.begin(), background_sync_data.txs.end()); + std::sort(sorted_bgs_cache.begin(), sorted_bgs_cache.end(), + [](const std::pair<crypto::hash, background_synced_tx_t>& l, const std::pair<crypto::hash, background_synced_tx_t>& r) + { + uint64_t left_index = l.second.index_in_background_sync_data; + uint64_t right_index = r.second.index_in_background_sync_data; + THROW_WALLET_EXCEPTION_IF( + (left_index < right_index && l.second.height > r.second.height) || + (left_index > right_index && l.second.height < r.second.height), + error::wallet_internal_error, "Unexpected background sync data order"); + return left_index < right_index; + }); + + // All txs in the background cache should have height >= sync start height, + // but not fatal if not + if (!sorted_bgs_cache.empty() && sorted_bgs_cache[0].second.height < background_sync_data.start_height) + MWARNING("First tx in background cache has height (" << sorted_bgs_cache[0].second.height << ") lower than sync start height (" << background_sync_data.start_height << ")"); + + // We want to process all background synced txs in order to make sure + // the wallet state updates correctly. First we remove all txs from the wallet + // from before the background sync start height, then re-process them in + // chronological order. The background cache should contain a superset of + // *all* the wallet's txs from after the background sync start height. + MDEBUG("Processing " << background_sync_data.txs.size() << " background synced txs starting from height " << background_sync_data.start_height); + detached_blockchain_data dbd = detach_blockchain(background_sync_data.start_height); + + for (const auto &bgs_tx : sorted_bgs_cache) + { + MDEBUG("Processing background synced tx " << bgs_tx.first); + + process_new_transaction(bgs_tx.first, bgs_tx.second.tx, bgs_tx.second.output_indices, bgs_tx.second.height, 0, bgs_tx.second.block_timestamp, + cryptonote::is_coinbase(bgs_tx.second.tx), false/*pool*/, bgs_tx.second.double_spend_seen, {}, {}, true/*ignore_callbacks*/); + + // Re-set destination addresses if they were previously set + if (m_confirmed_txs.find(bgs_tx.first) != m_confirmed_txs.end() && + dbd.detached_confirmed_txs_dests.find(bgs_tx.first) != dbd.detached_confirmed_txs_dests.end()) + { + m_confirmed_txs[bgs_tx.first].m_dests = std::move(dbd.detached_confirmed_txs_dests[bgs_tx.first]); + } + } + + m_blockchain = background_synced_chain; + m_last_block_reward = last_block_reward; + + MDEBUG("Finished processing background sync data"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::reset_background_sync_data(background_sync_data_t &background_sync_data) +{ + background_sync_data.first_refresh_done = false; + background_sync_data.start_height = get_blockchain_current_height(); + background_sync_data.txs.clear(); + + background_sync_data.wallet_refresh_from_block_height = m_refresh_from_block_height; + background_sync_data.subaddress_lookahead_major = m_subaddress_lookahead_major; + background_sync_data.subaddress_lookahead_minor = m_subaddress_lookahead_minor; + background_sync_data.wallet_refresh_type = m_refresh_type; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data) +{ + MDEBUG("Storing background cache (do_reset_background_sync_data=" << do_reset_background_sync_data << ")"); + + THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, + "Can only write a background cache when using a custom background password"); + THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error, + "No wallet file known, can't store background cache"); + + std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype)); + background_w2->prepare_file_names(make_background_wallet_file_name(m_wallet_file)); + + // Make sure background wallet is opened by this wallet + THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_w2->m_keys_file), + error::background_wallet_already_open, background_w2->m_wallet_file); + + // Load a background wallet2 instance using this wallet2 instance + std::string this_wallet2; + bool r = ::serialization::dump_binary(*this, this_wallet2); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to serialize wallet cache"); + + background_w2->clear(); + r = ::serialization::parse_binary(this_wallet2, *background_w2); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to deserialize wallet cache"); + + // Clear sensitive data from background cache not needed to sync + background_w2->clear_user_data(); + + background_w2->m_is_background_wallet = true; + if (do_reset_background_sync_data) + reset_background_sync_data(background_w2->m_background_sync_data); + else + background_w2->m_background_sync_data = m_background_sync_data; + background_w2->m_background_syncing = true; + + background_w2->m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); + background_w2->m_background_sync_type = m_background_sync_type; + background_w2->store(); + + MDEBUG("Background cache stored (" << background_w2->m_transfers.size() << " transfers, " << background_w2->m_background_sync_data.txs.size() << " background synced txs)"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store_background_keys(const crypto::chacha_key &custom_background_key) +{ + MDEBUG("Storing background keys"); + + THROW_WALLET_EXCEPTION_IF(m_wallet_file.empty(), error::wallet_internal_error, + "No wallet file known, can't store background keys"); + + const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); + bool r = store_keys(background_keys_file, custom_background_key, false/*watch_only*/, true/*background_keys_file*/); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, background_keys_file); + THROW_WALLET_EXCEPTION_IF(!is_background_keys_file_locked(), error::wallet_internal_error, background_keys_file + "\" should be locked"); + + // GUI uses the address file to differentiate non-mainnet wallets in the UI + const std::string background_address_file = make_background_wallet_file_name(m_wallet_file) + ".address.txt"; + if (m_nettype != MAINNET && !boost::filesystem::exists(background_address_file)) + { + r = save_to_file(background_address_file, m_account.get_public_address_str(m_nettype), true); + if (!r) MERROR("String with address text not saved"); + } + + MDEBUG("Background keys stored"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password) +{ + MDEBUG("Storing background sync wallet"); + THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, + "Can only write a background sync wallet when using a custom background password"); + THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, + "Can't write background sync wallet from an existing background cache"); + THROW_WALLET_EXCEPTION_IF(wallet_password == background_cache_password, + error::background_custom_password_same_as_wallet_password); + + // Set the background encryption key + crypto::chacha_key custom_background_key; + get_custom_background_key(background_cache_password, custom_background_key, m_kdf_rounds); + + // Keep the background encryption key in memory so the main wallet can update + // the background cache when it stores the main wallet cache + m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); + + if (m_wallet_file.empty() || m_keys_file.empty()) + return; + + // Save background keys file, then background cache, then update main wallet settings + store_background_keys(custom_background_key); + store_background_cache(custom_background_key, true/*do_reset_background_sync_data*/); + bool r = store_keys(m_keys_file, wallet_password, false/*watch_only*/); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + MDEBUG("Background sync wallet saved successfully"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password) +{ + MDEBUG("Setting background sync to type " << background_sync_type); + THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, + "Can't set background sync type from an existing background cache"); + verify_password_with_cached_key(wallet_password); + + if (background_sync_type != BackgroundSyncOff) + validate_background_cache_password_usage(background_sync_type, background_cache_password, m_multisig, m_watch_only, key_on_device()); + + THROW_WALLET_EXCEPTION_IF(background_sync_type == BackgroundSyncCustomPassword && wallet_password == background_cache_password, + error::background_custom_password_same_as_wallet_password); + + if (m_background_sync_type == background_sync_type && background_sync_type != BackgroundSyncCustomPassword) + return; // No need to make any changes + + if (!m_wallet_file.empty()) + { + // Delete existing background files if they already exist + const std::string old_background_wallet_file = make_background_wallet_file_name(m_wallet_file); + const std::string old_background_keys_file = make_background_keys_file_name(m_wallet_file); + const std::string old_background_address_file = old_background_wallet_file + ".address.txt"; + + // Make sure no other program is using the background wallet + THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(old_background_keys_file), + error::background_wallet_already_open, old_background_wallet_file); + + if (boost::filesystem::exists(old_background_wallet_file)) + if (!boost::filesystem::remove(old_background_wallet_file)) + LOG_ERROR("Error deleting background wallet file: " << old_background_wallet_file); + + if (boost::filesystem::exists(old_background_keys_file)) + if (!boost::filesystem::remove(old_background_keys_file)) + LOG_ERROR("Error deleting background keys file: " << old_background_keys_file); + + if (boost::filesystem::exists(old_background_address_file)) + if (!boost::filesystem::remove(old_background_address_file)) + LOG_ERROR("Error deleting background address file: " << old_background_address_file); + } + + m_background_sync_type = background_sync_type; + m_custom_background_key = boost::none; + + // Write the new files + switch (background_sync_type) + { + case BackgroundSyncOff: + case BackgroundSyncReusePassword: rewrite(m_wallet_file, wallet_password); break; + case BackgroundSyncCustomPassword: write_background_sync_wallet(wallet_password, background_cache_password.get()); break; + default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); + } + + MDEBUG("Done setting background sync type"); +} +//---------------------------------------------------------------------------------------------------- +/* + When background syncing, the wallet scans using just the view key, without + keeping the spend key in decrypted state. When a user returns to the wallet + and decrypts the spend key, the wallet processes the background synced txs, + then the wallet picks up scanning normally right where the background sync + left off. +*/ +void wallet2::start_background_sync() +{ + THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, + "must setup background sync first before using background sync"); + THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, + "Can't start background syncing from a background wallet (it is always background syncing)"); + + MDEBUG("Starting background sync"); + + if (m_background_syncing) + { + MDEBUG("Already background syncing"); + return; + } + + if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) + { + // Save the current state of the wallet cache. Only necessary when using a + // custom background password which uses distinct background wallet to sync. + // When reusing wallet password to sync we reuse the main wallet cache. + store(); + + // Wipe user data from the background wallet cache not needed to sync. + // Only wipe user data from background cache if wallet cache is stored + // on disk; otherwise we could lose the data. + clear_user_data(); + + // Wipe m_cache_key since it can be used to decrypt main wallet cache + m_cache_key.scrub(); + } + + reset_background_sync_data(m_background_sync_data); + m_background_syncing = true; + + // Wipe the spend key from memory + m_account.forget_spend_key(); + + MDEBUG("Background sync started at height " << m_background_sync_data.start_height); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key) +{ + MDEBUG("Stopping background sync"); + + // Verify provided password and spend secret key. If no spend secret key is + // provided, recover it from the wallet keys file + crypto::secret_key recovered_spend_key = crypto::null_skey; + if (!m_wallet_file.empty()) + { + THROW_WALLET_EXCEPTION_IF(!verify_password(wallet_password, recovered_spend_key), error::invalid_password); + } + else + { + verify_password_with_cached_key(wallet_password); + } + + if (spend_secret_key != crypto::null_skey) + { + THROW_WALLET_EXCEPTION_IF(!m_wallet_file.empty() && spend_secret_key != recovered_spend_key, + error::invalid_spend_key); + MDEBUG("Setting spend secret key with the provided key"); + recovered_spend_key = spend_secret_key; + } + + // Verify private spend key derives to wallet's public spend key + const auto verify_spend_key = [this](crypto::secret_key &recovered_spend_key) -> bool + { + crypto::public_key spend_public_key; + return recovered_spend_key != crypto::null_skey && + crypto::secret_key_to_public_key(recovered_spend_key, spend_public_key) && + m_account.get_keys().m_account_address.m_spend_public_key == spend_public_key; + }; + THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); + + THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, + "must setup background sync first before using background sync"); + THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, + "Can't stop background syncing from a background wallet"); + + if (!m_background_syncing) + return; + + // Copy background cache, we're about to overwrite it + const background_sync_data_t background_sync_data = m_background_sync_data; + const hashchain background_synced_chain = m_blockchain; + const uint64_t last_block_reward = m_last_block_reward; + + if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) + { + // Reload the wallet from disk + load(m_wallet_file, wallet_password); + THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); + } + m_background_syncing = false; + + // Set the plaintext spend key + m_account.set_spend_key(recovered_spend_key); + + // Encrypt the spend key when done if needed + epee::misc_utils::auto_scope_leave_caller keys_reencryptor; + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]{encrypt_keys(wallet_password);}); + + // Now we can use the decrypted spend key to process background cache + process_background_cache(background_sync_data, background_synced_chain, last_block_reward); + + // Reset the background cache after processing + reset_background_sync_data(m_background_sync_data); + + MDEBUG("Background sync stopped"); +} +//---------------------------------------------------------------------------------------------------- wallet2::payment_container wallet2::export_payments() const { payment_container payments; @@ -14302,9 +15243,13 @@ void wallet2::generate_genesis(cryptonote::block& b) const { //---------------------------------------------------------------------------------------------------- mms::multisig_wallet_state wallet2::get_multisig_wallet_state() const { + const multisig::multisig_account_status ms_status{get_multisig_status()}; + mms::multisig_wallet_state state; state.nettype = m_nettype; - state.multisig = multisig(&state.multisig_is_ready); + state.multisig = ms_status.multisig_is_active; + state.multisig_is_ready = ms_status.is_ready; + state.multisig_kex_is_done = ms_status.kex_is_done; state.has_multisig_partial_key_images = has_multisig_partial_key_images(); state.multisig_rounds_passed = m_multisig_rounds_passed; state.num_transfer_details = m_transfers.size(); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 5f884e374..34833bed6 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -57,6 +57,7 @@ #include "common/util.h" #include "crypto/chacha.h" #include "crypto/hash.h" +#include "multisig/multisig_account.h" #include "ringct/rctTypes.h" #include "ringct/rctOps.h" #include "checkpoints/checkpoints.h" @@ -85,6 +86,7 @@ class Serialization_portability_wallet_Test; class wallet_accessor_test; +namespace multisig { class multisig_account; } namespace tools { @@ -248,6 +250,20 @@ private: BackgroundMiningNo = 2, }; + enum BackgroundSyncType { + BackgroundSyncOff = 0, + BackgroundSyncReusePassword = 1, + BackgroundSyncCustomPassword = 2, + }; + + static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str) + { + if (background_sync_type_str == "off") return BackgroundSyncOff; + if (background_sync_type_str == "reuse-wallet-password") return BackgroundSyncReusePassword; + if (background_sync_type_str == "custom-background-password") return BackgroundSyncCustomPassword; + throw std::logic_error("Unknown background sync type"); + }; + enum ExportFormat { Binary = 0, Ascii, @@ -274,7 +290,12 @@ private: //! Just parses variables. static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); - static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); + static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) + { + crypto::secret_key spend_key = crypto::null_skey; + return verify_password(keys_file_name, password, no_spend_key, hwdev, kdf_rounds, spend_key); + }; + static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out); static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_client_factory>(new net::http::client_factory())); @@ -784,6 +805,54 @@ private: END_SERIALIZE() }; + struct background_synced_tx_t + { + uint64_t index_in_background_sync_data; + cryptonote::transaction tx; + std::vector<uint64_t> output_indices; + uint64_t height; + uint64_t block_timestamp; + bool double_spend_seen; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + VARINT_FIELD(index_in_background_sync_data) + + // prune tx; don't need to keep signature data + if (!tx.serialize_base(ar)) + return false; + + FIELD(output_indices) + VARINT_FIELD(height) + VARINT_FIELD(block_timestamp) + FIELD(double_spend_seen) + END_SERIALIZE() + }; + + struct background_sync_data_t + { + bool first_refresh_done = false; + uint64_t start_height = 0; + std::unordered_map<crypto::hash, background_synced_tx_t> txs; + + // Relevant wallet settings + uint64_t wallet_refresh_from_block_height; + size_t subaddress_lookahead_major; + size_t subaddress_lookahead_minor; + RefreshType wallet_refresh_type; + + BEGIN_SERIALIZE_OBJECT() + VERSION_FIELD(0) + FIELD(first_refresh_done) + FIELD(start_height) + FIELD(txs) + FIELD(wallet_refresh_from_block_height) + VARINT_FIELD(subaddress_lookahead_major) + VARINT_FIELD(subaddress_lookahead_minor) + VARINT_FIELD(wallet_refresh_type) + END_SERIALIZE() + }; + typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; struct parsed_block @@ -891,6 +960,23 @@ private: */ void restore(const std::string& wallet_, const epee::wipeable_string& password, const std::string &device_name, bool create_address_file = false); + private: + /*! + * \brief Decrypts the account keys + * \return an RAII reencryptor for the account keys + */ + epee::misc_utils::auto_scope_leave_caller decrypt_account_for_multisig(const epee::wipeable_string &password); + /*! + * \brief Creates an uninitialized multisig account + * \outparam: the uninitialized multisig account + */ + void get_uninitialized_multisig_account(multisig::multisig_account &account_out) const; + /*! + * \brief Reconstructs a multisig account from wallet2 state + * \outparam: the reconstructed multisig account + */ + void get_reconstructed_multisig_account(multisig::multisig_account &account_out) const; + public: /*! * \brief Creates a multisig wallet * \return empty if done, non empty if we need to send another string @@ -913,6 +999,13 @@ private: */ std::string get_multisig_first_kex_msg() const; /*! + * \brief Use multisig kex messages for an in-progress kex round to 'boost' the following round for another group member + */ + std::string get_multisig_key_exchange_booster(const epee::wipeable_string &password, + const std::vector<std::string> &kex_messages, + const std::uint32_t threshold, + const std::uint32_t num_signers); + /*! * Export multisig info * This will generate and remember new k values */ @@ -972,7 +1065,8 @@ private: /*! * \brief verifies given password is correct for default wallet keys file */ - bool verify_password(const epee::wipeable_string& password); + bool verify_password(const epee::wipeable_string& password) {crypto::secret_key key = crypto::null_skey; return verify_password(password, key);}; + bool verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out); cryptonote::account_base& get_account(){return m_account;} const cryptonote::account_base& get_account()const{return m_account;} @@ -991,13 +1085,13 @@ private: uint64_t max_reorg_depth() const {return m_max_reorg_depth;} bool deinit(); - bool init(std::string daemon_address = "http://localhost:8080", + bool init(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login = boost::none, const std::string &proxy = "", uint64_t upper_transaction_weight_limit = 0, bool trusted_daemon = true, epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect); - bool set_daemon(std::string daemon_address = "http://localhost:8080", + bool set_daemon(std::string daemon_address, boost::optional<epee::net_utils::http::login> daemon_login = boost::none, bool trusted_daemon = true, epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_autodetect, const std::string &proxy = ""); @@ -1059,7 +1153,8 @@ private: cryptonote::network_type nettype() const { return m_nettype; } bool watch_only() const { return m_watch_only; } - bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; + bool is_background_wallet() const { return m_is_background_wallet; } + multisig::multisig_account_status get_multisig_status() 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()) const; @@ -1079,10 +1174,10 @@ private: template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags); + uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx, const bool use_view_tags); void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::vector<size_t>& selected_transfers, size_t fake_outputs_count, std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, std::unordered_set<crypto::public_key> &valid_public_keys_cache, - uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags); + uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx, const rct::RCTConfig &rct_config, const bool use_view_tags); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); @@ -1104,10 +1199,10 @@ private: bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); - std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose - std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); - std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); - std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); + std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose + std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); + std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra); bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; void cold_tx_aux_import(const std::vector<pending_tx>& ptx, const std::vector<std::string>& tx_device_aux); void cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::vector<std::string> & tx_device_aux); @@ -1268,11 +1363,17 @@ private: return; } a & m_has_ever_refreshed_from_node; + if(ver < 31) + { + m_background_sync_data = background_sync_data_t{}; + return; + } + a & m_background_sync_data; } BEGIN_SERIALIZE_OBJECT() MAGIC_FIELD("monero wallet cache") - VERSION_FIELD(1) + VERSION_FIELD(2) FIELD(m_blockchain) FIELD(m_transfers) FIELD(m_account_public_address) @@ -1305,6 +1406,12 @@ private: return true; } FIELD(m_has_ever_refreshed_from_node) + if (version < 2) + { + m_background_sync_data = background_sync_data_t{}; + return true; + } + FIELD(m_background_sync_data) END_SERIALIZE() /*! @@ -1320,6 +1427,8 @@ private: * \return Whether path is valid format */ static bool wallet_valid_path_format(const std::string& file_path); + static std::string make_background_wallet_file_name(const std::string &wallet_file); + static std::string make_background_keys_file_name(const std::string &wallet_file); static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); @@ -1366,6 +1475,9 @@ private: void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } bool track_uses() const { return m_track_uses; } void track_uses(bool value) { m_track_uses = value; } + BackgroundSyncType background_sync_type() const { return m_background_sync_type; } + void setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password); + bool is_background_syncing() const { return m_background_syncing; } bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } @@ -1640,6 +1752,9 @@ private: uint64_t get_bytes_sent() const; uint64_t get_bytes_received() const; + void start_background_sync(); + void stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key = crypto::null_skey); + // MMS ------------------------------------------------------------------------------------------------- mms::message_store& get_message_store() { return m_message_store; }; const mms::message_store& get_message_store() const { return m_message_store; }; @@ -1672,6 +1787,9 @@ private: * \return Whether it was successful. */ bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false); + bool store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); + boost::optional<wallet2::keys_file_data> get_keys_file_data(const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); + bool store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file = false); /*! * \brief Load wallet keys information from wallet file. * \param keys_file_name Name of wallet file @@ -1685,6 +1803,7 @@ private: */ bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt); + void load_wallet_cache(const bool use_fs, const std::string& cache_buf = ""); void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false); bool should_skip_block(const cryptonote::block &b, uint64_t height) const; void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); @@ -1693,6 +1812,15 @@ private: void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; bool clear(); void clear_soft(bool keep_key_images=false); + /* + * clear_user_data clears data created by the user, which is mostly data + * that a view key cannot identify on chain. This function was initially + * added to ensure that a "background" wallet (a wallet that syncs with just + * a view key hot in memory) does not have any sensitive data loaded that it + * does not need in order to sync. Future devs should take care to ensure + * that this function deletes data that is not useful for background syncing + */ + void clear_user_data(); void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height, std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>>& process_pool_txs); void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes); void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false); @@ -1744,10 +1872,23 @@ private: bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); crypto::chacha_key get_ringdb_key(); void setup_keys(const epee::wipeable_string &password); + const crypto::chacha_key get_cache_key(); + void verify_password_with_cached_key(const epee::wipeable_string &password); + void verify_password_with_cached_key(const crypto::chacha_key &key); size_t get_transfer_details(const crypto::key_image &ki) const; tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids); void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries); void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd); + void write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password); + void process_background_cache_on_open(); + void process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_chain, uint64_t last_block_reward); + void reset_background_sync_data(background_sync_data_t &background_sync_data); + void store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data = true); + void store_background_keys(const crypto::chacha_key &custom_background_key); + + bool lock_background_keys_file(const std::string &background_keys_file); + bool unlock_background_keys_file(); + bool is_background_keys_file_locked() const; void register_devices(); hw::device& lookup_device(const std::string & device_descriptor); @@ -1859,6 +2000,8 @@ private: uint64_t m_ignore_outputs_above; uint64_t m_ignore_outputs_below; bool m_track_uses; + bool m_is_background_wallet; + BackgroundSyncType m_background_sync_type; bool m_show_wallet_name_when_locked; uint32_t m_inactivity_lock_timeout; BackgroundMiningSetupType m_setup_background_mining; @@ -1886,6 +2029,7 @@ private: uint64_t m_last_block_reward; std::unique_ptr<tools::file_locker> m_keys_file_locker; + std::unique_ptr<tools::file_locker> m_background_keys_file_locker; mms::message_store m_message_store; bool m_original_keys_available; @@ -1893,6 +2037,7 @@ private: crypto::secret_key m_original_view_secret_key; crypto::chacha_key m_cache_key; + boost::optional<crypto::chacha_key> m_custom_background_key = boost::none; std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh; bool m_unattended; @@ -1908,9 +2053,13 @@ private: static boost::mutex default_daemon_address_lock; static std::string default_daemon_address; + + bool m_background_syncing; + bool m_processing_background_cache; + background_sync_data_t m_background_sync_data; }; } -BOOST_CLASS_VERSION(tools::wallet2, 30) +BOOST_CLASS_VERSION(tools::wallet2, 31) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) @@ -1926,6 +2075,8 @@ BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) +BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx_t, 0) +BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0) namespace boost { @@ -2424,6 +2575,29 @@ namespace boost return; a & x.multisig_sigs; } + + template <class Archive> + inline void serialize(Archive& a, tools::wallet2::background_synced_tx_t &x, const boost::serialization::version_type ver) + { + a & x.index_in_background_sync_data; + a & x.tx; + a & x.output_indices; + a & x.height; + a & x.block_timestamp; + a & x.double_spend_seen; + } + + template <class Archive> + inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver) + { + a & x.first_refresh_done; + a & x.start_height; + a & x.txs; + a & x.wallet_refresh_from_block_height; + a & x.subaddress_lookahead_major; + a & x.subaddress_lookahead_minor; + a & x.wallet_refresh_type; + } } } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 5166f868f..0315a7102 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -63,6 +63,7 @@ namespace tools // invalid_password // invalid_priority // invalid_multisig_seed + // invalid_spend_key // refresh_error * // acc_outs_lookup_error // block_parse_error @@ -86,6 +87,7 @@ namespace tools // zero_amount // zero_destination // subtract_fee_from_bad_index + // nonzero_unlock_time // wallet_rpc_error * // daemon_busy // no_connection_to_daemon @@ -96,6 +98,9 @@ namespace tools // wallet_files_doesnt_correspond // scan_tx_error * // wont_reprocess_recent_txs_via_untrusted_daemon + // background_sync_error * + // background_wallet_already_open + // background_custom_password_same_as_wallet_password // // * - class with protected ctor @@ -303,6 +308,16 @@ namespace tools std::string to_string() const { return wallet_logic_error::to_string(); } }; + struct invalid_spend_key : public wallet_logic_error + { + explicit invalid_spend_key(std::string&& loc) + : wallet_logic_error(std::move(loc), "invalid spend key") + { + } + + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct invalid_pregenerated_random : public wallet_logic_error { @@ -592,20 +607,17 @@ namespace tools std::string && loc , sources_t const & sources , destinations_t const & destinations - , uint64_t unlock_time , cryptonote::network_type nettype ) : transfer_error(std::move(loc), "transaction was not constructed") , m_sources(sources) , m_destinations(destinations) - , m_unlock_time(unlock_time) , m_nettype(nettype) { } const sources_t& sources() const { return m_sources; } const destinations_t& destinations() const { return m_destinations; } - uint64_t unlock_time() const { return m_unlock_time; } std::string to_string() const { @@ -637,15 +649,12 @@ namespace tools cryptonote::print_money(dst.amount); } - ss << "\nunlock_time: " << m_unlock_time; - return ss.str(); } private: sources_t m_sources; destinations_t m_destinations; - uint64_t m_unlock_time; cryptonote::network_type m_nettype; }; //---------------------------------------------------------------------------------------------------- @@ -789,6 +798,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct nonzero_unlock_time : public transfer_error + { + explicit nonzero_unlock_time(std::string&& loc) + : transfer_error(std::move(loc), "transaction cannot have non-zero unlock time") + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_rpc_error : public wallet_logic_error { const std::string& request() const { return m_request; } @@ -945,6 +962,31 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct background_sync_error : public wallet_logic_error + { + protected: + explicit background_sync_error(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct background_wallet_already_open : public background_sync_error + { + explicit background_wallet_already_open(std::string&& loc, const std::string& background_wallet_file) + : background_sync_error(std::move(loc), "background wallet " + background_wallet_file + " is already opened by another wallet program") + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct background_custom_password_same_as_wallet_password : public background_sync_error + { + explicit background_custom_password_same_as_wallet_password(std::string&& loc) + : background_sync_error(std::move(loc), "custom background password must be different than wallet password") + { + } + }; + //---------------------------------------------------------------------------------------------------- #if !defined(_MSC_VER) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index d7aa80e0a..f998c88b7 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -65,7 +65,7 @@ using namespace epee; #define CHECK_MULTISIG_ENABLED() \ do \ { \ - if (m_wallet->multisig() && !m_wallet->is_multisig_enabled()) \ + if (m_wallet->get_multisig_status().multisig_is_active && !m_wallet->is_multisig_enabled()) \ { \ er.code = WALLET_RPC_ERROR_CODE_DISABLED; \ er.message = "This wallet is multisig, and multisig is disabled. Multisig is an experimental feature and may have bugs. Things that could go wrong include: funds sent to a multisig wallet can't be spent at all, can only be spent with the participation of a malicious group member, or can be stolen by a malicious group member. You can enable it by running this once in monero-wallet-cli: set enable-multisig-experimental 1"; \ @@ -73,6 +73,54 @@ using namespace epee; } \ } while(0) +#define CHECK_IF_BACKGROUND_SYNCING() \ + do \ + { \ + if (!m_wallet) { return not_open(er); } \ + if (m_wallet->is_background_wallet()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \ + er.message = "This command is disabled for background wallets."; \ + return false; \ + } \ + if (m_wallet->is_background_syncing()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \ + er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \ + return false; \ + } \ + } while(0) + +#define PRE_VALIDATE_BACKGROUND_SYNC() \ + do \ + { \ + if (!m_wallet) { return not_open(er); } \ + if (m_restricted) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_DENIED; \ + er.message = "Command unavailable in restricted mode."; \ + return false; \ + } \ + if (m_wallet->key_on_device()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ + er.message = "Command not supported by HW wallet"; \ + return false; \ + } \ + if (m_wallet->get_multisig_status().multisig_is_active) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ + er.message = "Multisig wallet cannot enable background sync"; \ + return false; \ + } \ + if (m_wallet->watch_only()) \ + { \ + er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \ + er.message = "Watch-only wallet cannot enable background sync"; \ + return false; \ + } \ + } while (0) + namespace { const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; @@ -245,7 +293,7 @@ namespace tools ); std::string temp = "monero-wallet-rpc." + bind_port + ".login"; - rpc_login_file = tools::private_file::create(temp); + rpc_login_file = tools::private_file::drop_and_recreate(temp); if (!rpc_login_file.handle()) { LOG_ERROR(tr("Failed to create file ") << temp << tr(". Check permissions or remove file")); @@ -291,6 +339,9 @@ namespace tools { if (!m_wallet) return; + // Background mining can be toggled from the main wallet + if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) + return; tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); if (setup == tools::wallet2::BackgroundMiningNo) @@ -459,7 +510,7 @@ namespace tools { res.balance = req.all_accounts ? m_wallet->balance_all(req.strict) : m_wallet->balance(req.account_index, req.strict); res.unlocked_balance = req.all_accounts ? m_wallet->unlocked_balance_all(req.strict, &res.blocks_to_unlock, &res.time_to_unlock) : m_wallet->unlocked_balance(req.account_index, req.strict, &res.blocks_to_unlock, &res.time_to_unlock); - res.multisig_import_needed = m_wallet->multisig() && m_wallet->has_multisig_partial_key_images(); + res.multisig_import_needed = m_wallet->get_multisig_status().multisig_is_active && m_wallet->has_multisig_partial_key_images(); std::map<uint32_t, std::map<uint32_t, uint64_t>> balance_per_subaddress_per_account; std::map<uint32_t, std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>> unlocked_balance_per_subaddress_per_account; if (req.all_accounts) @@ -581,6 +632,7 @@ namespace tools bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { if (req.count < 1 || req.count > 65536) { @@ -618,6 +670,7 @@ namespace tools bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->set_subaddress_label(req.index, req.label); @@ -680,6 +733,7 @@ namespace tools bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->add_subaddress_account(req.label); @@ -697,6 +751,7 @@ namespace tools bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->set_subaddress_label({req.account_index, 0}, req.label); @@ -712,6 +767,7 @@ namespace tools bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); for (const std::pair<const std::string, std::string>& p : account_tags.first) { @@ -731,6 +787,7 @@ namespace tools bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->set_account_tag(req.accounts, req.tag); @@ -746,6 +803,7 @@ namespace tools bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->set_account_tag(req.accounts, ""); @@ -761,6 +819,7 @@ namespace tools bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->set_account_tag_description(req.tag, req.description); @@ -791,6 +850,7 @@ namespace tools bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { if (req.key_image.empty()) @@ -819,6 +879,7 @@ namespace tools bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { if (req.key_image.empty()) @@ -847,6 +908,7 @@ namespace tools bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { if (req.key_image.empty()) @@ -874,6 +936,8 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er) { + CHECK_IF_BACKGROUND_SYNCING(); + crypto::hash8 integrated_payment_id = crypto::null_hash8; std::string extra_nonce; for (auto it = destinations.begin(); it != destinations.end(); it++) @@ -1024,7 +1088,7 @@ namespace tools fill(spent_key_images, key_image_list); } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); if (multisig_txset.empty()) @@ -1079,6 +1143,12 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + else if (req.unlock_time) + { + er.code = WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME; + er.message = "Transaction cannot have non-zero unlock time"; + return false; + } CHECK_MULTISIG_ENABLED(); @@ -1092,7 +1162,7 @@ namespace tools { uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices, req.subtract_fee_from_outputs); if (ptx_vector.empty()) { @@ -1133,6 +1203,12 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + else if (req.unlock_time) + { + er.code = WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME; + er.message = "Transaction cannot have non-zero unlock time"; + return false; + } CHECK_MULTISIG_ENABLED(); @@ -1147,7 +1223,7 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, priority, extra, req.account_index, req.subaddr_indices); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); if (ptx_vector.empty()) @@ -1191,6 +1267,7 @@ namespace tools } CHECK_MULTISIG_ENABLED(); + CHECK_IF_BACKGROUND_SYNCING(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) @@ -1272,6 +1349,7 @@ namespace tools er.message = "command not supported by watch-only wallet"; return false; } + CHECK_IF_BACKGROUND_SYNCING(); if(req.unsigned_txset.empty() && req.multisig_txset.empty()) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -1541,6 +1619,7 @@ namespace tools } CHECK_MULTISIG_ENABLED(); + CHECK_IF_BACKGROUND_SYNCING(); try { @@ -1569,6 +1648,12 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + else if (req.unlock_time) + { + er.code = WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME; + er.message = "Transaction cannot have non-zero unlock time"; + return false; + } CHECK_MULTISIG_ENABLED(); @@ -1604,7 +1689,7 @@ namespace tools { uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, subaddr_indices); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, priority, extra, req.account_index, subaddr_indices); return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.amounts_by_dest_list, res.fee_list, res.weight_list, res.multisig_txset, res.unsigned_txset, req.do_not_relay, res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, res.spent_key_images_list, er); @@ -1629,6 +1714,12 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + else if (req.unlock_time) + { + er.code = WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME; + er.message = "Transaction cannot have non-zero unlock time"; + return false; + } if (req.outputs < 1) { @@ -1661,7 +1752,7 @@ namespace tools { uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0); uint32_t priority = m_wallet->adjust_priority(req.priority); - std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, priority, extra); if (ptx_vector.empty()) { @@ -2066,10 +2157,11 @@ namespace tools if (req.key_type.compare("mnemonic") == 0) { epee::wipeable_string seed; - bool ready; - if (m_wallet->multisig(&ready)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (ms_status.multisig_is_active) { - if (!ready) + if (!ms_status.is_ready) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is multisig, but not yet finalized"; @@ -2090,6 +2182,7 @@ namespace tools er.message = "The wallet is watch-only. Cannot retrieve seed."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); if (!m_wallet->is_deterministic()) { er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC; @@ -2118,6 +2211,7 @@ namespace tools er.message = "The wallet is watch-only. Cannot retrieve spend key."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key); res.key = std::string(key.data(), key.size()); } @@ -2139,6 +2233,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); try { @@ -2152,6 +2247,79 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type); + boost::optional<epee::wipeable_string> background_cache_password = boost::none; + if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) + background_cache_password = boost::optional<epee::wipeable_string>(req.background_cache_password); + m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password); + } + catch (...) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + m_wallet->start_background_sync(); + } + catch (...) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + try + { + PRE_VALIDATE_BACKGROUND_SYNC(); + crypto::secret_key spend_secret_key = crypto::null_skey; + + // Load the spend key from seed if seed is provided + if (!req.seed.empty()) + { + crypto::secret_key recovery_key; + std::string language; + + if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language)) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Electrum-style word list failed verification"; + return false; + } + + if (!req.seed_offset.empty()) + recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset); + + // generate spend key + cryptonote::account_base account; + account.generate(recovery_key, true, false); + spend_secret_key = account.get_keys().m_spend_secret_key; + } + + m_wallet->stop_background_sync(req.wallet_password, spend_secret_key); + } + catch (...) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); @@ -2161,6 +2329,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key; if (req.signature_type == "spend" || req.signature_type == "") @@ -2253,6 +2422,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); if (req.txids.size() != req.notes.size()) { @@ -2325,6 +2495,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); m_wallet->set_attribute(req.key, req.value); @@ -2352,6 +2523,7 @@ namespace tools bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); crypto::hash txid; if (!epee::string_tools::hex_to_pod(req.txid, txid)) @@ -2443,6 +2615,7 @@ namespace tools bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); crypto::hash txid; if (!epee::string_tools::hex_to_pod(req.txid, txid)) @@ -2559,6 +2732,7 @@ namespace tools bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; if (!req.all) @@ -2801,6 +2975,7 @@ namespace tools er.message = "command not supported by HW wallet"; return false; } + CHECK_IF_BACKGROUND_SYNCING(); try { @@ -2830,6 +3005,7 @@ namespace tools er.message = "command not supported by HW wallet"; return false; } + CHECK_IF_BACKGROUND_SYNCING(); cryptonote::blobdata blob; if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob)) @@ -2855,6 +3031,7 @@ namespace tools bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); try { std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all); @@ -2891,6 +3068,7 @@ namespace tools er.message = "This command requires a trusted daemon."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); try { std::vector<std::pair<crypto::key_image, crypto::signature>> ski; @@ -2959,6 +3137,7 @@ namespace tools bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); + CHECK_IF_BACKGROUND_SYNCING(); const auto ab = m_wallet->get_address_book(); if (req.entries.empty()) { @@ -3004,6 +3183,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); cryptonote::address_parse_info info; er.message = ""; @@ -3046,6 +3226,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); const auto ab = m_wallet->get_address_book(); if (req.index >= ab.size()) @@ -3108,6 +3289,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); const auto ab = m_wallet->get_address_book(); if (req.index >= ab.size()) @@ -3178,6 +3360,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); std::unordered_set<crypto::hash> txids; std::list<std::string>::const_iterator i = req.txids.begin(); @@ -3217,6 +3400,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); try { m_wallet->rescan_spent(); @@ -3481,6 +3665,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } + CHECK_IF_BACKGROUND_SYNCING(); if (m_wallet->verify_password(req.old_password)) { try @@ -3979,7 +4164,14 @@ namespace tools bool wallet_rpc_server::on_is_multisig(const wallet_rpc::COMMAND_RPC_IS_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IS_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); - res.multisig = m_wallet->multisig(&res.ready, &res.threshold, &res.total); + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + res.multisig = ms_status.multisig_is_active; + res.kex_is_done = ms_status.kex_is_done; + res.ready = ms_status.is_ready; + res.threshold = ms_status.threshold; + res.total = ms_status.total; + return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -3992,7 +4184,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; er.message = "This wallet is already multisig"; @@ -4007,6 +4199,7 @@ namespace tools er.message = "wallet is watch-only and cannot be made multisig"; return false; } + CHECK_IF_BACKGROUND_SYNCING(); res.multisig_info = m_wallet->get_multisig_first_kex_msg(); return true; @@ -4021,7 +4214,7 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - if (m_wallet->multisig()) + if (m_wallet->get_multisig_status().multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; er.message = "This wallet is already multisig"; @@ -4034,6 +4227,7 @@ namespace tools er.message = "wallet is watch-only and cannot be made multisig"; return false; } + CHECK_IF_BACKGROUND_SYNCING(); try { @@ -4059,14 +4253,15 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - bool ready; - if (!m_wallet->multisig(&ready)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } - if (!ready) + if (!ms_status.is_ready) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is multisig, but not yet finalized"; @@ -4100,15 +4295,15 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } - if (!ready) + if (!ms_status.is_ready) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is multisig, but not yet finalized"; @@ -4116,7 +4311,7 @@ namespace tools } CHECK_MULTISIG_ENABLED(); - if (req.info.size() < threshold - 1) + if (req.info.size() + 1 < ms_status.threshold) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Needs multisig export info from more participants"; @@ -4180,9 +4375,9 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) + multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; @@ -4190,7 +4385,7 @@ namespace tools } CHECK_MULTISIG_ENABLED(); - if (req.multisig_info.size() + 1 < total) + if (req.multisig_info.size() + 1 < ms_status.total) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Needs multisig info from more participants"; @@ -4200,8 +4395,8 @@ namespace tools try { res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info, req.force_update_use_with_caution); - m_wallet->multisig(&ready); - if (ready) + ms_status = m_wallet->get_multisig_status(); + if (ms_status.is_ready) { res.address = m_wallet->get_account().get_public_address_str(m_wallet->nettype()); } @@ -4215,6 +4410,47 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); + if (m_restricted) + { + er.code = WALLET_RPC_ERROR_CODE_DENIED; + er.message = "Command unavailable in restricted mode."; + return false; + } + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (ms_status.is_ready) + { + er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; + er.message = "This wallet is multisig, and already finalized"; + return false; + } + + if (req.multisig_info.size() == 0) + { + er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; + er.message = "Needs multisig info from more participants"; + return false; + } + + try + { + res.multisig_info = m_wallet->get_multisig_key_exchange_booster(req.password, + req.multisig_info, + req.threshold, + req.num_signers); + } + catch (const std::exception &e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = std::string("Error calling exchange_multisig_info_booster: ") + e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx) { if (!m_wallet) return not_open(er); @@ -4224,15 +4460,15 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } - if (!ready) + if (!ms_status.is_ready) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is multisig, but not yet finalized"; @@ -4294,15 +4530,15 @@ namespace tools er.message = "Command unavailable in restricted mode."; return false; } - bool ready; - uint32_t threshold, total; - if (!m_wallet->multisig(&ready, &threshold, &total)) + const multisig::multisig_account_status ms_status{m_wallet->get_multisig_status()}; + + if (!ms_status.multisig_is_active) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is not multisig"; return false; } - if (!ready) + if (!ms_status.is_ready) { er.code = WALLET_RPC_ERROR_CODE_NOT_MULTISIG; er.message = "This wallet is multisig, but not yet finalized"; @@ -4327,7 +4563,7 @@ namespace tools return false; } - if (txs.m_signers.size() < threshold) + if (txs.m_signers.size() < ms_status.threshold) { er.code = WALLET_RPC_ERROR_CODE_THRESHOLD_NOT_REACHED; er.message = "Not enough signers signed this transaction."; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index bfb7013e2..954316b52 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -151,6 +151,7 @@ namespace tools MAP_JON_RPC_WE("import_multisig_info", on_import_multisig, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG) MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) MAP_JON_RPC_WE("exchange_multisig_keys", on_exchange_multisig_keys, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS) + MAP_JON_RPC_WE("get_multisig_key_exchange_booster", on_get_multisig_key_exchange_booster, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER) MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) MAP_JON_RPC_WE("validate_address", on_validate_address, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS) @@ -159,6 +160,9 @@ namespace tools MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES) MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT) MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) + MAP_JON_RPC_WE("setup_background_sync", on_setup_background_sync, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC) + MAP_JON_RPC_WE("start_background_sync", on_start_background_sync, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC) + MAP_JON_RPC_WE("stop_background_sync", on_stop_background_sync, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC) END_JSON_RPC_MAP() END_URI_MAP2() @@ -242,6 +246,7 @@ namespace tools bool on_import_multisig(const wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_IMPORT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_exchange_multisig_keys(const wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::request& req, wallet_rpc::COMMAND_RPC_EXCHANGE_MULTISIG_KEYS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_get_multisig_key_exchange_booster(const wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::request& req, wallet_rpc::COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); @@ -250,6 +255,9 @@ namespace tools bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index f9f534097..816004ccb 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -2323,12 +2323,14 @@ namespace wallet_rpc struct response_t { bool multisig; + bool kex_is_done; bool ready; uint32_t threshold; uint32_t total; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(multisig) + KV_SERIALIZE(kex_is_done) KV_SERIALIZE(ready) KV_SERIALIZE(threshold) KV_SERIALIZE(total) @@ -2479,6 +2481,35 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_GET_MULTISIG_KEY_EXCHANGE_BOOSTER + { + struct request_t + { + std::string password; + std::vector<std::string> multisig_info; + uint32_t threshold; + uint32_t num_signers; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(password) + KV_SERIALIZE(multisig_info) + KV_SERIALIZE(threshold) + KV_SERIALIZE(num_signers) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + std::string multisig_info; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(multisig_info) + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + struct COMMAND_RPC_SIGN_MULTISIG { struct request_t @@ -2698,5 +2729,69 @@ namespace wallet_rpc typedef epee::misc_utils::struct_init<response_t> response; }; + struct COMMAND_RPC_SETUP_BACKGROUND_SYNC + { + struct request_t + { + std::string background_sync_type; + std::string wallet_password; + std::string background_cache_password; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(background_sync_type) + KV_SERIALIZE(wallet_password) + KV_SERIALIZE_OPT(background_cache_password, (std::string)"") + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_START_BACKGROUND_SYNC + { + struct request_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; + + struct COMMAND_RPC_STOP_BACKGROUND_SYNC + { + struct request_t + { + std::string wallet_password; + std::string seed; + std::string seed_offset; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(wallet_password) + KV_SERIALIZE_OPT(seed, (std::string)"") + KV_SERIALIZE_OPT(seed_offset, (std::string)"") + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<request_t> request; + + struct response_t + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + typedef epee::misc_utils::struct_init<response_t> response; + }; } } diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 2988f52ad..8b0797b41 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -80,3 +80,6 @@ #define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47 #define WALLET_RPC_ERROR_CODE_DISABLED -48 #define WALLET_RPC_ERROR_CODE_PROXY_ALREADY_DEFINED -49 +#define WALLET_RPC_ERROR_CODE_NONZERO_UNLOCK_TIME -50 +#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -51 +#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -52 diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp index 4b00fc63f..676d56006 100644 --- a/tests/block_weight/block_weight.cpp +++ b/tests/block_weight/block_weight.cpp @@ -138,7 +138,7 @@ static uint32_t lcg() static void test(test_t t, uint64_t blocks) { - PREFIX(10); + PREFIX(HF_VERSION_2021_SCALING); for (uint64_t h = 0; h < LONG_TERM_BLOCK_WEIGHT_WINDOW; ++h) { @@ -177,8 +177,8 @@ static void test(test_t t, uint64_t blocks) } uint64_t ltw = bc->get_next_long_term_block_weight(w); cryptonote::block b; - b.major_version = 10; - b.minor_version = 10; + b.major_version = HF_VERSION_2021_SCALING; + b.minor_version = HF_VERSION_2021_SCALING; bc->get_db().add_block(std::make_pair(std::move(b), ""), w, ltw, bc->get_db().height(), bc->get_db().height(), {}); if (!bc->update_next_cumulative_weight_limit()) diff --git a/tests/block_weight/block_weight.py b/tests/block_weight/block_weight.py index 5ec09896d..cfd0900fb 100755 --- a/tests/block_weight/block_weight.py +++ b/tests/block_weight/block_weight.py @@ -18,14 +18,20 @@ ltembw = MEDIAN_THRESHOLD weights = [MEDIAN_THRESHOLD]*MEDIAN_WINDOW_SMALL # weights of recent blocks (B), with index -1 most recent lt_weights = [MEDIAN_THRESHOLD]*MEDIAN_WINDOW_BIG # long-term weights +# see contrib/epee/include/misc_language.h, get_mid +def get_mid(a, b): + return (a//2) + (b//2) + ((a - 2*(a//2)) + (b - 2*(b//2)))//2; + # Compute the median of a list def get_median(vec): - #temp = vec + if len(vec) == 1: + return vec[0] temp = sorted(vec) + n = len(temp) // 2 if len(temp) % 2 == 1: - return temp[len(temp)//2] + return temp[n] else: - return int((temp[len(temp)//2]+temp[len(temp)//2-1])//2) + return get_mid(temp[n-1], temp[n]) def LCG(): global lcg_seed @@ -46,7 +52,7 @@ def run(t, blocks): # determine the effective weight stmedian = get_median(weights[-MEDIAN_WINDOW_SMALL:]) - embw = min(max(MEDIAN_THRESHOLD,stmedian),int(MULTIPLIER_BIG*ltembw)) + embw = min(max(ltembw,stmedian),int(MULTIPLIER_BIG*ltembw)) # drop the lowest values weights = weights[1:] @@ -64,7 +70,7 @@ def run(t, blocks): else: sys.exit(1) weights.append(max_weight) - lt_weights.append(min(max_weight,int(ltembw + int(ltembw * 2 / 5)))) + lt_weights.append(min(max(max_weight, ltembw * 10 // 17),int(ltembw + int(ltembw * 7 / 10)))) #print "H %u, r %u, BW %u, EMBW %u, LTBW %u, LTEMBW %u, ltmedian %u" % (block, r, max_weight, embw, lt_weights[-1], ltembw, ltmedian) print("H %u, BW %u, EMBW %u, LTBW %u" % (block, max_weight, embw, lt_weights[-1])) diff --git a/tests/core_tests/block_validation.cpp b/tests/core_tests/block_validation.cpp index 071d2198e..fd5ab5a7f 100644 --- a/tests/core_tests/block_validation.cpp +++ b/tests/core_tests/block_validation.cpp @@ -350,7 +350,7 @@ bool gen_block_miner_tx_has_2_in::generate(std::vector<test_event_entry>& events destinations.push_back(de); transaction tmp_tx; - if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx, 0)) + if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx)) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_0); @@ -393,7 +393,7 @@ bool gen_block_miner_tx_with_txin_to_key::generate(std::vector<test_event_entry> destinations.push_back(de); transaction tmp_tx; - if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx, 0)) + if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tmp_tx)) return false; MAKE_MINER_TX_MANUALLY(miner_tx, blk_1); diff --git a/tests/core_tests/bulletproof_plus.cpp b/tests/core_tests/bulletproof_plus.cpp index ece03dd77..742670063 100644 --- a/tests/core_tests/bulletproof_plus.cpp +++ b/tests/core_tests/bulletproof_plus.cpp @@ -136,7 +136,7 @@ bool gen_bpp_tx_validation_base::generate_with(std::vector<test_event_entry>& ev std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct_txes.resize(rct_txes.size() + 1); - bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]); + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), tx_key, additional_tx_keys, true, rct_config[n]); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx && !post_tx(rct_txes.back(), n)) diff --git a/tests/core_tests/bulletproofs.cpp b/tests/core_tests/bulletproofs.cpp index f875edf74..278bf46a2 100644 --- a/tests/core_tests/bulletproofs.cpp +++ b/tests/core_tests/bulletproofs.cpp @@ -136,7 +136,7 @@ bool gen_bp_tx_validation_base::generate_with(std::vector<test_event_entry>& eve std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct_txes.resize(rct_txes.size() + 1); - bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]); + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), tx_key, additional_tx_keys, true, rct_config[n]); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx && !post_tx(rct_txes.back(), n)) diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 9aa0de8e5..8b2c2a577 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1043,7 +1043,7 @@ bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote vector<tx_destination_entry> 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); + return construct_tx_rct(from.get_keys(), sources, destinations, from.get_keys().m_account_address, std::vector<uint8_t>(), tx, 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, @@ -1060,7 +1060,7 @@ bool construct_tx_to_key(const std::vector<test_event_entry>& events, cryptonote fill_tx_destinations(from, destinations, fee, sources, destinations_all, false); - return construct_tx_rct(from.get_keys(), sources, destinations_all, get_address(from), std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); + return construct_tx_rct(from.get_keys(), sources, destinations_all, get_address(from), std::vector<uint8_t>(), tx, rct, range_proof_type, bp_version); } bool construct_tx_to_key(cryptonote::transaction& tx, @@ -1070,7 +1070,7 @@ bool construct_tx_to_key(cryptonote::transaction& tx, { vector<tx_destination_entry> destinations; fill_tx_destinations(from, get_address(to), amount, fee, sources, destinations, rct); - return construct_tx_rct(from.get_keys(), sources, destinations, get_address(from), std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); + return construct_tx_rct(from.get_keys(), sources, destinations, get_address(from), std::vector<uint8_t>(), tx, rct, range_proof_type, bp_version); } bool construct_tx_to_key(cryptonote::transaction& tx, @@ -1081,10 +1081,10 @@ bool construct_tx_to_key(cryptonote::transaction& tx, { vector<tx_destination_entry> all_destinations; fill_tx_destinations(from, destinations, fee, sources, all_destinations, rct); - return construct_tx_rct(from.get_keys(), sources, all_destinations, get_address(from), std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); + return construct_tx_rct(from.get_keys(), sources, all_destinations, get_address(from), std::vector<uint8_t>(), tx, rct, range_proof_type, bp_version); } -bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, cryptonote::transaction& tx, uint64_t unlock_time, bool rct, rct::RangeProofType range_proof_type, int bp_version) +bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, cryptonote::transaction& tx, bool rct, rct::RangeProofType range_proof_type, int bp_version) { std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0, 0}; @@ -1092,7 +1092,7 @@ bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std:: std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); + return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations_copy, change_addr, extra, tx, tx_key, additional_tx_keys, rct, rct_config); } transaction construct_tx_with_fee(std::vector<test_event_entry>& events, const block& blk_head, diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 41f7275d3..0a1eb232d 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -472,7 +472,7 @@ bool construct_tx_rct(const cryptonote::account_keys& sender_account_keys, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, - std::vector<uint8_t> extra, cryptonote::transaction& tx, uint64_t unlock_time, + std::vector<uint8_t> extra, cryptonote::transaction& tx, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0); diff --git a/tests/core_tests/chaingen_serialization.h b/tests/core_tests/chaingen_serialization.h index 7eac3987f..5a43e970e 100644 --- a/tests/core_tests/chaingen_serialization.h +++ b/tests/core_tests/chaingen_serialization.h @@ -35,6 +35,7 @@ #include <boost/archive/portable_binary_iarchive.hpp> #include <boost/filesystem/operations.hpp> +#include "common/util.h" namespace tools { @@ -110,7 +111,7 @@ namespace tools catch(...) { // if failed, try reading in unportable mode - boost::filesystem::copy_file(file_path, file_path + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + tools::copy_file(file_path, file_path + ".unportable"); data_file.close(); data_file.open( file_path, std::ios_base::binary | std::ios_base::in); if(data_file.fail()) diff --git a/tests/core_tests/double_spend.inl b/tests/core_tests/double_spend.inl index b6e3726cc..5ccf4de98 100644 --- a/tests/core_tests/double_spend.inl +++ b/tests/core_tests/double_spend.inl @@ -138,7 +138,7 @@ bool gen_double_spend_in_tx<txs_keeped_by_block>::generate(std::vector<test_even destinations.push_back(de); cryptonote::transaction tx_1; - if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1, 0)) + if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1)) return false; SET_EVENT_VISITOR_SETT(events, txs_keeped_by_block ? event_visitor_settings::set_txs_keeped_by_block : 0); diff --git a/tests/core_tests/integer_overflow.cpp b/tests/core_tests/integer_overflow.cpp index 6ffc3786b..2f48ec04a 100644 --- a/tests/core_tests/integer_overflow.cpp +++ b/tests/core_tests/integer_overflow.cpp @@ -174,7 +174,7 @@ bool gen_uint_overflow_2::generate(std::vector<test_event_entry>& events) const destinations.push_back(tx_destination_entry(sources.front().amount - MONEY_SUPPLY - MONEY_SUPPLY + 1 - TESTS_DEFAULT_FEE, bob_addr, false)); cryptonote::transaction tx_1; - if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1, 0)) + if (!construct_tx(miner_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_1)) return false; events.push_back(tx_1); @@ -200,7 +200,7 @@ bool gen_uint_overflow_2::generate(std::vector<test_event_entry>& events) const destinations.push_back(de); cryptonote::transaction tx_2; - if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_2, 0)) + if (!construct_tx(bob_account.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_2)) return false; events.push_back(tx_2); diff --git a/tests/core_tests/multisig.cpp b/tests/core_tests/multisig.cpp index ed6fd68b5..e33803325 100644 --- a/tests/core_tests/multisig.cpp +++ b/tests/core_tests/multisig.cpp @@ -310,7 +310,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry crypto::secret_key multisig_tx_key_entropy; auto sources_copy = sources; multisig::signing::tx_builder_ringct_t tx_builder; - CHECK_AND_ASSERT_MES(tx_builder.init(miner_account[creator].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, false, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); + CHECK_AND_ASSERT_MES(tx_builder.init(miner_account[creator].get_keys(), {}, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, false, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); // work out the permutation done on sources std::vector<size_t> ins_order; @@ -399,7 +399,7 @@ bool gen_multisig_tx_validation_base::generate_with(std::vector<test_event_entry } tools::apply_permutation(ins_order, k); multisig::signing::tx_builder_ringct_t signer_tx_builder; - CHECK_AND_ASSERT_MES(signer_tx_builder.init(miner_account[signer].get_keys(), {}, 0, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, true, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); + CHECK_AND_ASSERT_MES(signer_tx_builder.init(miner_account[signer].get_keys(), {}, 0, {0}, sources, destinations, {}, {rct::RangeProofPaddedBulletproof, 4}, true, true, tx_key, additional_tx_secret_keys, multisig_tx_key_entropy, tx), false, "error: multisig::signing::tx_builder_ringct_t::init"); MDEBUG("signing with k size " << k.size()); for (size_t n = 0; n < multisig::signing::kAlphaComponents; ++n) diff --git a/tests/core_tests/rct.cpp b/tests/core_tests/rct.cpp index 3f2cfbea3..9ea7a8e3b 100644 --- a/tests/core_tests/rct.cpp +++ b/tests/core_tests/rct.cpp @@ -122,7 +122,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes[n], 0, tx_key, additional_tx_keys, true); + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes[n], tx_key, additional_tx_keys, true); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); events.push_back(rct_txes[n]); starting_rct_tx_hashes.push_back(get_transaction_hash(rct_txes[n])); @@ -229,7 +229,7 @@ bool gen_rct_tx_validation_base::generate_with_full(std::vector<test_event_entry std::vector<crypto::secret_key> additional_tx_keys; std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[0].get_keys().m_account_address.m_spend_public_key] = {0,0}; - bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, 0, tx_key, additional_tx_keys, true, rct_config, use_view_tags); + bool r = construct_tx_and_get_tx_key(miner_accounts[0].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), tx, tx_key, additional_tx_keys, true, rct_config, use_view_tags); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx) diff --git a/tests/core_tests/rct2.cpp b/tests/core_tests/rct2.cpp index c8e0f6260..28e97b06d 100644 --- a/tests/core_tests/rct2.cpp +++ b/tests/core_tests/rct2.cpp @@ -136,7 +136,7 @@ bool gen_rct2_tx_validation_base::generate_with(std::vector<test_event_entry>& e std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[miner_accounts[n].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct_txes.resize(rct_txes.size() + 1); - bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), 0, tx_key, additional_tx_keys, true, rct_config[n]); + bool r = construct_tx_and_get_tx_key(miner_accounts[n].get_keys(), subaddresses, sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), rct_txes.back(), tx_key, additional_tx_keys, true, rct_config[n]); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (post_tx && !post_tx(rct_txes.back(), n)) diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index b5ecba9ae..c27e6b375 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -102,7 +102,7 @@ bool test_transaction_generation_and_ring_signature() destinations.push_back(td); transaction tx_rc1; - bool r = construct_tx(miner_acc2.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_rc1, 0); + bool r = construct_tx(miner_acc2.get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx_rc1); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); crypto::hash pref_hash = get_transaction_prefix_hash(tx_rc1); diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index bc8622539..a46b2a06a 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -212,7 +212,7 @@ bool gen_tx_unlock_time::generate(std::vector<test_event_entry>& events) const GENERATE_ACCOUNT(miner_account); MAKE_GENESIS_BLOCK(events, blk_0, miner_account, ts_start); - REWIND_BLOCKS_N(events, blk_1, blk_0, miner_account, 10); + REWIND_BLOCKS_N(events, blk_1, blk_0, miner_account, 20); REWIND_BLOCKS(events, blk_1r, blk_1, miner_account); auto make_tx_with_unlock_time = [&](uint64_t unlock_time) -> transaction @@ -222,9 +222,34 @@ bool gen_tx_unlock_time::generate(std::vector<test_event_entry>& events) const std::list<transaction> txs_0; + // Let's make sure that non-zero unlock times fail with a normal relay method + SET_EVENT_VISITOR_SETT(events, 0); // set relay_method::fluff + txs_0.push_back(make_tx_with_unlock_time(0)); events.push_back(txs_0.back()); + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) - 1)); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(get_block_height(blk_1r))); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) + 1)); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) + 2)); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(ts_start - 1)); + + DO_CALLBACK(events, "mark_invalid_tx"); + events.push_back(make_tx_with_unlock_time(time(0) + 60 * 60)); + + // We want to try adding these transactions with non-zero unlock times to the pool, but relay + // rules enforce otherwise, so we set the relay method to block + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block); // set relay_method::block + txs_0.push_back(make_tx_with_unlock_time(get_block_height(blk_1r) - 1)); events.push_back(txs_0.back()); @@ -596,6 +621,7 @@ bool gen_tx_check_input_unlock_time::generate(std::vector<test_event_entry>& eve events.push_back(txs_0.back()); }; + SET_EVENT_VISITOR_SETT(events, event_visitor_settings::set_txs_keeped_by_block); // set relay_method::block uint64_t blk_3_height = get_block_height(blk_1r) + 2; make_tx_to_acc(0, 0); make_tx_to_acc(1, blk_3_height - 1); @@ -604,6 +630,7 @@ bool gen_tx_check_input_unlock_time::generate(std::vector<test_event_entry>& eve make_tx_to_acc(4, time(0) - 1); make_tx_to_acc(5, time(0) + 60 * 60); MAKE_NEXT_BLOCK_TX_LIST(events, blk_2, blk_1r, miner_account, txs_0); + SET_EVENT_VISITOR_SETT(events, 0); // set relay_method::fluff std::list<transaction> txs_1; auto make_tx_from_acc = [&](size_t acc_idx, bool invalid) diff --git a/tests/core_tests/v2_tests.cpp b/tests/core_tests/v2_tests.cpp index f2d0121b6..9030f22ba 100644 --- a/tests/core_tests/v2_tests.cpp +++ b/tests/core_tests/v2_tests.cpp @@ -107,7 +107,7 @@ bool gen_v2_tx_validation_base::generate_with(std::vector<test_event_entry>& eve destinations.push_back(td); transaction tx; - bool r = construct_tx(miner_accounts[0].get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx, 0); + bool r = construct_tx(miner_accounts[0].get_keys(), sources, destinations, boost::none, std::vector<uint8_t>(), tx); CHECK_AND_ASSERT_MES(r, false, "failed to construct transaction"); if (!valid) DO_CALLBACK(events, "mark_invalid_tx"); diff --git a/tests/core_tests/wallet_tools.cpp b/tests/core_tests/wallet_tools.cpp index ee29fdad1..c81abbbaf 100644 --- a/tests/core_tests/wallet_tools.cpp +++ b/tests/core_tests/wallet_tools.cpp @@ -271,7 +271,7 @@ bool construct_tx_to_key(cryptonote::transaction& tx, { vector<tx_destination_entry> destinations; fill_tx_destinations(sender_wallet->get_account(), get_address(to), amount, fee, sources, destinations, rct); - return construct_tx_rct(sender_wallet, sources, destinations, get_address(sender_wallet), std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); + return construct_tx_rct(sender_wallet, sources, destinations, get_address(sender_wallet), std::vector<uint8_t>(), tx, rct, range_proof_type, bp_version); } bool construct_tx_to_key(cryptonote::transaction& tx, @@ -282,15 +282,15 @@ bool construct_tx_to_key(cryptonote::transaction& tx, { vector<tx_destination_entry> all_destinations; fill_tx_destinations(sender_wallet->get_account(), destinations, fee, sources, all_destinations, rct); - return construct_tx_rct(sender_wallet, sources, all_destinations, get_address(sender_wallet), std::vector<uint8_t>(), tx, 0, rct, range_proof_type, bp_version); + return construct_tx_rct(sender_wallet, sources, all_destinations, get_address(sender_wallet), std::vector<uint8_t>(), tx, rct, range_proof_type, bp_version); } -bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, cryptonote::transaction& tx, uint64_t unlock_time, bool rct, rct::RangeProofType range_proof_type, int bp_version) +bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, std::vector<uint8_t> extra, cryptonote::transaction& tx, bool rct, rct::RangeProofType range_proof_type, int bp_version) { subaddresses_t & subaddresses = wallet_accessor_test::get_subaddresses(sender_wallet); crypto::secret_key tx_key; std::vector<crypto::secret_key> additional_tx_keys; std::vector<tx_destination_entry> destinations_copy = destinations; rct::RCTConfig rct_config = {range_proof_type, bp_version}; - return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, unlock_time, tx_key, additional_tx_keys, rct, rct_config); + return construct_tx_and_get_tx_key(sender_wallet->get_account().get_keys(), subaddresses, sources, destinations_copy, change_addr, extra, tx, tx_key, additional_tx_keys, rct, rct_config); } diff --git a/tests/core_tests/wallet_tools.h b/tests/core_tests/wallet_tools.h index 2201e11f4..94921e56e 100644 --- a/tests/core_tests/wallet_tools.h +++ b/tests/core_tests/wallet_tools.h @@ -53,6 +53,7 @@ class wallet_accessor_test { public: static void set_account(tools::wallet2 * wallet, cryptonote::account_base& account); + static void set_password(tools::wallet2 * wallet, const epee::wipeable_string & password) { wallet->setup_keys(password); } static tools::wallet2::transfer_container & get_transfers(tools::wallet2 * wallet) { return wallet->m_transfers; } static subaddresses_t & get_subaddresses(tools::wallet2 * wallet) { return wallet->m_subaddresses; } static void 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); @@ -92,5 +93,5 @@ bool construct_tx_rct(tools::wallet2 * sender_wallet, std::vector<cryptonote::tx_source_entry>& sources, const std::vector<cryptonote::tx_destination_entry>& destinations, const boost::optional<cryptonote::account_public_address>& change_addr, - std::vector<uint8_t> extra, cryptonote::transaction& tx, uint64_t unlock_time, + std::vector<uint8_t> extra, cryptonote::transaction& tx, bool rct=false, rct::RangeProofType range_proof_type=rct::RangeProofBorromean, int bp_version = 0); diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 7e4d49ffa..e483352a4 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -12,8 +12,8 @@ import os USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]' DEFAULT_TESTS = [ 'address_book', 'bans', 'blockchain', 'cold_signing', 'daemon_info', 'get_output_distribution', - 'integrated_address', 'k_anonymity', 'mining', 'multisig', 'p2p', 'proofs', 'rpc_payment', - 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet' + 'http_digest_auth', 'integrated_address', 'k_anonymity', 'mining', 'multisig', 'p2p', 'proofs', + 'rpc_payment', 'sign_message', 'transfer', 'txpool', 'uri', 'validate_address', 'wallet' ] try: python = sys.argv[1] @@ -41,12 +41,12 @@ except: # a main offline monerod, does most of the tests # a restricted RPC monerod setup with RPC payment # two local online monerods connected to each other -N_MONERODS = 4 +N_MONERODS = 5 # 4 wallets connected to the main offline monerod # 1 wallet connected to the first local online monerod # 1 offline wallet -N_WALLETS = 6 +N_WALLETS = 7 WALLET_DIRECTORY = builddir + "/functional-tests-directory" FUNCTIONAL_TESTS_DIRECTORY = builddir + "/tests/functional_tests" @@ -58,15 +58,17 @@ monerod_extra = [ ["--rpc-payment-address", "44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--offline"], ["--add-exclusive-node", "127.0.0.1:18283"], ["--add-exclusive-node", "127.0.0.1:18282"], + ["--rpc-login", "md5_lover:Z1ON0101", "--offline"], ] -wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", WALLET_DIRECTORY, "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--log-level", "1", "--allow-mismatched-daemon-version"] +wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", WALLET_DIRECTORY, "--rpc-bind-port", "wallet_port", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--log-level", "1", "--allow-mismatched-daemon-version"] wallet_extra = [ - ["--daemon-port", "18180"], - ["--daemon-port", "18180"], - ["--daemon-port", "18180"], - ["--daemon-port", "18180"], - ["--daemon-port", "18182"], - ["--offline"], + ["--daemon-port", "18180", "--disable-rpc-login"], + ["--daemon-port", "18180", "--disable-rpc-login"], + ["--daemon-port", "18180", "--disable-rpc-login"], + ["--daemon-port", "18180", "--disable-rpc-login"], + ["--daemon-port", "18182", "--disable-rpc-login"], + ["--offline", "--disable-rpc-login"], + ["--daemon-port", "18184", "--daemon-login", "md5_lover:Z1ON0101", "--rpc-login", "kyle:reveille"], ] command_lines = [] diff --git a/tests/functional_tests/http_digest_auth.py b/tests/functional_tests/http_digest_auth.py new file mode 100644 index 000000000..7c22f9f30 --- /dev/null +++ b/tests/functional_tests/http_digest_auth.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024, 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. + +from framework.daemon import Daemon +from framework.wallet import Wallet + +import time + + +DAEMON_IDX = 4 +DAEMON_USER = "md5_lover" +DAEMON_PASS = "Z1ON0101" +WALLET_IDX = 6 +WALLET_USER = "kyle" +WALLET_PASS = "reveille" +WALLET_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" + +class HttpDigestAuthTest(): + def run_test(self): + self.test_daemon_login_required() + self.test_wallet_login_required() + + self.make_daemon_conn() + self.create_wallet() + + self.mine_through_wallet() + + def test_daemon_login_required(self): + print('Attempting to connect to daemon loginless with RPC digest authentication required...') + bad_daemon = Daemon(idx = DAEMON_IDX) + try: + res = bad_daemon.get_height() + assert(False) + except: + pass + + def test_wallet_login_required(self): + print('Attempting to connect to wallet server loginless with RPC digest authentication required...') + bad_wallet = Wallet(idx = WALLET_IDX) + try: + res = bad_wallet.get_balance() + assert(False) + except: + pass + + def make_daemon_conn(self): + print('Connecting to daemon with RPC digest authentication required...') + self.daemon = Daemon(idx = DAEMON_IDX, username = DAEMON_USER, password = DAEMON_PASS) + res = self.daemon.get_height() + self.daemon.pop_blocks(res.height - 1) + self.daemon.flush_txpool() + + def create_wallet(self): + print('Connecting to wallet server with RPC digest authentication required...') + self.wallet = Wallet(idx = WALLET_IDX, username = WALLET_USER, password = WALLET_PASS) + # close the wallet if any, will throw if none is loaded + try: self.wallet.close_wallet() + except: pass + res = self.wallet.restore_deterministic_wallet(seed = WALLET_SEED) + + def mine_through_wallet(self): + print('Telling login-required daemon to start mining through login-required wallet server...') + start_height = self.daemon.get_height().height + self.wallet.start_mining(2) + + print("Waiting a few seconds for mining to occur...") + for tries in range(20): + time.sleep(1) + + stop_height = self.daemon.get_height().height + if stop_height > start_height: + break + + print('Telling login-required daemon to stop mining through login-required wallet server...') + self.wallet.stop_mining() + + num_blocks_mined = stop_height - start_height + assert num_blocks_mined > 0 + print('Mined {} blocks!'.format(num_blocks_mined)) + +if __name__ == '__main__': + HttpDigestAuthTest().run_test() diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 8c4b27d24..a15348bca 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -85,7 +85,7 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, try { std::vector<tools::wallet2::pending_tx> ptx; - ptx = w1.create_transactions_2(dsts, mix_in_factor, 0, 0, std::vector<uint8_t>(), 0, {}); + ptx = w1.create_transactions_2(dsts, mix_in_factor, 0, std::vector<uint8_t>(), 0, {}); for (auto &p: ptx) w1.commit_tx(p); return true; diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py index 56a2514d9..1da075318 100755 --- a/tests/functional_tests/transfer.py +++ b/tests/functional_tests/transfer.py @@ -30,6 +30,7 @@ from __future__ import print_function import json +import util_resources import pprint from deepdiff import DeepDiff pp = pprint.PrettyPrinter(indent=2) @@ -46,6 +47,17 @@ seeds = [ 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', ] +def diff_transfers(actual_transfers, expected_transfers, ignore_order = True): + # The payments containers aren't ordered; re-scanning can lead to diff orders + diff = DeepDiff(actual_transfers, expected_transfers, ignore_order = ignore_order) + if diff != {}: + pp.pprint(diff) + assert diff == {} + +def diff_incoming_transfers(actual_transfers, expected_transfers): + # wallet2 m_transfers container is ordered and order should be the same across rescans + diff_transfers(actual_transfers, expected_transfers, ignore_order = False) + class TransferTest(): def run_test(self): self.reset() @@ -64,6 +76,8 @@ class TransferTest(): self.check_multiple_submissions() self.check_scan_tx() self.check_subtract_fee_from_outputs() + self.check_background_sync() + self.check_background_sync_reorg_recovery() def reset(self): print('Resetting blockchain') @@ -875,12 +889,6 @@ class TransferTest(): print('Testing scan_tx') - def diff_transfers(actual_transfers, expected_transfers): - diff = DeepDiff(actual_transfers, expected_transfers) - if diff != {}: - pp.pprint(diff) - assert diff == {} - # set up sender_wallet sender_wallet = self.wallet[0] try: sender_wallet.close_wallet() @@ -1162,5 +1170,385 @@ class TransferTest(): except AssertionError: pass + def check_background_sync(self): + daemon = Daemon() + + print('Testing background sync') + + # Some helper functions + def stop_with_wrong_inputs(wallet, wallet_password, seed = ''): + invalid = False + try: wallet.stop_background_sync(wallet_password = wallet_password, seed = seed) + except: invalid = True + assert invalid + + def open_with_wrong_password(wallet, filename, password): + invalid_password = False + try: wallet.open_wallet(filename, password = password) + except: invalid_password = True + assert invalid_password + + def restore_wallet(wallet, seed, filename = '', password = ''): + wallet.close_wallet() + if filename != '': + util_resources.remove_wallet_files(filename) + wallet.restore_deterministic_wallet(seed = seed, filename = filename, password = password) + wallet.auto_refresh(enable = False) + assert wallet.get_transfers() == {} + + def assert_correct_transfers(wallet, expected_transfers, expected_inc_transfers, expected_balance): + diff_transfers(wallet.get_transfers(), expected_transfers) + diff_incoming_transfers(wallet.incoming_transfers(transfer_type = 'all'), expected_inc_transfers) + assert wallet.get_balance().balance == expected_balance + + # Set up sender_wallet. Prepare to sweep single output to receiver. + # We're testing a sweep because it makes sure background sync can + # properly pick up txs which do not have a change output back to sender. + sender_wallet = self.wallet[0] + try: sender_wallet.close_wallet() + except: pass + sender_wallet.restore_deterministic_wallet(seed = seeds[0]) + sender_wallet.auto_refresh(enable = False) + sender_wallet.refresh() + res = sender_wallet.incoming_transfers(transfer_type = 'available') + unlocked = [x for x in res.transfers if x.unlocked and x.amount > 0] + assert len(unlocked) > 0 + ki = unlocked[0].key_image + amount = unlocked[0].amount + spent_txid = unlocked[0].tx_hash + sender_wallet.refresh() + res = sender_wallet.get_transfers() + out_len = 0 if 'out' not in res else len(res.out) + sender_starting_balance = sender_wallet.get_balance().balance + + # Background sync type options + reuse_password = sender_wallet.background_sync_options.reuse_password + custom_password = sender_wallet.background_sync_options.custom_password + + # set up receiver_wallet + receiver_wallet = self.wallet[1] + try: receiver_wallet.close_wallet() + except: pass + receiver_wallet.restore_deterministic_wallet(seed = seeds[1]) + receiver_wallet.auto_refresh(enable = False) + receiver_wallet.refresh() + res = receiver_wallet.get_transfers() + in_len = 0 if 'in' not in res else len(res['in']) + receiver_starting_balance = receiver_wallet.get_balance().balance + + # transfer from sender_wallet to receiver_wallet + dst = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' + res = sender_wallet.sweep_single(dst, key_image = ki) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + assert res.fee > 0 + fee = res.fee + assert res.amount == amount - fee + + expected_sender_balance = sender_starting_balance - amount + expected_receiver_balance = receiver_starting_balance + (amount - fee) + + print('Checking background sync on outgoing wallet') + sender_wallet.setup_background_sync(background_sync_type = reuse_password) + sender_wallet.start_background_sync() + # Mine block to an uninvolved wallet + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + # sender should still be able to scan the transfer normally because we + # spent an output that had a known key image + sender_wallet.refresh() + transfers = sender_wallet.get_transfers() + assert 'pending' not in transfers or len(transfers.pending) == 0 + assert 'pool' not in transfers or len (transfers.pool) == 0 + assert len(transfers.out) == out_len + 1 + tx = [x for x in transfers.out if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert tx.amount == amount - fee + assert tx.fee == fee + assert len(tx.destinations) == 1 + assert tx.destinations[0].amount == amount - fee + assert tx.destinations[0].address == dst + incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') + assert len([x for x in incoming_transfers.transfers if x.tx_hash == spent_txid and x.key_image == ki and x.spent]) == 1 + assert sender_wallet.get_balance().balance == expected_sender_balance + + # Restore and check background syncing outgoing wallet + restore_wallet(sender_wallet, seeds[0]) + sender_wallet.setup_background_sync(background_sync_type = reuse_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + for i, out_tx in enumerate(transfers.out): + if 'destinations' in out_tx: + del transfers.out[i]['destinations'] # destinations are not expected after wallet restore + # sender's balance should be higher because can't detect spends while + # background sync enabled, only receives + background_bal = sender_wallet.get_balance().balance + assert background_bal > expected_sender_balance + background_transfers = sender_wallet.get_transfers() + assert 'out' not in background_transfers or len(background_transfers.out) == 0 + assert 'in' in background_transfers and len(background_transfers['in']) > 0 + background_incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 + assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == spent_txid]) == 1 + + # Try to stop background sync with the wrong seed + stop_with_wrong_inputs(sender_wallet, wallet_password = '', seed = seeds[1]) + + # Stop background sync and check transfers update correctly + sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Check stopping a wallet with wallet files saved to disk + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + stop_with_wrong_inputs(sender_wallet, 'wrong_password') + sender_wallet.stop_background_sync(wallet_password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Close wallet while background syncing, then reopen + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') + sender_wallet.open_wallet('test1', password = 'test_password') + # It should reopen with spend key loaded and correctly scan all transfers + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Close wallet while syncing normally, then reopen + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') + sender_wallet.open_wallet('test1', password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Create background cache using custom password, then use it to sync, then reopen main wallet + for background_cache_password in ['background_password', '']: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = background_cache_password) + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + open_with_wrong_password(sender_wallet, 'test1.background', 'test_password') + sender_wallet.open_wallet('test1.background', password = background_cache_password) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) + sender_wallet.close_wallet() + sender_wallet.open_wallet('test1', password = 'test_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Check that main wallet keeps background cache encrypted with custom password in sync + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = 'background_password') + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + sender_wallet.close_wallet() + sender_wallet.open_wallet('test1.background', password = 'background_password') + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + # Try using wallet password as custom background password + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + same_password = False + try: sender_wallet.setup_background_sync(background_sync_type = custom_password, wallet_password = 'test_password', background_cache_password = 'test_password') + except: same_password = True + assert same_password + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + + # Turn off background sync + for background_sync_type in [reuse_password, custom_password]: + restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) + if background_sync_type == custom_password: + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert util_resources.file_exists('test1.background') + assert util_resources.file_exists('test1.background.keys') + else: + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.open_wallet('test1', password = 'test_password') + sender_wallet.setup_background_sync(background_sync_type = sender_wallet.background_sync_options.off, wallet_password = 'test_password') + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.close_wallet() + assert not util_resources.file_exists('test1.background') + assert not util_resources.file_exists('test1.background.keys') + sender_wallet.open_wallet('test1', password = 'test_password') + + # Sanity check against outgoing wallet restored at height 0 + sender_wallet.close_wallet() + sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = 0) + sender_wallet.refresh() + assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) + + print('Checking background sync on incoming wallet') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password) + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + transfers = receiver_wallet.get_transfers() + assert 'pending' not in transfers or len(transfers.pending) == 0 + assert 'pool' not in transfers or len (transfers.pool) == 0 + assert len(transfers['in']) == in_len + 1 + tx = [x for x in transfers['in'] if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert tx.amount == amount - fee + assert tx.fee == fee + incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image == '' and not x.spent]) == 1 + assert receiver_wallet.get_balance().balance == expected_receiver_balance + + # Restore and check background syncing incoming wallet + restore_wallet(receiver_wallet, seeds[1]) + receiver_wallet.setup_background_sync(background_sync_type = reuse_password) + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + if 'out' in transfers: + for i, out_tx in enumerate(transfers.out): + if 'destinations' in out_tx: + del transfers.out[i]['destinations'] # destinations are not expected after wallet restore + background_bal = receiver_wallet.get_balance().balance + assert background_bal >= expected_receiver_balance + background_transfers = receiver_wallet.get_transfers() + assert 'out' not in background_transfers or len(background_transfers.out) == 0 + assert 'in' in background_transfers and len(background_transfers['in']) > 0 + background_incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 + assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == txid]) == 1 + + # Stop background sync and check transfers update correctly + receiver_wallet.stop_background_sync(wallet_password = '', seed = seeds[1]) + diff_transfers(receiver_wallet.get_transfers(), transfers) + incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') + assert len(background_incoming_transfers) == len(incoming_transfers) + assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image != '' and not x.spent]) == 1 + assert receiver_wallet.get_balance().balance == expected_receiver_balance + + # Check a fresh incoming wallet with wallet files saved to disk and encrypted with password + restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + assert_correct_transfers(receiver_wallet, background_transfers, background_incoming_transfers, background_bal) + stop_with_wrong_inputs(receiver_wallet, 'wrong_password') + receiver_wallet.stop_background_sync(wallet_password = 'test_password') + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Close receiver's wallet while background sync is enabled then reopen + restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') + receiver_wallet.setup_background_sync(background_sync_type = reuse_password, wallet_password = 'test_password') + receiver_wallet.start_background_sync() + receiver_wallet.refresh() + diff_transfers(receiver_wallet.get_transfers(), background_transfers) + diff_incoming_transfers(receiver_wallet.incoming_transfers(transfer_type = 'all'), background_incoming_transfers) + assert receiver_wallet.get_balance().balance == background_bal + receiver_wallet.close_wallet() + receiver_wallet.open_wallet('test2', password = 'test_password') + # It should reopen with spend key loaded and correctly scan all transfers + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Sanity check against incoming wallet restored at height 0 + receiver_wallet.close_wallet() + receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = 0) + receiver_wallet.refresh() + assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) + + # Clean up + util_resources.remove_wallet_files('test1') + util_resources.remove_wallet_files('test2') + for i in range(2): + self.wallet[i].close_wallet() + self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) + + def check_background_sync_reorg_recovery(self): + daemon = Daemon() + + print('Testing background sync reorg recovery') + + # Disconnect daemon from peers + daemon.out_peers(0) + + # Background sync type options + sender_wallet = self.wallet[0] + reuse_password = sender_wallet.background_sync_options.reuse_password + custom_password = sender_wallet.background_sync_options.custom_password + + for background_sync_type in [reuse_password, custom_password]: + # Set up wallet saved to disk + sender_wallet.close_wallet() + util_resources.remove_wallet_files('test1') + sender_wallet.restore_deterministic_wallet(seed = seeds[0], filename = 'test1', password = '') + sender_wallet.auto_refresh(enable = False) + sender_wallet.refresh() + sender_starting_balance = sender_wallet.get_balance().balance + + # Send tx and mine a block + amount = 1000000000000 + assert sender_starting_balance > amount + dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount} + res = sender_wallet.transfer([dst]) + assert len(res.tx_hash) == 32*2 + txid = res.tx_hash + + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + + # Make sure the wallet can see the tx + sender_wallet.refresh() + transfers = sender_wallet.get_transfers() + assert 'pool' not in transfers or len (transfers.pool) == 0 + tx = [x for x in transfers.out if x.txid == txid] + assert len(tx) == 1 + tx = tx[0] + assert sender_wallet.get_balance().balance < (sender_starting_balance - amount) + + # Pop the block while background syncing + background_cache_password = None if background_sync_type == reuse_password else 'background_password' + sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = '', background_cache_password = background_cache_password) + sender_wallet.start_background_sync() + daemon.pop_blocks(1) + daemon.flush_txpool() + + daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) + + # Make sure the wallet can no longer see the tx + sender_wallet.refresh() + sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) + transfers = sender_wallet.get_transfers() + no_tx = [x for x in transfers.out if x.txid == txid] + assert len(no_tx) == 0 + assert sender_wallet.get_balance().balance == sender_starting_balance + + # Clean up + daemon.out_peers(12) + util_resources.remove_wallet_files('test1') + self.wallet[0].close_wallet() + self.wallet[0].restore_deterministic_wallet(seed = seeds[0]) + if __name__ == '__main__': TransferTest().run_test() diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py index c12506146..36a5fa32c 100755 --- a/tests/functional_tests/util_resources.py +++ b/tests/functional_tests/util_resources.py @@ -37,6 +37,8 @@ from __future__ import print_function import subprocess import psutil +import os +import errno def available_ram_gb(): ram_bytes = psutil.virtual_memory().available @@ -51,3 +53,26 @@ def get_time_pi_seconds(cores, app_dir='.'): miliseconds = int(decoded) return miliseconds / 1000.0 + +def remove_file(name): + WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] + assert WALLET_DIRECTORY != '' + try: + os.unlink(WALLET_DIRECTORY + '/' + name) + except OSError as e: + if e.errno != errno.ENOENT: + raise + +def get_file_path(name): + WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] + assert WALLET_DIRECTORY != '' + return WALLET_DIRECTORY + '/' + name + +def remove_wallet_files(name): + for suffix in ['', '.keys', '.background', '.background.keys', '.address.txt']: + remove_file(name + suffix) + +def file_exists(name): + WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] + assert WALLET_DIRECTORY != '' + return os.path.isfile(WALLET_DIRECTORY + '/' + name) diff --git a/tests/functional_tests/wallet.py b/tests/functional_tests/wallet.py index 3bb4459d6..f3b011f8b 100755 --- a/tests/functional_tests/wallet.py +++ b/tests/functional_tests/wallet.py @@ -34,8 +34,7 @@ from __future__ import print_function import sys -import os -import errno +import util_resources from framework.wallet import Wallet from framework.daemon import Daemon @@ -54,24 +53,6 @@ class WalletTest(): self.change_password() self.store() - def remove_file(self, name): - WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] - assert WALLET_DIRECTORY != '' - try: - os.unlink(WALLET_DIRECTORY + '/' + name) - except OSError as e: - if e.errno != errno.ENOENT: - raise - - def remove_wallet_files(self, name): - for suffix in ['', '.keys']: - self.remove_file(name + suffix) - - def file_exists(self, name): - WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] - assert WALLET_DIRECTORY != '' - return os.path.isfile(WALLET_DIRECTORY + '/' + name) - def reset(self): print('Resetting blockchain') daemon = Daemon() @@ -333,7 +314,7 @@ class WalletTest(): try: wallet.close_wallet() except: pass - self.remove_wallet_files('test1') + util_resources.remove_wallet_files('test1') 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' res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1') @@ -359,7 +340,7 @@ class WalletTest(): wallet.close_wallet() - self.remove_wallet_files('test1') + util_resources.remove_wallet_files('test1') def store(self): print('Testing store') @@ -369,22 +350,26 @@ class WalletTest(): try: wallet.close_wallet() except: pass - self.remove_wallet_files('test1') + util_resources.remove_wallet_files('test1') 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' res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1') assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' assert res.seed == seed - self.remove_file('test1') - assert self.file_exists('test1.keys') - assert not self.file_exists('test1') + util_resources.remove_file('test1') + assert util_resources.file_exists('test1.keys') + assert not util_resources.file_exists('test1') wallet.store() - assert self.file_exists('test1.keys') - assert self.file_exists('test1') + assert util_resources.file_exists('test1.keys') + assert util_resources.file_exists('test1') wallet.close_wallet() - self.remove_wallet_files('test1') + + wallet.open_wallet(filename = 'test1', password = '') + wallet.close_wallet() + + util_resources.remove_wallet_files('test1') if __name__ == '__main__': diff --git a/tests/hash/main.cpp b/tests/hash/main.cpp index 23455f7f3..c1ad80c7c 100644 --- a/tests/hash/main.cpp +++ b/tests/hash/main.cpp @@ -43,7 +43,7 @@ #include "warnings.h" #include "crypto/hash.h" #include "crypto/variant2_int_sqrt.h" -#include "randomx/src/blake2/blake2.h" +#include "crypto/blake2b.h" #include "../io.h" using namespace std; diff --git a/tests/performance_tests/check_tx_signature.h b/tests/performance_tests/check_tx_signature.h index de979856a..2ae4a2613 100644 --- a/tests/performance_tests/check_tx_signature.h +++ b/tests/performance_tests/check_tx_signature.h @@ -72,7 +72,7 @@ public: std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct::RCTConfig rct_config{range_proof_type, bp_version}; - if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, rct_config)) + if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, tx_key, additional_tx_keys, rct, rct_config)) return false; get_transaction_prefix_hash(m_tx, m_tx_prefix_hash); @@ -136,7 +136,7 @@ public: m_txes.resize(a_num_txes + (extra_outs > 0 ? 1 : 0)); for (size_t n = 0; n < a_num_txes; ++n) { - if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_txes[n], 0, tx_key, additional_tx_keys, true, {rct::RangeProofPaddedBulletproof, 2})) + if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_txes[n], tx_key, additional_tx_keys, true, {rct::RangeProofPaddedBulletproof, 2})) return false; } @@ -147,7 +147,7 @@ public: for (size_t n = 1; n < extra_outs; ++n) destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address, false)); - if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_txes.back(), 0, tx_key, additional_tx_keys, true, {rct::RangeProofMultiOutputBulletproof, 2})) + if (!construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_txes.back(), tx_key, additional_tx_keys, true, {rct::RangeProofMultiOutputBulletproof, 2})) return false; } diff --git a/tests/performance_tests/construct_tx.h b/tests/performance_tests/construct_tx.h index 4b3f061c4..14a603ca5 100644 --- a/tests/performance_tests/construct_tx.h +++ b/tests/performance_tests/construct_tx.h @@ -74,7 +74,7 @@ public: std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[this->m_miners[this->real_source_idx].get_keys().m_account_address.m_spend_public_key] = {0,0}; rct::RCTConfig rct_config{range_proof_type, bp_version}; - return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, 0, tx_key, additional_tx_keys, rct, rct_config); + return cryptonote::construct_tx_and_get_tx_key(this->m_miners[this->real_source_idx].get_keys(), subaddresses, this->m_sources, m_destinations, cryptonote::account_public_address{}, std::vector<uint8_t>(), m_tx, tx_key, additional_tx_keys, rct, rct_config); } private: diff --git a/tests/performance_tests/ge_frombytes_vartime.h b/tests/performance_tests/ge_frombytes_vartime.h index d543ed720..d3d717195 100644 --- a/tests/performance_tests/ge_frombytes_vartime.h +++ b/tests/performance_tests/ge_frombytes_vartime.h @@ -57,7 +57,7 @@ public: std::vector<tx_destination_entry> destinations; destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address, false)); - if (!construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, boost::none, std::vector<uint8_t>(), m_tx, 0)) + if (!construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, boost::none, std::vector<uint8_t>(), m_tx)) return false; const cryptonote::txin_to_key& txin = boost::get<cryptonote::txin_to_key>(m_tx.vin[0]); diff --git a/tests/performance_tests/ge_tobytes.h b/tests/performance_tests/ge_tobytes.h index a3c6121b1..7adf1dcea 100644 --- a/tests/performance_tests/ge_tobytes.h +++ b/tests/performance_tests/ge_tobytes.h @@ -57,7 +57,7 @@ public: std::vector<tx_destination_entry> destinations; destinations.push_back(tx_destination_entry(1, m_alice.get_keys().m_account_address, false)); - if (!construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, boost::none, std::vector<uint8_t>(), m_tx, 0)) + if (!construct_tx(this->m_miners[this->real_source_idx].get_keys(), this->m_sources, destinations, boost::none, std::vector<uint8_t>(), m_tx)) return false; const cryptonote::txin_to_key& txin = boost::get<cryptonote::txin_to_key>(m_tx.vin[0]); diff --git a/tests/performance_tests/performance_tests.h b/tests/performance_tests/performance_tests.h index 0f16ff8fc..68679a36c 100644 --- a/tests/performance_tests/performance_tests.h +++ b/tests/performance_tests/performance_tests.h @@ -249,7 +249,7 @@ bool run_test(const std::string &filter, ParamsT ¶ms_shuttle, const char* te double stddev = runner.get_stddev(); double npskew = runner.get_non_parametric_skew(); - std::vector<TimingsDatabase::instance> prev_instances = params.td.get(test_name); + const TimingsDatabase::instance* prev_instance = params.td.get_most_recent(test_name); params.td.add(test_name, {time(NULL), runner.get_size(), min, max, mean, med, stddev, npskew, quantiles}); std::cout << (params.verbose ? " time per call: " : " ") << time_per_call << " " << unit << "/call" << (params.verbose ? "\n" : ""); @@ -260,15 +260,14 @@ bool run_test(const std::string &filter, ParamsT ¶ms_shuttle, const char* te uint64_t p95s = quantiles[9] / scale; uint64_t stddevs = stddev / scale; std::string cmp; - if (!prev_instances.empty()) + if (prev_instance) { - const TimingsDatabase::instance &prev_instance = prev_instances.back(); - if (!runner.is_same_distribution(prev_instance.npoints, prev_instance.mean, prev_instance.stddev)) + if (!runner.is_same_distribution(prev_instance->npoints, prev_instance->mean, prev_instance->stddev)) { - double pc = fabs(100. * (prev_instance.mean - runner.get_mean()) / prev_instance.mean); - cmp = ", " + std::to_string(pc) + "% " + (mean > prev_instance.mean ? "slower" : "faster"); + double pc = fabs(100. * (prev_instance->mean - runner.get_mean()) / prev_instance->mean); + cmp = ", " + std::to_string(pc) + "% " + (mean > prev_instance->mean ? "slower" : "faster"); } - cmp += " -- " + std::to_string(prev_instance.mean); + cmp += " -- " + std::to_string(prev_instance->mean); } std::cout << " (min " << mins << " " << unit << ", 90th " << p95s << " " << unit << ", median " << meds << " " << unit << ", std dev " << stddevs << " " << unit << ")" << cmp; } diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index b21da95c0..0fcd10cef 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -2206,6 +2206,7 @@ bool gen_trezor_wallet_passphrase::generate(std::vector<test_event_entry>& event const auto wallet_path = (m_wallet_dir / "alice2").string(); const epee::wipeable_string& password = epee::wipeable_string("test-pass"); + wallet_accessor_test::set_password(m_wl_alice2.get(), password); m_wl_alice2->store_to(wallet_path, password); // Positive load diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt index 212f834d2..567ca8b00 100644 --- a/tests/unit_tests/CMakeLists.txt +++ b/tests/unit_tests/CMakeLists.txt @@ -51,7 +51,6 @@ set(unit_tests_sources epee_serialization.cpp epee_utils.cpp expect.cpp - fee.cpp json_serialization.cpp get_xtype_from_string.cpp hashchain.cpp diff --git a/tests/unit_tests/crypto.cpp b/tests/unit_tests/crypto.cpp index 88ecb5853..9f79ed60e 100644 --- a/tests/unit_tests/crypto.cpp +++ b/tests/unit_tests/crypto.cpp @@ -112,6 +112,7 @@ TEST(Crypto, tree_branch) { crypto::hash inputs[6]; crypto::hash branch[8]; + crypto::hash branch_1[8 + 1]; crypto::hash root, root2; size_t depth; uint32_t path, path2; @@ -296,12 +297,16 @@ TEST(Crypto, tree_branch) ASSERT_FALSE(crypto::is_branch_in_tree(inputs[5].data, root.data, (const char(*)[32])branch, depth, path)); ASSERT_FALSE(crypto::is_branch_in_tree(crypto::null_hash.data, root.data, (const char(*)[32])branch, depth, path)); + // a version with an extra (dummy) hash + memcpy(branch_1, branch, sizeof(branch)); + branch_1[depth] = crypto::null_hash; + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth - 1, path)); - ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth + 1, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch_1, depth + 1, path)); ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 1)); ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 2)); ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])branch, depth, path ^ 3)); - ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])(branch + 1), depth, path)); + ASSERT_FALSE(crypto::is_branch_in_tree(inputs[4].data, root.data, (const char(*)[32])(branch_1 + 1), depth, path)); // five, not found ASSERT_FALSE(crypto::tree_branch((const char(*)[32])inputs, 5, crypto::null_hash.data, (char(*)[32])branch, &depth, &path)); diff --git a/tests/unit_tests/fee.cpp b/tests/unit_tests/fee.cpp deleted file mode 100644 index 1df1a4f04..000000000 --- a/tests/unit_tests/fee.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2014-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. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#include "gtest/gtest.h" - -#include "cryptonote_core/blockchain.h" - -using namespace cryptonote; - -namespace -{ - static uint64_t clamp_fee(uint64_t fee) - { - static uint64_t mask = 0; - if (mask == 0) - { - mask = 1; - for (size_t n = PER_KB_FEE_QUANTIZATION_DECIMALS; n < CRYPTONOTE_DISPLAY_DECIMAL_POINT; ++n) - mask *= 10; - } - return (fee + mask - 1) / mask * mask; - } - - //-------------------------------------------------------------------------------------------------------------------- - class fee : public ::testing::Test - { - }; - - // try with blocks ~ 1GB. Passing 2 GB will break on 32 bit systems - - TEST_F(fee, 10xmr) - { - // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2, 3), clamp_fee(2000000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2, 3), clamp_fee(2000000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100, 3), clamp_fee(2000000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, 1, 3), 2000000000); - - // higher is inverse proportional - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2, 3), clamp_fee(2000000000 / 2)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10, 3), clamp_fee(2000000000 / 10)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000, 3), clamp_fee(2000000000 / 1000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(10000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 20000ull, 3), clamp_fee(2000000000 / 20000)); - } - - TEST_F(fee, 1xmr) - { - // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2, 3), clamp_fee(200000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2, 3), clamp_fee(200000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100, 3), clamp_fee(200000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, 1, 3), 200000000); - - // higher is inverse proportional - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2, 3), clamp_fee(200000000 / 2)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10, 3), clamp_fee(200000000 / 10)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000, 3), clamp_fee(200000000 / 1000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(1000000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 20000ull, 3), clamp_fee(200000000 / 20000)); - } - - TEST_F(fee, dot3xmr) - { - // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 and lower are clamped - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2, 3), clamp_fee(60000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 2, 3), clamp_fee(60000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 / 100, 3), clamp_fee(60000000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, 1, 3), 60000000); - - // higher is inverse proportional - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2, 3), clamp_fee(60000000 / 2)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10, 3), clamp_fee(60000000 / 10)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000, 3), clamp_fee(60000000 / 1000)); - ASSERT_EQ(Blockchain::get_dynamic_base_fee(300000000000, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 20000ull, 3), clamp_fee(60000000 / 20000)); - } - - static bool is_more_or_less(double x, double y) - { - return fabs(y - x) < 0.001; - } - - static const double MAX_MULTIPLIER = 166.f; - - TEST_F(fee, double_at_full) - { - static const uint64_t block_rewards[] = { - 20000000000000ull, // 20 monero - 13000000000000ull, - 1000000000000ull, - 600000000000ull, // .6 monero, minimum reward per block at 2min - 300000000000ull, // .3 monero, minimum reward per block at 1min - }; - static const uint64_t median_block_weights[] = { - CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2, - CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 2, - CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 10, - CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 1000, - // with clamping, the formula does not hold for such large blocks and small fees - // CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V2 * 20000ull - }; - - for (uint64_t block_reward: block_rewards) - { - for (uint64_t median_block_weight: median_block_weights) - { - ASSERT_TRUE(is_more_or_less(Blockchain::get_dynamic_base_fee(block_reward, median_block_weight, 3) * (median_block_weight / 1024.) * MAX_MULTIPLIER / (double)block_reward, 1.992 * 1000 / 1024)); - } - } - } -} diff --git a/tests/unit_tests/json_serialization.cpp b/tests/unit_tests/json_serialization.cpp index aa46b68dc..9525d23ea 100644 --- a/tests/unit_tests/json_serialization.cpp +++ b/tests/unit_tests/json_serialization.cpp @@ -78,7 +78,7 @@ namespace test std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses; subaddresses[from.m_account_address.m_spend_public_key] = {0,0}; - if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, 0, tx_key, extra_keys, rct, { bulletproof ? rct::RangeProofBulletproof : rct::RangeProofBorromean, bulletproof ? 2 : 0 })) + if (!cryptonote::construct_tx_and_get_tx_key(from, subaddresses, actual_sources, to, boost::none, {}, tx, tx_key, extra_keys, rct, { bulletproof ? rct::RangeProofBulletproof : rct::RangeProofBorromean, bulletproof ? 2 : 0 })) throw std::runtime_error{"transaction construction error"}; return tx; diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp index 15ece14a8..d686df87d 100644 --- a/tests/unit_tests/levin.cpp +++ b/tests/unit_tests/levin.cpp @@ -591,6 +591,7 @@ TEST_F(levin_notify, defaulted) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_FALSE(status.has_outgoing); } EXPECT_TRUE(notifier.send_txs({}, random_generator_(), cryptonote::relay_method::local)); @@ -611,6 +612,7 @@ TEST_F(levin_notify, fluff_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -658,6 +660,7 @@ TEST_F(levin_notify, stem_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -731,6 +734,7 @@ TEST_F(levin_notify, stem_no_outs_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_FALSE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -788,6 +792,7 @@ TEST_F(levin_notify, local_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -897,6 +902,7 @@ TEST_F(levin_notify, forward_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -970,6 +976,7 @@ TEST_F(levin_notify, block_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1000,6 +1007,7 @@ TEST_F(levin_notify, none_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1030,6 +1038,7 @@ TEST_F(levin_notify, fluff_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1077,6 +1086,7 @@ TEST_F(levin_notify, stem_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1145,6 +1155,7 @@ TEST_F(levin_notify, stem_no_outs_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_FALSE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1202,6 +1213,7 @@ TEST_F(levin_notify, local_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1303,6 +1315,7 @@ TEST_F(levin_notify, forward_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1371,6 +1384,7 @@ TEST_F(levin_notify, block_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1401,6 +1415,7 @@ TEST_F(levin_notify, none_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1431,6 +1446,7 @@ TEST_F(levin_notify, private_fluff_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1483,6 +1499,7 @@ TEST_F(levin_notify, private_stem_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1535,6 +1552,7 @@ TEST_F(levin_notify, private_local_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1587,6 +1605,7 @@ TEST_F(levin_notify, private_forward_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1639,6 +1658,7 @@ TEST_F(levin_notify, private_block_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1670,6 +1690,7 @@ TEST_F(levin_notify, private_none_without_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1700,6 +1721,7 @@ TEST_F(levin_notify, private_fluff_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1751,6 +1773,7 @@ TEST_F(levin_notify, private_stem_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1802,6 +1825,7 @@ TEST_F(levin_notify, private_local_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1853,6 +1877,7 @@ TEST_F(levin_notify, private_forward_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1904,6 +1929,7 @@ TEST_F(levin_notify, private_block_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1934,6 +1960,7 @@ TEST_F(levin_notify, private_none_with_padding) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -1966,6 +1993,7 @@ TEST_F(levin_notify, stem_mappings) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -2090,6 +2118,7 @@ TEST_F(levin_notify, fluff_multiple) const auto status = notifier.get_status(); EXPECT_FALSE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.new_out_connection(); io_service_.poll(); @@ -2206,12 +2235,14 @@ TEST_F(levin_notify, noise) const auto status = notifier.get_status(); EXPECT_TRUE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_FALSE(status.has_outgoing); } ASSERT_LT(0u, io_service_.poll()); { const auto status = notifier.get_status(); EXPECT_TRUE(status.has_noise); EXPECT_TRUE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.run_stems(); @@ -2298,12 +2329,14 @@ TEST_F(levin_notify, noise_stem) const auto status = notifier.get_status(); EXPECT_TRUE(status.has_noise); EXPECT_FALSE(status.connections_filled); + EXPECT_FALSE(status.has_outgoing); } ASSERT_LT(0u, io_service_.poll()); { const auto status = notifier.get_status(); EXPECT_TRUE(status.has_noise); EXPECT_TRUE(status.connections_filled); + EXPECT_TRUE(status.has_outgoing); } notifier.run_stems(); diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index a79b81e84..1b3a455c0 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -149,6 +149,7 @@ static void check_results(const std::vector<std::string> &intermediate_infos, std::unordered_set<crypto::secret_key> unique_privkeys; rct::key composite_pubkey = rct::identity(); + ASSERT_TRUE(wallets.size() > 0); wallets[0].decrypt_keys(""); crypto::public_key spend_pubkey = wallets[0].get_account().get_keys().m_account_address.m_spend_public_key; crypto::secret_key view_privkey = wallets[0].get_account().get_keys().m_view_secret_key; @@ -156,32 +157,48 @@ static void check_results(const std::vector<std::string> &intermediate_infos, EXPECT_TRUE(crypto::secret_key_to_public_key(view_privkey, view_pubkey)); wallets[0].encrypt_keys(""); - for (size_t i = 0; i < wallets.size(); ++i) + // at the end of multisig kex, all wallets should emit a post-kex message with the same two pubkeys + std::vector<crypto::public_key> post_kex_msg_pubkeys; + ASSERT_TRUE(intermediate_infos.size() == wallets.size()); + for (const std::string &intermediate_info : intermediate_infos) { - EXPECT_TRUE(!intermediate_infos[i].empty()); - bool ready; - uint32_t threshold, total; - EXPECT_TRUE(wallets[i].multisig(&ready, &threshold, &total)); - EXPECT_TRUE(ready); - EXPECT_TRUE(threshold == M); - EXPECT_TRUE(total == wallets.size()); + multisig::multisig_kex_msg post_kex_msg; + EXPECT_TRUE(!intermediate_info.empty()); + EXPECT_NO_THROW(post_kex_msg = intermediate_info); - wallets[i].decrypt_keys(""); + if (post_kex_msg_pubkeys.size() != 0) + EXPECT_TRUE(post_kex_msg_pubkeys == post_kex_msg.get_msg_pubkeys()); //assumes sorting is always the same + else + post_kex_msg_pubkeys = post_kex_msg.get_msg_pubkeys(); - if (i != 0) - { - // "equals" is transitive relation so we need only to compare first wallet's address to each others' addresses. - // no need to compare 0's address with itself. - EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == - wallets[i].get_account().get_public_address_str(cryptonote::TESTNET)); - - EXPECT_EQ(spend_pubkey, wallets[i].get_account().get_keys().m_account_address.m_spend_public_key); - EXPECT_EQ(view_privkey, wallets[i].get_account().get_keys().m_view_secret_key); - EXPECT_EQ(view_pubkey, wallets[i].get_account().get_keys().m_account_address.m_view_public_key); - } + EXPECT_TRUE(post_kex_msg_pubkeys.size() == 2); + } + + // the post-kex pubkeys should equal the account's public view and spend keys + EXPECT_TRUE(std::find(post_kex_msg_pubkeys.begin(), post_kex_msg_pubkeys.end(), spend_pubkey) != post_kex_msg_pubkeys.end()); + EXPECT_TRUE(std::find(post_kex_msg_pubkeys.begin(), post_kex_msg_pubkeys.end(), view_pubkey) != post_kex_msg_pubkeys.end()); + + // each wallet should have the same state (private view key, public spend key), and the public spend key should be + // reproducible from the private spend keys found in each account + for (tools::wallet2 &wallet : wallets) + { + wallet.decrypt_keys(""); + const multisig::multisig_account_status ms_status{wallet.get_multisig_status()}; + EXPECT_TRUE(ms_status.multisig_is_active); + EXPECT_TRUE(ms_status.kex_is_done); + EXPECT_TRUE(ms_status.is_ready); + EXPECT_TRUE(ms_status.threshold == M); + EXPECT_TRUE(ms_status.total == wallets.size()); + + EXPECT_TRUE(wallets[0].get_account().get_public_address_str(cryptonote::TESTNET) == + wallet.get_account().get_public_address_str(cryptonote::TESTNET)); + + EXPECT_EQ(spend_pubkey, wallet.get_account().get_keys().m_account_address.m_spend_public_key); + EXPECT_EQ(view_privkey, wallet.get_account().get_keys().m_view_secret_key); + EXPECT_EQ(view_pubkey, wallet.get_account().get_keys().m_account_address.m_view_public_key); // sum together unique multisig keys - for (const auto &privkey : wallets[i].get_account().get_keys().m_multisig_keys) + for (const auto &privkey : wallet.get_account().get_keys().m_multisig_keys) { EXPECT_NE(privkey, crypto::null_skey); @@ -189,17 +206,17 @@ static void check_results(const std::vector<std::string> &intermediate_infos, { unique_privkeys.insert(privkey); crypto::public_key pubkey; - crypto::secret_key_to_public_key(privkey, pubkey); + EXPECT_TRUE(crypto::secret_key_to_public_key(privkey, pubkey)); EXPECT_NE(privkey, crypto::null_skey); EXPECT_NE(pubkey, crypto::null_pkey); EXPECT_NE(pubkey, rct::rct2pk(rct::identity())); rct::addKeys(composite_pubkey, composite_pubkey, rct::pk2rct(pubkey)); } } - wallets[i].encrypt_keys(""); + wallet.encrypt_keys(""); } - // final key via sums should equal the wallets' public spend key + // final key via sum of privkeys should equal the wallets' public spend key wallets[0].decrypt_keys(""); EXPECT_EQ(wallets[0].get_account().get_keys().m_account_address.m_spend_public_key, rct::rct2pk(composite_pubkey)); wallets[0].encrypt_keys(""); @@ -226,10 +243,8 @@ static void make_wallets(const unsigned int M, const unsigned int N, const bool } // wallets should not be multisig yet - for (const auto &wallet: wallets) - { - ASSERT_FALSE(wallet.multisig()); - } + for (const auto& wallet: wallets) + ASSERT_FALSE(wallet.get_multisig_status().multisig_is_active); // make wallets multisig, get second round kex messages (if appropriate) std::vector<std::string> intermediate_infos(wallets.size()); @@ -242,16 +257,15 @@ static void make_wallets(const unsigned int M, const unsigned int N, const bool ++rounds_complete; // perform kex rounds until kex is complete - bool ready; - wallets[0].multisig(&ready); - while (!ready) + multisig::multisig_account_status ms_status{wallets[0].get_multisig_status()}; + while (!ms_status.is_ready) { if (force_update) intermediate_infos = exchange_round_force_update(wallets, intermediate_infos, rounds_complete + 1); else intermediate_infos = exchange_round(wallets, intermediate_infos); - wallets[0].multisig(&ready); + ms_status = wallets[0].get_multisig_status(); ++rounds_complete; } @@ -260,6 +274,104 @@ static void make_wallets(const unsigned int M, const unsigned int N, const bool check_results(intermediate_infos, wallets, M); } +static void make_wallets_boosting(std::vector<tools::wallet2>& wallets, unsigned int M) +{ + ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT); + ASSERT_TRUE(M <= wallets.size()); + std::uint32_t kex_rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M); + std::uint32_t rounds_required = multisig::multisig_setup_rounds_required(wallets.size(), M); + std::uint32_t rounds_complete{0}; + + // initialize wallets, get first round multisig kex msgs + std::vector<std::string> initial_infos(wallets.size()); + + for (size_t i = 0; i < wallets.size(); ++i) + { + make_wallet(i, wallets[i]); + + wallets[i].decrypt_keys(""); + initial_infos[i] = wallets[i].get_multisig_first_kex_msg(); + wallets[i].encrypt_keys(""); + } + + // wallets should not be multisig yet + for (const auto &wallet: wallets) + { + const multisig::multisig_account_status ms_status{wallet.get_multisig_status()}; + ASSERT_FALSE(ms_status.multisig_is_active); + } + + // get round 2 booster messages for wallet0 (if appropriate) + auto initial_infos_truncated = initial_infos; + initial_infos_truncated.erase(initial_infos_truncated.begin()); + + std::vector<std::string> wallet0_booster_infos; + wallet0_booster_infos.reserve(wallets.size() - 1); + + if (rounds_complete + 1 < kex_rounds_required) + { + for (size_t i = 1; i < wallets.size(); ++i) + { + wallet0_booster_infos.push_back( + wallets[i].get_multisig_key_exchange_booster("", initial_infos_truncated, M, wallets.size()) + ); + } + } + + // make wallets multisig + std::vector<std::string> intermediate_infos(wallets.size()); + + for (size_t i = 0; i < wallets.size(); ++i) + intermediate_infos[i] = wallets[i].make_multisig("", initial_infos, M); + + ++rounds_complete; + + // perform all kex rounds + // boost wallet0 each round, so wallet0 is always 1 round ahead + std::string wallet0_intermediate_info; + std::vector<std::string> new_infos(intermediate_infos.size()); + multisig::multisig_account_status ms_status{wallets[0].get_multisig_status()}; + while (!ms_status.is_ready) + { + // use booster infos to update wallet0 'early' + if (rounds_complete < kex_rounds_required) + new_infos[0] = wallets[0].exchange_multisig_keys("", wallet0_booster_infos); + else + { + // force update the post-kex round with wallet0's post-kex message since wallet0 is 'ahead' of the other wallets + wallet0_booster_infos = {wallets[0].exchange_multisig_keys("", {})}; + new_infos[0] = wallets[0].exchange_multisig_keys("", wallet0_booster_infos, true); + } + + // get wallet0 booster infos for next round + if (rounds_complete + 1 < kex_rounds_required) + { + // remove wallet0 info for this round (so boosters have incomplete kex message set) + auto intermediate_infos_truncated = intermediate_infos; + intermediate_infos_truncated.erase(intermediate_infos_truncated.begin()); + + // obtain booster messages from all other wallets + for (size_t i = 1; i < wallets.size(); ++i) + { + wallet0_booster_infos[i-1] = + wallets[i].get_multisig_key_exchange_booster("", intermediate_infos_truncated, M, wallets.size()); + } + } + + // update other wallets + for (size_t i = 1; i < wallets.size(); ++i) + new_infos[i] = wallets[i].exchange_multisig_keys("", intermediate_infos); + + intermediate_infos = new_infos; + ++rounds_complete; + ms_status = wallets[0].get_multisig_status(); + } + + EXPECT_EQ(rounds_required, rounds_complete); + + check_results(intermediate_infos, wallets, M); +} + TEST(multisig, make_1_2) { make_wallets(1, 2, false); @@ -296,6 +408,12 @@ TEST(multisig, make_2_4) make_wallets(2, 4, true); } +TEST(multisig, make_2_4_boosting) +{ + std::vector<tools::wallet2> wallets(4); + make_wallets_boosting(wallets, 2); +} + TEST(multisig, multisig_kex_msg) { using namespace multisig; diff --git a/tests/unit_tests/scaling_2021.cpp b/tests/unit_tests/scaling_2021.cpp index d90f0f9e6..59e036d10 100644 --- a/tests/unit_tests/scaling_2021.cpp +++ b/tests/unit_tests/scaling_2021.cpp @@ -30,68 +30,26 @@ // - https://github.com/ArticMine/Monero-Documents/blob/master/MoneroScaling2021.pdf // - https://github.com/monero-project/research-lab/issues/70 -#define IN_UNIT_TESTS - #include "gtest/gtest.h" #include "cryptonote_core/blockchain.h" -#include "cryptonote_core/tx_pool.h" -#include "cryptonote_core/cryptonote_core.h" -#include "blockchain_db/testdb.h" - -namespace -{ - -class TestDB: public cryptonote::BaseTestDB -{ -public: - TestDB() { m_open = true; } -}; - -} - -#define PREFIX_WINDOW(hf_version,window) \ - struct get_test_options { \ - const std::pair<uint8_t, uint64_t> hard_forks[3]; \ - const cryptonote::test_options test_options = { \ - hard_forks, \ - window, \ - }; \ - 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::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) - -#define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW) TEST(fee_2021_scaling, relay_fee_cases_from_pdf) { - PREFIX_WINDOW(HF_VERSION_2021_SCALING, CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE); - - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 300000, HF_VERSION_2021_SCALING-1), 8000); - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 300000, HF_VERSION_2021_SCALING), 38000); - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1425000, HF_VERSION_2021_SCALING-1), 1684 /*1680*/); - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1425000, HF_VERSION_2021_SCALING), 1684 /*1680*/); - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1500000, HF_VERSION_2021_SCALING-1), 1600); - ASSERT_EQ(bc->get_dynamic_base_fee(1200000000000, 1500000, HF_VERSION_2021_SCALING), 1520); - - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 300000, HF_VERSION_2021_SCALING-1), 4000); - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 300000, HF_VERSION_2021_SCALING), 19000); - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1425000, HF_VERSION_2021_SCALING-1), 842 /*840*/); - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1425000, HF_VERSION_2021_SCALING), 842 /*840*/); - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1500000, HF_VERSION_2021_SCALING-1), 800); - ASSERT_EQ(bc->get_dynamic_base_fee(600000000000, 1500000, HF_VERSION_2021_SCALING), 760); + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(1200000000000, 300000), 38000); + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(1200000000000, 1425000), 1684 /*1680*/); + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(1200000000000, 1500000), 1520); + + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(600000000000, 300000), 19000); + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(600000000000, 1425000), 842 /*840*/); + ASSERT_EQ(cryptonote::Blockchain::get_dynamic_base_fee(600000000000, 1500000), 760); } TEST(fee_2021_scaling, wallet_fee_cases_from_pdf) { - PREFIX_WINDOW(HF_VERSION_2021_SCALING, CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE); std::vector<uint64_t> fees; fees.clear(); - bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 300000, 300000, fees); + cryptonote::Blockchain::get_dynamic_base_fee_estimate_2021_scaling(600000000000, 300000, 300000, fees); ASSERT_EQ(fees.size(), 4); ASSERT_EQ(fees[0], 20000); ASSERT_EQ(fees[1], 80000); @@ -99,7 +57,7 @@ TEST(fee_2021_scaling, wallet_fee_cases_from_pdf) ASSERT_EQ(fees[3], 4000000); fees.clear(); - bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 15000000, 300000, fees); + cryptonote::Blockchain::get_dynamic_base_fee_estimate_2021_scaling(600000000000, 15000000, 300000, fees); ASSERT_EQ(fees.size(), 4); ASSERT_EQ(fees[0], 20000); ASSERT_EQ(fees[1], 80000); @@ -107,7 +65,7 @@ TEST(fee_2021_scaling, wallet_fee_cases_from_pdf) ASSERT_EQ(fees[3], 1300000); fees.clear(); - bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 1425000, 1425000, fees); + cryptonote::Blockchain::get_dynamic_base_fee_estimate_2021_scaling(600000000000, 1425000, 1425000, fees); ASSERT_EQ(fees.size(), 4); ASSERT_EQ(fees[0], 890); ASSERT_EQ(fees[1], 3600); @@ -115,7 +73,7 @@ TEST(fee_2021_scaling, wallet_fee_cases_from_pdf) ASSERT_EQ(fees[3], 850000 /* 842000 */); fees.clear(); - bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 1500000, 1500000, fees); + cryptonote::Blockchain::get_dynamic_base_fee_estimate_2021_scaling(600000000000, 1500000, 1500000, fees); ASSERT_EQ(fees.size(), 4); ASSERT_EQ(fees[0], 800); ASSERT_EQ(fees[1], 3200); @@ -123,7 +81,7 @@ TEST(fee_2021_scaling, wallet_fee_cases_from_pdf) ASSERT_EQ(fees[3], 800000); fees.clear(); - bc->get_dynamic_base_fee_estimate_2021_scaling(10, 600000000000, 75000000, 1500000, fees); + cryptonote::Blockchain::get_dynamic_base_fee_estimate_2021_scaling(600000000000, 75000000, 1500000, fees); ASSERT_EQ(fees.size(), 4); ASSERT_EQ(fees[0], 800); ASSERT_EQ(fees[1], 3200); diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index ab81acefc..2af8012ed 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -51,11 +51,21 @@ using namespace std; using namespace crypto; +static_assert(!std::is_trivially_copyable<std::vector<unsigned char>>(), + "should fail to compile when applying blob serializer"); +static_assert(!std::is_trivially_copyable<std::string>(), + "should fail to compile when applying blob serializer"); + struct Struct { int32_t a; int32_t b; char blob[8]; + + bool operator==(const Struct &other) const + { + return a == other.a && b == other.b && 0 == memcmp(blob, other.blob, sizeof(blob)); + } }; template <class Archive> @@ -1207,3 +1217,90 @@ TEST(Serialization, adl_free_function) const std::string expected = "{\"custom_fieldname\": " + std::to_string(msg.size()) + '"' + epee::string_tools::buff_to_hex_nodelimer(msg) + "\"}"; EXPECT_EQ(expected, ss.str()); } + +using Tuple3 = std::tuple<uint16_t, std::string, uint64_t>; +using Tuple4 = std::tuple<int32_t, std::string, uint64_t, Struct>; + +TEST(Serialization, tuple_3_4_backwards_compatibility) +{ + std::string serialized; + + //////////////////////////////////////// + + Tuple3 t3{1876, "Hullabaloo", 1963}; + EXPECT_TRUE(::serialization::dump_binary(t3, serialized)); + + EXPECT_EQ("0354070a48756c6c6162616c6f6fab0f", + epee::string_tools::buff_to_hex_nodelimer(serialized)); + + Tuple3 t3_recovered; + EXPECT_TRUE(::serialization::parse_binary(serialized, t3_recovered)); + EXPECT_EQ(t3, t3_recovered); + + ///////////////////////////////////////// + + Tuple4 t4{1999, "Caneck Caneck", (uint64_t)-1, {20229, 242, {1, 1, 2, 3, 5, 8, 13, 21}}}; + EXPECT_TRUE(::serialization::dump_binary(t4, serialized)); + + EXPECT_EQ("04cf0700000d43616e65636b2043616e65636bffffffffffffffffff01054f0000f20000000101020305080d15", + epee::string_tools::buff_to_hex_nodelimer(serialized)); + + Tuple4 t4_recovered; + EXPECT_TRUE(::serialization::parse_binary(serialized, t4_recovered)); + EXPECT_EQ(t4, t4_recovered); +} + +struct Tupler +{ + std::tuple<> t0; + std::tuple<int8_t> t1; + std::tuple<uint8_t, int16_t> t2; + Tuple3 t3_backcompat; + Tuple3 t3_compact; + Tuple4 t4_backcompat; + Tuple4 t4_compact; + std::tuple<uint32_t, std::string, bool, int64_t, Struct> t5; + + BEGIN_SERIALIZE_OBJECT() + FIELD(t0) + FIELD(t1) + FIELD(t2) + FIELD(t3_backcompat) + TUPLE_COMPACT_FIELD(t3_compact) + FIELD(t4_backcompat) + TUPLE_COMPACT_FIELD(t4_compact) + TUPLE_COMPACT_FIELD(t5) + END_SERIALIZE() +}; + +bool operator==(const Tupler &a, const Tupler &b) +{ + return a.t0 == b.t0 && a.t1 == b.t1 && a.t2 == b.t2 && a.t3_backcompat == b.t3_backcompat && + a.t3_compact == b.t3_compact && a.t4_backcompat == b.t4_backcompat && a.t5 == b.t5; +} + +TEST(Serialization, tuple_many_tuples) +{ + Tupler tupler{ + {}, + {69}, + {42, 420}, + {1876, "Hullabaloo", 1963}, + {1876, "Hullabaloo", 1963}, + {1999, "Caneck Caneck", (uint64_t)-1, {20229, 242, {1, 1, 2, 3, 5, 8, 13, 21}}}, + {1999, "Caneck Caneck", (uint64_t)-1, {20229, 242, {1, 1, 2, 3, 5, 8, 13, 21}}}, + {72982, "He is now rising from affluence to poverty.", false, 256, + { + 13, 37, { 1, 1, 1, 2, 3, 7, 11, 26 } + } + } + }; + + std::string serialized; + EXPECT_TRUE(::serialization::dump_binary(tupler, serialized)); + + Tupler tupler_recovered; + EXPECT_TRUE(::serialization::parse_binary(serialized, tupler_recovered)); + + EXPECT_EQ(tupler, tupler_recovered); +} diff --git a/tests/unit_tests/wallet_storage.cpp b/tests/unit_tests/wallet_storage.cpp index ff01e452c..c38839a1c 100644 --- a/tests/unit_tests/wallet_storage.cpp +++ b/tests/unit_tests/wallet_storage.cpp @@ -33,6 +33,7 @@ #include "file_io_utils.h" #include "wallet/wallet2.h" +#include "common/util.h" using namespace boost::filesystem; using namespace epee::file_io_utils; @@ -52,8 +53,8 @@ TEST(wallet_storage, store_to_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); + tools::copy_file(source_wallet_file.string(), interm_wallet_file.string()); + tools::copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys"); ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); @@ -143,8 +144,8 @@ TEST(wallet_storage, change_password_same_file) 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); + tools::copy_file(source_wallet_file.string(), interm_wallet_file.string()); + tools::copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys"); ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); @@ -182,8 +183,8 @@ TEST(wallet_storage, change_password_different_file) 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); + tools::copy_file(source_wallet_file.string(), interm_wallet_file.string()); + tools::copy_file(source_wallet_file.string() + ".keys", interm_wallet_file.string() + ".keys"); ASSERT_TRUE(is_file_exist(interm_wallet_file.string())); ASSERT_TRUE(is_file_exist(interm_wallet_file.string() + ".keys")); diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp index f1bb90a41..9eecd509a 100644 --- a/tests/unit_tests/wipeable_string.cpp +++ b/tests/unit_tests/wipeable_string.cpp @@ -211,3 +211,15 @@ TEST(wipeable_string, to_hex) ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"", 0)) == epee::wipeable_string("")); ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"abc", 3)) == epee::wipeable_string("616263")); } + +TEST(wipeable_string, to_string) +{ + // Converting a wipeable_string to a string defeats the purpose of wipeable_string, + // but nice to know this works + std::string str; + { + epee::wipeable_string wipeable_str("foo"); + str = std::string(wipeable_str.data(), wipeable_str.size()); + } + ASSERT_TRUE(str == std::string("foo")); +} diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py index c7831d1ee..4ac24332d 100644 --- a/utils/python-rpc/framework/daemon.py +++ b/utils/python-rpc/framework/daemon.py @@ -33,11 +33,12 @@ from .rpc import JSONRPC class Daemon(object): - def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0, restricted_rpc = False): + def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0, restricted_rpc = False, username=None, password=None): base = 18480 if restricted_rpc else 18180 self.host = host self.port = port - self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else base+idx)) + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else base+idx), + username, password) def getblocktemplate(self, address, prev_block = "", client = ""): getblocktemplate = { diff --git a/utils/python-rpc/framework/rpc.py b/utils/python-rpc/framework/rpc.py index 6d9a4b27e..567bdd78d 100644 --- a/utils/python-rpc/framework/rpc.py +++ b/utils/python-rpc/framework/rpc.py @@ -28,6 +28,7 @@ # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import requests +from requests.auth import HTTPDigestAuth import json class Response(dict): @@ -60,14 +61,17 @@ class Response(dict): return True class JSONRPC(object): - def __init__(self, url): + def __init__(self, url, username=None, password=None): self.url = url + self.username = username + self.password = password def send_request(self, path, inputs, result_field = None): res = requests.post( self.url + path, data=json.dumps(inputs), - headers={'content-type': 'application/json'}) + headers={'content-type': 'application/json'}, + auth=HTTPDigestAuth(self.username, self.password) if self.username is not None else None) res = res.json() assert 'error' not in res, res diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 18af4edc4..8e67b0107 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -33,10 +33,11 @@ from .rpc import JSONRPC class Wallet(object): - def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0): + def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0, username=None, password=None): self.host = host self.port = port - self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18090+idx)) + self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, + port=port if port else 18090+idx), username, password) def transfer(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False, subtract_fee_from_outputs = []): transfer = { @@ -539,6 +540,20 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(exchange_multisig_keys) + def get_multisig_key_exchange_booster(self, multisig_info, threshold, num_signers, password = ''): + exchange_multisig_keys = { + 'method': 'get_multisig_key_exchange_booster', + 'params' : { + 'multisig_info': multisig_info, + 'threshold': threshold, + 'num_signers': num_signers, + 'password': password, + }, + 'jsonrpc': '2.0', + 'id': '0' + } + return self.rpc.send_json_rpc_request(get_multisig_key_exchange_booster) + def export_multisig_info(self): export_multisig_info = { 'method': 'export_multisig_info', @@ -1138,3 +1153,45 @@ class Wallet(object): 'id': '0' } return self.rpc.send_json_rpc_request(frozen) + + class BackgroundSyncOptions(object): + def __init__(self): + self.off = 'off' + self.reuse_password = 'reuse-wallet-password' + self.custom_password = 'custom-background-password' + background_sync_options = BackgroundSyncOptions() + + def setup_background_sync(self, background_sync_type = background_sync_options.off, wallet_password = '', background_cache_password = ''): + setup_background_sync = { + 'method': 'setup_background_sync', + 'jsonrpc': '2.0', + 'params' : { + 'background_sync_type': background_sync_type, + 'wallet_password': wallet_password, + 'background_cache_password': background_cache_password, + }, + 'id': '0' + } + return self.rpc.send_json_rpc_request(setup_background_sync) + + def start_background_sync(self): + start_background_sync = { + 'method': 'start_background_sync', + 'jsonrpc': '2.0', + 'params' : {}, + 'id': '0' + } + return self.rpc.send_json_rpc_request(start_background_sync) + + def stop_background_sync(self, wallet_password = '', seed = '', seed_offset = ''): + stop_background_sync = { + 'method': 'stop_background_sync', + 'jsonrpc': '2.0', + 'params' : { + 'wallet_password': wallet_password, + 'seed': seed, + 'seed_offset': seed_offset, + }, + 'id': '0' + } + return self.rpc.send_json_rpc_request(stop_background_sync) |