diff options
102 files changed, 1862 insertions, 1296 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 69040d0af..3b327374d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: key: ccache-${{ runner.os }}-build-${{ github.sha }} restore-keys: ccache-${{ runner.os }}-build- - name: install dependencies - run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc ldns expat libunwind-headers protobuf ccache + run: HOMEBREW_NO_AUTO_UPDATE=1 brew install boost hidapi openssl zmq libpgm miniupnpc expat libunwind-headers protobuf ccache - name: build run: | ${{env.CCACHE_SETTINGS}} @@ -151,7 +151,7 @@ jobs: - name: install monero dependencies run: ${{env.APT_INSTALL_LINUX}} - name: install Python dependencies - run: pip install requests psutil monotonic + run: pip install requests psutil monotonic zmq - name: tests env: CTEST_OUTPUT_ON_FAILURE: ON diff --git a/.github/workflows/depends.yml b/.github/workflows/depends.yml index 32df7dc42..2c809d0a5 100644 --- a/.github/workflows/depends.yml +++ b/.github/workflows/depends.yml @@ -36,13 +36,13 @@ jobs: packages: "python3 gperf g++-aarch64-linux-gnu" - name: "i686 Win" host: "i686-w64-mingw32" - packages: "python3 g++-mingw-w64-i686 qttools5-dev-tools" + packages: "python3 g++-mingw-w64-i686" - name: "i686 Linux" host: "i686-pc-linux-gnu" packages: "gperf cmake g++-multilib python3-zmq" - name: "Win64" host: "x86_64-w64-mingw32" - packages: "cmake python3 g++-mingw-w64-x86-64 qttools5-dev-tools" + packages: "cmake python3 g++-mingw-w64-x86-64" - name: "x86_64 Linux" host: "x86_64-unknown-linux-gnu" packages: "gperf cmake python3-zmq libdbus-1-dev libharfbuzz-dev" @@ -55,6 +55,9 @@ jobs: - name: "x86_64 Freebsd" host: "x86_64-unknown-freebsd" packages: "clang-8 gperf cmake python3-zmq libdbus-1-dev libharfbuzz-dev" + - name: "ARMv8 Android" + host: "aarch64-linux-android" + packages: "gperf cmake python3" name: ${{ matrix.toolchain.name }} steps: - uses: actions/checkout@v1 @@ -754,7 +754,7 @@ WARN_LOGFILE = # spaces. # Note: If this tag is empty the current directory is searched. -INPUT = . +INPUT = contrib/epee external/easylogging++ src # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -805,7 +805,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = */build/* */contrib/depends/* +EXCLUDE_PATTERNS = */src/crypto/crypto_ops_builder/ref10* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -117,10 +117,11 @@ See [LICENSE](LICENSE). If you want to help out, see [CONTRIBUTING](docs/CONTRIBUTING.md) for a set of guidelines. -## Scheduled software upgrades +## Scheduled software/network upgrades -Monero uses a fixed-schedule software upgrade (hard fork) mechanism to implement new features. This means that users of Monero (end users and service providers) should run current versions and upgrade their software on a regular schedule. Software upgrades occur during the months of April and October. The required software for these upgrades will be available prior to the scheduled date. Please check the repository prior to this date for the proper Monero software version. Below is the historical schedule and the projected schedule for the next upgrade. -Dates are provided in the format YYYY-MM-DD. +Monero uses a scheduled software/network upgrade (hard fork) mechanism to implement new features into the Monero software and network. This means that users of Monero (end users and service providers) should run current versions and upgrade their software when new releases are available. Software upgrades occur when new features are developed and implemented in the codebase. Network upgrades occur in tandem with software upgrades that modify the consensus rules of the Monero network. The required software for network upgrades will be available prior to the scheduled network upgrade date. Please check the repository prior to this date for the proper Monero software version. Below is the historical schedule and the projected schedule for the next upgrade. + +Dates are provided in the format YYYY-MM-DD. The "Minimum" is the software version that follows the new consensus rules. The "Recommended" version may include bug fixes and other new features that do not affect the consensus rules. | Software upgrade block height | Date | Fork version | Minimum Monero version | Recommended Monero version | Details | @@ -173,12 +174,11 @@ library archives (`.a`). | libzmq | 4.2.0 | NO | `libzmq3-dev` | `zeromq` | `zeromq-devel` | `zeromq-devel` | NO | ZeroMQ library | | OpenPGM | ? | NO | `libpgm-dev` | `libpgm` | | `openpgm-devel` | NO | For ZeroMQ | | libnorm[2] | ? | NO | `libnorm-dev` | | | | YES | For ZeroMQ | -| libunbound | 1.4.16 | YES | `libunbound-dev` | `unbound` | `unbound-devel` | `unbound-devel` | NO | DNS resolver | +| libunbound | 1.4.16 | NO | `libunbound-dev` | `unbound` | `unbound-devel` | `unbound-devel` | NO | DNS resolver | | libsodium | ? | NO | `libsodium-dev` | `libsodium` | `libsodium-devel` | `libsodium-devel` | NO | cryptography | | libunwind | any | NO | `libunwind8-dev` | `libunwind` | `libunwind-devel` | `libunwind-devel` | YES | Stack traces | | liblzma | any | NO | `liblzma-dev` | `xz` | `liblzma-devel` | `xz-devel` | YES | For libunwind | | libreadline | 6.3.0 | NO | `libreadline6-dev` | `readline` | `readline-devel` | `readline-devel` | YES | Input editing | -| ldns | 1.6.17 | NO | `libldns-dev` | `ldns` | `libldns-devel` | `ldns-devel` | YES | SSL toolkit | | expat | 1.1 | NO | `libexpat1-dev` | `expat` | `expat-devel` | `expat-devel` | YES | XML parsing | | GTest | 1.5 | YES | `libgtest-dev`[1] | `gtest` | `gtest-devel` | `gtest-devel` | YES | Test suite | | ccache | any | NO | `ccache` | `ccache` | `ccache` | `ccache` | YES | Compil. cache | @@ -205,23 +205,23 @@ then: Install all dependencies at once on Debian/Ubuntu: ``` -sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libldns-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache doxygen graphviz +sudo apt update && sudo apt install build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache doxygen graphviz ``` Install all dependencies at once on Arch: ``` -sudo pacman -Syu --needed base-devel cmake boost openssl zeromq libpgm unbound libsodium libunwind xz readline ldns expat gtest python3 ccache doxygen graphviz qt5-tools hidapi libusb protobuf systemd +sudo pacman -Syu --needed base-devel cmake boost openssl zeromq libpgm unbound libsodium libunwind xz readline expat gtest python3 ccache doxygen graphviz qt5-tools hidapi libusb protobuf systemd ``` Install all dependencies at once on Fedora: ``` -sudo dnf install gcc gcc-c++ cmake pkgconf boost-devel openssl-devel zeromq-devel openpgm-devel unbound-devel libsodium-devel libunwind-devel xz-devel readline-devel ldns-devel expat-devel gtest-devel ccache doxygen graphviz qt5-linguist hidapi-devel libusbx-devel protobuf-devel protobuf-compiler systemd-devel +sudo dnf install gcc gcc-c++ cmake pkgconf boost-devel openssl-devel zeromq-devel openpgm-devel unbound-devel libsodium-devel libunwind-devel xz-devel readline-devel expat-devel gtest-devel ccache doxygen graphviz qt5-linguist hidapi-devel libusbx-devel protobuf-devel protobuf-compiler systemd-devel ``` Install all dependencies at once on openSUSE: ``` -sudo zypper ref && sudo zypper in cppzmq-devel ldns-devel libboost_chrono-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_program_options-devel libboost_regex-devel libboost_serialization-devel libboost_system-devel libboost_thread-devel libexpat-devel libminiupnpc-devel libsodium-devel libunwind-devel unbound-devel cmake doxygen ccache fdupes gcc-c++ libevent-devel libopenssl-devel pkgconf-pkg-config readline-devel xz-devel libqt5-qttools-devel patterns-devel-C-C++-devel_C_C++ +sudo zypper ref && sudo zypper in cppzmq-devel libboost_chrono-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_program_options-devel libboost_regex-devel libboost_serialization-devel libboost_system-devel libboost_thread-devel libexpat-devel libminiupnpc-devel libsodium-devel libunwind-devel unbound-devel cmake doxygen ccache fdupes gcc-c++ libevent-devel libopenssl-devel pkgconf-pkg-config readline-devel xz-devel libqt5-qttools-devel patterns-devel-C-C++-devel_C_C++ ``` Install all dependencies at once on macOS with the provided Brewfile: diff --git a/contrib/brew/Brewfile b/contrib/brew/Brewfile index 36b073b25..c74e7b2a2 100644 --- a/contrib/brew/Brewfile +++ b/contrib/brew/Brewfile @@ -25,7 +25,6 @@ brew "unbound" brew "libsodium" brew "miniupnpc" brew "readline" -brew "ldns" brew "expat" brew "ccache" brew "doxygen" diff --git a/contrib/depends/Makefile b/contrib/depends/Makefile index 3df1d677e..479b2e1c9 100644 --- a/contrib/depends/Makefile +++ b/contrib/depends/Makefile @@ -110,8 +110,7 @@ $(host_arch)_$(host_os)_id_string+=$(shell $(host_CXX) --version 2>/dev/null) $(host_arch)_$(host_os)_id_string+=$(shell $(host_RANLIB) --version 2>/dev/null) $(host_arch)_$(host_os)_id_string+=$(shell $(host_STRIP) --version 2>/dev/null) -qt_packages_$(NO_QT) = $(qt_packages) -packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) $(qt_packages_) +packages += $($(host_arch)_$(host_os)_packages) $($(host_os)_packages) native_packages += $($(host_arch)_$(host_os)_native_packages) $($(host_os)_native_packages) all_packages = $(packages) $(native_packages) diff --git a/contrib/depends/README.md b/contrib/depends/README.md index 1aa5b276f..2f1d8424a 100644 --- a/contrib/depends/README.md +++ b/contrib/depends/README.md @@ -1,6 +1,6 @@ ### Usage -To build dependencies for the current arch+OS: +To build dependencies for the current arch+OS, from this working directory: ```bash make @@ -20,10 +20,12 @@ make HOST=x86_64-w64-mingw32 -j4 A toolchain will be generated that's suitable for plugging into Monero's cmake. In the above example, a dir named x86_64-w64-mingw32 will be -created. To use it for Monero: +created. To use it for Monero, from the top of the Monero source tree: ```bash -cmake -DCMAKE_TOOLCHAIN=`pwd`/contrib/depends/x86_64-w64-mingw32 +mkdir build +cd build +cmake -DCMAKE_TOOLCHAIN_FILE=$PWD/../contrib/depends/x86_64-w64-mingw32/share/toolchain.cmake .. ``` Common `host-platform-triplets` for cross compilation are: diff --git a/contrib/depends/config.site.in b/contrib/depends/config.site.in index dd91bcb2a..11dab8981 100644 --- a/contrib/depends/config.site.in +++ b/contrib/depends/config.site.in @@ -7,27 +7,12 @@ ac_tool_prefix=${host_alias}- if test -z $with_boost; then with_boost=$depends_prefix fi -if test -z $with_qt_plugindir; then - with_qt_plugindir=$depends_prefix/plugins -fi -if test -z $with_qt_translationdir; then - with_qt_translationdir=$depends_prefix/translations -fi if test x@host_os@ = xdarwin; then BREW=no PORT=no fi -if test x@host_os@ = xmingw32; then - if test -z $with_qt_incdir; then - with_qt_incdir=$depends_prefix/include - fi - if test -z $with_qt_libdir; then - with_qt_libdir=$depends_prefix/lib - fi -fi - PATH=$depends_prefix/native/bin:$PATH PKG_CONFIG="`which pkg-config` --static" diff --git a/contrib/depends/packages/graphviz.mk b/contrib/depends/packages/graphviz.mk deleted file mode 100644 index 1c4bc1b71..000000000 --- a/contrib/depends/packages/graphviz.mk +++ /dev/null @@ -1,30 +0,0 @@ -package=graphviz -$(package)_version=2.40.1 -$(package)_download_path=www.graphviz.org/pub/graphviz/stable/SOURCES/ -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=ca5218fade0204d59947126c38439f432853543b0818d9d728c589dfe7f3a421 - -define $(package)_preprocess_cmds - ./autogen.sh -endef - -define $(package)_set_vars - $(package)_config_opts=--disable-shared --enable-multibye --without-purify --without-curses - $(package)_config_opts_release=--disable-debug-mode - $(package)_config_opts_linux=--with-pic -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef - -define $(package)_postprocess_cmds -endef diff --git a/contrib/depends/packages/ldns.mk b/contrib/depends/packages/ldns.mk deleted file mode 100644 index 90c63e821..000000000 --- a/contrib/depends/packages/ldns.mk +++ /dev/null @@ -1,34 +0,0 @@ -package=ldns -$(package)_version=1.7.1 -$(package)_download_path=https://www.nlnetlabs.nl/downloads/$(package)/ -$(package)_file_name=$(package)-$($(package)_version).tar.gz -$(package)_sha256_hash=8ac84c16bdca60e710eea75782356f3ac3b55680d40e1530d7cea474ac208229 -$(package)_dependencies=openssl - -define $(package)_set_vars - $(package)_config_opts=--disable-shared --enable-static --with-drill - $(package)_config_opts+=--with-ssl=$(host_prefix) - $(package)_config_opts_release=--disable-debug-mode - $(package)_config_opts_linux=--with-pic -endef - -define $(package)_preprocess_cmds - cp -f $(BASEDIR)/config.guess $(BASEDIR)/config.sub . -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install-h install-lib -endef - -define $(package)_postprocess_cmds - rm lib/*.la -endef - diff --git a/contrib/depends/packages/libICE.mk b/contrib/depends/packages/libICE.mk deleted file mode 100644 index a897d9aed..000000000 --- a/contrib/depends/packages/libICE.mk +++ /dev/null @@ -1,23 +0,0 @@ -package=libICE -$(package)_version=1.0.9 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/lib/ -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=8f7032f2c1c64352b5423f6b48a8ebdc339cc63064af34d66a6c9aa79759e202 -$(package)_dependencies=xtrans xproto - -define $(package)_set_vars - $(package)_config_opts=--disable-static --disable-docs --disable-specs --without-xsltproc - $(package)_config_opts_linux=--with-pic -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/contrib/depends/packages/libSM.mk b/contrib/depends/packages/libSM.mk deleted file mode 100644 index 83fcd4cdb..000000000 --- a/contrib/depends/packages/libSM.mk +++ /dev/null @@ -1,23 +0,0 @@ -package=libSM -$(package)_version=1.2.2 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/lib/ -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=0baca8c9f5d934450a70896c4ad38d06475521255ca63b717a6510fdb6e287bd -$(package)_dependencies=xtrans xproto libICE - -define $(package)_set_vars - $(package)_config_opts=--without-libuuid --without-xsltproc --disable-docs --disable-static - $(package)_config_opts_linux=--with-pic -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk index 11e2cb7be..d2d1eca85 100644 --- a/contrib/depends/packages/packages.mk +++ b/contrib/depends/packages/packages.mk @@ -1,4 +1,4 @@ -packages:=boost openssl zeromq libiconv expat ldns unbound +packages:=boost openssl zeromq libiconv expat unbound # ccache is useless in gitian builds ifneq ($(GITIAN),1) @@ -20,7 +20,6 @@ freebsd_packages = ncurses readline sodium linux_packages = eudev ncurses readline sodium $(hardware_packages) linux_native_packages = $(hardware_native_packages) -qt_packages = qt ifeq ($(build_tests),ON) packages += gtest diff --git a/contrib/depends/packages/qt.mk b/contrib/depends/packages/qt.mk deleted file mode 100644 index 76c50f3fd..000000000 --- a/contrib/depends/packages/qt.mk +++ /dev/null @@ -1,175 +0,0 @@ -PACKAGE=qt -$(package)_version=5.15.1 -$(package)_download_path=https://download.qt.io/official_releases/qt/5.15/$($(package)_version)/submodules -$(package)_suffix=everywhere-src-$($(package)_version).tar.xz -$(package)_file_name=qtbase-$($(package)_suffix) -$(package)_sha256_hash=33960404d579675b7210de103ed06a72613bfc4305443e278e2d32a3eb1f3d8c -$(package)_build_subdir=qtbase -$(package)_qt_libs=corelib -$(package)_patches=fix_qt_pkgconfig.patch fix_no_printer.patch fix_rcc_determinism.patch no-xlib.patch - -$(package)_qttranslations_file_name=qttranslations-$($(package)_suffix) -$(package)_qttranslations_sha256_hash=46e0c0e3a511fbcc803a4146204062e47f6ed43b34d98a3c27372a03b8746bd8 - -$(package)_qttools_file_name=qttools-$($(package)_suffix) -$(package)_qttools_sha256_hash=c98ee5f0f980bf68cbf0c94d62434816a92441733de50bd9adbe9b9055f03498 - -$(package)_extra_sources = $($(package)_qttranslations_file_name) -$(package)_extra_sources += $($(package)_qttools_file_name) - -define $(package)_set_vars -$(package)_config_opts_release = -release -$(package)_config_opts_debug = -debug -$(package)_config_opts += -bindir $(build_prefix)/bin -$(package)_config_opts += -c++std c++11 -$(package)_config_opts += -confirm-license -$(package)_config_opts += -dbus-runtime -$(package)_config_opts += -hostprefix $(build_prefix) -$(package)_config_opts += -no-compile-examples -$(package)_config_opts += -no-cups -$(package)_config_opts += -no-egl -$(package)_config_opts += -no-eglfs -$(package)_config_opts += -no-evdev -$(package)_config_opts += -no-gui -$(package)_config_opts += -no-freetype -$(package)_config_opts += -no-gif -$(package)_config_opts += -no-glib -$(package)_config_opts += -no-icu -$(package)_config_opts += -no-ico -$(package)_config_opts += -no-iconv -$(package)_config_opts += -no-kms -$(package)_config_opts += -no-linuxfb -$(package)_config_opts += -no-libjpeg -$(package)_config_opts += -no-libudev -$(package)_config_opts += -no-mtdev -$(package)_config_opts += -no-openvg -$(package)_config_opts += -no-reduce-relocations -$(package)_config_opts += -no-sql-db2 -$(package)_config_opts += -no-sql-ibase -$(package)_config_opts += -no-sql-oci -$(package)_config_opts += -no-sql-tds -$(package)_config_opts += -no-sql-mysql -$(package)_config_opts += -no-sql-odbc -$(package)_config_opts += -no-sql-psql -$(package)_config_opts += -no-sql-sqlite -$(package)_config_opts += -no-sql-sqlite2 -$(package)_config_opts += -no-use-gold-linker -$(package)_config_opts += -nomake examples -$(package)_config_opts += -nomake tests -$(package)_config_opts += -opensource -$(package)_config_opts += -no-openssl -$(package)_config_opts += -optimized-qmake -$(package)_config_opts += -pch -$(package)_config_opts += -pkg-config -$(package)_config_opts += -prefix $(host_prefix) -$(package)_config_opts += -no-libpng -$(package)_config_opts += -qt-pcre -$(package)_config_opts += -qt-harfbuzz -$(package)_config_opts += -no-zlib -$(package)_config_opts += -static -$(package)_config_opts += -silent -$(package)_config_opts += -v -$(package)_config_opts += -no-feature-bearermanagement -$(package)_config_opts += -no-feature-colordialog -$(package)_config_opts += -no-feature-dial -$(package)_config_opts += -no-feature-filesystemwatcher -$(package)_config_opts += -no-feature-fontcombobox -$(package)_config_opts += -no-feature-ftp -$(package)_config_opts += -no-feature-image_heuristic_mask -$(package)_config_opts += -no-feature-keysequenceedit -$(package)_config_opts += -no-feature-lcdnumber -$(package)_config_opts += -no-feature-pdf -$(package)_config_opts += -no-feature-printdialog -$(package)_config_opts += -no-feature-printer -$(package)_config_opts += -no-feature-printpreviewdialog -$(package)_config_opts += -no-feature-printpreviewwidget -$(package)_config_opts += -no-feature-sessionmanager -$(package)_config_opts += -no-feature-sql -$(package)_config_opts += -no-feature-statemachine -$(package)_config_opts += -no-feature-syntaxhighlighter -$(package)_config_opts += -no-feature-textbrowser -$(package)_config_opts += -no-feature-textodfwriter -$(package)_config_opts += -no-feature-topleveldomain -$(package)_config_opts += -no-feature-udpsocket -$(package)_config_opts += -no-feature-undocommand -$(package)_config_opts += -no-feature-undogroup -$(package)_config_opts += -no-feature-undostack -$(package)_config_opts += -no-feature-undoview -$(package)_config_opts += -no-feature-vnc -$(package)_config_opts += -no-feature-wizard -$(package)_config_opts_linux = -no-fontconfig -$(package)_config_opts_linux += -no-opengl -$(package)_config_opts_linux += -no-xcb -$(package)_config_opts_linux += -no-feature-xlib -endef - -define $(package)_fetch_cmds -$(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ -$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttranslations_file_name),$($(package)_qttranslations_file_name),$($(package)_qttranslations_sha256_hash)) && \ -$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttools_file_name),$($(package)_qttools_file_name),$($(package)_qttools_sha256_hash)) -endef - -define $(package)_extract_cmds - mkdir -p $($(package)_extract_dir) && \ - echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - echo "$($(package)_qttranslations_sha256_hash) $($(package)_source_dir)/$($(package)_qttranslations_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - echo "$($(package)_qttools_sha256_hash) $($(package)_source_dir)/$($(package)_qttools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ - mkdir qtbase && \ - tar --strip-components=1 -xf $($(package)_source) -C qtbase && \ - mkdir qttranslations && \ - tar --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttranslations_file_name) -C qttranslations && \ - mkdir qttools && \ - tar --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttools_file_name) -C qttools -endef - - -define $(package)_preprocess_cmds - sed -i.old "s|FT_Get_Font_Format|FT_Get_X11_Font_Format|" qtbase/src/platformsupport/fontdatabases/freetype/qfontengine_ft.cpp && \ - sed -i.old "s|updateqm.commands = \$$$$\$$$$LRELEASE|updateqm.commands = $($(package)_extract_dir)/qttools/bin/lrelease|" qttranslations/translations/translations.pro && \ - sed -i.old "/updateqm.depends =/d" qttranslations/translations/translations.pro && \ - sed -i.old "s/src_plugins.depends = src_sql src_network/src_plugins.depends = src_network/" qtbase/src/src.pro && \ - cp -r qtbase/mkspecs/linux-arm-gnueabi-g++ qtbase/mkspecs/bitcoin-linux-g++ && \ - sed -i.old "s/arm-linux-gnueabi-/$(host)-/g" qtbase/mkspecs/bitcoin-linux-g++/qmake.conf && \ - patch -p1 -i $($(package)_patch_dir)/fix_qt_pkgconfig.patch && \ - patch -p1 -i $($(package)_patch_dir)/fix_no_printer.patch && \ - echo "!host_build: QMAKE_CFLAGS += $($(package)_cflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ - echo "!host_build: QMAKE_CXXFLAGS += $($(package)_cxxflags) $($(package)_cppflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ - echo "!host_build: QMAKE_LFLAGS += $($(package)_ldflags)" >> qtbase/mkspecs/common/gcc-base.conf && \ - patch -p1 -i $($(package)_patch_dir)/no-xlib.patch && \ - echo "QMAKE_LINK_OBJECT_MAX = 10" >> qtbase/mkspecs/win32-g++/qmake.conf && \ - echo "QMAKE_LINK_OBJECT_SCRIPT = object_script" >> qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "s|QMAKE_CFLAGS += |!host_build: QMAKE_CFLAGS = $($(package)_cflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "s|QMAKE_CXXFLAGS += |!host_build: QMAKE_CXXFLAGS = $($(package)_cxxflags) $($(package)_cppflags) |" qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "0,/^QMAKE_LFLAGS_/s|^QMAKE_LFLAGS_|!host_build: QMAKE_LFLAGS = $($(package)_ldflags)\n&|" qtbase/mkspecs/win32-g++/qmake.conf && \ - sed -i.old "s/LIBRARY_PATH/(CROSS_)?\0/g" qtbase/mkspecs/features/toolchain.prf -endef - -define $(package)_config_cmds - export PKG_CONFIG_SYSROOT_DIR=/ && \ - export PKG_CONFIG_LIBDIR=$(host_prefix)/lib/pkgconfig && \ - export PKG_CONFIG_PATH=$(host_prefix)/share/pkgconfig && \ - ./configure $($(package)_config_opts) && \ - echo "CONFIG += force_bootstrap" >> mkspecs/qconfig.pri && \ - $(MAKE) sub-src-clean && \ - cd ../qttranslations && ../qtbase/bin/qmake qttranslations.pro -o Makefile && \ - cd translations && ../../qtbase/bin/qmake translations.pro -o Makefile && cd ../.. &&\ - cd qttools/src/linguist/lrelease/ && ../../../../qtbase/bin/qmake lrelease.pro -o Makefile -endef - -define $(package)_build_cmds - $(MAKE) -C src $(addprefix sub-,$($(package)_qt_libs)) && \ - $(MAKE) -C ../qttools/src/linguist/lrelease && \ - $(MAKE) -C ../qttranslations -endef - -define $(package)_stage_cmds - $(MAKE) -C src INSTALL_ROOT=$($(package)_staging_dir) $(addsuffix -install_subtargets,$(addprefix sub-,$($(package)_qt_libs))) && cd .. &&\ - $(MAKE) -C qttools/src/linguist/lrelease INSTALL_ROOT=$($(package)_staging_dir) install_target && \ - $(MAKE) -C qttranslations INSTALL_ROOT=$($(package)_staging_dir) install_subtargets -endef - -define $(package)_postprocess_cmds - rm -rf native/mkspecs/ native/lib/ lib/cmake/ && \ - rm -f lib/lib*.la lib/*.prl plugins/*/*.prl -endef diff --git a/contrib/depends/packages/unbound.mk b/contrib/depends/packages/unbound.mk index 9336524f3..421c51f7f 100644 --- a/contrib/depends/packages/unbound.mk +++ b/contrib/depends/packages/unbound.mk @@ -3,7 +3,7 @@ $(package)_version=1.15.0 $(package)_download_path=https://www.nlnetlabs.nl/downloads/$(package)/ $(package)_file_name=$(package)-$($(package)_version).tar.gz $(package)_sha256_hash=a480dc6c8937447b98d161fe911ffc76cfaffa2da18788781314e81339f1126f -$(package)_dependencies=openssl expat ldns +$(package)_dependencies=openssl expat $(package)_patches=disable-glibc-reallocarray.patch diff --git a/contrib/depends/packages/xproto.mk b/contrib/depends/packages/xproto.mk deleted file mode 100644 index 52fe253c7..000000000 --- a/contrib/depends/packages/xproto.mk +++ /dev/null @@ -1,21 +0,0 @@ -package=xproto -$(package)_version=7.0.26 -$(package)_download_path=https://xorg.freedesktop.org/releases/individual/proto -$(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=636162c1759805a5a0114a369dffdeccb8af8c859ef6e1445f26a4e6e046514f - -define $(package)_set_vars -$(package)_config_opts=--disable-shared -endef - -define $(package)_config_cmds - $($(package)_autoconf) -endef - -define $(package)_build_cmds - $(MAKE) -endef - -define $(package)_stage_cmds - $(MAKE) DESTDIR=$($(package)_staging_dir) install -endef diff --git a/contrib/depends/patches/qt/fix_no_printer.patch b/contrib/depends/patches/qt/fix_no_printer.patch deleted file mode 100644 index 137235613..000000000 --- a/contrib/depends/patches/qt/fix_no_printer.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- x/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h -+++ y/qtbase/src/plugins/platforms/cocoa/qprintengine_mac_p.h -@@ -52,6 +52,7 @@ - // - - #include <QtCore/qglobal.h> -+#include <qpa/qplatformprintdevice.h> - - #ifndef QT_NO_PRINTER - ---- x/qtbase/src/plugins/plugins.pro -+++ y/qtbase/src/plugins/plugins.pro -@@ -9,6 +9,3 @@ qtHaveModule(gui) { - !android:qtConfig(library): SUBDIRS *= generic - } - qtHaveModule(widgets): SUBDIRS += styles -- --!winrt:qtHaveModule(printsupport): \ -- SUBDIRS += printsupport diff --git a/contrib/depends/patches/qt/fix_qt_pkgconfig.patch b/contrib/depends/patches/qt/fix_qt_pkgconfig.patch deleted file mode 100644 index 73f4d89f7..000000000 --- a/contrib/depends/patches/qt/fix_qt_pkgconfig.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- old/qtbase/mkspecs/features/qt_module.prf -+++ new/qtbase/mkspecs/features/qt_module.prf -@@ -269,7 +269,7 @@ load(qt_installs) - load(qt_targets) - - # this builds on top of qt_common --!internal_module:if(unix|mingw):!if(darwin:debug_and_release:CONFIG(debug, debug|release)) { -+if(unix|mingw):!if(darwin:debug_and_release:CONFIG(debug, debug|release)) { - CONFIG += create_pc - QMAKE_PKGCONFIG_DESTDIR = pkgconfig - host_build: \ diff --git a/contrib/depends/patches/qt/fix_rcc_determinism.patch b/contrib/depends/patches/qt/fix_rcc_determinism.patch deleted file mode 100644 index c1b07fe23..000000000 --- a/contrib/depends/patches/qt/fix_rcc_determinism.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- old/qtbase/src/tools/rcc/rcc.cpp -+++ new/qtbase/src/tools/rcc/rcc.cpp -@@ -207,7 +207,11 @@ void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) - if (lib.formatVersion() >= 2) { - // last modified time stamp - const QDateTime lastModified = m_fileInfo.lastModified(); -- lib.writeNumber8(quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0)); -+ quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); -+ static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); -+ if (sourceDate != 0) -+ lastmod = sourceDate; -+ lib.writeNumber8(lastmod); - if (text || pass1) - lib.writeChar('\n'); - } diff --git a/contrib/depends/patches/qt/no-xlib.patch b/contrib/depends/patches/qt/no-xlib.patch deleted file mode 100644 index 6800d398c..000000000 --- a/contrib/depends/patches/qt/no-xlib.patch +++ /dev/null @@ -1,69 +0,0 @@ -From 9563cef873ae82e06f60708d706d054717e801ce Mon Sep 17 00:00:00 2001 -From: Carl Dong <contact@carldong.me> -Date: Thu, 18 Jul 2019 17:22:05 -0400 -Subject: [PATCH] Wrap xlib related code blocks in #if's - -They are not necessary to compile QT. ---- - qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp | 8 ++++++++ - 1 file changed, 8 insertions(+) - -diff --git a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp -index 7c62c2e2b3..c05c6c0a07 100644 ---- a/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp -+++ b/qtbase/src/plugins/platforms/xcb/qxcbcursor.cpp -@@ -49,7 +49,9 @@ - #include <QtGui/QWindow> - #include <QtGui/QBitmap> - #include <QtGui/private/qguiapplication_p.h> -+#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) - #include <X11/cursorfont.h> -+#endif - #include <xcb/xfixes.h> - #include <xcb/xcb_image.h> - -@@ -391,6 +393,7 @@ void QXcbCursor::changeCursor(QCursor *cursor, QWindow *window) - xcb_flush(xcb_connection()); - } - -+#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) - static int cursorIdForShape(int cshape) - { - int cursorId = 0; -@@ -444,6 +447,7 @@ static int cursorIdForShape(int cshape) - } - return cursorId; - } -+#endif - - xcb_cursor_t QXcbCursor::createNonStandardCursor(int cshape) - { -@@ -556,7 +560,9 @@ static xcb_cursor_t loadCursor(void *dpy, int cshape) - xcb_cursor_t QXcbCursor::createFontCursor(int cshape) - { - xcb_connection_t *conn = xcb_connection(); -+#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) - int cursorId = cursorIdForShape(cshape); -+#endif - xcb_cursor_t cursor = XCB_NONE; - - // Try Xcursor first -@@ -586,6 +592,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) - // Non-standard X11 cursors are created from bitmaps - cursor = createNonStandardCursor(cshape); - -+#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library) - // Create a glpyh cursor if everything else failed - if (!cursor && cursorId) { - cursor = xcb_generate_id(conn); -@@ -593,6 +600,7 @@ xcb_cursor_t QXcbCursor::createFontCursor(int cshape) - cursorId, cursorId + 1, - 0xFFFF, 0xFFFF, 0xFFFF, 0, 0, 0); - } -+#endif - - if (cursor && cshape >= 0 && cshape < Qt::LastCursor && connection()->hasXFixes()) { - const char *name = cursorNames[cshape].front(); ---- -2.22.0 - diff --git a/contrib/depends/toolchain.cmake.in b/contrib/depends/toolchain.cmake.in index 5f9501329..570065560 100644 --- a/contrib/depends/toolchain.cmake.in +++ b/contrib/depends/toolchain.cmake.in @@ -27,8 +27,6 @@ SET(Terminfo_LIBRARY @prefix@/lib/libtinfo.a) SET(UNBOUND_INCLUDE_DIR @prefix@/include) SET(UNBOUND_LIBRARIES @prefix@/lib/libunbound.a) -SET(LRELEASE_PATH @prefix@/native/bin CACHE FILEPATH "path to lrelease" FORCE) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Android") SET(LIBUNWIND_INCLUDE_DIR @prefix@/include) SET(LIBUNWIND_LIBRARIES @prefix@/lib/libunwind.a) diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index ffb3f3b7e..848b8ffc4 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -63,15 +63,11 @@ bool handled = false; \ if(false) return true; //just a stub to have "else if" -#define MAP_URI2(pattern, callback) else if(std::string::npos != query_info.m_URI.find(pattern)) return callback(query_info, response_info, &m_conn_context); - -#define MAP_URI_AUTO_XML2(s_pattern, callback_f, command_type) //TODO: don't think i ever again will use xml - ambiguous and "overtagged" format - #define MAP_URI_AUTO_JON2_IF(s_pattern, callback_f, command_type, cond) \ else if((query_info.m_URI == s_pattern) && (cond)) \ { \ handled = true; \ - uint64_t ticks = misc_utils::get_tick_count(); \ + uint64_t ticks = epee::misc_utils::get_tick_count(); \ boost::value_initialized<command_type::request> req; \ bool parse_res = epee::serialization::load_t_from_json(static_cast<command_type::request&>(req), query_info.m_body); \ if (!parse_res) \ @@ -107,7 +103,7 @@ else if(query_info.m_URI == s_pattern) \ { \ handled = true; \ - uint64_t ticks = misc_utils::get_tick_count(); \ + uint64_t ticks = epee::misc_utils::get_tick_count(); \ boost::value_initialized<command_type::request> req; \ bool parse_res = epee::serialization::load_t_from_binary(static_cast<command_type::request&>(req), epee::strspan<uint8_t>(query_info.m_body)); \ if (!parse_res) \ @@ -117,7 +113,7 @@ response_info.m_response_comment = "Bad request"; \ return true; \ } \ - uint64_t ticks1 = misc_utils::get_tick_count(); \ + uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ boost::value_initialized<command_type::response> resp;\ MINFO(m_conn_context << "calling " << s_pattern); \ bool res = false; \ @@ -129,7 +125,7 @@ response_info.m_response_comment = "Internal Server Error"; \ return true; \ } \ - uint64_t ticks2 = misc_utils::get_tick_count(); \ + uint64_t ticks2 = epee::misc_utils::get_tick_count(); \ epee::byte_slice buffer; \ epee::serialization::store_t_to_binary(static_cast<command_type::response&>(resp), buffer, 64 * 1024); \ uint64_t ticks3 = epee::misc_utils::get_tick_count(); \ @@ -139,8 +135,6 @@ MDEBUG( s_pattern << "() processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms"); \ } -#define CHAIN_URI_MAP2(callback) else {callback(query_info, response_info, m_conn_context);handled = true;} - #define END_URI_MAP2() return handled;} @@ -225,26 +219,6 @@ #define MAP_JON_RPC_WE(method_name, callback_f, command_type) MAP_JON_RPC_WE_IF(method_name, callback_f, command_type, true) -#define MAP_JON_RPC_WERI(method_name, callback_f, command_type) \ - else if(callback_name == method_name) \ -{ \ - PREPARE_OBJECTS_FROM_JSON(command_type) \ - epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \ - fail_resp.jsonrpc = "2.0"; \ - fail_resp.id = req.id; \ - MINFO(m_conn_context << "calling RPC method " << method_name); \ - bool res = false; \ - try { res = callback_f(req.params, resp.result, fail_resp.error, response_info, &m_conn_context); } \ - catch (const std::exception &e) { MERROR(m_conn_context << "Failed to " << #callback_f << "(): " << e.what()); } \ - if (!res) \ - { \ - epee::serialization::store_t_to_json(static_cast<epee::json_rpc::error_response&>(fail_resp), response_info.m_body); \ - return true; \ - } \ - FINALIZE_OBJECTS_TO_JSON(method_name) \ - return true;\ -} - #define MAP_JON_RPC(method_name, callback_f, command_type) \ else if(callback_name == method_name) \ { \ diff --git a/contrib/epee/include/net/net_helper.h b/contrib/epee/include/net/net_helper.h index 0a35797fd..ee344561d 100644 --- a/contrib/epee/include/net/net_helper.h +++ b/contrib/epee/include/net/net_helper.h @@ -688,119 +688,6 @@ namespace net_utils std::atomic<uint64_t> m_bytes_sent; std::atomic<uint64_t> m_bytes_received; }; - - - /************************************************************************/ - /* */ - /************************************************************************/ - class async_blocked_mode_client: public blocked_mode_client - { - public: - async_blocked_mode_client():m_send_deadline(blocked_mode_client::m_io_service) - { - - // No deadline is required until the first socket operation is started. We - // set the deadline to positive infinity so that the actor takes no action - // until a specific deadline is set. - m_send_deadline.expires_at(boost::posix_time::pos_infin); - - // Start the persistent actor that checks for deadline expiry. - check_send_deadline(); - } - ~async_blocked_mode_client() - { - m_send_deadline.cancel(); - } - - bool shutdown() - { - blocked_mode_client::shutdown(); - m_send_deadline.cancel(); - return true; - } - - inline - bool send(const void* data, size_t sz) - { - try - { - /* - m_send_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); - - // Set up the variable that receives the result of the asynchronous - // operation. The error code is set to would_block to signal that the - // operation is incomplete. Asio guarantees that its asynchronous - // operations will never fail with would_block, so any other value in - // ec indicates completion. - boost::system::error_code ec = boost::asio::error::would_block; - - // Start the asynchronous operation itself. The boost::lambda function - // object is used as a callback and will update the ec variable when the - // operation completes. The blocking_udp_client.cpp example shows how you - // can use boost::bind rather than boost::lambda. - boost::asio::async_write(m_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); - - // Block until the asynchronous operation has completed. - while(ec == boost::asio::error::would_block) - { - m_io_service.run_one(); - }*/ - - boost::system::error_code ec; - - size_t writen = write(data, sz, ec); - - if (!writen || ec) - { - LOG_PRINT_L3("Problems at write: " << ec.message()); - return false; - }else - { - m_send_deadline.expires_at(boost::posix_time::pos_infin); - } - } - - catch(const boost::system::system_error& er) - { - LOG_ERROR("Some problems at connect, message: " << er.what()); - return false; - } - catch(...) - { - LOG_ERROR("Some fatal problems."); - return false; - } - - return true; - } - - - private: - - boost::asio::deadline_timer m_send_deadline; - - void check_send_deadline() - { - // Check whether the deadline has passed. We compare the deadline against - // the current time since a new asynchronous operation may have moved the - // deadline before this actor had a chance to run. - if (m_send_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now()) - { - // The deadline has passed. The socket is closed so that any outstanding - // asynchronous operations are cancelled. This allows the blocked - // connect(), read_line() or write_line() functions to return. - LOG_PRINT_L3("Timed out socket"); - m_ssl_socket->next_layer().close(); - - // There is no longer an active deadline. The expiry is set to positive - // infinity so that the actor takes no action until a new deadline is set. - m_send_deadline.expires_at(boost::posix_time::pos_infin); - } - - // Put the actor back to sleep. - m_send_deadline.async_wait(boost::bind(&async_blocked_mode_client::check_send_deadline, this)); - } - }; } } diff --git a/contrib/epee/include/string_coding.h b/contrib/epee/include/string_coding.h index 82050ef96..0d9c6c244 100644 --- a/contrib/epee/include/string_coding.h +++ b/contrib/epee/include/string_coding.h @@ -34,100 +34,6 @@ namespace epee { namespace string_encoding { - inline std::string convert_to_ansii(const std::wstring& str_from) - { - - std::string res(str_from.begin(), str_from.end()); - return res; - /* - std::string result; - std::locale loc; - for(unsigned int i= 0; i < str_from.size(); ++i) - { - result += std::use_facet<std::ctype<wchar_t> >(loc).narrow(str_from[i]); - } - return result; - */ - - //return boost::lexical_cast<std::string>(str_from); - /* - std::string str_trgt; - if(!str_from.size()) - return str_trgt; - int cb = ::WideCharToMultiByte( code_page, 0, str_from.data(), (__int32)str_from.size(), 0, 0, 0, 0 ); - if(!cb) - return str_trgt; - str_trgt.resize(cb); - ::WideCharToMultiByte( code_page, 0, str_from.data(), (int)str_from.size(), - (char*)str_trgt.data(), (int)str_trgt.size(), 0, 0); - return str_trgt;*/ - } - - inline std::string convert_to_ansii(const std::string& str_from) - { - return str_from; - } - - inline std::wstring convert_to_unicode(const std::string& str_from) - { - std::wstring result; - std::locale loc; - for(unsigned int i= 0; i < str_from.size(); ++i) - { - result += std::use_facet<std::ctype<wchar_t> >(loc).widen(str_from[i]); - } - return result; - - //return boost::lexical_cast<std::wstring>(str_from); - /* - std::wstring str_trgt; - if(!str_from.size()) - return str_trgt; - - int cb = ::MultiByteToWideChar( code_page, 0, str_from.data(), (int)str_from.size(), 0, 0 ); - if(!cb) - return str_trgt; - - str_trgt.resize(cb); - ::MultiByteToWideChar( code_page, 0, str_from.data(),(int)str_from.size(), - (wchar_t*)str_trgt.data(),(int)str_trgt.size()); - return str_trgt;*/ - } - inline std::wstring convert_to_unicode(const std::wstring& str_from) - { - return str_from; - } - - template<class target_string> - inline target_string convert_to_t(const std::wstring& str_from); - - template<> - inline std::string convert_to_t<std::string>(const std::wstring& str_from) - { - return convert_to_ansii(str_from); - } - - template<> - inline std::wstring convert_to_t<std::wstring>(const std::wstring& str_from) - { - return str_from; - } - - template<class target_string> - inline target_string convert_to_t(const std::string& str_from); - - template<> - inline std::string convert_to_t<std::string>(const std::string& str_from) - { - return str_from; - } - - template<> - inline std::wstring convert_to_t<std::wstring>(const std::string& str_from) - { - return convert_to_unicode(str_from); - } - inline std::string& base64_chars() { diff --git a/contrib/epee/src/file_io_utils.cpp b/contrib/epee/src/file_io_utils.cpp index a8348431c..c0798a510 100644 --- a/contrib/epee/src/file_io_utils.cpp +++ b/contrib/epee/src/file_io_utils.cpp @@ -29,7 +29,7 @@ #include <fstream> #include <boost/filesystem/path.hpp> #include <boost/filesystem/operations.hpp> -#ifdef WIN32 +#ifdef _WIN32 #include <windows.h> #include "string_tools.h" #endif @@ -70,7 +70,7 @@ namespace file_io_utils bool save_string_to_file(const std::string& path_to_file, const std::string& str) { -#ifdef WIN32 +#ifdef _WIN32 std::wstring wide_path; try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); @@ -104,7 +104,7 @@ namespace file_io_utils bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size) { -#ifdef WIN32 +#ifdef _WIN32 std::wstring wide_path; try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -153,7 +153,7 @@ namespace file_io_utils bool get_file_size(const std::string& path_to_file, uint64_t &size) { -#ifdef WIN32 +#ifdef _WIN32 std::wstring wide_path; try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; } HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); diff --git a/contrib/gitian/DOCKRUN.md b/contrib/gitian/DOCKRUN.md index 96998b1fe..06e4894a2 100644 --- a/contrib/gitian/DOCKRUN.md +++ b/contrib/gitian/DOCKRUN.md @@ -57,7 +57,8 @@ The dockrun.sh script will do everything to build the binaries. Just specify the version to build as its only argument, e.g. ```bash -./dockrun.sh v0.17.3.0 +VERSION=v0.18.1.0 +./dockrun.sh $VERSION ``` The build should run to completion with no errors, and will display the SHA256 checksums @@ -78,7 +79,7 @@ e.g. ```bash # Run build processes with 8 threads -OPT="-j 8" ./dockrun.sh v0.17.3.0 +OPT="-j 8" ./dockrun.sh $VERSION ``` Post-build @@ -98,16 +99,16 @@ more builder/var/install-linux.log more builder/var/build-linux.log ``` -You can find the compiled archives inside of the container at the following directory (be sure to replace `v0.17.3.0` with the version being built): +You can find the compiled archives inside of the container at the following directory: ```bash docker exec -it gitrun /bin/bash -ls -la out/v0.17.3.0/ +ls -la out/$VERSION/ ``` -To copy the compiled archives to the local host out of the Docker container, you can run the following (be sure to replace `v0.17.3.0` with the version being built): +To copy the compiled archives to the local host out of the Docker container, you can run the following: ```bash mkdir out -docker cp gitrun:/home/ubuntu/out/v0.17.3.0 out +docker cp gitrun:/home/ubuntu/out/$VERSION out ``` diff --git a/docs/PORTABLE_STORAGE.md b/docs/PORTABLE_STORAGE.md index 675ca818c..70e7ff954 100644 --- a/docs/PORTABLE_STORAGE.md +++ b/docs/PORTABLE_STORAGE.md @@ -158,7 +158,7 @@ that most will be familiar with): ```json { - "short_quote": "Give me liberty or give me death!", + "short_quote": "Give me liberty or give me death", "long_quote": "Monero is more than just a technology. It's also what the technology stands for.", "signed_32bit_int": 20140418, "array_of_bools": [true, false, true, true], @@ -169,9 +169,51 @@ that most will be familiar with): } ``` -This would translate to: +This object would translate into the following bytes when serialized into epee portable storage format. The bytes are represented in hex, with comments and whitespace added for readability. -![Epee binary storage format example](/docs/images/storage_binary_example.png) +``` +01 11 01 01 01 01 02 01 // Signature +01 // Version +14 // Varint number of section entries (5) +0b // Length of next section key (11) +73 68 6f 72 74 5f 71 75 6f 74 65 // Section key ("short_quote") +0a // Type code (STRING) +80 // Varint length of string (32) +47 69 76 65 20 6d 65 20 6c 69 62 65 72 74 79 20 // STRING value ("Give me liberty ") +6f 72 20 67 69 76 65 20 6d 65 20 64 65 61 74 68 // STRING value cont. ("or give me death") +0a // Length of next section key (10) +6c 6f 6e 67 5f 71 75 6f 74 65 // Section key ("long_quote") +0a // Type code (STRING) +41 01 // Varint length of string (80). Note it's 2 bytes +4d 6f 6e 65 72 6f 20 69 73 20 6d 6f 72 65 20 74 // STRING value ("Monero is more t") +68 61 6e 20 6a 75 73 74 20 61 20 74 65 63 68 6e // STRING value cont. ("han just a techn") +6f 6c 6f 67 79 2e 20 49 74 27 73 20 61 6c 73 6f // STRING value cont. ("ology. It's also") +20 77 68 61 74 20 74 68 65 20 74 65 63 68 6e 6f // STRING value cont. (" what the techno") +6c 6f 67 79 20 73 74 61 6e 64 73 20 66 6f 72 2e // STRING value cont. ("logy stands for.") +10 // Length of next section key (16) +73 69 67 6e 65 64 5f 33 32 62 69 74 5f 69 6e 74 // Section key ("signed_32bit_int") +02 // type code (INT32) +82 51 33 01 // INT32 value (20140418) +0e // Length of next section key (14) +61 72 72 61 79 5f 6f 66 5f 62 6f 6f 6c 73 // Section key ("array_of_bools") +8b // Type code (BOOL | FLAG_ARRAY) +10 // Varint number of array elements (4) +01 00 01 01 // Array BOOL values [true, false, true, true] +0e // Length of next section key (14) +6e 65 73 74 65 64 5f 73 65 63 74 69 6f 6e // Section key ("nested_section") +0c // Type code (OBJECT) +08 // Varint number of inner section entries (2) +06 // Length of first inner section key (6) +64 6f 75 62 6c 65 // Section key ("double") +09 // Type code (DOUBLE) +9a 99 99 99 99 99 1b c0 // DOUBLE value (-6.9) +12 // Length of second inner section key (18) +75 6e 73 69 67 6e 65 64 5f 36 34 62 69 74 5f 69 // Section key ("unsigned_64bit_i") +6e 74 // Section key cont ("nt") +05 // Type code (UINT64) +c7 71 ac b5 af 98 32 9a // UINT64 value (11111111111111111111) + +``` ## Monero specifics diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md index 632366985..cfd04f98d 100644 --- a/docs/RELEASE_CHECKLIST.md +++ b/docs/RELEASE_CHECKLIST.md @@ -1,15 +1,20 @@ +# Monero hard-fork release check-list + - [ ] Security audit - [ ] Code audit - [ ] Ledger integration - - [ ] Implemented in Monero codebase (if needed) - - [ ] Ledger app integration coded by Ledger + - [ ] Ledger notified + - [ ] Pull request made against Monero codebase (if needed) + - [ ] Pull request merged into Monero codebase (if needed) + - [ ] Ledger app integration coded - [ ] Ledger Monero app update available - [ ] Trezor integration - - [ ] Implemented in Monero codebase (if needed) - - [ ] Trezor app integration coded by Trezor - - [ ] Trezor firmware update available (if needed) + - [ ] Trezor notified + - [ ] Pull request made against Monero codebase (if needed) + - [ ] Pull request merged into Monero codebase (if needed) + - [ ] Trezor firmware update coded + - [ ] Trezor firmware update available - [ ] Fork height set - - [ ] Monero-announce mailer notice - [ ] Twitter announcement - [ ] Reddit announcement - [ ] Getmonero.org announcement @@ -26,13 +31,15 @@ - [ ] Edge Wallet - [ ] Exodus - [ ] XMRWallet + - [ ] Feather Wallet - [ ] Notify exchanges - - [ ] https://web.getmonero.org/community/merchants/#exchanges + - [ ] https://www.getmonero.org/community/merchants/#exchanges - [ ] Notify 3rd party payment processors - - [ ] https://web.getmonero.org/community/merchants/#payment-gateways + - [ ] https://www.getmonero.org/community/merchants/#payment-gateways + - [ ] BTCPayServer - [ ] Notify mining pools - [ ] https://miningpoolstats.stream/monero -- [ ] Release tagged +- [ ] Release branch created - [ ] Update src/version.cpp.in with new version AND new name (if necessary) - [ ] Update Gitian YML files in contrib/gitian/ to the new version number - [ ] Update README.md with new fork table entry (or at least update the Recommended Monero version) @@ -46,16 +53,21 @@ - [ ] Trezor - [ ] Release-specific testing - [ ] RPC testing/update RPC documentation +- [ ] Stagenet forked +- [ ] Stagenet testing/verification + - [ ] Ledger + - [ ] Trezor + - [ ] Release-specific testing - [ ] CLI reproducible builds validated - [ ] CLI released - - [ ] https://web.getmonero.org/downloads/ updated + - [ ] https://www.getmonero.org/downloads/ updated - [ ] Update hashes.txt on website - [ ] Update downloads.yml on website - [ ] Update auto-update DNS records - [ ] Update redirects on downloads box - [ ] Update seed nodes - [ ] GUI released - - [ ] https://web.getmonero.org/downloads/ updated + - [ ] https://www.getmonero.org/downloads/ updated - [ ] Update hashes.txt on website - [ ] Update hashes.txt.sig on website - [ ] Update downloads.yml on website diff --git a/docs/images/storage_binary_example.png b/docs/images/storage_binary_example.png Binary files differdeleted file mode 100644 index 1baa20cc1..000000000 --- a/docs/images/storage_binary_example.png +++ /dev/null diff --git a/external/randomx b/external/randomx -Subproject 85c527a62301b7b8be89d941020308b1cb92b75 +Subproject 261d58c77fc5547c0aa7fdfeb58421ba7e0e6e1 diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 263948fa2..a163ef98c 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1883,16 +1883,18 @@ public: } virtual ~db_txn_guard() { - if (active) - stop(); + stop(); } void stop() { - if (readonly) - db->block_rtxn_stop(); - else - db->block_wtxn_stop(); - active = false; + if (active) + { + if (readonly) + db->block_rtxn_stop(); + else + db->block_wtxn_stop(); + active = false; + } } void abort() { diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index db7fa6c7c..f80013d02 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -465,6 +465,32 @@ void mdb_txn_safe::increment_txns(int i) num_active_txns += i; } +#define TXN_PREFIX(flags); \ + mdb_txn_safe auto_txn; \ + mdb_txn_safe* txn_ptr = &auto_txn; \ + if (m_batch_active) \ + txn_ptr = m_write_txn; \ + else \ + { \ + if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ + throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ + } \ + +#define TXN_PREFIX_RDONLY() \ + MDB_txn *m_txn; \ + mdb_txn_cursors *m_cursors; \ + mdb_txn_safe auto_txn; \ + bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \ + if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \ + else auto_txn.uncheck() +#define TXN_POSTFIX_RDONLY() + +#define TXN_POSTFIX_SUCCESS() \ + do { \ + if (! m_batch_active) \ + auto_txn.commit(); \ + } while(0) + void lmdb_resized(MDB_env *env, int isactive) { mdb_txn_safe::prevent_new_txns(); @@ -713,21 +739,20 @@ uint64_t BlockchainLMDB::get_estimated_batch_size(uint64_t batch_num_blocks, uin } else { - MDB_txn *rtxn; - mdb_txn_cursors *rcurs; - bool my_rtxn = block_rtxn_start(&rtxn, &rcurs); - for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num) { - // we have access to block weight, which will be greater or equal to block size, - // so use this as a proxy. If it's too much off, we might have to check actual size, - // which involves reading more data, so is not really wanted - size_t block_weight = get_block_weight(block_num); - total_block_size += block_weight; - // Track number of blocks being totalled here instead of assuming, in case - // some blocks were to be skipped for being outliers. - ++num_blocks_used; + TXN_PREFIX_RDONLY(); + for (uint64_t block_num = block_start; block_num <= block_stop; ++block_num) + { + // we have access to block weight, which will be greater or equal to block size, + // so use this as a proxy. If it's too much off, we might have to check actual size, + // which involves reading more data, so is not really wanted + size_t block_weight = get_block_weight(block_num); + total_block_size += block_weight; + // Track number of blocks being totalled here instead of assuming, in case + // some blocks were to be skipped for being outliers. + ++num_blocks_used; + } } - if (my_rtxn) block_rtxn_stop(); avg_block_size = total_block_size / (num_blocks_used ? num_blocks_used : 1); MDEBUG("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size); } @@ -1678,32 +1703,6 @@ void BlockchainLMDB::unlock() check_open(); } -#define TXN_PREFIX(flags); \ - mdb_txn_safe auto_txn; \ - mdb_txn_safe* txn_ptr = &auto_txn; \ - if (m_batch_active) \ - txn_ptr = m_write_txn; \ - else \ - { \ - if (auto mdb_res = lmdb_txn_begin(m_env, NULL, flags, auto_txn)) \ - throw0(DB_ERROR(lmdb_error(std::string("Failed to create a transaction for the db in ")+__FUNCTION__+": ", mdb_res).c_str())); \ - } \ - -#define TXN_PREFIX_RDONLY() \ - MDB_txn *m_txn; \ - mdb_txn_cursors *m_cursors; \ - mdb_txn_safe auto_txn; \ - bool my_rtxn = block_rtxn_start(&m_txn, &m_cursors); \ - if (my_rtxn) auto_txn.m_tinfo = m_tinfo.get(); \ - else auto_txn.uncheck() -#define TXN_POSTFIX_RDONLY() - -#define TXN_POSTFIX_SUCCESS() \ - do { \ - if (! m_batch_active) \ - auto_txn.commit(); \ - } while(0) - // 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 @@ -3923,13 +3922,20 @@ void BlockchainLMDB::block_rtxn_stop() const LOG_PRINT_L3("BlockchainLMDB::" << __func__); mdb_txn_reset(m_tinfo->m_ti_rtxn); memset(&m_tinfo->m_ti_rflags, 0, sizeof(m_tinfo->m_ti_rflags)); + /* cancel out the increment from rtxn_start */ + mdb_txn_safe::increment_txns(-1); } bool BlockchainLMDB::block_rtxn_start() const { MDB_txn *mtxn; mdb_txn_cursors *mcur; - return block_rtxn_start(&mtxn, &mcur); + /* auto_txn is only used for the create gate */ + mdb_txn_safe auto_txn; + bool ret = block_rtxn_start(&mtxn, &mcur); + if (ret) + auto_txn.increment_txns(1); /* remember there is an active readtxn */ + return ret; } void BlockchainLMDB::block_wtxn_start() diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp index 6ab6ff4fe..e00421f87 100644 --- a/src/common/dns_utils.cpp +++ b/src/common/dns_utils.cpp @@ -102,7 +102,6 @@ get_builtin_ds(void) { static const char * const ds[] = { - ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n", ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D\n", NULL }; @@ -521,7 +520,7 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std // send all requests in parallel std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForIO(); tools::threadpool::waiter waiter(tpool); for (size_t n = 0; n < dns_urls.size(); ++n) { diff --git a/src/common/threadpool.h b/src/common/threadpool.h index ce1bc5799..53421e18b 100644 --- a/src/common/threadpool.h +++ b/src/common/threadpool.h @@ -42,10 +42,14 @@ namespace tools class threadpool { public: - static threadpool& getInstance() { + static threadpool& getInstanceForCompute() { static threadpool instance; return instance; } + static threadpool& getInstanceForIO() { + static threadpool instance(8); + return instance; + } static threadpool *getNewForUnitTests(unsigned max_threads = 0) { return new threadpool(max_threads); } diff --git a/src/crypto/hash-extra-jh.c b/src/crypto/hash-extra-jh.c index 4d7481c07..52efd4ae3 100644 --- a/src/crypto/hash-extra-jh.c +++ b/src/crypto/hash-extra-jh.c @@ -36,7 +36,9 @@ #include "jh.h" #include "hash-ops.h" +#define JH_HASH_BITLEN HASH_SIZE * 8 + void hash_extra_jh(const void *data, size_t length, char *hash) { - int r = jh_hash(HASH_SIZE * 8, data, 8 * length, (uint8_t*)hash); - assert(SUCCESS == r); + // No need to check for failure b/c jh_hash only fails for invalid hash size + jh_hash(JH_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); } diff --git a/src/crypto/hash-extra-skein.c b/src/crypto/hash-extra-skein.c index 9ea9c4faa..3eacaba58 100644 --- a/src/crypto/hash-extra-skein.c +++ b/src/crypto/hash-extra-skein.c @@ -34,7 +34,9 @@ #include "hash-ops.h" #include "skein.h" +#define SKEIN_HASH_BITLEN HASH_SIZE * 8 + void hash_extra_skein(const void *data, size_t length, char *hash) { - int r = skein_hash(8 * HASH_SIZE, data, 8 * length, (uint8_t*)hash); - assert(SKEIN_SUCCESS == r); + // No need to check for failure b/c skein_hash only fails for invalid hash size + skein_hash(SKEIN_HASH_BITLEN, data, 8 * length, (uint8_t*)hash); } diff --git a/src/crypto/keccak.c b/src/crypto/keccak.c index f098cbdf0..6616d3530 100644 --- a/src/crypto/keccak.c +++ b/src/crypto/keccak.c @@ -123,7 +123,7 @@ void keccak(const uint8_t *in, size_t inlen, uint8_t *md, int mdlen) size_t i, rsiz, rsizw; static_assert(HASH_DATA_AREA <= sizeof(temp), "Bad keccak preconditions"); - if (mdlen <= 0 || (mdlen > 100 && sizeof(st) != (size_t)mdlen)) + if (mdlen <= 0 || (mdlen >= 100 && sizeof(st) != (size_t)mdlen)) { local_abort("Bad keccak use"); } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 388013f96..829e5fc70 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -989,7 +989,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index) + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device* hwdev) { // If there is no view tag to check, the output can possibly belong to the account. // Will need to derive the output pub key to be certain whether or not the output belongs to the account. @@ -1002,7 +1002,15 @@ namespace cryptonote // Therefore can fail out early to avoid expensive crypto ops needlessly deriving output public key to // determine if output belongs to the account. crypto::view_tag derived_view_tag; - crypto::derive_view_tag(derivation, output_index, derived_view_tag); + if (hwdev != nullptr) + { + bool r = hwdev->derive_view_tag(derivation, output_index, derived_view_tag); + CHECK_AND_ASSERT_MES(r, false, "Failed to derive view tag"); + } + else + { + crypto::derive_view_tag(derivation, output_index, derived_view_tag); + } return view_tag == derived_view_tag; } //--------------------------------------------------------------- @@ -1012,7 +1020,7 @@ namespace cryptonote bool r = acc.get_device().generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); crypto::public_key pk; - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device())) { r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); @@ -1026,7 +1034,7 @@ namespace cryptonote CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys"); r = acc.get_device().generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "Failed to generate key derivation"); - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &acc.get_device())) { r = acc.get_device().derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk); CHECK_AND_ASSERT_MES(r, false, "Failed to derive public key"); @@ -1040,7 +1048,7 @@ namespace cryptonote { // try the shared tx pubkey crypto::public_key subaddress_spendkey; - if (out_can_be_to_acc(view_tag_opt, derivation, output_index)) + if (out_can_be_to_acc(view_tag_opt, derivation, output_index, &hwdev)) { CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); @@ -1052,7 +1060,7 @@ namespace cryptonote if (!additional_derivations.empty()) { CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations"); - if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index)) + if (out_can_be_to_acc(view_tag_opt, additional_derivations[output_index], output_index, &hwdev)) { CHECK_AND_ASSERT_MES(hwdev.derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey), boost::none, "Failed to derive subaddress public key"); auto found = subaddresses.find(subaddress_spendkey); diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 8f5459ca7..b97c9d499 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -91,7 +91,7 @@ namespace cryptonote bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id); void set_tx_out(const uint64_t amount, const crypto::public_key& output_public_key, const bool use_view_tags, const crypto::view_tag& view_tag, tx_out& out); bool check_output_types(const transaction& tx, const uint8_t hf_version); - bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index); + bool out_can_be_to_acc(const boost::optional<crypto::view_tag>& view_tag_opt, const crypto::key_derivation& derivation, const size_t output_index, hw::device *hwdev = nullptr); bool is_out_to_acc(const account_keys& acc, const crypto::public_key& output_public_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index, const boost::optional<crypto::view_tag>& view_tag_opt = boost::optional<crypto::view_tag>()); struct subaddress_receive_info { diff --git a/src/cryptonote_basic/hardfork.h b/src/cryptonote_basic/hardfork.h index 32f669c71..f43710f4f 100644 --- a/src/cryptonote_basic/hardfork.h +++ b/src/cryptonote_basic/hardfork.h @@ -231,6 +231,11 @@ namespace cryptonote */ uint64_t get_window_size() const { return window_size; } + /** + * @brief returns info for all known hard forks + */ + const std::vector<hardfork_t>& get_hardforks() const { return heights; } + private: uint8_t get_block_version(uint64_t height) const; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index c37dfe9e7..135dd3df0 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3434,7 +3434,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, std::vector < uint64_t > results; results.resize(tx.vin.size(), 0); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); int threads = tpool.get_max_concurrency(); @@ -3868,7 +3868,7 @@ void Blockchain::get_dynamic_base_fee_estimate_2021_scaling(uint64_t grace_block epee::misc_utils::rolling_median_t<uint64_t> rm = m_long_term_block_weights_cache_rolling_median; for (size_t i = 0; i < grace_blocks; ++i) rm.insert(0); - const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); + const uint64_t Mlw_penalty_free_zone_for_wallet = std::max<uint64_t>(rm.size() == 0 ? 0 : rm.median(), CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE_V5); // Msw: median over [100 - grace blocks] past + [grace blocks] future blocks CHECK_AND_ASSERT_THROW_MES(grace_blocks <= 100, "Grace blocks invalid In 2021 fee scaling estimate."); @@ -5137,7 +5137,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete return true; bool blocks_exist = false; - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); unsigned threads = tpool.get_max_concurrency(); blocks.resize(blocks_entry.size()); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 7a94f6358..4795fc55c 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -159,6 +159,13 @@ namespace cryptonote bool deinit(); /** + * @brief get a set of blockchain checkpoint hashes + * + * @return set of blockchain checkpoint hashes + */ + const checkpoints& get_checkpoints() const { return m_checkpoints; } + + /** * @brief assign a set of blockchain checkpoint hashes * * @param chk_pts the set of checkpoints to assign @@ -893,6 +900,13 @@ namespace cryptonote uint64_t get_earliest_ideal_height_for_version(uint8_t version) const { return m_hardfork->get_earliest_ideal_height_for_version(version); } /** + * @brief returns info for all known hard forks + * + * @return the hardforks + */ + const std::vector<hardfork_t>& get_hardforks() const { return m_hardfork->get_hardforks(); } + + /** * @brief get information about hardfork voting for a version * * @param version the version in question diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index a78f5d673..d8c782f78 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -252,6 +252,10 @@ namespace cryptonote m_pprotocol = &m_protocol_stub; } //----------------------------------------------------------------------------------- + const checkpoints& core::get_checkpoints() const + { + return m_blockchain_storage.get_checkpoints(); + } void core::set_checkpoints(checkpoints&& chk_pts) { m_blockchain_storage.set_checkpoints(std::move(chk_pts)); @@ -1009,7 +1013,7 @@ namespace cryptonote CRITICAL_REGION_LOCAL(m_incoming_tx_lock); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); epee::span<tx_blob_entry>::const_iterator it = tx_blobs.begin(); for (size_t i = 0; i < tx_blobs.size(); i++, ++it) { @@ -1406,21 +1410,66 @@ namespace cryptonote return true; } //----------------------------------------------------------------------------------------------- + bool core::notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const + { + if (!m_zmq_pub) + return true; + + if (tx_blobs.size() != tx_hashes.size() || tx_blobs.size() != txs.size() || tx_blobs.size() != just_broadcasted.size()) + return false; + + /* Publish txs via ZMQ that are "just broadcasted" by the daemon. This is + done here in addition to `handle_incoming_txs` in order to guarantee txs + are pub'd via ZMQ when we know the daemon has/will broadcast to other + nodes & *after* the tx is visible in the pool. This should get called + when the user submits a tx to a daemon in the "fluff" epoch relaying txs + via a public network. */ + if (std::count(just_broadcasted.begin(), just_broadcasted.end(), true) == 0) + return true; + + std::vector<txpool_event> results{}; + results.resize(tx_blobs.size()); + for (std::size_t i = 0; i < results.size(); ++i) + { + results[i].tx = std::move(txs[i]); + results[i].hash = std::move(tx_hashes[i]); + results[i].blob_size = tx_blobs[i].size(); + results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, results[i].blob_size); + results[i].res = just_broadcasted[i]; + } + + m_zmq_pub(std::move(results)); + + return true; + } + //----------------------------------------------------------------------------------------------- void core::on_transactions_relayed(const epee::span<const cryptonote::blobdata> tx_blobs, const relay_method tx_relay) { + // lock ensures duplicate txs aren't pub'd via zmq + CRITICAL_REGION_LOCAL(m_incoming_tx_lock); + std::vector<crypto::hash> tx_hashes{}; tx_hashes.resize(tx_blobs.size()); + std::vector<cryptonote::transaction> txs{}; + txs.resize(tx_blobs.size()); + for (std::size_t i = 0; i < tx_blobs.size(); ++i) { - cryptonote::transaction tx{}; - if (!parse_and_validate_tx_from_blob(tx_blobs[i], tx, tx_hashes[i])) + if (!parse_and_validate_tx_from_blob(tx_blobs[i], txs[i], tx_hashes[i])) { LOG_ERROR("Failed to parse relayed transaction"); return; } } - m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay); + + std::vector<bool> just_broadcasted{}; + just_broadcasted.reserve(tx_hashes.size()); + + m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay, just_broadcasted); + + if (m_zmq_pub && matches_category(tx_relay, relay_category::legacy)) + notify_txpool_event(tx_blobs, epee::to_span(tx_hashes), epee::to_span(txs), just_broadcasted); } //----------------------------------------------------------------------------------------------- bool core::get_block_template(block& b, const account_public_address& adr, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash) @@ -1607,10 +1656,6 @@ namespace cryptonote if (((size_t)-1) <= 0xffffffff && block_blob.size() >= 0x3fffffff) MWARNING("This block's size is " << block_blob.size() << ", closing on the 32 bit limit"); - // load json & DNS checkpoints every 10min/hour respectively, - // and verify them with respect to what blocks we already have - CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); - block lb; if (!b) { diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index 0b36730b6..6dc513570 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -437,6 +437,13 @@ namespace cryptonote void set_cryptonote_protocol(i_cryptonote_protocol* pprotocol); /** + * @copydoc Blockchain::get_checkpoints + * + * @note see Blockchain::get_checkpoints() + */ + const checkpoints& get_checkpoints() const; + + /** * @copydoc Blockchain::set_checkpoints * * @note see Blockchain::set_checkpoints() @@ -1036,6 +1043,13 @@ namespace cryptonote bool relay_txpool_transactions(); /** + * @brief sends notification of txpool events to subscribers + * + * @return true on success, false otherwise + */ + bool notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const; + + /** * @brief checks DNS versions * * @return true on success, false otherwise diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index a68da0e62..2a514ceae 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -402,6 +402,19 @@ namespace cryptonote m_txpool_max_weight = bytes; } //--------------------------------------------------------------------------------- + void tx_memory_pool::reduce_txpool_weight(size_t weight) + { + if (weight > m_txpool_weight) + { + MERROR("Underflow in txpool weight"); + m_txpool_weight = 0; + } + else + { + m_txpool_weight -= weight; + } + } + //--------------------------------------------------------------------------------- void tx_memory_pool::prune(size_t bytes) { CRITICAL_REGION_LOCAL(m_transactions_lock); @@ -423,8 +436,14 @@ namespace cryptonote txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(txid, meta)) { - MERROR("Failed to find tx_meta in txpool"); - return; + static bool warned = false; + if (!warned) + { + MERROR("Failed to find tx_meta in txpool (will only print once)"); + warned = true; + } + --it; + continue; } // don't prune the kept_by_block ones, they're likely added because we're adding a block with those if (meta.kept_by_block) @@ -442,7 +461,7 @@ namespace cryptonote // remove first, in case this throws, so key images aren't removed MINFO("Pruning tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= meta.weight; + reduce_txpool_weight(meta.weight); remove_transaction_keyimages(tx, txid); MINFO("Pruned tx " << txid << " from txpool: weight: " << meta.weight << ", fee/byte: " << it->first.first); m_txs_by_fee_and_receive_time.erase(it--); @@ -562,7 +581,7 @@ namespace cryptonote // remove first, in case this throws, so key images aren't removed m_blockchain.remove_txpool_tx(id); - m_txpool_weight -= tx_weight; + reduce_txpool_weight(tx_weight); remove_transaction_keyimages(tx, id); lock.commit(); } @@ -725,7 +744,7 @@ namespace cryptonote { // remove first, so we only remove key images if the tx removal succeeds m_blockchain.remove_txpool_tx(txid); - m_txpool_weight -= entry.second; + reduce_txpool_weight(entry.second); remove_transaction_keyimages(tx, txid); } } @@ -820,8 +839,10 @@ namespace cryptonote return true; } //--------------------------------------------------------------------------------- - void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method) + void tx_memory_pool::set_relayed(const epee::span<const crypto::hash> hashes, const relay_method method, std::vector<bool> &just_broadcasted) { + just_broadcasted.clear(); + crypto::random_poisson_seconds embargo_duration{dandelionpp_embargo_average}; const auto now = std::chrono::system_clock::now(); uint64_t next_relay = uint64_t{std::numeric_limits<time_t>::max()}; @@ -831,12 +852,14 @@ namespace cryptonote LockedTXN lock(m_blockchain.get_db()); for (const auto& hash : hashes) { + bool was_just_broadcasted = false; try { txpool_tx_meta_t meta; if (m_blockchain.get_txpool_tx_meta(hash, meta)) { // txes can be received as "stem" or "fluff" in either order + const bool already_broadcasted = meta.matches(relay_category::broadcasted); meta.upgrade_relay_method(method); meta.relayed = true; @@ -849,6 +872,9 @@ namespace cryptonote meta.last_relayed_time = std::chrono::system_clock::to_time_t(now); m_blockchain.update_txpool_tx(hash, meta); + + // wait until db update succeeds to ensure tx is visible in the pool + was_just_broadcasted = !already_broadcasted && meta.matches(relay_category::broadcasted); } } catch (const std::exception &e) @@ -856,6 +882,7 @@ namespace cryptonote MERROR("Failed to update txpool transaction metadata: " << e.what()); // continue } + just_broadcasted.emplace_back(was_just_broadcasted); } lock.commit(); set_if_less(m_next_check, time_t(next_relay)); @@ -917,26 +944,61 @@ namespace cryptonote { CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); - const relay_category category = include_sensitive ? relay_category::all : relay_category::broadcasted; - backlog.reserve(m_blockchain.get_txpool_tx_count(include_sensitive)); - txpool_tx_meta_t tmp_meta; - m_blockchain.for_all_txpool_txes([this, &backlog, &tmp_meta](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref *bd){ - transaction tx; - if (!(meta.pruned ? parse_and_validate_tx_base_from_blob(*bd, tx) : parse_and_validate_tx_from_blob(*bd, tx))) + + std::vector<tx_block_template_backlog_entry> tmp; + uint64_t total_weight = 0; + + // First get everything from the mempool, filter it later + m_blockchain.for_all_txpool_txes([&tmp, &total_weight](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*){ + tmp.emplace_back(tx_block_template_backlog_entry{txid, meta.weight, meta.fee}); + total_weight += meta.weight; + return true; + }, false, include_sensitive ? relay_category::all : relay_category::broadcasted); + + // Limit backlog to 112.5% of current median weight. This is enough to mine a full block with the optimal block reward + const uint64_t median_weight = m_blockchain.get_current_cumulative_block_weight_median(); + const uint64_t max_backlog_weight = median_weight + (median_weight / 8); + + // If the total weight is too high, choose the best paying transactions + if (total_weight > max_backlog_weight) + std::sort(tmp.begin(), tmp.end(), [](const auto& a, const auto& b){ return a.fee * b.weight > b.fee * a.weight; }); + + backlog.clear(); + uint64_t w = 0; + + std::unordered_set<crypto::key_image> k_images; + + for (const tx_block_template_backlog_entry& e : tmp) + { + try { - MERROR("Failed to parse tx from txpool"); - // continue - return true; - } - tx.set_hash(txid); + txpool_tx_meta_t meta; + if (!m_blockchain.get_txpool_tx_meta(e.id, meta)) + continue; - tmp_meta = meta; + cryptonote::blobdata txblob; + if (!m_blockchain.get_txpool_tx_blob(e.id, txblob, relay_category::all)) + continue; - if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx)) - backlog.push_back({txid, meta.weight, meta.fee}); + cryptonote::transaction tx; + if (is_transaction_ready_to_go(meta, e.id, txblob, tx)) + { + if (have_key_images(k_images, tx)) + continue; + append_key_images(k_images, tx); - return true; - }, true, category); + backlog.push_back(e); + w += e.weight; + if (w > max_backlog_weight) + break; + } + } + catch (const std::exception &e) + { + MERROR("Failed to check transaction readiness: " << e.what()); + // continue, not fatal + } + } } //------------------------------------------------------------------ void tx_memory_pool::get_transaction_stats(struct txpool_stats& stats, bool include_sensitive) const diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 62bef6c06..45623fd14 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -266,7 +266,11 @@ namespace cryptonote void get_transaction_backlog(std::vector<tx_backlog_entry>& backlog, bool include_sensitive = false) const; /** - * @brief get (hash, weight, fee) for all transactions in the pool - the minimum required information to create a block template + * @brief get (hash, weight, fee) for transactions in the pool - the minimum required information to create a block template + * + * Not all transactions in the pool will be returned for performance reasons + * If there are too many transactions in the pool, only the highest-paying transactions + * will be returned - but enough for the miner to create a full block * * @param backlog return-by-reference that data * @param include_sensitive return stempool, anonymity-pool, and unrelayed txes @@ -353,8 +357,10 @@ namespace cryptonote * * @param hashes list of tx hashes that are about to be relayed * @param tx_relay update how the tx left this node + * @param just_broadcasted true if a tx was just broadcasted + * */ - void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay); + void set_relayed(epee::span<const crypto::hash> hashes, relay_method tx_relay, std::vector<bool> &just_broadcasted); /** * @brief get the total number of transactions in the pool @@ -406,6 +412,13 @@ namespace cryptonote */ void set_txpool_max_weight(size_t bytes); + /** + * @brief reduce the cumulative txpool weight by the weight provided + * + * @param weight the weight to reduce the total txpool weight by + */ + void reduce_txpool_weight(size_t weight); + #define CURRENT_MEMPOOL_ARCHIVE_VER 11 #define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13 diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index af3031263..bd2f8ce85 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -537,6 +537,10 @@ namespace cryptonote MLOG_PEER_STATE("requesting chain"); } + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); + return 1; } //------------------------------------------------------------------------------------------------------------------------ @@ -819,6 +823,10 @@ namespace cryptonote post_notify<NOTIFY_REQUEST_CHAIN>(r, context); MLOG_PEER_STATE("requesting chain"); } + + // load json & DNS checkpoints every 10min/hour respectively, + // and verify them with respect to what blocks we already have + CHECK_AND_ASSERT_MES(m_core.update_checkpoints(), 1, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints."); } } else diff --git a/src/daemon/main.cpp b/src/daemon/main.cpp index 73d9ebce1..3d90e0855 100644 --- a/src/daemon/main.cpp +++ b/src/daemon/main.cpp @@ -83,7 +83,7 @@ uint16_t parse_public_rpc_port(const po::variables_map &vm) } uint16_t rpc_port; - if (!string_tools::get_xtype_from_string(rpc_port, rpc_port_str)) + if (!epee::string_tools::get_xtype_from_string(rpc_port, rpc_port_str)) { throw std::runtime_error("invalid RPC port " + rpc_port_str); } diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 4fe0b5f72..a88c35638 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -1063,7 +1063,7 @@ bool t_rpc_command_executor::print_transaction(crypto::hash transaction_hash, cryptonote::blobdata blob; std::string source = as_hex.empty() ? pruned_as_hex + prunable_as_hex : as_hex; bool pruned = !pruned_as_hex.empty() && prunable_as_hex.empty(); - if (!string_tools::parse_hexstr_to_binbuff(source, blob)) + if (!epee::string_tools::parse_hexstr_to_binbuff(source, blob)) { tools::fail_msg_writer() << "Failed to parse tx to get json format"; } diff --git a/src/device/device.hpp b/src/device/device.hpp index eca91006f..392703a24 100644 --- a/src/device/device.hpp +++ b/src/device/device.hpp @@ -177,6 +177,7 @@ namespace hw { virtual bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) = 0; virtual bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) = 0; virtual bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) = 0; + virtual bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) = 0; // alternative prototypes available in libringct rct::key scalarmultKey(const rct::key &P, const rct::key &a) diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp index 7d3543652..58149cdbf 100644 --- a/src/device/device_default.hpp +++ b/src/device/device_default.hpp @@ -101,7 +101,7 @@ namespace hw { bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override; bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override; bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override; - bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag); + bool derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag) override; /* ======================================================================= */ diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp index aa73e998c..22a1fd986 100644 --- a/src/device/device_ledger.cpp +++ b/src/device/device_ledger.cpp @@ -43,6 +43,10 @@ namespace hw { #ifdef WITH_DEVICE_LEDGER + namespace { + bool apdu_verbose =true; + } + #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.ledger" @@ -266,6 +270,7 @@ namespace hw { #define INS_DERIVE_PUBLIC_KEY 0x36 #define INS_DERIVE_SECRET_KEY 0x38 #define INS_GEN_KEY_IMAGE 0x3A + #define INS_DERIVE_VIEW_TAG 0x3B #define INS_SECRET_KEY_ADD 0x3C #define INS_SECRET_KEY_SUB 0x3E #define INS_GENERATE_KEYPAIR 0x40 @@ -1308,6 +1313,54 @@ namespace hw { return true; } + bool device_ledger::derive_view_tag(const crypto::key_derivation &derivation, const std::size_t output_index, crypto::view_tag &view_tag){ + #ifdef DEBUG_HWDEVICE + crypto::key_derivation derivation_x; + if ((this->mode == TRANSACTION_PARSE) && has_view_key) { + derivation_x = derivation; + } else { + derivation_x = hw::ledger::decrypt(derivation); + } + const std::size_t output_index_x = output_index; + crypto::view_tag view_tag_x; + log_hexbuffer("derive_view_tag: [[IN]] derivation ", derivation_x.data, 32); + log_message ("derive_view_tag: [[IN]] output_index", std::to_string(output_index_x)); + this->controle_device->derive_view_tag(derivation_x, output_index_x, view_tag_x); + log_hexbuffer("derive_view_tag: [[OUT]] view_tag ", &view_tag_x.data, 1); + #endif + + if ((this->mode == TRANSACTION_PARSE) && has_view_key) { + //If we are in TRANSACTION_PARSE, the given derivation has been retrieved uncrypted (wihtout the help + //of the device), so continue that way. + MDEBUG( "derive_view_tag : PARSE mode with known viewkey"); + crypto::derive_view_tag(derivation, output_index, view_tag); + } else { + AUTO_LOCK_CMD(); + int offset = set_command_header_noopt(INS_DERIVE_VIEW_TAG); + //derivation + this->send_secret((unsigned char*)derivation.data, offset); + //index + this->buffer_send[offset+0] = output_index>>24; + this->buffer_send[offset+1] = output_index>>16; + this->buffer_send[offset+2] = output_index>>8; + this->buffer_send[offset+3] = output_index>>0; + offset += 4; + + this->buffer_send[4] = offset-5; + this->length_send = offset; + this->exchange(); + + //view tag + memmove(&view_tag.data, &this->buffer_recv[0], 1); + } + + #ifdef DEBUG_HWDEVICE + hw::ledger::check1("derive_view_tag", "view_tag", &view_tag_x.data, &view_tag.data); + #endif + + return true; + } + /* ======================================================================= */ /* TRANSACTION */ /* ======================================================================= */ @@ -1548,7 +1601,6 @@ namespace hw { const size_t output_index_x = output_index; const bool need_additional_txkeys_x = need_additional_txkeys; const bool use_view_tags_x = use_view_tags; - const crypto::view_tag view_tag_x = view_tag; std::vector<crypto::secret_key> additional_tx_keys_x; for (const auto &k: additional_tx_keys) { @@ -1558,6 +1610,7 @@ namespace hw { std::vector<crypto::public_key> additional_tx_public_keys_x; std::vector<rct::key> amount_keys_x; crypto::public_key out_eph_public_key_x; + crypto::view_tag view_tag_x; log_message ("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version_x)); //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data, 32); @@ -1575,11 +1628,15 @@ namespace hw { if(need_additional_txkeys_x) { log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32); } + log_message ("generate_output_ephemeral_keys: [[IN]] use_view_tags", std::to_string(use_view_tags_x)); this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x, additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x, use_view_tags_x, view_tag_x); if(need_additional_txkeys_x) { log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32); } + if(use_view_tags_x) { + log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] view_tag", &view_tag_x.data, 1); + } log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", (char*)amount_keys_x.back().bytes, 32); log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data, 32); #endif @@ -1633,6 +1690,9 @@ namespace hw { memset(&this->buffer_send[offset], 0, 32); offset += 32; } + //use_view_tags + this->buffer_send[offset] = use_view_tags; + offset++; this->buffer_send[4] = offset-5; this->length_send = offset; @@ -1663,6 +1723,14 @@ namespace hw { recv_len -= 32; } + if (use_view_tags) + { + ASSERT_X(recv_len>=1, "Not enough data from device"); + memmove(&view_tag.data, &this->buffer_recv[offset], 1); + offset++; + recv_len -= 1; + } + // add ABPkeys this->add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change, need_additional_txkeys, output_index, @@ -1675,6 +1743,9 @@ namespace hw { hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data, additional_tx_public_keys.back().data); } hw::ledger::check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data, out_eph_public_key.data); + if (use_view_tags) { + hw::ledger::check1("generate_output_ephemeral_keys", "view_tag", &view_tag_x.data, &view_tag.data); + } #endif return true; @@ -1860,7 +1931,7 @@ namespace hw { // ====== Aout, Bout, AKout, C, v, k ====== kv_offset = data_offset; - if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) { C_offset = kv_offset+ (8)*outputs_size; } else { C_offset = kv_offset+ (32+32)*outputs_size; @@ -1877,7 +1948,7 @@ namespace hw { offset = set_command_header(INS_VALIDATE, 0x02, i+1); //options this->buffer_send[offset] = (i==outputs_size-1)? 0x00:0x80 ; - this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG)?0x02:0x00; + this->buffer_send[offset] |= (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus)?0x02:0x00; offset += 1; //is_subaddress this->buffer_send[offset] = outKeys.is_subaddress; @@ -1898,7 +1969,7 @@ namespace hw { memmove(this->buffer_send+offset, data+C_offset,32); offset += 32; C_offset += 32; - if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG) { + if (type==rct::RCTTypeBulletproof2 || type==rct::RCTTypeCLSAG || type==rct::RCTTypeBulletproofPlus) { //k memset(this->buffer_send+offset, 0, 32); offset += 32; diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp index 074bfaa8d..5a1e7d75c 100644 --- a/src/device/device_ledger.hpp +++ b/src/device/device_ledger.hpp @@ -44,7 +44,7 @@ namespace hw { /* Minimal supported version */ #define MINIMAL_APP_VERSION_MAJOR 1 - #define MINIMAL_APP_VERSION_MINOR 6 + #define MINIMAL_APP_VERSION_MINOR 8 #define MINIMAL_APP_VERSION_MICRO 0 #define VERSION(M,m,u) ((M)<<16|(m)<<8|(u)) @@ -87,10 +87,6 @@ namespace hw { #define SW_PROTOCOL_NOT_SUPPORTED 0x6e00 #define SW_UNKNOWN 0x6f00 - namespace { - bool apdu_verbose =true; - } - void set_apdu_verbose(bool verbose); class ABPkeys { @@ -249,6 +245,7 @@ namespace hw { bool derive_public_key(const crypto::key_derivation &derivation, const std::size_t output_index, const crypto::public_key &pub, crypto::public_key &derived_pub) override; bool secret_key_to_public_key(const crypto::secret_key &sec, crypto::public_key &pub) override; bool generate_key_image(const crypto::public_key &pub, const crypto::secret_key &sec, crypto::key_image &image) override; + bool derive_view_tag(const crypto::key_derivation &derivation, const size_t output_index, crypto::view_tag &view_tag) override; /* ======================================================================= */ /* TRANSACTION */ diff --git a/src/device/log.cpp b/src/device/log.cpp index 9b882b784..b159632d9 100644 --- a/src/device/log.cpp +++ b/src/device/log.cpp @@ -165,6 +165,10 @@ namespace hw { void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) { check(msg, info, h, d, 8, crypted); } + + void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted) { + check(msg, info, h, d, 1, crypted); + } #endif } diff --git a/src/device/log.hpp b/src/device/log.hpp index 660adc63e..a0c89ec71 100644 --- a/src/device/log.hpp +++ b/src/device/log.hpp @@ -75,6 +75,7 @@ namespace hw { void check32(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); void check8(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); + void check1(const std::string &msg, const std::string &info, const char *h, const char *d, bool crypted=false); void set_check_verbose(bool verbose); #endif diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index 6f7ae9a6b..3f7b10be4 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -324,8 +324,8 @@ namespace trezor { std::vector<protocol::ki::MoneroTransferDetails> mtds; std::vector<protocol::ki::MoneroExportedKeyImage> kis; - protocol::ki::key_image_data(wallet, transfers, mtds, client_version() <= 1); - protocol::ki::generate_commitment(mtds, transfers, req, client_version() <= 1); + protocol::ki::key_image_data(wallet, transfers, mtds); + protocol::ki::generate_commitment(mtds, transfers, req); EVENT_PROGRESS(0.); this->set_msg_addr<messages::monero::MoneroKeyImageExportInitRequest>(req.get()); @@ -511,7 +511,7 @@ namespace trezor { tools::wallet2::signed_tx_set & signed_tx, hw::tx_aux_data & aux_data) { - CHECK_AND_ASSERT_THROW_MES(unsigned_tx.transfers.first == 0, "Unsuported non zero offset"); + CHECK_AND_ASSERT_THROW_MES(std::get<0>(unsigned_tx.transfers) == 0, "Unsuported non zero offset"); TREZOR_AUTO_LOCK_CMD(); require_connected(); @@ -522,7 +522,7 @@ namespace trezor { const size_t num_tx = unsigned_tx.txes.size(); m_num_transations_to_sign = num_tx; signed_tx.key_images.clear(); - signed_tx.key_images.resize(unsigned_tx.transfers.second.size()); + signed_tx.key_images.resize(std::get<2>(unsigned_tx.transfers).size()); for(size_t tx_idx = 0; tx_idx < num_tx; ++tx_idx) { std::shared_ptr<protocol::tx::Signer> signer; @@ -566,8 +566,8 @@ namespace trezor { cpend.key_images = key_images; // KI sync - for(size_t cidx=0, trans_max=unsigned_tx.transfers.second.size(); cidx < trans_max; ++cidx){ - signed_tx.key_images[cidx] = unsigned_tx.transfers.second[cidx].m_key_image; + for(size_t cidx=0, trans_max=std::get<2>(unsigned_tx.transfers).size(); cidx < trans_max; ++cidx){ + signed_tx.key_images[cidx] = std::get<2>(unsigned_tx.transfers)[cidx].m_key_image; } size_t num_sources = cdata.tx_data.sources.size(); @@ -579,9 +579,9 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(src_idx < cdata.tx.vin.size(), "Invalid idx_mapped"); size_t idx_map_src = cdata.tx_data.selected_transfers[idx_mapped]; - CHECK_AND_ASSERT_THROW_MES(idx_map_src >= unsigned_tx.transfers.first, "Invalid offset"); + CHECK_AND_ASSERT_THROW_MES(idx_map_src >= std::get<0>(unsigned_tx.transfers), "Invalid offset"); - idx_map_src -= unsigned_tx.transfers.first; + idx_map_src -= std::get<0>(unsigned_tx.transfers); CHECK_AND_ASSERT_THROW_MES(idx_map_src < signed_tx.key_images.size(), "Invalid key image index"); const auto vini = boost::get<cryptonote::txin_to_key>(cdata.tx.vin[src_idx]); @@ -635,11 +635,7 @@ namespace trezor { } // Step: sort - auto perm_req = signer->step_permutation(); - if (perm_req){ - auto perm_ack = this->client_exchange<messages::monero::MoneroTransactionInputsPermutationAck>(perm_req); - signer->step_permutation_ack(perm_ack); - } + signer->sort_ki(); EVENT_PROGRESS(3, 1, 1); // Step: input_vini @@ -697,13 +693,13 @@ namespace trezor { unsigned device_trezor::client_version() { auto trezor_version = get_version(); - if (trezor_version <= pack_version(2, 0, 10)){ - throw exc::TrezorException("Trezor firmware 2.0.10 and lower are not supported. Please update."); + if (trezor_version < pack_version(2, 4, 3)){ + throw exc::TrezorException("Minimal Trezor firmware version is 2.4.3. Please update."); } - unsigned client_version = 1; - if (trezor_version >= pack_version(2, 3, 1)){ - client_version = 3; + unsigned client_version = 3; + if (trezor_version >= pack_version(2, 5, 2)){ + client_version = 4; } #ifdef WITH_TREZOR_DEBUGGING @@ -739,14 +735,6 @@ namespace trezor { CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty"); CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data"); CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features - const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0; - - if (nonce_required && init_msg->tsx_data().payment_id().size() == 8){ - // Versions 2.0.9 and lower do not support payment ID - if (get_version() <= pack_version(2, 0, 9)) { - throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update."); - } - } } void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data) diff --git a/src/device_trezor/trezor/protocol.cpp b/src/device_trezor/trezor/protocol.cpp index a400e82c7..0e59a16ba 100644 --- a/src/device_trezor/trezor/protocol.cpp +++ b/src/device_trezor/trezor/protocol.cpp @@ -38,6 +38,7 @@ #include <crypto/hmac-keccak.h> #include <ringct/rctSigs.h> #include <ringct/bulletproofs.h> +#include <ringct/bulletproofs_plus.h> #include "cryptonote_config.h" #include <sodium.h> #include <sodium/crypto_verify_32.h> @@ -145,8 +146,7 @@ namespace ki { bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals) + std::vector<MoneroTransferDetails> & res) { for(auto & td : transfers){ ::crypto::public_key tx_pub_key = wallet->get_tx_pub_key_from_received_outs(td); @@ -159,11 +159,7 @@ namespace ki { cres.set_internal_output_index(td.m_internal_output_index); cres.set_sub_addr_major(td.m_subaddr_index.major); cres.set_sub_addr_minor(td.m_subaddr_index.minor); - if (need_all_additionals) { - for (auto &aux : additional_tx_pub_keys) { - cres.add_additional_tx_pub_keys(key_to_string(aux)); - } - } else if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { + if (!additional_tx_pub_keys.empty() && additional_tx_pub_keys.size() > td.m_internal_output_index) { cres.add_additional_tx_pub_keys(key_to_string(additional_tx_pub_keys[td.m_internal_output_index])); } } @@ -194,8 +190,7 @@ namespace ki { void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices) + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req) { req = std::make_shared<messages::monero::MoneroKeyImageExportInitRequest>(); @@ -219,16 +214,6 @@ namespace ki { auto & st = search.first->second; st.insert(cur.m_subaddr_index.minor); } - - if (need_subaddr_indices) { - for (auto &x: sub_indices) { - auto subs = req->add_subs(); - subs->set_account(x.first); - for (auto minor : x.second) { - subs->add_minor_indices(minor); - } - } - } } void live_refresh_ack(const ::crypto::secret_key & view_key_priv, @@ -399,7 +384,7 @@ namespace tx { m_tx_idx = tx_idx; m_ct.tx_data = cur_src_tx(); m_multisig = false; - m_client_version = 1; + m_client_version = 3; } void Signer::extract_payment_id(){ @@ -474,25 +459,19 @@ namespace tx { auto & cur = src.outputs[i]; auto out = dst->add_outputs(); - if (i == src.real_output || need_ring_indices || client_version() <= 1) { + if (i == src.real_output || need_ring_indices) { out->set_idx(cur.first); } - if (i == src.real_output || need_ring_keys || client_version() <= 1) { + if (i == src.real_output || need_ring_keys) { translate_rct_key(out->mutable_key(), &(cur.second)); } } dst->set_real_out_tx_key(key_to_string(src.real_out_tx_key)); dst->set_real_output_in_tx_index(src.real_output_in_tx_index); - - if (client_version() <= 1) { - for (auto &cur : src.real_out_additional_tx_keys) { - dst->add_real_out_additional_tx_keys(key_to_string(cur)); - } - } else if (!src.real_out_additional_tx_keys.empty()) { + if (!src.real_out_additional_tx_keys.empty()) { dst->add_real_out_additional_tx_keys(key_to_string(src.real_out_additional_tx_keys.at(src.real_output_in_tx_index))); } - dst->set_amount(src.amount); dst->set_rct(src.rct); dst->set_mask(key_to_string(src.mask)); @@ -532,7 +511,7 @@ namespace tx { m_ct.tx.version = 2; m_ct.tx.unlock_time = tx.unlock_time; - m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 1); + m_client_version = (m_aux_data->client_version ? m_aux_data->client_version.get() : 3); tsx_data.set_version(1); tsx_data.set_client_version(client_version()); @@ -543,18 +522,13 @@ namespace tx { tsx_data.set_monero_version(std::string(MONERO_VERSION) + "|" + MONERO_VERSION_TAG); tsx_data.set_hard_fork(m_aux_data->hard_fork ? m_aux_data->hard_fork.get() : 0); - if (client_version() <= 1){ - assign_to_repeatable(tsx_data.mutable_minor_indices(), tx.subaddr_indices.begin(), tx.subaddr_indices.end()); - } - // Rsig decision auto rsig_data = tsx_data.mutable_rsig_data(); m_ct.rsig_type = get_rsig_type(tx.rct_config, tx.splitted_dsts.size()); rsig_data->set_rsig_type(m_ct.rsig_type); - if (tx.rct_config.range_proof_type != rct::RangeProofBorromean){ - m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); - rsig_data->set_bp_version((uint32_t) m_ct.bp_version); - } + CHECK_AND_ASSERT_THROW_MES(tx.rct_config.range_proof_type != rct::RangeProofBorromean, "Borromean rsig not supported"); + m_ct.bp_version = (m_aux_data->bp_version ? m_aux_data->bp_version.get() : 1); + rsig_data->set_bp_version((uint32_t) m_ct.bp_version); generate_rsig_batch_sizes(m_ct.grouping_vct, m_ct.rsig_type, tx.splitted_dsts.size()); assign_to_repeatable(rsig_data->mutable_grouping(), m_ct.grouping_vct.begin(), m_ct.grouping_vct.end()); @@ -652,22 +626,6 @@ namespace tx { }); } - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> Signer::step_permutation(){ - sort_ki(); - if (client_version() >= 2){ - return nullptr; - } - - auto res = std::make_shared<messages::monero::MoneroTransactionInputsPermutationRequest>(); - assign_to_repeatable(res->mutable_perm(), m_ct.source_permutation.begin(), m_ct.source_permutation.end()); - - return res; - } - - void Signer::step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack){ - - } - std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> Signer::step_set_vini_input(size_t idx){ CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx_data.sources.size(), "Invalid transaction index"); CHECK_AND_ASSERT_THROW_MES(idx < m_ct.tx.vin.size(), "Invalid transaction index"); @@ -711,8 +669,10 @@ namespace tx { } void Signer::step_set_output_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetOutputAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); cryptonote::tx_out tx_out; rct::Bulletproof bproof{}; + rct::BulletproofPlus bproof_plus{}; rct::ctkey out_pk{}; rct::ecdhTuple ecdh{}; @@ -727,7 +687,7 @@ namespace tx { rsig_buff = rsig_data.rsig(); } - if (client_version() >= 1 && rsig_data.has_mask()){ + if (rsig_data.has_mask()){ rct::key cmask{}; string_to_key(cmask, rsig_data.mask()); m_ct.rsig_gamma.emplace_back(cmask); @@ -751,22 +711,32 @@ namespace tx { memcpy(ecdh.amount.bytes, ack->ecdh_info().data(), 8); } - if (has_rsig && is_req_bulletproof() && !cn_deserialize(rsig_buff, bproof)){ - throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); - } - m_ct.tx.vout.emplace_back(tx_out); m_ct.tx_out_hmacs.push_back(ack->vouti_hmac()); m_ct.tx_out_pk.emplace_back(out_pk); m_ct.tx_out_ecdh.emplace_back(ecdh); - // ClientV0, if no rsig was generated on Trezor, do not continue. - // ClientV1+ generates BP after all masks in the current batch are generated - if (!has_rsig || (client_version() >= 1 && is_offloading())){ + rsig_v bp_obj{}; + if (has_rsig) { + bool deserialize_success; + if (is_req_bulletproof_plus()) { + deserialize_success = cn_deserialize(rsig_buff, bproof_plus); + bp_obj = bproof_plus; + } else { + deserialize_success = cn_deserialize(rsig_buff, bproof); + bp_obj = bproof; + } + if (!deserialize_success) { + throw exc::ProtocolException("Cannot deserialize bulletproof rangesig"); + } + } + + // Generates BP after all masks in the current batch are generated + if (!has_rsig || is_offloading()){ return; } - process_bproof(bproof); + process_bproof(bp_obj); m_ct.cur_batch_idx += 1; m_ct.cur_output_in_batch_idx = 0; } @@ -791,13 +761,21 @@ namespace tx { masks.push_back(m_ct.rsig_gamma[bidx]); } - auto bp = bulletproof_PROVE(amounts, masks); - auto serRsig = cn_serialize(bp); - m_ct.tx_out_rsigs.emplace_back(bp); + std::string serRsig; + if (is_req_bulletproof_plus()) { + auto bp = bulletproof_plus_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } else { + auto bp = bulletproof_PROVE(amounts, masks); + serRsig = cn_serialize(bp); + m_ct.tx_out_rsigs.emplace_back(bp); + } + rsig_data.set_rsig(serRsig); } - void Signer::process_bproof(rct::Bulletproof & bproof){ + void Signer::process_bproof(rsig_v & bproof){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_batch_idx < m_ct.grouping_vct.size(), "Invalid batch index"); auto batch_size = m_ct.grouping_vct[m_ct.cur_batch_idx]; for (size_t i = 0; i < batch_size; ++i){ @@ -806,12 +784,22 @@ namespace tx { rct::key commitment = m_ct.tx_out_pk[bidx].mask; commitment = rct::scalarmultKey(commitment, rct::INV_EIGHT); - bproof.V.push_back(commitment); + if (is_req_bulletproof_plus()) { + boost::get<rct::BulletproofPlus>(bproof).V.push_back(commitment); + } else { + boost::get<rct::Bulletproof>(bproof).V.push_back(commitment); + } } m_ct.tx_out_rsigs.emplace_back(bproof); - if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { - throw exc::ProtocolException("Returned range signature is invalid"); + if (is_req_bulletproof_plus()) { + if (!rct::bulletproof_plus_VERIFY(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } + } else { + if (!rct::bulletproof_VERIFY(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs.back()))) { + throw exc::ProtocolException("Returned range signature is invalid"); + } } } @@ -840,6 +828,7 @@ namespace tx { } void Signer::step_all_outs_set_ack(std::shared_ptr<const messages::monero::MoneroTransactionAllOutSetAck> ack, hw::device &hwdev){ + CHECK_AND_ASSERT_THROW_MES(is_req_bulletproof(), "Borromean rsig not supported"); m_ct.rv = std::make_shared<rct::rctSig>(); m_ct.rv->txnFee = ack->rv().txn_fee(); m_ct.rv->type = static_cast<uint8_t>(ack->rv().rv_type()); @@ -864,24 +853,15 @@ namespace tx { // RctSig auto num_sources = m_ct.tx_data.sources.size(); - if (is_simple() || is_req_bulletproof()){ - auto dst = &m_ct.rv->pseudoOuts; - if (is_bulletproof()){ - dst = &m_ct.rv->p.pseudoOuts; - } - - dst->clear(); - for (const auto &pseudo_out : m_ct.pseudo_outs) { - dst->emplace_back(); - string_to_key(dst->back(), pseudo_out); - } - - m_ct.rv->mixRing.resize(num_sources); - } else { - m_ct.rv->mixRing.resize(m_ct.tsx_data.mixin()); - m_ct.rv->mixRing[0].resize(num_sources); + auto dst = &m_ct.rv->p.pseudoOuts; + dst->clear(); + for (const auto &pseudo_out : m_ct.pseudo_outs) { + dst->emplace_back(); + string_to_key(dst->back(), pseudo_out); } + m_ct.rv->mixRing.resize(num_sources); + CHECK_AND_ASSERT_THROW_MES(m_ct.tx_out_pk.size() == m_ct.tx_out_ecdh.size(), "Invalid vector sizes"); for(size_t i = 0; i < m_ct.tx_out_ecdh.size(); ++i){ m_ct.rv->outPk.push_back(m_ct.tx_out_pk[i]); @@ -889,10 +869,10 @@ namespace tx { } for(size_t i = 0; i < m_ct.tx_out_rsigs.size(); ++i){ - if (is_bulletproof()){ - m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); + if (is_req_bulletproof_plus()) { + m_ct.rv->p.bulletproofs_plus.push_back(boost::get<rct::BulletproofPlus>(m_ct.tx_out_rsigs[i])); } else { - m_ct.rv->p.rangeSigs.push_back(boost::get<rct::rangeSig>(m_ct.tx_out_rsigs[i])); + m_ct.rv->p.bulletproofs.push_back(boost::get<rct::Bulletproof>(m_ct.tx_out_rsigs[i])); } } @@ -936,8 +916,8 @@ namespace tx { void Signer::step_sign_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSignInputAck> ack){ m_ct.signatures.push_back(ack->signature()); - // Sync updated pseudo_outputs, client_version>=1, HF10+ - if (client_version() >= 1 && ack->has_pseudo_out()){ + // Sync updated pseudo_outputs + if (ack->has_pseudo_out()){ CHECK_AND_ASSERT_THROW_MES(m_ct.cur_input_idx < m_ct.pseudo_outs.size(), "Invalid pseudo-out index"); m_ct.pseudo_outs[m_ct.cur_input_idx] = ack->pseudo_out(); if (is_bulletproof()){ @@ -955,6 +935,8 @@ namespace tx { } void Signer::step_final_ack(std::shared_ptr<const messages::monero::MoneroTransactionFinalAck> ack){ + CHECK_AND_ASSERT_THROW_MES(is_clsag(), "Only CLSAGs signatures are supported"); + if (m_multisig){ auto & cout_key = ack->cout_key(); for(auto & cur : m_ct.couts){ @@ -975,47 +957,34 @@ namespace tx { m_ct.enc_keys = ack->tx_enc_keys(); // Opening the sealed signatures - if (client_version() >= 3){ - if(!ack->has_opening_key()){ - throw exc::ProtocolException("Client version 3+ requires sealed signatures"); - } + if(!ack->has_opening_key()){ + throw exc::ProtocolException("Client version 3+ requires sealed signatures"); + } - for(size_t i = 0; i < m_ct.signatures.size(); ++i){ - CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); - std::string nonce = compute_sealing_key(ack->opening_key(), i, true); - std::string key = compute_sealing_key(ack->opening_key(), i, false); - size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; - std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); - uint8_t * buff = plaintext.get(); - - protocol::crypto::chacha::decrypt( - m_ct.signatures[i].data(), - m_ct.signatures[i].size(), - reinterpret_cast<const uint8_t *>(key.data()), - reinterpret_cast<const uint8_t *>(nonce.data()), - reinterpret_cast<char *>(buff), &plen); - m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); - } + for(size_t i = 0; i < m_ct.signatures.size(); ++i){ + CHECK_AND_ASSERT_THROW_MES(m_ct.signatures[i].size() > crypto::chacha::TAG_SIZE, "Invalid signature size"); + std::string nonce = compute_sealing_key(ack->opening_key(), i, true); + std::string key = compute_sealing_key(ack->opening_key(), i, false); + size_t plen = m_ct.signatures[i].size() - crypto::chacha::TAG_SIZE; + std::unique_ptr<uint8_t[]> plaintext(new uint8_t[plen]); + uint8_t * buff = plaintext.get(); + + protocol::crypto::chacha::decrypt( + m_ct.signatures[i].data(), + m_ct.signatures[i].size(), + reinterpret_cast<const uint8_t *>(key.data()), + reinterpret_cast<const uint8_t *>(nonce.data()), + reinterpret_cast<char *>(buff), &plen); + m_ct.signatures[i].assign(reinterpret_cast<const char *>(buff), plen); } - if (m_ct.rv->type == rct::RCTTypeCLSAG){ - m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::clsag clsag; - if (!cn_deserialize(m_ct.signatures[i], clsag)) { - throw exc::ProtocolException("Cannot deserialize clsag[i]"); - } - m_ct.rv->p.CLSAGs.push_back(clsag); - } - } else { - m_ct.rv->p.MGs.reserve(m_ct.signatures.size()); - for (size_t i = 0; i < m_ct.signatures.size(); ++i) { - rct::mgSig mg; - if (!cn_deserialize(m_ct.signatures[i], mg)) { - throw exc::ProtocolException("Cannot deserialize mg[i]"); - } - m_ct.rv->p.MGs.push_back(mg); + m_ct.rv->p.CLSAGs.reserve(m_ct.signatures.size()); + for (size_t i = 0; i < m_ct.signatures.size(); ++i) { + rct::clsag clsag; + if (!cn_deserialize(m_ct.signatures[i], clsag)) { + throw exc::ProtocolException("Cannot deserialize clsag[i]"); } + m_ct.rv->p.CLSAGs.push_back(clsag); } m_ct.tx.rct_signatures = *(m_ct.rv); diff --git a/src/device_trezor/trezor/protocol.hpp b/src/device_trezor/trezor/protocol.hpp index 858db1520..7ffadd9aa 100644 --- a/src/device_trezor/trezor/protocol.hpp +++ b/src/device_trezor/trezor/protocol.hpp @@ -116,8 +116,7 @@ namespace ki { */ bool key_image_data(wallet_shim * wallet, const std::vector<tools::wallet2::transfer_details> & transfers, - std::vector<MoneroTransferDetails> & res, - bool need_all_additionals=false); + std::vector<MoneroTransferDetails> & res); /** * Computes a hash over MoneroTransferDetails. Commitment used in the KI sync. @@ -129,8 +128,7 @@ namespace ki { */ void generate_commitment(std::vector<MoneroTransferDetails> & mtds, const std::vector<tools::wallet2::transfer_details> & transfers, - std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req, - bool need_subaddr_indices=false); + std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> & req); /** * Processes Live refresh step response, parses KI, checks the signature @@ -166,7 +164,7 @@ namespace tx { ::crypto::secret_key compute_enc_key(const ::crypto::secret_key & private_view_key, const std::string & aux, const std::string & salt); std::string compute_sealing_key(const std::string & master_key, size_t idx, bool is_iv=false); - typedef boost::variant<rct::rangeSig, rct::Bulletproof> rsig_v; + typedef boost::variant<rct::Bulletproof, rct::BulletproofPlus> rsig_v; /** * Transaction signer state holder. @@ -232,8 +230,8 @@ namespace tx { } const tools::wallet2::transfer_details & get_transfer(size_t idx) const { - CHECK_AND_ASSERT_THROW_MES(idx < m_unsigned_tx->transfers.second.size() + m_unsigned_tx->transfers.first && idx >= m_unsigned_tx->transfers.first, "Invalid transfer index"); - return m_unsigned_tx->transfers.second[idx - m_unsigned_tx->transfers.first]; + CHECK_AND_ASSERT_THROW_MES(idx < std::get<2>(m_unsigned_tx->transfers).size() + std::get<0>(m_unsigned_tx->transfers) && idx >= std::get<0>(m_unsigned_tx->transfers), "Invalid transfer index"); + return std::get<2>(m_unsigned_tx->transfers)[idx - std::get<0>(m_unsigned_tx->transfers)]; } const tools::wallet2::transfer_details & get_source_transfer(size_t idx) const { @@ -247,7 +245,7 @@ namespace tx { void compute_integrated_indices(TsxData * tsx_data); bool should_compute_bp_now() const; void compute_bproof(messages::monero::MoneroTransactionRsigData & rsig_data); - void process_bproof(rct::Bulletproof & bproof); + void process_bproof(rsig_v & bproof); void set_tx_input(MoneroTransactionSourceEntry * dst, size_t idx, bool need_ring_keys=false, bool need_ring_indices=false); public: @@ -260,8 +258,6 @@ namespace tx { void step_set_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionSetInputAck> ack); void sort_ki(); - std::shared_ptr<messages::monero::MoneroTransactionInputsPermutationRequest> step_permutation(); - void step_permutation_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputsPermutationAck> ack); std::shared_ptr<messages::monero::MoneroTransactionInputViniRequest> step_set_vini_input(size_t idx); void step_set_vini_input_ack(std::shared_ptr<const messages::monero::MoneroTransactionInputViniAck> ack); @@ -290,11 +286,15 @@ namespace tx { return m_client_version; } - bool is_simple() const { + uint8_t get_rv_type() const { if (!m_ct.rv){ throw std::invalid_argument("RV not initialized"); } - auto tp = m_ct.rv->type; + return m_ct.rv->type; + } + + bool is_simple() const { + auto tp = get_rv_type(); return tp == rct::RCTTypeSimple; } @@ -302,12 +302,27 @@ namespace tx { return m_ct.tx_data.rct_config.range_proof_type != rct::RangeProofBorromean; } + bool is_req_clsag() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version >= 3; + } + + bool is_req_bulletproof_plus() const { + return is_req_bulletproof() && m_ct.tx_data.rct_config.bp_version == 4; // rct::genRctSimple + } + bool is_bulletproof() const { - if (!m_ct.rv){ - throw std::invalid_argument("RV not initialized"); - } - auto tp = m_ct.rv->type; - return tp == rct::RCTTypeBulletproof || tp == rct::RCTTypeBulletproof2 || tp == rct::RCTTypeCLSAG; + auto tp = get_rv_type(); + return rct::is_rct_bulletproof(tp) || rct::is_rct_bulletproof_plus(tp); + } + + bool is_bulletproof_plus() const { + auto tp = get_rv_type(); + return rct::is_rct_bulletproof_plus(tp); + } + + bool is_clsag() const { + auto tp = get_rv_type(); + return rct::is_rct_clsag(tp); } bool is_offloading() const { diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index f13e74b0f..eedd1511d 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -50,7 +50,6 @@ using namespace std; using namespace epee; using namespace cryptonote; -using boost::lexical_cast; namespace po = boost::program_options; #undef MONERO_DEFAULT_LOG_CATEGORY @@ -84,6 +83,9 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str try { + if (total == 0) + throw std::runtime_error("Signer group of size 0 is not allowed."); + // create M wallets first std::vector<boost::shared_ptr<tools::wallet2>> wallets(total); for (size_t n = 0; n < total; ++n) @@ -118,13 +120,17 @@ static bool generate_multisig(uint32_t threshold, uint32_t total, const std::str ss << " " << name << std::endl; } - //exchange keys unless exchange_multisig_keys returns no extra info - while (!kex_msgs_intermediate[0].empty()) + // exchange keys until the wallets are done + bool ready{false}; + wallets[0]->multisig(&ready); + while (!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); } std::string address = wallets[0]->get_account().get_public_address_str(wallets[0]->nettype()); diff --git a/src/multisig/multisig_account.cpp b/src/multisig/multisig_account.cpp index 9bdcf2dbc..401c6f1fe 100644 --- a/src/multisig/multisig_account.cpp +++ b/src/multisig/multisig_account.cpp @@ -127,7 +127,7 @@ namespace multisig bool multisig_account::multisig_is_ready() const { if (main_kex_rounds_done()) - return m_kex_rounds_complete >= multisig_kex_rounds_required(m_signers.size(), m_threshold) + 1; + return m_kex_rounds_complete >= multisig_setup_rounds_required(m_signers.size(), m_threshold); else return false; } @@ -175,19 +175,20 @@ namespace multisig // only mutate account if update succeeds multisig_account temp_account{*this}; temp_account.set_multisig_config(threshold, std::move(signers)); - temp_account.kex_update_impl(expanded_msgs_rnd1); + temp_account.kex_update_impl(expanded_msgs_rnd1, false); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- // multisig_account: EXTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs) + void multisig_account::kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool force_update_use_with_caution /*= false*/) { CHECK_AND_ASSERT_THROW_MES(account_is_active(), "multisig account: tried to update kex, but kex isn't initialized yet."); CHECK_AND_ASSERT_THROW_MES(!multisig_is_ready(), "multisig account: tried to update kex, but kex is already complete."); multisig_account temp_account{*this}; - temp_account.kex_update_impl(expanded_msgs); + temp_account.kex_update_impl(expanded_msgs, force_update_use_with_caution); *this = std::move(temp_account); } //---------------------------------------------------------------------------------------------------------------------- @@ -200,4 +201,11 @@ namespace multisig return num_signers - threshold + 1; } //---------------------------------------------------------------------------------------------------------------------- + // EXTERNAL + //---------------------------------------------------------------------------------------------------------------------- + std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold) + { + return multisig_kex_rounds_required(num_signers, threshold) + 1; + } + //---------------------------------------------------------------------------------------------------------------------- } //namespace multisig diff --git a/src/multisig/multisig_account.h b/src/multisig/multisig_account.h index 7b372bbff..9cd0942d4 100644 --- a/src/multisig/multisig_account.h +++ b/src/multisig/multisig_account.h @@ -169,12 +169,20 @@ namespace multisig * - The main interface for multisig key exchange, this handles all the work of processing input messages, * creating new messages for new rounds, and finalizing the multisig shared public key when kex is complete. * param: expanded_msgs - kex messages corresponding to the account's 'in progress' round + * param: force_update_use_with_caution - try to force the account to update with messages from an incomplete signer set. + * - If this is the post-kex verification round, only require one input message. + * - Force updating here should only be done if we can safely assume an honest signer subgroup of size 'threshold' + * will complete the account. + * - If this is an intermediate round, only require messages from 'num signers - 1 - (round - 1)' other signers. + * - 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); + void kex_update(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool force_update_use_with_caution = false); private: // implementation of kex_update() (non-transactional) - void kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs); + 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 @@ -245,4 +253,13 @@ namespace multisig * return: number of kex rounds required */ std::uint32_t multisig_kex_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); + + /** + * brief: multisig_setup_rounds_required - The number of setup rounds required to produce an M-of-N shared key. + * - A participant must complete all kex rounds and 1 initialization round. + * param: num_signers - number of participants in multisig (N) + * param: threshold - threshold of multisig (M) + * return: number of setup rounds required + */ + std::uint32_t multisig_setup_rounds_required(const std::uint32_t num_signers, const std::uint32_t threshold); } //namespace multisig diff --git a/src/multisig/multisig_account_kex_impl.cpp b/src/multisig/multisig_account_kex_impl.cpp index be9ed9cb2..243058b81 100644 --- a/src/multisig/multisig_account_kex_impl.cpp +++ b/src/multisig/multisig_account_kex_impl.cpp @@ -74,7 +74,7 @@ namespace multisig "Multisig threshold may not be larger than number of signers."); CHECK_AND_ASSERT_THROW_MES(threshold > 0, "Multisig threshold must be > 0."); CHECK_AND_ASSERT_THROW_MES(round > 0, "Multisig kex round must be > 0."); - CHECK_AND_ASSERT_THROW_MES(round <= multisig_kex_rounds_required(num_signers, threshold) + 1, + CHECK_AND_ASSERT_THROW_MES(round <= multisig_setup_rounds_required(num_signers, threshold), "Trying to process multisig kex for an invalid round."); } //---------------------------------------------------------------------------------------------------------------------- @@ -181,7 +181,8 @@ namespace multisig * Key aggregation via aggregation coefficients prevents key cancellation attacks. * See: https://www.getmonero.org/resources/research-lab/pubs/MRL-0009.pdf * param: final_keys - address components (public keys) obtained from other participants (not shared with local) - * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation coefficient (return by reference) + * param: privkeys_inout - private keys of address components known by local; each key will be multiplied by an aggregation + * coefficient (return by reference) * return: final multisig public spend key for the account */ //---------------------------------------------------------------------------------------------------------------------- @@ -199,7 +200,8 @@ namespace multisig for (std::size_t multisig_keys_index{0}; multisig_keys_index < privkeys_inout.size(); ++multisig_keys_index) { crypto::public_key pubkey; - CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), "Failed to derive public key"); + CHECK_AND_ASSERT_THROW_MES(crypto::secret_key_to_public_key(privkeys_inout[multisig_keys_index], pubkey), + "Failed to derive public key"); own_keys_mapping[pubkey] = multisig_keys_index; @@ -307,8 +309,7 @@ namespace multisig * INTERNAL * * brief: multisig_kex_msgs_sanitize_pubkeys - Sanitize multisig kex messages. - * - Removes duplicates from msg pubkeys, ignores pubkeys equal to the local account's signing key, - * ignores messages signed by the local account, ignores keys found in input 'exclusion set', + * - Removes duplicates from msg pubkeys, ignores keys found in input 'exclusion set', * constructs map of pubkey:origins. * - Requires that all input msgs have the same round number. * @@ -316,15 +317,13 @@ namespace multisig * * - If the messages' round numbers are all '1', then only the message signing pubkey is considered * 'recommended'. Furthermore, the 'exclusion set' is ignored. - * param: own_pubkey - local account's signing key (key used to sign multisig messages) * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - pubkeys to exclude from output set * outparam: sanitized_pubkeys_out - processed pubkeys obtained from msgs, mapped to their origins * return: round number shared by all input msgs */ //---------------------------------------------------------------------------------------------------------------------- - static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const crypto::public_key &own_pubkey, - const std::vector<multisig_kex_msg> &expanded_msgs, + static std::uint32_t multisig_kex_msgs_sanitize_pubkeys(const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, multisig_keyset_map_memsafe_t &sanitized_pubkeys_out) { @@ -339,10 +338,6 @@ namespace multisig // - origins = all the signing pubkeys that recommended a given msg pubkey for (const auto &expanded_msg : expanded_msgs) { - // ignore messages from self - if (expanded_msg.get_signing_pubkey() == own_pubkey) - continue; - // in round 1, only the signing pubkey is treated as a msg pubkey if (round == 1) { @@ -355,10 +350,6 @@ namespace multisig // copy all pubkeys from message into list for (const auto &pubkey : expanded_msg.get_msg_pubkeys()) { - // ignore own pubkey - if (pubkey == own_pubkey) - continue; - // ignore pubkeys in 'ignore' set if (std::find(exclude_pubkeys.begin(), exclude_pubkeys.end(), pubkey) != exclude_pubkeys.end()) continue; @@ -375,6 +366,31 @@ namespace multisig /** * INTERNAL * + * brief: remove_key_from_mapped_sets - Remove a specified key from the mapped sets in a multisig keyset map. + * param: key_to_remove - specified key to remove + * inoutparam: keyset_inout - keyset to update + */ + //---------------------------------------------------------------------------------------------------------------------- + static void remove_key_from_mapped_sets(const crypto::public_key &key_to_remove, + multisig_keyset_map_memsafe_t &keyset_inout) + { + // remove specified key from each mapped set + for (auto keyset_it = keyset_inout.begin(); keyset_it != keyset_inout.end();) + { + // remove specified key from this set + keyset_it->second.erase(key_to_remove); + + // remove empty keyset positions or increment iterator + if (keyset_it->second.size() == 0) + keyset_it = keyset_inout.erase(keyset_it); + else + ++keyset_it; + } + } + //---------------------------------------------------------------------------------------------------------------------- + /** + * INTERNAL + * * brief: evaluate_multisig_kex_round_msgs - Evaluate pubkeys from a kex round in order to prepare for the next round. * - Sanitizes input msgs. * - Require uniqueness in: 'exclude_pubkeys'. @@ -392,6 +408,8 @@ namespace multisig * param: signers - expected participants in multisig kex * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - derivations held by the local account corresponding to round 'expected_round' + * param: incomplete_signer_set - only require the minimum number of signers to complete this round + * minimum = num_signers - (round num - 1) (including local signer) * return: fully sanitized and validated pubkey:origins map for building the account's next kex round message */ //---------------------------------------------------------------------------------------------------------------------- @@ -400,7 +418,8 @@ namespace multisig const std::uint32_t expected_round, const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, - const std::vector<crypto::public_key> &exclude_pubkeys) + const std::vector<crypto::public_key> &exclude_pubkeys, + const bool incomplete_signer_set) { // exclude_pubkeys should all be unique for (auto it = exclude_pubkeys.begin(); it != exclude_pubkeys.end(); ++it) @@ -410,21 +429,31 @@ namespace multisig } // sanitize input messages - multisig_keyset_map_memsafe_t pubkey_origins_map; - const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, exclude_pubkeys, pubkey_origins_map); + multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]] + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, exclude_pubkeys, pubkey_origins_map); CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + // remove the local signer from each origins set in the sanitized pubkey map + // note: intermediate kex rounds only need keys from other signers to make progress (keys from self are useless) + remove_key_from_mapped_sets(base_pubkey, pubkey_origins_map); + // evaluate pubkeys collected - std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; + std::unordered_map<crypto::public_key, std::unordered_set<crypto::public_key>> origin_pubkeys_map; //map: [origin: [pubkeys]] // 1. each pubkey should be recommended by a precise number of signers + const std::size_t num_recommendations_per_pubkey_required{ + incomplete_signer_set + ? 1 + : round + }; + for (const auto &pubkey_and_origins : pubkey_origins_map) { // expected amount = round_num // With each successive round, pubkeys are shared by incrementally larger groups, // starting at 1 in round 1 (i.e. the local multisig key to start kex with). - CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() == round, + CHECK_AND_ASSERT_THROW_MES(pubkey_and_origins.second.size() >= num_recommendations_per_pubkey_required, "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); // map (sanitized) pubkeys back to origins @@ -433,8 +462,18 @@ namespace multisig } // 2. the number of unique signers recommending pubkeys should equal the number of signers passed in (minus the local signer) - CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() == signers.size() - 1, - "Number of unique other signers does not equal number of other signers that recommended pubkeys."); + // - if an incomplete set is allowed, then we need at least one signer to represent each subgroup in this round that + // doesn't include the local signer + const std::size_t num_signers_required{ + incomplete_signer_set + ? signers.size() - 1 - (round - 1) + : signers.size() - 1 + }; + + CHECK_AND_ASSERT_THROW_MES(origin_pubkeys_map.size() >= num_signers_required, + "Number of unique other signers recommending pubkeys does not equal number of required other signers " + "(kex round: " << round << ", num signers found: " << origin_pubkeys_map.size() << ", num signers required: " << + num_signers_required << ")."); // 3. each origin should recommend a precise number of pubkeys @@ -461,19 +500,20 @@ namespace multisig // other signers: (N - 2) choose (msg_round_num - 1) // - Each signer recommends keys they share with other signers. - // - In each round, a signer shares a key with 'round num - 1' other signers. - // - Since 'origins pubkey map' excludes keys shared with the local account, - // only keys shared with participants 'other than local and self' will be in the map (e.g. N - 2 signers). - // - So other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). - // - Each origin should have a shared key with each group of size 'round - 1'. - // Note: Keys shared with local are ignored to facilitate kex round boosting, where one or more signers may + // - In each round, every group of size 'round num' will have a key. From a single signer's perspective, + // they will share a key with every group of size 'round num - 1' of other signers. + // - Since 'origins pubkey map' excludes keys shared with the local account, only keys shared with participants + // 'other than local and self' will be in the map (e.g. N - 2 signers). + // - Other signers will recommend (N - 2) choose (msg_round_num - 1) pubkeys (after removing keys shared with local). + // Note: Keys shared with local are filtered out to facilitate kex round boosting, where one or more signers may // have boosted the local signer (implying they didn't have access to the local signer's previous round msg). const std::uint32_t expected_recommendations_others = n_choose_k_f(signers.size() - 2, round - 1); // local: (N - 1) choose (msg_round_num - 1) const std::uint32_t expected_recommendations_self = n_choose_k_f(signers.size() - 1, round - 1); - // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we return early for that case + // note: expected_recommendations_others would be 0 in the last round of 1-of-N, but we don't call this function for + // that case CHECK_AND_ASSERT_THROW_MES(expected_recommendations_self > 0 && expected_recommendations_others > 0, "Bad num signers or round num (possibly numerical limits exceeded)."); @@ -485,7 +525,7 @@ namespace multisig for (const auto &origin_and_pubkeys : origin_pubkeys_map) { CHECK_AND_ASSERT_THROW_MES(origin_and_pubkeys.second.size() == expected_recommendations_others, - "A pubkey recommended by multisig kex messages had an unexpected number of recommendations."); + "A multisig signer recommended an unexpected number of pubkeys."); // 2 (continued). only expected signers should be recommending keys CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin_and_pubkeys.first) != signers.end(), @@ -507,6 +547,7 @@ namespace multisig * param: expected_round - expected kex round of input messages * param: signers - expected participants in multisig kex * param: expanded_msgs - set of multisig kex messages to process + * param: incomplete_signer_set - only require the minimum amount of messages to complete this round (1 message) * return: sanitized and validated pubkey:origins map */ //---------------------------------------------------------------------------------------------------------------------- @@ -514,15 +555,20 @@ namespace multisig const crypto::public_key &base_pubkey, const std::uint32_t expected_round, const std::vector<crypto::public_key> &signers, - const std::vector<multisig_kex_msg> &expanded_msgs) + const std::vector<multisig_kex_msg> &expanded_msgs, + const bool incomplete_signer_set) { // sanitize input messages const std::vector<crypto::public_key> dummy; - multisig_keyset_map_memsafe_t pubkey_origins_map; - const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(base_pubkey, expanded_msgs, dummy, pubkey_origins_map); + multisig_keyset_map_memsafe_t pubkey_origins_map; //map: [pubkey : [origins]] + const std::uint32_t round = multisig_kex_msgs_sanitize_pubkeys(expanded_msgs, dummy, pubkey_origins_map); CHECK_AND_ASSERT_THROW_MES(round == expected_round, "Kex messages were for round [" << round << "], but expected round is [" << expected_round << "]"); + // 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) + // evaluate pubkeys collected // 1) there should only be two pubkeys @@ -533,17 +579,26 @@ namespace multisig CHECK_AND_ASSERT_THROW_MES(pubkey_origins_map.begin()->second == (++(pubkey_origins_map.begin()))->second, "Multisig post-kex round messages from other signers did not all recommend the same pubkey pair."); - // 3) all signers should be present in the recommendation list + // 3) all signers should be present in the recommendation list (unless an incomplete list is permitted) auto origins = pubkey_origins_map.begin()->second; - origins.insert(base_pubkey); //add self + origins.insert(base_pubkey); //add self if missing - CHECK_AND_ASSERT_THROW_MES(origins.size() == signers.size(), - "Multisig post-kex round message origins don't line up with multisig signer set."); + const std::size_t num_signers_required{ + incomplete_signer_set + ? 1 + : signers.size() + }; + + CHECK_AND_ASSERT_THROW_MES(origins.size() >= num_signers_required, + "Multisig post-kex round message origins don't line up with multisig signer set " + "(num signers found: " << origins.size() << ", num signers required: " << num_signers_required << ")."); - for (const crypto::public_key &signer : signers) + for (const crypto::public_key &origin : origins) { - CHECK_AND_ASSERT_THROW_MES(origins.find(signer) != origins.end(), - "Could not find an expected signer in multisig post-kex round messages (all signers expected)."); + // note: if num_signers_required == signers.size(), then this test will ensure all signers are present in 'origins', + // which contains only unique pubkeys + CHECK_AND_ASSERT_THROW_MES(std::find(signers.begin(), signers.end(), origin) != signers.end(), + "An unknown origin recommended a multisig post-kex verification messsage."); } return pubkey_origins_map; @@ -564,6 +619,7 @@ namespace multisig * param: expanded_msgs - set of multisig kex messages to process * param: exclude_pubkeys - 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. + * param: incomplete_signer_set - allow messages from an incomplete signer set * 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. @@ -578,6 +634,7 @@ namespace multisig const std::vector<crypto::public_key> &signers, const std::vector<multisig_kex_msg> &expanded_msgs, const std::vector<crypto::public_key> &exclude_pubkeys, + const bool incomplete_signer_set, multisig_keyset_map_memsafe_t &keys_to_origins_map_out) { check_multisig_config(current_round, threshold, signers.size()); @@ -598,7 +655,8 @@ namespace multisig current_round, signers, expanded_msgs, - exclude_pubkeys); + exclude_pubkeys, + incomplete_signer_set); } else //(current_round == kex_rounds_required + 1) { @@ -606,7 +664,8 @@ namespace multisig evaluated_pubkeys = evaluate_multisig_post_kex_round_msgs(base_pubkey, current_round, signers, - expanded_msgs); + expanded_msgs, + incomplete_signer_set); } // prepare keys-to-origins map for updating the multisig account @@ -693,9 +752,9 @@ namespace multisig { // post-kex verification round: check that the multisig pubkey and common pubkey were recommended by other signers CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_multisig_pubkey) > 0, - "Multisig post-kex round: expected multisig pubkey wasn't found in other signers' messages."); + "Multisig post-kex round: expected multisig pubkey wasn't found in input messages."); CHECK_AND_ASSERT_THROW_MES(result_keys_to_origins_map.count(m_common_pubkey) > 0, - "Multisig post-kex round: expected common pubkey wasn't found in other signers' messages."); + "Multisig post-kex round: expected common pubkey wasn't found in input messages."); // save keys that should be recommended to other signers // - for convenience, re-recommend the post-kex verification message once an account is complete @@ -790,7 +849,8 @@ namespace multisig //---------------------------------------------------------------------------------------------------------------------- // multisig_account: INTERNAL //---------------------------------------------------------------------------------------------------------------------- - void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs) + void multisig_account::kex_update_impl(const std::vector<multisig_kex_msg> &expanded_msgs, + const bool incomplete_signer_set) { // check messages are for the expected kex round check_messages_round(expanded_msgs, m_kex_rounds_complete + 1); @@ -816,6 +876,7 @@ namespace multisig m_signers, expanded_msgs, exclude_pubkeys, + incomplete_signer_set, result_keys_to_origins_map); // finish account update diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp index 21040317c..477a7907d 100644 --- a/src/ringct/rctSigs.cpp +++ b/src/ringct/rctSigs.cpp @@ -1333,7 +1333,7 @@ namespace rct { try { if (semantics) { - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results(rv.outPk.size(), false); DP("range proofs verified?"); @@ -1383,7 +1383,7 @@ namespace rct { { PERF_TIMER(verRctSemanticsSimple); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); std::deque<bool> results; std::vector<const Bulletproof*> bp_proofs; @@ -1536,7 +1536,7 @@ namespace rct { const size_t threads = std::max(rv.outPk.size(), rv.mixRing.size()); std::deque<bool> results(threads); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); const keyV &pseudoOuts = bulletproof || bulletproof_plus ? rv.p.pseudoOuts : rv.pseudoOuts; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 5304333ff..16bcf2c04 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -2290,6 +2290,12 @@ namespace cryptonote return m_bootstrap_daemon->handle_result(false, {}); } + if (bootstrap_daemon_height < m_core.get_checkpoints().get_max_height()) + { + MINFO("Bootstrap daemon height is lower than the latest checkpoint"); + return m_bootstrap_daemon->handle_result(false, {}); + } + if (!m_p2p.get_payload_object().no_sync()) { uint64_t top_height = m_core.get_current_blockchain_height(); @@ -2855,6 +2861,10 @@ namespace cryptonote res.version = CORE_RPC_VERSION; res.release = MONERO_VERSION_IS_RELEASE; + res.current_height = m_core.get_current_blockchain_height(); + res.target_height = m_p2p.get_payload_object().is_synchronized() ? 0 : m_core.get_target_blockchain_height(); + for (const auto &hf : m_core.get_blockchain_storage().get_hardforks()) + res.hard_forks.push_back({hf.version, hf.height}); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 0274f4db8..b87412ca6 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -47,10 +47,6 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "daemon.rpc" -// yes, epee doesn't properly use its full namespace when calling its -// functions from macros. *sigh* -using namespace epee; - namespace cryptonote { /************************************************************************/ diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 1be2610ff..e1222f6eb 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 10 +#define CORE_RPC_VERSION_MINOR 11 #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) @@ -2123,15 +2123,34 @@ namespace cryptonote }; typedef epee::misc_utils::struct_init<request_t> request; + struct hf_entry + { + uint8_t hf_version; + uint64_t height; + + bool operator==(const hf_entry& hfe) const { return hf_version == hfe.hf_version && height == hfe.height; } + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(hf_version) + KV_SERIALIZE(height) + END_KV_SERIALIZE_MAP() + }; + struct response_t: public rpc_response_base { uint32_t version; bool release; + uint64_t current_height; + uint64_t target_height; + std::vector<hf_entry> hard_forks; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_PARENT(rpc_response_base) KV_SERIALIZE(version) KV_SERIALIZE(release) + KV_SERIALIZE_OPT(current_height, (uint64_t)0) + KV_SERIALIZE_OPT(target_height, (uint64_t)0) + KV_SERIALIZE_OPT(hard_forks, std::vector<hf_entry>()) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<response_t> response; diff --git a/src/serialization/tuple.h b/src/serialization/tuple.h new file mode 100644 index 000000000..6d98e05b0 --- /dev/null +++ b/src/serialization/tuple.h @@ -0,0 +1,169 @@ +// Copyright (c) 2014-2020, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once +#include <memory> +#include "serialization.h" + +namespace serialization +{ + namespace detail + { + template <typename Archive, class T> + bool serialize_tuple_element(Archive& ar, T& e) + { + return ::do_serialize(ar, e); + } + + template <typename Archive> + bool serialize_tuple_element(Archive& ar, uint64_t& e) + { + ar.serialize_varint(e); + return true; + } + } +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<false>& ar, std::tuple<E0,E1,E2>& p) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 3) + 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; +} + +template <template <bool> class Archive, class E0, class E1, class E2> +inline bool do_serialize(Archive<true>& ar, std::tuple<E0,E1,E2>& p) +{ + 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; +} + +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) +{ + size_t cnt; + ar.begin_array(cnt); + if (!ar.good()) + return false; + if (cnt != 4) + 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; +} + +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; +} + diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a8f4e5a07..f59af575e 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -181,7 +181,6 @@ namespace const command_line::arg_descriptor<bool> arg_restore_from_seed = {"restore-from-seed", sw::tr("alias for --restore-deterministic-wallet"), false}; const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; - const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor<std::string> arg_restore_date = {"restore-date", sw::tr("Restore from estimated blockchain height on specified date"), ""}; const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; @@ -244,7 +243,7 @@ namespace const char* USAGE_IMPORT_OUTPUTS("import_outputs <filename>"); const char* USAGE_SHOW_TRANSFER("show_transfer <txid>"); const char* USAGE_MAKE_MULTISIG("make_multisig <threshold> <string1> [<string>...]"); - const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys <string> [<string>...]"); + const char* USAGE_EXCHANGE_MULTISIG_KEYS("exchange_multisig_keys [force-update-use-with-caution] <string> [<string>...]"); const char* USAGE_EXPORT_MULTISIG_INFO("export_multisig_info <filename>"); const char* USAGE_IMPORT_MULTISIG_INFO("import_multisig_info <filename> [<filename>...]"); const char* USAGE_SIGN_MULTISIG("sign_multisig <filename>"); @@ -1139,11 +1138,22 @@ bool simple_wallet::make_multisig_main(const std::vector<std::string> &args, boo bool simple_wallet::exchange_multisig_keys(const std::vector<std::string> &args) { CHECK_MULTISIG_ENABLED(); - exchange_multisig_keys_main(args, false); + bool force_update_use_with_caution = false; + + auto local_args = args; + if (args.size() >= 1 && local_args[0] == "force-update-use-with-caution") + { + force_update_use_with_caution = true; + local_args.erase(local_args.begin()); + } + + exchange_multisig_keys_main(local_args, force_update_use_with_caution, false); return true; } -bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms) { +bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> &args, + const bool force_update_use_with_caution, + const bool called_by_mms) { CHECK_MULTISIG_ENABLED(); bool ready; if (m_wallet->key_on_device()) @@ -1169,15 +1179,9 @@ bool simple_wallet::exchange_multisig_keys_main(const std::vector<std::string> & return false; } - if (args.size() < 1) - { - PRINT_USAGE(USAGE_EXCHANGE_MULTISIG_KEYS); - return false; - } - try { - std::string multisig_extra_info = m_wallet->exchange_multisig_keys(orig_pwd_container->password(), args); + 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) @@ -3233,8 +3237,7 @@ bool simple_wallet::scan_tx(const std::vector<std::string> &args) } simple_wallet::simple_wallet() - : m_allow_mismatched_daemon_version(false) - , m_refresh_progress_reporter(*this) + : m_refresh_progress_reporter(*this) , m_idle_run(true) , m_auto_refresh_enabled(false) , m_auto_refresh_refreshing(false) @@ -4118,6 +4121,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) epee::wipeable_string multisig_keys; epee::wipeable_string password; + epee::wipeable_string seed_pass; if (!handle_command_line(vm)) return false; @@ -4134,6 +4138,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if(!ask_wallet_create_if_needed()) return false; } + bool enable_multisig = false; + if (m_restore_multisig_wallet) { + fail_msg_writer() << tr("Multisig is disabled."); + fail_msg_writer() << tr("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."); + if (!command_line::is_yes(input_line("Do you want to continue restoring a multisig wallet?", true))) { + message_writer() << tr("You have canceled restoring a multisig wallet."); + return false; + } + enable_multisig = true; + } + if (!m_generate_new.empty() || m_restoring) { if (!m_subaddress_lookahead.empty() && !parse_subaddress_lookahead(m_subaddress_lookahead)) @@ -4213,19 +4228,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) auto pwd_container = password_prompter(tr("Enter seed offset passphrase, empty if none"), false); if (std::cin.eof() || !pwd_container) return false; - epee::wipeable_string seed_pass = pwd_container->password(); - if (!seed_pass.empty()) - { - if (m_restore_multisig_wallet) - { - crypto::secret_key key; - crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); - sc_reduce32((unsigned char*)key.data); - multisig_keys = m_wallet->decrypt<epee::wipeable_string>(std::string(multisig_keys.data(), multisig_keys.size()), key, true); - } - else - m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); - } + seed_pass = pwd_container->password(); + if (!seed_pass.empty() && !m_restore_multisig_wallet) + m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } if (!m_generate_from_view_key.empty()) { @@ -4568,7 +4573,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_wallet_file = m_generate_new; boost::optional<epee::wipeable_string> r; if (m_restore_multisig_wallet) - r = new_wallet(vm, multisig_keys, old_language); + r = new_wallet(vm, multisig_keys, seed_pass, old_language); else r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); @@ -4667,6 +4672,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } m_wallet->set_refresh_from_block_height(m_restore_height); } + if (enable_multisig) + m_wallet->enable_multisig(true); m_wallet->rewrite(m_wallet_file, password); } else @@ -4755,7 +4762,6 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet) || command_line::get_arg(vm, arg_restore_from_seed); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); - m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); m_restore_height = command_line::get_arg(vm, arg_restore_height); m_restore_date = command_line::get_arg(vm, arg_restore_date); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); @@ -4786,12 +4792,20 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) uint32_t version_ = 0; if (!version) version = &version_; - if (!m_wallet->check_connection(version)) + bool wallet_is_outdated = false, daemon_is_outdated = false; + if (!m_wallet->check_connection(version, NULL, 200000, &wallet_is_outdated, &daemon_is_outdated)) { if (!silent) { if (m_wallet->is_offline()) fail_msg_writer() << tr("wallet failed to connect to daemon, because it is set to offline mode"); + else if (wallet_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon, because it is not up to date. ") << + tr("Please make sure you are running the latest wallet."); + else if (daemon_is_outdated) + fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << + tr("Daemon is not up to date. " + "Please make sure the daemon is running the latest version or change the daemon address using the 'set_daemon' command."); else fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " << tr("Daemon either is not started or wrong port was passed. " @@ -4799,7 +4813,7 @@ bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version) } return false; } - if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) + if (!m_wallet->is_mismatched_daemon_version_allowed() && ((*version >> 16) != CORE_RPC_VERSION_MAJOR)) { if (!silent) fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address(); @@ -5057,7 +5071,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr } //---------------------------------------------------------------------------------------------------- boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::program_options::variables_map& vm, - const epee::wipeable_string &multisig_keys, const std::string &old_language) + const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language) { std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> rc; try { rc = tools::wallet2::make_new(vm, false, password_prompter); } @@ -5091,7 +5105,16 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr try { - m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file); + if (seed_pass.empty()) + m_wallet->generate(m_wallet_file, std::move(rc.second).password(), multisig_keys, create_address_file); + else + { + crypto::secret_key key; + crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + 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) @@ -7912,8 +7935,10 @@ bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, bool simple_wallet::accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs) { std::string extra_message; - if (!txs.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)txs.transfers.second.size()).str(); + if (!std::get<2>(txs.new_transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.new_transfers).size()).str(); + else if (!std::get<2>(txs.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(txs.transfers).size()).str(); return accept_loaded_tx([&txs](){return txs.txes.size();}, [&txs](size_t n)->const tools::wallet2::tx_construction_data&{return txs.txes[n];}, extra_message); } //---------------------------------------------------------------------------------------------------- @@ -10627,7 +10652,6 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_restore_multisig_wallet ); command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); - command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); command_line::add_arg(desc_params, arg_restore_date); command_line::add_arg(desc_params, arg_do_not_relay); @@ -11157,7 +11181,8 @@ void simple_wallet::mms_next(const std::vector<std::string> &args) mms::message m = ms.get_message_by_id(data.message_ids[i]); sig_args[i] = m.content; } - command_successful = exchange_multisig_keys_main(sig_args, true); + // todo: update mms to enable 'key exchange force updating' + command_successful = exchange_multisig_keys_main(sig_args, false, true); break; } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 6a9fa149d..7c45d45e8 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -101,7 +101,7 @@ namespace cryptonote boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional<crypto::secret_key>& spendkey, const crypto::secret_key& viewkey); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm, - const epee::wipeable_string &multisig_keys, const std::string &old_language); + const epee::wipeable_string &multisig_keys, const epee::wipeable_string &seed_pass, const std::string &old_language); boost::optional<epee::wipeable_string> new_wallet(const boost::program_options::variables_map& vm); boost::optional<epee::wipeable_string> open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); @@ -235,7 +235,7 @@ namespace cryptonote bool make_multisig(const std::vector<std::string>& args); bool make_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool exchange_multisig_keys(const std::vector<std::string> &args); - bool exchange_multisig_keys_main(const std::vector<std::string> &args, bool called_by_mms); + bool exchange_multisig_keys_main(const std::vector<std::string> &args, const bool force_update_use_with_caution, const bool called_by_mms); bool export_multisig(const std::vector<std::string>& args); bool export_multisig_main(const std::vector<std::string>& args, bool called_by_mms); bool import_multisig(const std::vector<std::string>& args); @@ -429,7 +429,6 @@ namespace cryptonote bool m_restore_deterministic_wallet; // recover flag bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation - bool m_allow_mismatched_daemon_version; bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional bool m_do_not_relay; diff --git a/src/version.cpp.in b/src/version.cpp.in index c6d473bf9..91fdc9902 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.18.0.0" +#define DEF_MONERO_VERSION "0.18.1.0" #define DEF_MONERO_RELEASE_NAME "Fluorine Fermi" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG #define DEF_MONERO_VERSION_IS_RELEASE @VERSION_IS_RELEASE@ diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 1ee2e20b6..c4d3856d4 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -63,8 +63,8 @@ namespace { static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; // Default refresh interval when connected to remote node static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10; - // Connection timeout 30 sec - static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; + // Connection timeout 20 sec + static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 20; std::string get_default_ringdb_path(cryptonote::network_type nettype) { @@ -1146,8 +1146,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file // Check tx data and construct confirmation message std::string extra_message; - if (!transaction->m_unsigned_tx_set.transfers.second.empty()) - extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.second.size()).str(); + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(transaction->m_unsigned_tx_set.transfers).size()).str(); transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); setStatus(transaction->status(), transaction->errorString()); @@ -1396,12 +1396,12 @@ string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t thres return string(); } -std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info) { +std::string WalletImpl::exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution /*= false*/) { try { clearStatus(); checkMultisigWalletNotReady(m_wallet); - return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info); + return m_wallet->exchange_multisig_keys(epee::wipeable_string(m_password), info, force_update_use_with_caution); } catch (const exception& e) { LOG_ERROR("Error on exchanging multisig keys: " << e.what()); setStatusError(string(tr("Failed to exchange multisig keys: ")) + e.what()); @@ -2173,9 +2173,15 @@ bool WalletImpl::connectToDaemon() Wallet::ConnectionStatus WalletImpl::connected() const { uint32_t version = 0; - m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + bool wallet_is_outdated = false, daemon_is_outdated = false; + m_is_connected = m_wallet->check_connection(&version, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS, &wallet_is_outdated, &daemon_is_outdated); if (!m_is_connected) - return Wallet::ConnectionStatus_Disconnected; + { + if (!m_wallet->light_wallet() && (wallet_is_outdated || daemon_is_outdated)) + return Wallet::ConnectionStatus_WrongVersion; + else + return Wallet::ConnectionStatus_Disconnected; + } // Version check is not implemented in light wallets nodes/wallets if (!m_wallet->light_wallet() && (version >> 16) != CORE_RPC_VERSION_MAJOR) return Wallet::ConnectionStatus_WrongVersion; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 018b2a0ed..ec2d7e9b3 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -146,7 +146,7 @@ public: MultisigState multisig() const override; 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) override; + std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution = false) override; bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector<std::string>& images) override; bool hasMultisigPartialKeyImages() const override; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index b67bce60c..0ae84adb9 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -796,9 +796,10 @@ struct Wallet /** * @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N) * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call + * @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages * @return new info string if more rounds required or an empty string if wallet creation is done */ - virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info) = 0; + virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0; /** * @brief exportMultisigImages - exports transfers' key images * @param images - output paramter for hex encoded array of images diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 7810abdd2..0a9ea8f7b 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -82,18 +82,21 @@ void NodeRPCProxy::invalidate() m_rpc_payment_seed_hash = crypto::null_hash; m_rpc_payment_next_seed_hash = crypto::null_hash; m_height_time = 0; + m_target_height_time = 0; m_rpc_payment_diff = 0; m_rpc_payment_credits_per_hash_found = 0; m_rpc_payment_height = 0; m_rpc_payment_cookie = 0; + m_daemon_hard_forks.clear(); } -boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) +boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height) { if (m_offline) return boost::optional<std::string>("offline"); if (m_rpc_version == 0) { + const time_t now = time(NULL); cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); { @@ -101,9 +104,28 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout); RETURN_ON_RPC_RESPONSE_ERROR(r, epee::json_rpc::error{}, resp_t, "get_version"); } + m_rpc_version = resp_t.version; + m_daemon_hard_forks.clear(); + for (const auto &hf : resp_t.hard_forks) + m_daemon_hard_forks.push_back(std::make_pair(hf.hf_version, hf.height)); + if (resp_t.current_height > 0 || resp_t.target_height > 0) + { + m_height = resp_t.current_height; + m_target_height = resp_t.target_height; + m_height_time = now; + m_target_height_time = now; + } } + rpc_version = m_rpc_version; + daemon_hard_forks = m_daemon_hard_forks; + boost::optional<std::string> result = get_height(height); + if (result) + return result; + result = get_target_height(target_height); + if (result) + return result; return boost::optional<std::string>(); } @@ -138,6 +160,7 @@ boost::optional<std::string> NodeRPCProxy::get_info() m_adjusted_time = resp_t.adjusted_time; m_get_info_time = now; m_height_time = now; + m_target_height_time = now; } return boost::optional<std::string>(); } @@ -160,6 +183,13 @@ boost::optional<std::string> NodeRPCProxy::get_height(uint64_t &height) boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) { + const time_t now = time(NULL); + if (now < m_target_height_time + 30) // re-cache every 30 seconds + { + height = m_target_height; + return boost::optional<std::string>(); + } + auto res = get_info(); if (res) return res; diff --git a/src/wallet/node_rpc_proxy.h b/src/wallet/node_rpc_proxy.h index 07675cdb0..e320565ac 100644 --- a/src/wallet/node_rpc_proxy.h +++ b/src/wallet/node_rpc_proxy.h @@ -48,7 +48,7 @@ public: void invalidate(); void set_offline(bool offline) { m_offline = offline; } - boost::optional<std::string> get_rpc_version(uint32_t &version); + boost::optional<std::string> get_rpc_version(uint32_t &rpc_version, std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, uint64_t &height, uint64_t &target_height); boost::optional<std::string> get_height(uint64_t &height); void set_height(uint64_t h); boost::optional<std::string> get_target_height(uint64_t &height); @@ -103,6 +103,8 @@ private: crypto::hash m_rpc_payment_next_seed_hash; uint32_t m_rpc_payment_cookie; time_t m_height_time; + time_t m_target_height_time; + std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks; }; } diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 195763949..a1a51f7de 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -47,6 +47,7 @@ using namespace epee; #include "cryptonote_config.h" +#include "hardforks/hardforks.h" #include "cryptonote_core/tx_sanity_check.h" #include "wallet_rpc_helpers.h" #include "wallet2.h" @@ -275,6 +276,7 @@ struct options { const command_line::arg_descriptor<bool> no_dns = {"no-dns", tools::wallet2::tr("Do not use DNS"), false}; const command_line::arg_descriptor<bool> offline = {"offline", tools::wallet2::tr("Do not connect to a daemon, nor use DNS"), false}; const command_line::arg_descriptor<std::string> extra_entropy = {"extra-entropy", tools::wallet2::tr("File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)")}; + const command_line::arg_descriptor<bool> allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", tools::wallet2::tr("Allow communicating with a daemon that uses a different version"), false}; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file, std::string &mms_file) @@ -484,6 +486,9 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl add_extra_entropy_thread_safe(data.data(), data.size()); } + if (command_line::has_arg(vm, opts.allow_mismatched_daemon_version)) + wallet->allow_mismatched_daemon_version(true); + try { if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) @@ -1218,7 +1223,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std m_export_format(ExportFormat::Binary), m_load_deprecated_formats(false), m_credits_target(0), - m_enable_multisig(false) + m_enable_multisig(false), + m_has_ever_refreshed_from_node(false), + m_allow_mismatched_daemon_version(false) { set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); } @@ -1278,6 +1285,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.no_dns); command_line::add_arg(desc_params, opts.offline); command_line::add_arg(desc_params, opts.extra_entropy); + command_line::add_arg(desc_params, opts.allow_mismatched_daemon_version); } std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -1338,6 +1346,7 @@ bool wallet2::set_daemon(std::string daemon_address, boost::optional<epee::net_u } m_rpc_payment_state.expected_spent = 0; m_rpc_payment_state.discrepancy = 0; + m_rpc_version = 0; m_node_rpc_proxy.invalidate(); } @@ -2716,7 +2725,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry THROW_WALLET_EXCEPTION_IF(blocks.size() != parsed_blocks.size(), error::wallet_internal_error, "size mismatch"); THROW_WALLET_EXCEPTION_IF(!m_blockchain.is_in_bounds(current_index), error::out_of_hashchain_bounds_error); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); size_t num_txes = 0; @@ -2914,6 +2923,26 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo refresh(trusted_daemon, start_height, blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- +void check_block_hard_fork_version(cryptonote::network_type nettype, uint8_t hf_version, uint64_t height, bool &wallet_is_outdated, bool &daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + wallet_is_outdated = static_cast<size_t>(hf_version) > wallet_num_hard_forks; + if (wallet_is_outdated) + return; + + // check block's height falls within wallet's expected range for block's given version + uint64_t start_height = hf_version == 1 ? 0 : wallet_hard_forks[hf_version - 1].height; + uint64_t end_height = static_cast<size_t>(hf_version) + 1 > wallet_num_hard_forks + ? std::numeric_limits<uint64_t>::max() + : wallet_hard_forks[hf_version].height; + + daemon_is_outdated = height < start_height || height >= end_height; +} +//---------------------------------------------------------------------------------------------------- void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, const std::vector<cryptonote::block_complete_entry> &prev_blocks, const std::vector<parsed_block> &prev_parsed_blocks, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<parsed_block> &parsed_blocks, bool &last, bool &error, std::exception_ptr &exception) { error = false; @@ -2939,7 +2968,7 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks pull_blocks(start_height, blocks_start_height, short_chain_history, blocks, o_indices, current_height); THROW_WALLET_EXCEPTION_IF(blocks.size() != o_indices.size(), error::wallet_internal_error, "Mismatched sizes of blocks and o_indices"); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); parsed_blocks.resize(blocks.size()); for (size_t i = 0; i < blocks.size(); ++i) @@ -2955,6 +2984,23 @@ void wallet2::pull_and_parse_next_blocks(uint64_t start_height, uint64_t &blocks error = true; break; } + + if (!m_allow_mismatched_daemon_version) + { + // make sure block's hard fork version is expected at the block's height + uint8_t hf_version = parsed_blocks[i].block.major_version; + uint64_t height = blocks_start_height + i; + bool wallet_is_outdated = false; + bool daemon_is_outdated = false; + check_block_hard_fork_version(m_nettype, hf_version, height, wallet_is_outdated, daemon_is_outdated); + THROW_WALLET_EXCEPTION_IF(wallet_is_outdated || daemon_is_outdated, error::incorrect_fork_version, + "Unexpected hard fork version v" + std::to_string(hf_version) + " at height " + std::to_string(height) + ". " + + (wallet_is_outdated + ? "Make sure your wallet is up to date" + : "Make sure the node you are connected to is running the latest version") + ); + } + parsed_blocks[i].o_indices = std::move(o_indices[i]); } @@ -3372,7 +3418,7 @@ std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> wallet2::create return cache; } //---------------------------------------------------------------------------------------------------- -void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool) +void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool, uint64_t max_blocks) { if (m_offline) { @@ -3419,11 +3465,15 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? m_transfers.back().m_txid : null_hash; std::list<crypto::hash> short_chain_history; - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); uint64_t blocks_start_height; std::vector<cryptonote::block_complete_entry> blocks; std::vector<parsed_block> parsed_blocks; + // TODO moneromooo-monero says this about the "refreshed" variable: + // "I had to reorder some code to fix... a timing info leak IIRC. In turn, this undid something I had fixed before, ... a subtle race condition with the txpool. + // It was pretty subtle IIRC, and so I needed time to think about how to refix it after the move, and I never got to it." + // https://github.com/monero-project/monero/pull/6097 bool refreshed = false; std::shared_ptr<std::map<std::pair<uint64_t, uint64_t>, size_t>> output_tracker_cache; hw::device &hwdev = m_account.get_device(); @@ -3464,7 +3514,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo update_pool_state(process_pool_txs, true); bool first = true, last = false; - while(m_run.load(std::memory_order_relaxed)) + while(m_run.load(std::memory_order_relaxed) && blocks_fetched < max_blocks) { uint64_t next_blocks_start_height; std::vector<cryptonote::block_complete_entry> next_blocks; @@ -3535,6 +3585,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo throw std::runtime_error("proxy exception in refresh thread"); } + m_has_ever_refreshed_from_node = true; + if(!first && blocks_start_height == next_blocks_start_height) { m_node_rpc_proxy.set_height(m_blockchain.size()); @@ -3571,6 +3623,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); throw; } + catch (const error::incorrect_fork_version&) + { + THROW_WALLET_EXCEPTION_IF(!waiter.wait(), error::wallet_internal_error, "Exception in thread pool"); + throw; + } catch (const std::exception&) { blocks_fetched += added_blocks; @@ -4181,6 +4238,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st m_auto_mine_for_rpc_payment_threshold = -1.0f; m_credits_target = 0; m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; } else if(json.IsObject()) { @@ -4684,7 +4742,8 @@ void wallet2::init_type(hw::device::device_type device_type) } /*! - * \brief Generates a wallet or restores one. + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. * \param wallet_ Name of wallet file * \param password Password of wallet file * \param multisig_data The multisig restore info and keys @@ -4743,11 +4802,6 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& crypto::public_key local_signer; THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed); THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed); - rct::key skey = rct::zero(); - for (const auto &msk: multisig_keys) - sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); - THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); - memwipe(&skey, sizeof(rct::key)); m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); @@ -4758,6 +4812,8 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& m_multisig = true; m_multisig_threshold = threshold; m_multisig_signers = multisig_signers; + // wallet is assumed already finalized + m_multisig_rounds_passed = multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold); setup_keys(password); create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); @@ -5075,12 +5131,11 @@ std::string wallet2::make_multisig(const epee::wipeable_string &password, } //---------------------------------------------------------------------------------------------------- std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &kex_messages) + 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"); - CHECK_AND_ASSERT_THROW_MES(!ready, "Multisig wallet creation process has already been finished"); - 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; @@ -5099,13 +5154,6 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor ); } - // open kex messages - std::vector<multisig::multisig_kex_msg> expanded_msgs; - expanded_msgs.reserve(kex_messages.size()); - - for (const auto &msg : kex_messages) - expanded_msgs.emplace_back(msg); - // reconstruct multisig account multisig::multisig_keyset_map_memsafe_t kex_origins_map; @@ -5126,8 +5174,25 @@ std::string wallet2::exchange_multisig_keys(const epee::wipeable_string &passwor "" }; + // 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) + if (kex_messages.size() == 0) + { + CHECK_AND_ASSERT_THROW_MES(multisig_account.main_kex_rounds_done(), + "Exchange multisig keys: there are no kex messages but the main kex rounds are not done."); + + return multisig_account.get_next_kex_round_msg(); + } + + // open kex messages + std::vector<multisig::multisig_kex_msg> expanded_msgs; + expanded_msgs.reserve(kex_messages.size()); + + for (const auto &msg : kex_messages) + expanded_msgs.emplace_back(msg); + // update multisig kex - multisig_account.kex_update(expanded_msgs); + multisig_account.kex_update(expanded_msgs, force_update_use_with_caution); // update wallet state @@ -5208,7 +5273,7 @@ bool wallet2::multisig(bool *ready, uint32_t *threshold, uint32_t *total) const if (ready) { *ready = !(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) + 1); + (m_multisig_rounds_passed == multisig::multisig_setup_rounds_required(m_multisig_signers.size(), m_multisig_threshold)); } return true; } @@ -5324,7 +5389,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) +bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout, bool *wallet_is_outdated, bool *daemon_is_outdated) { THROW_WALLET_EXCEPTION_IF(!m_is_initialized, error::wallet_not_initialized); @@ -5361,20 +5426,99 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout) } } - if (!m_rpc_version) + if (!m_rpc_version && !check_version(version, wallet_is_outdated, daemon_is_outdated)) + return false; + if (version) + *version = m_rpc_version; + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + uint32_t rpc_version; + std::vector<std::pair<uint8_t, uint64_t>> daemon_hard_forks; + uint64_t height; + uint64_t target_height; + if (m_node_rpc_proxy.get_rpc_version(rpc_version, daemon_hard_forks, height, target_height)) { - cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); - cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); - bool r = invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t); - if(!r || resp_t.status != CORE_RPC_STATUS_OK) { - if(version) - *version = 0; + if(version) + *version = 0; + return false; + } + + // check wallet compatibility with daemon's hard fork version + if (!m_allow_mismatched_daemon_version) + if (!check_hard_fork_version(m_nettype, daemon_hard_forks, height, target_height, wallet_is_outdated, daemon_is_outdated)) return false; + + m_rpc_version = rpc_version; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated) +{ + const size_t wallet_num_hard_forks = nettype == TESTNET ? num_testnet_hard_forks + : nettype == STAGENET ? num_stagenet_hard_forks : num_mainnet_hard_forks; + const hardfork_t *wallet_hard_forks = nettype == TESTNET ? testnet_hard_forks + : nettype == STAGENET ? stagenet_hard_forks : mainnet_hard_forks; + + // First check if wallet or daemon is outdated (whether either are unaware of + // a hard fork). Then check if fork has passed rendering versions incompatible + if (daemon_hard_forks.size() > 0) + { + bool daemon_outdated = daemon_hard_forks.size() < wallet_num_hard_forks; + bool wallet_outdated = daemon_hard_forks.size() > wallet_num_hard_forks; + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + if (wallet_is_outdated) + *wallet_is_outdated = wallet_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[daemon_hard_forks.size()].height; + + // If the daemon missed the fork, then technically it is no longer part of + // the Monero network. Don't connect. + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + else if (wallet_outdated) + { + uint64_t wallet_missed_fork_height = daemon_hard_forks[wallet_num_hard_forks].second; + + // If the wallet missed the fork, then technically it is no longer able + // to communicate with the Monero network. Don't connect. + bool wallet_missed_fork = height >= wallet_missed_fork_height || target_height >= wallet_missed_fork_height; + if (wallet_missed_fork) + return false; } - m_rpc_version = resp_t.version; } - if (version) - *version = m_rpc_version; + else + { + // Non-updated daemons won't return daemon_hard_forks in response to + // get_version. Fall back to extra call to get_hard_fork_info by version. + uint64_t daemon_fork_height; + get_hard_fork_info(wallet_num_hard_forks-1/* wallet expects "double fork" pattern */, daemon_fork_height); + bool daemon_outdated = daemon_fork_height == std::numeric_limits<uint64_t>::max(); + + if (daemon_is_outdated) + *daemon_is_outdated = daemon_outdated; + + if (daemon_outdated) + { + uint64_t daemon_missed_fork_height = wallet_hard_forks[wallet_num_hard_forks-2].height; + bool daemon_missed_fork = height >= daemon_missed_fork_height || target_height >= daemon_missed_fork_height; + if (daemon_missed_fork) + return false; + } + + // Don't need to check if wallet is outdated here because the daemons updated + // for a future hard fork will serve daemon_hard_forks above. The check for + // an outdated wallet is done above using daemon_hard_forks. + } return true; } @@ -6604,9 +6748,9 @@ bool wallet2::sign_tx(const std::string &unsigned_filename, const std::string &s //---------------------------------------------------------------------------------------------------- bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pending_tx> &txs, signed_tx_set &signed_txes) { - if (!exported_txs.new_transfers.second.empty()) + if (!std::get<2>(exported_txs.new_transfers).empty()) import_outputs(exported_txs.new_transfers); - else + else if (!std::get<2>(exported_txs.transfers).empty()) import_outputs(exported_txs.transfers); // sign the transactions @@ -10800,7 +10944,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_ { txs.txes.push_back(get_construction_data_with_decrypted_short_payment_id(tx, m_account.get_device())); } - txs.transfers = std::make_pair(0, m_transfers); + txs.transfers = std::make_tuple(0, m_transfers.size(), m_transfers); auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev); CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface"); @@ -13103,18 +13247,29 @@ void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vect m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); } //---------------------------------------------------------------------------------------------------- -std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all) const +std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wallet2::export_outputs(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs); std::vector<tools::wallet2::exported_transfer_details> outs; + // invalid cases + THROW_WALLET_EXCEPTION_IF(count == 0, error::wallet_internal_error, "Nothing requested"); + THROW_WALLET_EXCEPTION_IF(!all && start > 0, error::wallet_internal_error, "Incremental mode is incompatible with non-zero start"); + + // valid cases: + // all: all outputs, subject to start/count + // !all: incremental, subject to count + // for convenience, start/count are allowed to go past the valid range, then nothing is returned + size_t offset = 0; if (!all) while (offset < m_transfers.size() && (m_transfers[offset].m_key_image_known && !m_transfers[offset].m_key_image_request)) ++offset; + else + offset = start; outs.reserve(m_transfers.size() - offset); - for (size_t n = offset; n < m_transfers.size(); ++n) + for (size_t n = offset; n < m_transfers.size() && n - offset < count; ++n) { const transfer_details &td = m_transfers[n]; @@ -13132,20 +13287,22 @@ std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> wall etd.m_flags.m_key_image_partial = td.m_key_image_partial; etd.m_amount = td.m_amount; etd.m_additional_tx_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + etd.m_subaddr_index_major = td.m_subaddr_index.major; + etd.m_subaddr_index_minor = td.m_subaddr_index.minor; outs.push_back(etd); } - return std::make_pair(offset, outs); + return std::make_tuple(offset, m_transfers.size(), outs); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::export_outputs_to_str(bool all) const +std::string wallet2::export_outputs_to_str(bool all, uint32_t start, uint32_t count) const { PERF_TIMER(export_outputs_to_str); std::stringstream oss; binary_archive<true> ar(oss); - auto outputs = export_outputs(all); + auto outputs = export_outputs(all, start, count); THROW_WALLET_EXCEPTION_IF(!::serialization::serialize(ar, outputs), error::wallet_internal_error, "Failed to serialize output data"); std::string magic(OUTPUT_EXPORT_FILE_MAGIC, strlen(OUTPUT_EXPORT_FILE_MAGIC)); @@ -13158,21 +13315,35 @@ std::string wallet2::export_outputs_to_str(bool all) const return magic + ciphertext; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of"); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - transfer_details td = outputs.second[i]; + transfer_details td = output_array[i]; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13206,6 +13377,8 @@ process: THROW_WALLET_EXCEPTION_IF(td.m_internal_output_index >= td.m_tx.vout.size(), error::wallet_internal_error, "Internal index is out of range"); crypto::public_key out_key = td.get_public_key(); + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13224,24 +13397,38 @@ process: return m_transfers.size(); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) +size_t wallet2::import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs) { PERF_TIMER(import_outputs); - THROW_WALLET_EXCEPTION_IF(outputs.first > m_transfers.size(), error::wallet_internal_error, + THROW_WALLET_EXCEPTION_IF(m_has_ever_refreshed_from_node, error::wallet_internal_error, + "Hot wallets cannot import outputs"); + + // we can now import piecemeal + const size_t offset = std::get<0>(outputs); + const size_t num_outputs = std::get<1>(outputs); + const std::vector<tools::wallet2::exported_transfer_details> &output_array = std::get<2>(outputs); + + THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Imported outputs omit more outputs that we know of. Try using export_outputs all."); - const size_t offset = outputs.first; + THROW_WALLET_EXCEPTION_IF(offset >= num_outputs, error::wallet_internal_error, + "Offset is larger than total outputs"); + THROW_WALLET_EXCEPTION_IF(output_array.size() > num_outputs - offset, error::wallet_internal_error, + "Offset is larger than total outputs"); + const size_t original_size = m_transfers.size(); - m_transfers.resize(offset + outputs.second.size()); - for (size_t i = 0; i < offset; ++i) - m_transfers[i].m_key_image_request = false; - for (size_t i = 0; i < outputs.second.size(); ++i) + if (offset + output_array.size() > m_transfers.size()) + m_transfers.resize(offset + output_array.size()); + else if (num_outputs < m_transfers.size()) + m_transfers.resize(num_outputs); + + for (size_t i = 0; i < output_array.size(); ++i) { - exported_transfer_details etd = outputs.second[i]; + exported_transfer_details etd = output_array[i]; transfer_details &td = m_transfers[i + offset]; - // setup td with "cheao" loaded data + // setup td with "cheap" loaded data td.m_block_height = 0; td.m_txid = crypto::null_hash; td.m_global_output_index = etd.m_global_output_index; @@ -13254,6 +13441,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall td.m_key_image_known = etd.m_flags.m_key_image_known; td.m_key_image_request = etd.m_flags.m_key_image_request; td.m_key_image_partial = false; + td.m_subaddr_index.major = etd.m_subaddr_index_major; + td.m_subaddr_index.minor = etd.m_subaddr_index_minor; // skip those we've already imported, or which have different data if (i + offset < original_size) @@ -13294,6 +13483,8 @@ size_t wallet2::import_outputs(const std::pair<uint64_t, std::vector<tools::wall const crypto::public_key &tx_pub_key = etd.m_tx_pubkey; const std::vector<crypto::public_key> &additional_tx_pub_keys = etd.m_additional_tx_keys; const crypto::public_key& out_key = etd.m_pubkey; + if (should_expand(td.m_subaddr_index)) + create_one_off_subaddress(td.m_subaddr_index); bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); if (should_expand(td.m_subaddr_index)) @@ -13350,7 +13541,7 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) { std::string body(data, headerlen); - std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> new_outputs; try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13360,9 +13551,9 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) } catch (...) {} if (!loaded) - new_outputs.second.clear(); + std::get<2>(new_outputs).clear(); - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; if (!loaded) try { binary_archive<false> ar{epee::strspan<std::uint8_t>(body)}; @@ -13387,15 +13578,16 @@ size_t wallet2::import_outputs_from_str(const std::string &outputs_st) if (!loaded) { - outputs.first = 0; - outputs.second = {}; + std::get<0>(outputs) = 0; + std::get<1>(outputs) = 0; + std::get<2>(outputs) = {}; } - imported_outputs = new_outputs.second.empty() ? import_outputs(outputs) : import_outputs(new_outputs); + imported_outputs = !std::get<2>(new_outputs).empty() ? import_outputs(new_outputs) : !std::get<2>(outputs).empty() ? import_outputs(outputs) : 0; } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs") + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to import outputs: ") + e.what()); } return imported_outputs; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 16e898ad8..3ee40a5f0 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -63,6 +63,7 @@ #include "serialization/crypto.h" #include "serialization/string.h" #include "serialization/pair.h" +#include "serialization/tuple.h" #include "serialization/containers.h" #include "wallet_errors.h" @@ -401,9 +402,13 @@ private: } m_flags; uint64_t m_amount; std::vector<crypto::public_key> m_additional_tx_keys; + uint32_t m_subaddr_index_major; + uint32_t m_subaddr_index_minor; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(0) + VERSION_FIELD(1) + if (version < 1) + return false; FIELD(m_pubkey) VARINT_FIELD(m_internal_output_index) VARINT_FIELD(m_global_output_index) @@ -411,6 +416,8 @@ private: FIELD(m_flags.flags) VARINT_FIELD(m_amount) FIELD(m_additional_tx_keys) + VARINT_FIELD(m_subaddr_index_major) + VARINT_FIELD(m_subaddr_index_minor) END_SERIALIZE() }; @@ -667,16 +674,32 @@ private: struct unsigned_tx_set { std::vector<tx_construction_data> txes; - std::pair<size_t, wallet2::transfer_container> transfers; - std::pair<size_t, std::vector<wallet2::exported_transfer_details>> new_transfers; + std::tuple<uint64_t, uint64_t, wallet2::transfer_container> transfers; + std::tuple<uint64_t, uint64_t, std::vector<wallet2::exported_transfer_details>> new_transfers; BEGIN_SERIALIZE_OBJECT() - VERSION_FIELD(1) + VERSION_FIELD(2) FIELD(txes) - if (version >= 1) - FIELD(new_transfers) - else - FIELD(transfers) + if (version == 0) + { + std::pair<size_t, wallet2::transfer_container> v0_transfers; + FIELD(v0_transfers); + std::get<0>(transfers) = std::get<0>(v0_transfers); + std::get<1>(transfers) = std::get<0>(v0_transfers) + std::get<1>(v0_transfers).size(); + std::get<2>(transfers) = std::get<1>(v0_transfers); + return true; + } + if (version == 1) + { + std::pair<size_t, std::vector<wallet2::exported_transfer_details>> v1_transfers; + FIELD(v1_transfers); + std::get<0>(new_transfers) = std::get<0>(v1_transfers); + std::get<1>(new_transfers) = std::get<0>(v1_transfers) + std::get<1>(v1_transfers).size(); + std::get<2>(new_transfers) = std::get<1>(v1_transfers); + return true; + } + + FIELD(new_transfers) END_SERIALIZE() }; @@ -794,7 +817,8 @@ private: }; /*! - * \brief Generates a wallet or restores one. + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. * \param wallet_ Name of wallet file * \param password Password of wallet file * \param multisig_data The multisig restore info and keys @@ -862,7 +886,8 @@ private: * to other participants */ std::string exchange_multisig_keys(const epee::wipeable_string &password, - const std::vector<std::string> &kex_messages); + const std::vector<std::string> &kex_messages, + const bool force_update_use_with_caution = false); /*! * \brief Get initial message to start multisig key exchange (before 'make_multisig()' is called) * \return string to send to other participants @@ -998,7 +1023,7 @@ private: bool is_deprecated() const; void refresh(bool trusted_daemon); void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched); - void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true); + void refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blocks_fetched, bool& received_money, bool check_pool = true, uint64_t max_blocks = std::numeric_limits<uint64_t>::max()); bool refresh(bool trusted_daemon, uint64_t & blocks_fetched, bool& received_money, bool& ok); void set_refresh_type(RefreshType refresh_type) { m_refresh_type = refresh_type; } @@ -1068,7 +1093,9 @@ private: bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector<crypto::hash> &txids); std::vector<pending_tx> create_unmixable_sweep_transactions(); void discard_unmixable_outputs(); - bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000); + bool check_connection(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 200000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL); + bool check_version(uint32_t *version, bool *wallet_is_outdated, bool *daemon_is_outdated); + bool check_hard_fork_version(cryptonote::network_type nettype, const std::vector<std::pair<uint8_t, uint64_t>> &daemon_hard_forks, const uint64_t height, const uint64_t target_height, bool *wallet_is_outdated, bool *daemon_is_outdated); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1, const boost::optional<uint32_t>& subaddr_account = boost::none, const std::set<uint32_t>& subaddr_indices = {}) const; @@ -1206,11 +1233,17 @@ private: if(ver < 29) return; a & m_rpc_client_secret_key; + if(ver < 30) + { + m_has_ever_refreshed_from_node = false; + return; + } + a & m_has_ever_refreshed_from_node; } BEGIN_SERIALIZE_OBJECT() MAGIC_FIELD("monero wallet cache") - VERSION_FIELD(0) + VERSION_FIELD(1) FIELD(m_blockchain) FIELD(m_transfers) FIELD(m_account_public_address) @@ -1236,6 +1269,12 @@ private: FIELD(m_device_last_key_image_sync) FIELD(m_cold_key_images) FIELD(m_rpc_client_secret_key) + if (version < 1) + { + m_has_ever_refreshed_from_node = false; + return true; + } + FIELD(m_has_ever_refreshed_from_node) END_SERIALIZE() /*! @@ -1323,6 +1362,8 @@ private: void credits_target(uint64_t threshold) { m_credits_target = threshold; } bool is_multisig_enabled() const { return m_enable_multisig; } void enable_multisig(bool enable) { m_enable_multisig = enable; } + bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; } + void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; } bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); @@ -1447,10 +1488,10 @@ private: bool verify_with_public_key(const std::string &data, const crypto::public_key &public_key, const std::string &signature) const; // Import/Export wallet data - std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false) const; - std::string export_outputs_to_str(bool all = false) const; - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); - size_t import_outputs(const std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> export_outputs(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + std::string export_outputs_to_str(bool all = false, uint32_t start = 0, uint32_t count = 0xffffffff) const; + size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::exported_transfer_details>> &outputs); + size_t import_outputs(const std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> &outputs); size_t import_outputs_from_str(const std::string &outputs_st); payment_container export_payments() const; void import_payments(const payment_container &payments); @@ -1840,6 +1881,7 @@ private: rpc_payment_state_t m_rpc_payment_state; uint64_t m_credits_target; bool m_enable_multisig; + bool m_allow_mismatched_daemon_version; // Aux transaction data from device serializable_unordered_map<crypto::hash, std::string> m_tx_device; @@ -1883,11 +1925,13 @@ private: ExportFormat m_export_format; bool m_load_deprecated_formats; + bool m_has_ever_refreshed_from_node; + static boost::mutex default_daemon_address_lock; static std::string default_daemon_address; }; } -BOOST_CLASS_VERSION(tools::wallet2, 29) +BOOST_CLASS_VERSION(tools::wallet2, 30) 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) @@ -1898,7 +1942,7 @@ BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 18) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) -BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) +BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 1) 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) @@ -1908,6 +1952,17 @@ namespace boost { namespace serialization { + template<class Archive, class F, class S, class T> + inline void serialize( + Archive & ar, + std::tuple<F, S, T> & t, + const unsigned int /* file_version */ + ){ + ar & boost::serialization::make_nvp("f", std::get<0>(t)); + ar & boost::serialization::make_nvp("s", std::get<1>(t)); + ar & boost::serialization::make_nvp("t", std::get<2>(t)); + } + template <class Archive> inline typename std::enable_if<!Archive::is_loading::value, void>::type initialize_transfer_details(Archive &a, tools::wallet2::transfer_details &x, const boost::serialization::version_type ver) { @@ -2268,7 +2323,17 @@ namespace boost inline void serialize(Archive &a, tools::wallet2::unsigned_tx_set &x, const boost::serialization::version_type ver) { a & x.txes; - a & x.transfers; + if (ver == 0) + { + // load old version + std::pair<size_t, tools::wallet2::transfer_container> old_transfers; + a & old_transfers; + std::get<0>(x.transfers) = std::get<0>(old_transfers); + std::get<1>(x.transfers) = std::get<0>(old_transfers) + std::get<1>(old_transfers).size(); + std::get<2>(x.transfers) = std::get<1>(old_transfers); + return; + } + throw std::runtime_error("Boost serialization not supported for newest unsigned_tx_set"); } template <class Archive> diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index df594aa21..0b8512163 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -439,6 +439,16 @@ namespace tools std::string to_string() const { return refresh_error::to_string(); } }; //---------------------------------------------------------------------------------------------------- + struct incorrect_fork_version : public refresh_error + { + explicit incorrect_fork_version(std::string&& loc, const std::string& message) + : refresh_error(std::move(loc), message) + { + } + + std::string to_string() const { return refresh_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct signature_check_failed : public wallet_logic_error { explicit signature_check_failed(std::string&& loc, const std::string& message) diff --git a/src/wallet/wallet_rpc_payments.cpp b/src/wallet/wallet_rpc_payments.cpp index 61eaa8070..8474fb568 100644 --- a/src/wallet/wallet_rpc_payments.cpp +++ b/src/wallet/wallet_rpc_payments.cpp @@ -144,7 +144,7 @@ bool wallet2::search_for_rpc_payment(uint64_t credits_target, uint32_t n_threads n_threads = boost::thread::hardware_concurrency(); std::vector<crypto::hash> hash(n_threads); - tools::threadpool& tpool = tools::threadpool::getInstance(); + tools::threadpool& tpool = tools::threadpool::getInstanceForCompute(); tools::threadpool::waiter waiter(tpool); const uint32_t local_nonce = nonce += n_threads; // wrapping's OK diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7ec5fc7a1..ec9b09774 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -60,6 +60,7 @@ using namespace epee; #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" #define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds +#define REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE 256 // just to split refresh in separate calls to play nicer with other threads #define CHECK_MULTISIG_ENABLED() \ do \ @@ -79,6 +80,7 @@ namespace const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", "Restricts to view-only commands", false}; const command_line::arg_descriptor<std::string> arg_wallet_dir = {"wallet-dir", "Directory for newly created wallets"}; const command_line::arg_descriptor<bool> arg_prompt_for_password = {"prompt-for-password", "Prompts for password when not provided", false}; + const command_line::arg_descriptor<bool> arg_no_initial_sync = {"no-initial-sync", "Skips the initial sync before listening for connections", false}; constexpr const char default_rpc_username[] = "monero"; @@ -149,12 +151,17 @@ namespace tools return true; if (boost::posix_time::microsec_clock::universal_time() < m_last_auto_refresh_time + boost::posix_time::seconds(m_auto_refresh_period)) return true; + uint64_t blocks_fetched = 0; try { - if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon()); + bool received_money = false; + if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon(), 0, blocks_fetched, received_money, true, REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE); } catch (const std::exception& ex) { LOG_ERROR("Exception at while refreshing, what=" << ex.what()); } - m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); + // if we got the max amount of blocks, do not set the last refresh time, we did only part of the refresh and will + // continue asap, and only set the last refresh time once the refresh is actually finished + if (blocks_fetched < REFRESH_INFICATIVE_BLOCK_CHUNK_SIZE) + m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time(); return true; }, 1000); m_net_server.add_idle_handler([this](){ @@ -397,7 +404,6 @@ namespace tools bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; entry.txid = string_tools::pod_to_hex(txid); entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); - entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id); if (entry.payment_id.substr(16).find_first_not_of('0') == std::string::npos) entry.payment_id = entry.payment_id.substr(0,16); entry.height = 0; @@ -2792,7 +2798,7 @@ namespace tools try { - res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all)); + res.outputs_data_hex = epee::string_tools::buff_to_hex_nodelimer(m_wallet->export_outputs_to_str(req.all, req.start, req.count)); } catch (const std::exception &e) { @@ -3254,7 +3260,7 @@ namespace tools if (!m_wallet) return not_open(er); cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req; cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res; - bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res); + bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res, std::chrono::seconds(60)); // this waits till stopped, and if randomx has just started initializing its dataset, it might be a while if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -4146,13 +4152,6 @@ namespace tools er.message = "This wallet is not multisig"; return false; } - - if (ready) - { - er.code = WALLET_RPC_ERROR_CODE_ALREADY_MULTISIG; - er.message = "This wallet is multisig, and already finalized"; - return false; - } CHECK_MULTISIG_ENABLED(); if (req.multisig_info.size() + 1 < total) @@ -4164,7 +4163,7 @@ namespace tools try { - res.multisig_info = m_wallet->exchange_multisig_keys(req.password, req.multisig_info); + 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) { @@ -4528,6 +4527,7 @@ public: const auto password_file = command_line::get_arg(vm, arg_password_file); const auto prompt_for_password = command_line::get_arg(vm, arg_prompt_for_password); const auto password_prompt = prompt_for_password ? password_prompter : nullptr; + const auto no_initial_sync = command_line::get_arg(vm, arg_no_initial_sync); if(!wallet_file.empty() && !from_json.empty()) { @@ -4596,7 +4596,8 @@ public: try { - wal->refresh(wal->is_trusted_daemon()); + if (!no_initial_sync) + wal->refresh(wal->is_trusted_daemon()); } catch (const std::exception& e) { @@ -4707,6 +4708,7 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); command_line::add_arg(desc_params, arg_rpc_client_secret_key); + command_line::add_arg(desc_params, arg_no_initial_sync); daemonizer::init_options(hidden_options, desc_params); desc_params.add(hidden_options); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index ecfc8e673..60df6296f 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -47,7 +47,7 @@ // advance which version they will stop working with // Don't go over 32767 for any of these #define WALLET_RPC_VERSION_MAJOR 1 -#define WALLET_RPC_VERSION_MINOR 25 +#define WALLET_RPC_VERSION_MINOR 26 #define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools @@ -1785,9 +1785,13 @@ namespace wallet_rpc struct request_t { bool all; + uint32_t start; + uint32_t count; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(all) + KV_SERIALIZE_OPT(start, 0u) + KV_SERIALIZE_OPT(count, 0xffffffffu) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; @@ -2531,10 +2535,12 @@ namespace wallet_rpc { std::string password; std::vector<std::string> multisig_info; + bool force_update_use_with_caution; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(password) KV_SERIALIZE(multisig_info) + KV_SERIALIZE_OPT(force_update_use_with_caution, false) END_KV_SERIALIZE_MAP() }; typedef epee::misc_utils::struct_init<request_t> request; diff --git a/tests/README.md b/tests/README.md index 908482c99..c63294e9b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -54,7 +54,7 @@ Functional tests are located under the `tests/functional_tests` directory. Building all the tests requires installing the following dependencies: ```bash -pip install requests psutil monotonic +pip install requests psutil monotonic zmq ``` First, run a regtest daemon in the offline mode and with a fixed difficulty: diff --git a/tests/core_proxy/core_proxy.h b/tests/core_proxy/core_proxy.h index fe10dde6e..6d0597ad9 100644 --- a/tests/core_proxy/core_proxy.h +++ b/tests/core_proxy/core_proxy.h @@ -90,6 +90,7 @@ namespace tests bool get_test_drop_download_height() {return true;} bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } + bool update_checkpoints(const bool skip_dns = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {} diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 144e87bc2..9bcae19d0 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -661,7 +661,7 @@ void block_tracker::process(const block* blk, const transaction * tx, size_t i) for (size_t j = 0; j < tx->vout.size(); ++j) { const tx_out &out = tx->vout[j]; - if (typeid(cryptonote::txout_to_key) != out.target.type()) { // out_to_key + if (typeid(cryptonote::txout_to_key) != out.target.type() && typeid(cryptonote::txout_to_tagged_key) != out.target.type()) { continue; } diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 5c8c9f79f..d5c9edc55 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -778,7 +778,7 @@ inline bool do_replay_events_get_core(std::vector<test_event_entry>& events, cry t_test_class validator; bool ret = replay_events_through_core<t_test_class>(c, events, validator); - tools::threadpool::getInstance().recycle(); + tools::threadpool::getInstanceForCompute().recycle(); // c.deinit(); return ret; } diff --git a/tests/data/fuzz/cold-outputs/out-all-6 b/tests/data/fuzz/cold-outputs/out-all-6 Binary files differindex d24fc604f..8016928e3 100644 --- a/tests/data/fuzz/cold-outputs/out-all-6 +++ b/tests/data/fuzz/cold-outputs/out-all-6 diff --git a/tests/functional_tests/CMakeLists.txt b/tests/functional_tests/CMakeLists.txt index 5511cab1c..f7747b515 100644 --- a/tests/functional_tests/CMakeLists.txt +++ b/tests/functional_tests/CMakeLists.txt @@ -67,7 +67,7 @@ target_link_libraries(make_test_signature monero_add_minimal_executable(cpu_power_test cpu_power_test.cpp) find_program(PYTHON3_FOUND python3 REQUIRED) -execute_process(COMMAND ${PYTHON3_FOUND} "-c" "import requests; import psutil; import monotonic; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) +execute_process(COMMAND ${PYTHON3_FOUND} "-c" "import requests; import psutil; import monotonic; import zmq; print('OK')" OUTPUT_VARIABLE REQUESTS_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE) if (REQUESTS_OUTPUT STREQUAL "OK") add_test( NAME functional_tests_rpc @@ -76,6 +76,6 @@ if (REQUESTS_OUTPUT STREQUAL "OK") NAME check_missing_rpc_methods COMMAND ${PYTHON3_FOUND} "${CMAKE_CURRENT_SOURCE_DIR}/check_missing_rpc_methods.py" "${CMAKE_SOURCE_DIR}") else() - message(WARNING "functional_tests_rpc and check_missing_rpc_methods skipped, needs the 'requests', 'psutil' and 'monotonic' python modules") + message(WARNING "functional_tests_rpc and check_missing_rpc_methods skipped, needs the 'requests', 'psutil', 'monotonic', and 'zmq' python modules") set(CTEST_CUSTOM_TESTS_IGNORE ${CTEST_CUSTOM_TESTS_IGNORE} functional_tests_rpc check_missing_rpc_methods) endif() diff --git a/tests/functional_tests/cold_signing.py b/tests/functional_tests/cold_signing.py index 31d5780bb..bd48671a2 100755 --- a/tests/functional_tests/cold_signing.py +++ b/tests/functional_tests/cold_signing.py @@ -34,13 +34,22 @@ from __future__ import print_function from framework.daemon import Daemon from framework.wallet import Wallet +import random + +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' +STANDARD_ADDRESS = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' +SUBADDRESS = '84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF' class ColdSigningTest(): def run_test(self): self.reset() self.create(0) self.mine() - self.transfer() + for piecemeal_output_export in [False, True]: + self.transfer(piecemeal_output_export) + for piecemeal_output_export in [False, True]: + self.self_transfer_to_subaddress(piecemeal_output_export) + self.transfer_after_empty_export_import() def reset(self): print('Resetting blockchain') @@ -57,17 +66,15 @@ class ColdSigningTest(): try: self.hot_wallet.close_wallet() except: pass - self.cold_wallet = Wallet(idx = 1) + self.cold_wallet = Wallet(idx = 5) # close the wallet if any, will throw if none is loaded try: self.cold_wallet.close_wallet() except: pass - 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 = self.cold_wallet.restore_deterministic_wallet(seed = seed) - self.cold_wallet.set_daemon('127.0.0.1:11111', ssl_support = "disabled") + res = self.cold_wallet.restore_deterministic_wallet(seed = SEED) spend_key = self.cold_wallet.query_key("spend_key").key view_key = self.cold_wallet.query_key("view_key").key - res = self.hot_wallet.generate_from_keys(viewkey = view_key, address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm') + res = self.hot_wallet.generate_from_keys(viewkey = view_key, address = STANDARD_ADDRESS) ok = False try: res = self.hot_wallet.query_key("spend_key") @@ -79,28 +86,62 @@ class ColdSigningTest(): assert ok assert self.cold_wallet.query_key("view_key").key == view_key assert self.cold_wallet.get_address().address == self.hot_wallet.get_address().address + assert self.cold_wallet.get_address().address == STANDARD_ADDRESS def mine(self): print("Mining some blocks") daemon = Daemon() wallet = Wallet() - daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80) + daemon.generateblocks(STANDARD_ADDRESS, 80) wallet.refresh() - def transfer(self): - daemon = Daemon() + def export_import(self, piecemeal_output_export): + self.hot_wallet.refresh() - print("Creating transaction in hot wallet") + if piecemeal_output_export: + res = self.hot_wallet.incoming_transfers() + num_outputs = len(res.transfers) + done = [False] * num_outputs + while len([x for x in done if not done[x]]) > 0: + start = int(random.random() * num_outputs) + if start == num_outputs: + num_outputs -= 1 + count = 1 + int(random.random() * 5) + res = self.hot_wallet.export_outputs(all = True, start = start, count = count) - dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000} + # the hot wallet cannot import outputs + ok = False + try: + self.hot_wallet.import_outputs(res.outputs_data_hex) + except: + ok = True + assert ok + + try: + self.cold_wallet.import_outputs(res.outputs_data_hex) + except Exception as e: + # this just means we selected later outputs first, without filling + # new outputs first + if 'Imported outputs omit more outputs that we know of' not in str(e): + raise + for i in range(start, start + count): + if i < len(done): + done[i] = True + else: + res = self.hot_wallet.export_outputs() + self.cold_wallet.import_outputs(res.outputs_data_hex) - self.hot_wallet.refresh() - res = self.hot_wallet.export_outputs() - self.cold_wallet.import_outputs(res.outputs_data_hex) res = self.cold_wallet.export_key_images(True) self.hot_wallet.import_key_images(res.signed_key_images, offset = res.offset) + def create_tx(self, destination_addr, piecemeal_output_export): + daemon = Daemon() + + dst = {'address': destination_addr, 'amount': 1000000000000} + + self.export_import(piecemeal_output_export) + res = self.hot_wallet.transfer([dst], ring_size = 16, get_tx_key = False) assert len(res.tx_hash) == 32*2 txid = res.tx_hash @@ -125,11 +166,11 @@ class ColdSigningTest(): assert desc.unlock_time == 0 assert desc.payment_id in ['', '0000000000000000'] assert desc.change_amount == desc.amount_in - 1000000000000 - fee - assert desc.change_address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert desc.change_address == STANDARD_ADDRESS assert desc.fee == fee assert len(desc.recipients) == 1 rec = desc.recipients[0] - assert rec.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert rec.address == destination_addr assert rec.amount == 1000000000000 res = self.cold_wallet.sign_transfer(unsigned_txset) @@ -148,7 +189,7 @@ class ColdSigningTest(): assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 1 assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0 - daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1) + daemon.generateblocks(STANDARD_ADDRESS, 1) self.hot_wallet.refresh() res = self.hot_wallet.get_transfers() @@ -160,6 +201,47 @@ class ColdSigningTest(): res = self.cold_wallet.get_tx_key(txid) assert len(res.tx_key) == 64 + self.export_import(piecemeal_output_export) + + def transfer(self, piecemeal_output_export): + print("Creating transaction in hot wallet") + self.create_tx(STANDARD_ADDRESS, piecemeal_output_export) + + res = self.cold_wallet.get_address() + assert len(res['addresses']) == 1 + assert res['addresses'][0].address == STANDARD_ADDRESS + assert res['addresses'][0].used + + res = self.hot_wallet.get_address() + assert len(res['addresses']) == 1 + assert res['addresses'][0].address == STANDARD_ADDRESS + assert res['addresses'][0].used + + def self_transfer_to_subaddress(self, piecemeal_output_export): + print("Self-spending to subaddress in hot wallet") + self.create_tx(SUBADDRESS, piecemeal_output_export) + + res = self.cold_wallet.get_address() + assert len(res['addresses']) == 2 + assert res['addresses'][0].address == STANDARD_ADDRESS + assert res['addresses'][0].used + assert res['addresses'][1].address == SUBADDRESS + assert res['addresses'][1].used + + res = self.hot_wallet.get_address() + assert len(res['addresses']) == 2 + assert res['addresses'][0].address == STANDARD_ADDRESS + assert res['addresses'][0].used + assert res['addresses'][1].address == SUBADDRESS + assert res['addresses'][1].used + + def transfer_after_empty_export_import(self): + print("Creating transaction in hot wallet after empty export & import") + start_len = len(self.hot_wallet.get_transfers()['in']) + self.export_import(False) + assert start_len == len(self.hot_wallet.get_transfers()['in']) + self.create_tx(STANDARD_ADDRESS, False) + assert start_len == len(self.hot_wallet.get_transfers()['in']) - 1 class Guard: def __enter__(self): diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py index 450552cf8..9975cdfa2 100755 --- a/tests/functional_tests/functional_tests_rpc.py +++ b/tests/functional_tests/functional_tests_rpc.py @@ -40,27 +40,29 @@ except: N_MONERODS = 4 # 4 wallets connected to the main offline monerod -# a wallet connected to the first local online monerod -N_WALLETS = 5 +# 1 wallet connected to the first local online monerod +# 1 offline wallet +N_WALLETS = 6 WALLET_DIRECTORY = builddir + "/functional-tests-directory" FUNCTIONAL_TESTS_DIRECTORY = builddir + "/tests/functional_tests" DIFFICULTY = 10 -monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1"] +monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", str(DIFFICULTY), "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--zmq-pub", "monerod_zmq_pub", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--data-dir", "monerod_data_dir", "--log-level", "1"] monerod_extra = [ ["--offline"], ["--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"], ] -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"] +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_extra = [ ["--daemon-port", "18180"], ["--daemon-port", "18180"], ["--daemon-port", "18180"], ["--daemon-port", "18180"], ["--daemon-port", "18182"], + ["--offline"], ] command_lines = [] @@ -69,7 +71,7 @@ outputs = [] ports = [] for i in range(N_MONERODS): - command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else builddir + "/functional-tests-directory/monerod" + str(i) if x == "monerod_data_dir" else x for x in monerod_base]) + command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else "tcp://127.0.0.1:" + str(18480+i) if x == "monerod_zmq_pub" else builddir + "/functional-tests-directory/monerod" + str(i) if x == "monerod_data_dir" else x for x in monerod_base]) if i < len(monerod_extra): command_lines[-1] += monerod_extra[i] outputs.append(open(FUNCTIONAL_TESTS_DIRECTORY + '/monerod' + str(i) + '.log', 'a+')) @@ -97,6 +99,8 @@ try: os.environ['MAKE_TEST_SIGNATURE'] = FUNCTIONAL_TESTS_DIRECTORY + '/make_test_signature' os.environ['SEEDHASH_EPOCH_BLOCKS'] = "8" os.environ['SEEDHASH_EPOCH_LAG'] = "4" + if not 'MINING_SILENT' in os.environ: + os.environ['MINING_SILENT'] = "1" for i in range(len(command_lines)): #print('Running: ' + str(command_lines[i])) diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py index f1aa15c14..a315ae97f 100755 --- a/tests/functional_tests/mining.py +++ b/tests/functional_tests/mining.py @@ -221,7 +221,7 @@ class MiningTest(): available_ram = util_resources.available_ram_gb() threshold_ram = 3 self.print_mining_info("Available RAM = " + str(round(available_ram, 1)) + " GB") - if available_ram < threshold_ram: + if available_ram < threshold_ram and not self.is_mining_silent(): print("Warning! Available RAM =", round(available_ram, 1), "GB is less than the reasonable threshold =", threshold_ram, ". The RX init might exceed the calculated timeout.") @@ -255,7 +255,7 @@ class MiningTest(): assert res.hash == block_hash def is_mining_silent(self): - return 'MINING_SILENT' in os.environ + return 'MINING_SILENT' in os.environ and os.environ['MINING_SILENT'] != "0" def print_mining_info(self, msg): if self.is_mining_silent(): diff --git a/tests/functional_tests/txpool.py b/tests/functional_tests/txpool.py index e92b5a530..b7f55d04c 100755 --- a/tests/functional_tests/txpool.py +++ b/tests/functional_tests/txpool.py @@ -35,6 +35,7 @@ from __future__ import print_function from framework.daemon import Daemon from framework.wallet import Wallet +from framework.zmq import Zmq class TransferTest(): def run_test(self): @@ -105,6 +106,10 @@ class TransferTest(): def check_txpool(self): daemon = Daemon() wallet = Wallet() + zmq = Zmq() + + zmq_topic = "json-minimal-txpool_add" + zmq.sub(zmq_topic) res = daemon.get_info() height = res.height @@ -142,6 +147,21 @@ class TransferTest(): min_bytes = min(min_bytes, x.blob_size) max_bytes = max(max_bytes, x.blob_size) + print('Checking all txs received via zmq') + for i in range(len(txes.keys())): + zmq_event = zmq.recv(zmq_topic) + assert len(zmq_event) == 1 + + zmq_tx = zmq_event[0] + + x = [x for x in res.transactions if x.id_hash == zmq_tx["id"]] + assert len(x) == 1 + + x = x[0] + assert x.blob_size == zmq_tx["blob_size"] + assert x.weight == zmq_tx["weight"] + assert x.fee == zmq_tx["fee"] + res = daemon.get_transaction_pool_hashes() assert sorted(res.tx_hashes) == sorted(txes.keys()) diff --git a/tests/fuzz/cold-outputs.cpp b/tests/fuzz/cold-outputs.cpp index a7e8a6530..fddd5359b 100644 --- a/tests/fuzz/cold-outputs.cpp +++ b/tests/fuzz/cold-outputs.cpp @@ -50,7 +50,7 @@ BEGIN_INIT_SIMPLE_FUZZER() END_INIT_SIMPLE_FUZZER() BEGIN_SIMPLE_FUZZER() - std::pair<uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; + std::tuple<uint64_t, uint64_t, std::vector<tools::wallet2::transfer_details>> outputs; binary_archive<false> ar{{buf, len}}; ::serialization::serialize(ar, outputs); size_t n_outputs = wallet->import_outputs(outputs); diff --git a/tests/trezor/trezor_tests.cpp b/tests/trezor/trezor_tests.cpp index 0db05760a..0d61610be 100644 --- a/tests/trezor/trezor_tests.cpp +++ b/tests/trezor/trezor_tests.cpp @@ -52,7 +52,6 @@ namespace po = boost::program_options; namespace { const command_line::arg_descriptor<std::string> arg_filter = { "filter", "Regular expression filter for which tests to run" }; - const command_line::arg_descriptor<bool> arg_generate_and_play_test_data = {"generate_and_play_test_data", ""}; const command_line::arg_descriptor<std::string> arg_trezor_path = {"trezor_path", "Path to the trezor device to use, has to support debug link", ""}; const command_line::arg_descriptor<bool> arg_heavy_tests = {"heavy_tests", "Runs expensive tests (volume tests with real device)", false}; const command_line::arg_descriptor<std::string> arg_chain_path = {"chain_path", "Path to the serialized blockchain, speeds up testing", ""}; @@ -138,7 +137,7 @@ int main(int argc, char* argv[]) hw::register_device(HW_TREZOR_NAME, ensure_trezor_test_device()); // shim device for call tracking // Bootstrapping common chain & accounts - const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", 12); + const uint8_t initial_hf = (uint8_t)get_env_long("TEST_MIN_HF", HF_VERSION_CLSAG); const uint8_t max_hf = (uint8_t)get_env_long("TEST_MAX_HF", HF_VERSION_CLSAG); auto sync_test = get_env_long("TEST_KI_SYNC", 1); MINFO("Test versions " << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"); @@ -162,6 +161,10 @@ int main(int argc, char* argv[]) // Transaction tests for(uint8_t hf=initial_hf; hf <= max_hf + 1; ++hf) { + if (hf == 14) { // HF 14 is skipped. + continue; + } + if (hf > initial_hf || hf > max_hf) { daemon->stop_and_deinit(); @@ -201,12 +204,14 @@ int main(int argc, char* argv[]) TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_1norm_2sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_2utxo_sub_acc_to_1norm_2sub, core, trezor_base); TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_7outs, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_4utxo_to_15outs, core, trezor_base); TREZOR_COMMON_TEST_CASE(wallet_api_tests, core, trezor_base); } if (trezor_base.heavy_tests()) { TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo, core, trezor_base); + TREZOR_COMMON_TEST_CASE(gen_trezor_many_utxo_many_txo, core, trezor_base); } core->deinit(); @@ -555,7 +560,7 @@ static void expand_tsx(cryptonote::transaction &tx) rv.p.MGs[n].II[0] = rct::ki2rct(boost::get<txin_to_key>(tx.vin[n]).k_image); } } - else if (rv.type == rct::RCTTypeCLSAG) + else if (rv.type == rct::RCTTypeCLSAG || rv.type == rct::RCTTypeBulletproofPlus) { if (!tx.pruned) { @@ -1269,6 +1274,8 @@ void gen_trezor_base::set_hard_fork(uint8_t hf) rct_config({rct::RangeProofPaddedBulletproof, 2}); } else if (hf == HF_VERSION_CLSAG){ rct_config({rct::RangeProofPaddedBulletproof, 3}); + } else if (hf == HF_VERSION_BULLETPROOF_PLUS){ + rct_config({rct::RangeProofPaddedBulletproof, 4}); } else { throw std::runtime_error("Unsupported HF"); } @@ -1655,7 +1662,7 @@ bool gen_trezor_1utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1671,7 +1678,7 @@ bool gen_trezor_1utxo_paymentid_short::generate(std::vector<test_event_entry>& e TREZOR_TEST_PREFIX(); TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1688,7 +1695,7 @@ bool gen_trezor_1utxo_paymentid_short_integrated::generate(std::vector<test_even TREZOR_TEST_PREFIX(); TREZOR_SKIP_IF_VERSION_LEQ(hw::trezor::pack_version(2, 0, 9)); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(boost::none, MK_COINS(1), -1, -1) @@ -1705,7 +1712,7 @@ bool gen_trezor_4utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1720,7 +1727,7 @@ bool gen_trezor_4utxo_acc1::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 1) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1735,7 +1742,7 @@ bool gen_trezor_4utxo_to_sub::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1750,7 +1757,7 @@ bool gen_trezor_4utxo_to_2sub::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1766,7 +1773,7 @@ bool gen_trezor_4utxo_to_1norm_2sub::generate(std::vector<test_event_entry>& eve { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1783,7 +1790,7 @@ bool gen_trezor_2utxo_sub_acc_to_1norm_2sub::generate(std::vector<test_event_ent { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources_to_sub_acc(2, MK_COINS(1) >> 2, -1, -1) @@ -1800,7 +1807,7 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(4, MK_COINS(1), -1, -1) @@ -1817,11 +1824,39 @@ bool gen_trezor_4utxo_to_7outs::generate(std::vector<test_event_entry>& events) TREZOR_TEST_SUFFIX(); } +bool gen_trezor_4utxo_to_15outs::generate(std::vector<test_event_entry>& events) +{ + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->compute_sources(4, MK_COINS(1), -1, -1) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve.get(), false, 1000) + ->rct_config(m_rct_config) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); +} + bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events) { TREZOR_TEST_PREFIX(); t_builder->cur_height(num_blocks(events) - 1) - ->mixin(TREZOR_TEST_MIXIN) + ->mixin(num_mixin()) ->fee(TREZOR_TEST_FEE) ->from(m_wl_alice.get(), 0) ->compute_sources(110, MK_COINS(1), -1, -1) @@ -1832,6 +1867,35 @@ bool gen_trezor_many_utxo::generate(std::vector<test_event_entry>& events) TREZOR_TEST_SUFFIX(); } +bool gen_trezor_many_utxo_many_txo::generate(std::vector<test_event_entry>& events) +{ + TREZOR_TEST_PREFIX(); + t_builder->cur_height(num_blocks(events) - 1) + ->mixin(num_mixin()) + ->fee(TREZOR_TEST_FEE) + ->from(m_wl_alice.get(), 0) + ->compute_sources(40, MK_COINS(1), -1, -1) + ->add_destination(m_eve_account, false, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 1}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 2}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({0, 3}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({1, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({2, 4}), true, 1000) + ->add_destination(m_wl_eve->get_subaddress({3, 4}), true, 1000) + ->rct_config(m_rct_config) + ->build_tx(); + + TREZOR_TEST_SUFFIX(); +} + void wallet_api_tests::init() { m_wallet_dir = boost::filesystem::unique_path(); @@ -1873,7 +1937,7 @@ bool wallet_api_tests::generate(std::vector<test_event_entry>& events) Monero::PendingTransaction * transaction = w->createTransaction(recepient_address, "", MK_COINS(10), - TREZOR_TEST_MIXIN, + num_mixin(), Monero::PendingTransaction::Priority_Medium, 0, std::set<uint32_t>{}); diff --git a/tests/trezor/trezor_tests.h b/tests/trezor/trezor_tests.h index 2953cc2bd..f7684da12 100644 --- a/tests/trezor/trezor_tests.h +++ b/tests/trezor/trezor_tests.h @@ -37,7 +37,9 @@ #include "../core_tests/wallet_tools.h" #define TREZOR_TEST_FEE 90000000000 -#define TREZOR_TEST_MIXIN 11 +#define TREZOR_TEST_CLSAG_MIXIN 11 +#define TREZOR_TEST_HF15_MIXIN 16 +#define TREZOR_TEST_MIXIN TREZOR_TEST_CLSAG_MIXIN /************************************************************************/ /* */ @@ -93,6 +95,7 @@ public: bool heavy_tests() const { return m_heavy_tests; } void rct_config(rct::RCTConfig rct_config) { m_rct_config = rct_config; } uint8_t cur_hf() const { return m_hard_forks.size() > 0 ? m_hard_forks.back().first : 0; } + size_t num_mixin() const { return m_top_hard_fork >= HF_VERSION_BULLETPROOF_PLUS ? TREZOR_TEST_HF15_MIXIN : TREZOR_TEST_CLSAG_MIXIN; } cryptonote::network_type nettype() const { return m_network_type; } std::shared_ptr<mock_daemon> daemon() const { return m_daemon; } void daemon(std::shared_ptr<mock_daemon> daemon){ m_daemon = std::move(daemon); } @@ -306,12 +309,24 @@ public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_4utxo_to_15outs : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + class gen_trezor_many_utxo : public gen_trezor_base { public: bool generate(std::vector<test_event_entry>& events) override; }; +class gen_trezor_many_utxo_many_txo : public gen_trezor_base +{ +public: + bool generate(std::vector<test_event_entry>& events) override; +}; + // Wallet::API tests class wallet_api_tests : public gen_trezor_base { diff --git a/tests/unit_tests/epee_boosted_tcp_server.cpp b/tests/unit_tests/epee_boosted_tcp_server.cpp index c08a86a5e..70f1b966f 100644 --- a/tests/unit_tests/epee_boosted_tcp_server.cpp +++ b/tests/unit_tests/epee_boosted_tcp_server.cpp @@ -212,11 +212,14 @@ TEST(test_epee_connection, test_lifetime) server.get_config_shared()->set_handler(new command_handler_t, &command_handler_t::destroy); io_context.post([&io_context, &work, &endpoint, &server]{ - auto scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&work]{ + shared_state_ptr shared_state; + auto scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&work, &shared_state]{ work.reset(); + if (shared_state) + shared_state->set_handler(nullptr, nullptr); }); - shared_state_ptr shared_state(std::make_shared<shared_state_t>()); + shared_state = std::make_shared<shared_state_t>(); shared_state->set_handler(new command_handler_t, &command_handler_t::destroy); auto create_connection = [&io_context, &endpoint, &shared_state] { diff --git a/tests/unit_tests/multisig.cpp b/tests/unit_tests/multisig.cpp index 5ddd78955..3b3c4197c 100644 --- a/tests/unit_tests/multisig.cpp +++ b/tests/unit_tests/multisig.cpp @@ -96,8 +96,46 @@ static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wall new_infos.reserve(infos.size()); for (size_t i = 0; i < wallets.size(); ++i) + new_infos.push_back(wallets[i].exchange_multisig_keys("", infos)); + + return new_infos; +} + +static std::vector<std::string> exchange_round_force_update(std::vector<tools::wallet2>& wallets, + const std::vector<std::string>& infos, + const std::size_t round_in_progress) +{ + EXPECT_TRUE(wallets.size() == infos.size()); + std::vector<std::string> new_infos; + std::vector<std::string> temp_force_update_infos; + new_infos.reserve(infos.size()); + + // when force-updating, we only need at most 'num_signers - 1 - (round - 1)' messages from other signers + size_t num_other_messages_required{wallets.size() - 1 - (round_in_progress - 1)}; + if (num_other_messages_required > wallets.size()) + num_other_messages_required = 0; //overflow case for post-kex verification round of 1-of-N + + for (size_t i = 0; i < wallets.size(); ++i) { - new_infos.push_back(wallets[i].exchange_multisig_keys("", infos)); + temp_force_update_infos.clear(); + temp_force_update_infos.reserve(num_other_messages_required + 1); + temp_force_update_infos.push_back(infos[i]); //always include the local signer's message for this round + + size_t infos_collected{0}; + for (size_t wallet_index = 0; wallet_index < wallets.size(); ++wallet_index) + { + // skip the local signer's message + if (wallet_index == i) + continue; + + temp_force_update_infos.push_back(infos[wallet_index]); + ++infos_collected; + + if (infos_collected == num_other_messages_required) + break; + } + + new_infos.push_back(wallets[i].exchange_multisig_keys("", temp_force_update_infos, true)); } return new_infos; @@ -105,7 +143,7 @@ static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wall static void check_results(const std::vector<std::string> &intermediate_infos, std::vector<tools::wallet2>& wallets, - std::uint32_t M) + const std::uint32_t M) { // check results std::unordered_set<crypto::secret_key> unique_privkeys; @@ -167,11 +205,12 @@ static void check_results(const std::vector<std::string> &intermediate_infos, wallets[0].encrypt_keys(""); } -static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M) +static void make_wallets(const unsigned int M, const unsigned int N, const bool force_update) { + std::vector<tools::wallet2> wallets(N); ASSERT_TRUE(wallets.size() > 1 && wallets.size() <= KEYS_COUNT); ASSERT_TRUE(M <= wallets.size()); - std::uint32_t total_rounds_required = multisig::multisig_kex_rounds_required(wallets.size(), M) + 1; + std::uint32_t total_rounds_required = multisig::multisig_setup_rounds_required(wallets.size(), M); std::uint32_t rounds_complete{0}; // initialize wallets, get first round multisig kex msgs @@ -207,9 +246,12 @@ static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M) wallets[0].multisig(&ready); while (!ready) { - intermediate_infos = exchange_round(wallets, intermediate_infos); - wallets[0].multisig(&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); ++rounds_complete; } @@ -220,38 +262,38 @@ static void make_wallets(std::vector<tools::wallet2>& wallets, unsigned int M) TEST(multisig, make_1_2) { - std::vector<tools::wallet2> wallets(2); - make_wallets(wallets, 1); + make_wallets(1, 2, false); + make_wallets(1, 2, true); } TEST(multisig, make_1_3) { - std::vector<tools::wallet2> wallets(3); - make_wallets(wallets, 1); + make_wallets(1, 3, false); + make_wallets(1, 3, true); } TEST(multisig, make_2_2) { - std::vector<tools::wallet2> wallets(2); - make_wallets(wallets, 2); + make_wallets(2, 2, false); + make_wallets(2, 2, true); } TEST(multisig, make_3_3) { - std::vector<tools::wallet2> wallets(3); - make_wallets(wallets, 3); + make_wallets(3, 3, false); + make_wallets(3, 3, true); } TEST(multisig, make_2_3) { - std::vector<tools::wallet2> wallets(3); - make_wallets(wallets, 2); + make_wallets(2, 3, false); + make_wallets(2, 3, true); } TEST(multisig, make_2_4) { - std::vector<tools::wallet2> wallets(4); - make_wallets(wallets, 2); + make_wallets(2, 4, false); + make_wallets(2, 4, true); } TEST(multisig, multisig_kex_msg) @@ -272,9 +314,7 @@ TEST(multisig, multisig_kex_msg) signing_skey = rct::rct2sk(rct::skGen()); } - crypto::secret_key ancillary_skey = rct::rct2sk(rct::skGen()); - while (ancillary_skey == crypto::null_skey) - ancillary_skey = rct::rct2sk(rct::skGen()); + const crypto::secret_key ancillary_skey{rct::rct2sk(rct::skGen())}; // misc. edge cases EXPECT_NO_THROW((multisig_kex_msg{})); @@ -312,8 +352,8 @@ TEST(multisig, multisig_kex_msg) // test that keys can be recovered if stored in a message and the message's reverse // round 1 - multisig_kex_msg msg_rnd1{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}; - multisig_kex_msg msg_rnd1_reverse{msg_rnd1.get_msg()}; + const multisig_kex_msg msg_rnd1{1, signing_skey, std::vector<crypto::public_key>{pubkey1}, ancillary_skey}; + const multisig_kex_msg msg_rnd1_reverse{msg_rnd1.get_msg()}; EXPECT_EQ(msg_rnd1.get_round(), 1); EXPECT_EQ(msg_rnd1.get_round(), msg_rnd1_reverse.get_round()); EXPECT_EQ(msg_rnd1.get_signing_pubkey(), signing_pubkey); @@ -324,8 +364,8 @@ TEST(multisig, multisig_kex_msg) EXPECT_EQ(msg_rnd1.get_msg_privkey(), msg_rnd1_reverse.get_msg_privkey()); // round 2 - multisig_kex_msg msg_rnd2{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}; - multisig_kex_msg msg_rnd2_reverse{msg_rnd2.get_msg()}; + const multisig_kex_msg msg_rnd2{2, signing_skey, std::vector<crypto::public_key>{pubkey1, pubkey2}, ancillary_skey}; + const multisig_kex_msg msg_rnd2_reverse{msg_rnd2.get_msg()}; EXPECT_EQ(msg_rnd2.get_round(), 2); EXPECT_EQ(msg_rnd2.get_round(), msg_rnd2_reverse.get_round()); EXPECT_EQ(msg_rnd2.get_signing_pubkey(), signing_pubkey); diff --git a/tests/unit_tests/node_server.cpp b/tests/unit_tests/node_server.cpp index 6c8cd9f8d..584f98f7a 100644 --- a/tests/unit_tests/node_server.cpp +++ b/tests/unit_tests/node_server.cpp @@ -72,6 +72,7 @@ public: bool get_test_drop_download_height() const {return true;} bool prepare_handle_incoming_blocks(const std::vector<cryptonote::block_complete_entry> &blocks_entry, std::vector<cryptonote::block> &blocks) { return true; } bool cleanup_handle_incoming_blocks(bool force_sync = false) { return true; } + bool update_checkpoints(const bool skip_dns = false) { return true; } uint64_t get_target_blockchain_height() const { return 1; } size_t get_block_sync_size(uint64_t height) const { return BLOCKS_SYNCHRONIZING_DEFAULT_COUNT; } virtual void on_transactions_relayed(epee::span<const cryptonote::blobdata> tx_blobs, cryptonote::relay_method tx_relay) {} diff --git a/utils/fish/monero-wallet-cli.fish b/utils/fish/monero-wallet-cli.fish index c9c878dba..2dc6db55d 100644 --- a/utils/fish/monero-wallet-cli.fish +++ b/utils/fish/monero-wallet-cli.fish @@ -30,6 +30,7 @@ complete -c monero-wallet-cli -l tx-notify -r -d "Run a program for each new inc complete -c monero-wallet-cli -l no-dns -d "Do not use DNS" complete -c monero-wallet-cli -l offline -d "Do not connect to a daemon, nor use DNS" complete -c monero-wallet-cli -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-cli -l allow-mismatched-daemon-version -d "Allow communicating with a daemon that uses a different version" complete -c monero-wallet-cli -l wallet-file -r -F -d "Use wallet <arg>" complete -c monero-wallet-cli -l generate-new-wallet -r -F -d "Generate new wallet and save it to <arg>" complete -c monero-wallet-cli -l generate-from-device -r -F -d "Generate new wallet from device and save it to <arg>" @@ -45,7 +46,6 @@ complete -c monero-wallet-cli -l restore-from-seed -d "alias for --restore-deter complete -c monero-wallet-cli -l restore-multisig-wallet -d "Recover multisig wallet using Electrum-style mnemonic seed" complete -c monero-wallet-cli -l non-deterministic -d "Generate non-deterministic view and spend keys" complete -c monero-wallet-cli -l electrum-seed -r -d "Specify Electrum seed for wallet recovery/creation" -complete -c monero-wallet-cli -l allow-mismatched-daemon-version -d "Allow communicating with a daemon that uses a different RPC version" complete -c monero-wallet-cli -l restore-height -r -d "Restore from specific blockchain height. Default: 0" complete -c monero-wallet-cli -l restore-date -r -d "Restore from estimated blockchain height on specified date" complete -c monero-wallet-cli -l do-not-relay -d "The newly created transaction will not be relayed to the monero network" diff --git a/utils/fish/monero-wallet-rpc.fish b/utils/fish/monero-wallet-rpc.fish index a64e112ef..d89f58b49 100644 --- a/utils/fish/monero-wallet-rpc.fish +++ b/utils/fish/monero-wallet-rpc.fish @@ -30,6 +30,7 @@ complete -c monero-wallet-rpc -l tx-notify -r -d "Run a program for each new inc complete -c monero-wallet-rpc -l no-dns -d "Do not use DNS" complete -c monero-wallet-rpc -l offline -d "Do not connect to a daemon, nor use DNS" complete -c monero-wallet-rpc -l extra-entropy -r -F -d "File containing extra entropy to initialize the PRNG (any data, aim for 256 bits of entropy to be useful, which typically means more than 256 bits of data)" +complete -c monero-wallet-cli -l allow-mismatched-daemon-version -d "Allow communicating with a daemon that uses a different version" complete -c monero-wallet-rpc -l rpc-bind-port -r -d "Sets bind port for server" complete -c monero-wallet-rpc -l disable-rpc-login -d "Disable HTTP authentication for RPC connections served by this process" complete -c monero-wallet-rpc -l restricted-rpc -d "Restricts to view-only commands" diff --git a/utils/gpg_keys/jeffro256.asc b/utils/gpg_keys/jeffro256.asc new file mode 100644 index 000000000..b99e8dbee --- /dev/null +++ b/utils/gpg_keys/jeffro256.asc @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQGNBGJ4bksBDAC3gpvvtd8VU5Zeg84NsEivmlt40STekUaiYoFVZb0zanQVjh6E +uJVWK7r5UFpHD0FM4F+ADxYKL83uS8Ew0RPkiL4Uq6GYyWA3MANs02qhg/Tzt+ws +sj5ZawJ8CFsOJHX2zFOi3L4f22QcSqu+JAUQwsgfgZDf3t3sI26WSkEkoH2xV++R +nclvez1SjYb9u3YOEUvp1uu7SIOKjhOhr7H2eL1hme48la+XEud8NZi7U9AXV93e +tTiuaVUAykHnNzxjMDrJiRRHrW79mDq2lm1O2jBk338Ie4cXc5hBHA3tilpDbwF1 +XU1C9X7Ld9o+ftzzJlSAsKgrUAt8tx8570U90mTojA6Ed26uswqkn4DIZXwPQNW4 +1e7S1flq4250P0DSs62wKdLEjev1MVgxMU3uh1FyuCKLCNY6y46tjCS3AYGKYchF +QvABmrwVHjrRjnXrPevqwcp+pDGX1w2lm3jsiB94K8MRKzsQSuxy/r6NgSd/uOY7 +IQjFRep5xaln1w8AEQEAAbQiamVmZnJvMjU2IDxqZWZmcm8yNTZAdHV0YW5vdGEu +Y29tPokB1AQTAQoAPhYhBC6qyTDmuQywGcAegG95eXpuOSRCBQJieG5LAhsDBQkD +w0FFBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEG95eXpuOSRCrK0L/AoCFk3/ +qES8yraiSZgKuvqgfejZGua93ebaZ1LjNDq3nsQMjqDcpeehFINGvnSObTy6vOu+ +PgJe5gGNbeLhEhcb7HwrESLChoodvIJjbiBRUdOmSQ95rxiXtALTsAHL3QTzOEkO +Eo3lvt8dSwfW5Tf/Fertzs/LqHPz9zWZNe/xegRd6QbuGO0bG04yi0xlQ428kPUs +MTZpE3zSpsF5WYseLP3YBbleli0/ijHf79yTf4n8Yv079Fy8qe2+9B+EhUzberCE +GczBUA9vBZ808/UkX7CqXRYXQQRtWXRcSh8EhzSOfglY8d2I2MBIDZLZO7pPL8Jn +faeF8o1SBg5xowQe+PQIAn10m9SfRs7VDczKMftK4yDeDkQvswUrC4C+vmUAsD7Q +yLK+zFucpchl/yje8eotpjnIvHsbPlz0AeEOEmDQzEG5+RUfwXL6Yy04dZSr50IV +i3G3iIZFW2dSwyhkSmXAVXvGPnK4LdqVbk1+XJaUdqMWnOhoRXLAh/Xil7kBjQRi +eG5LAQwAxmTy1EccAVpmeQNkmcoaGRgEKLbn8XMiJMB7ETVzIWGQpi2I8vvYfUd3 +16rm9uzYBMFKi+VSIhjaELDH10c0sQkonMSAiSjIYrqJ0yKBMjeGXoPJBCN3vi4e +lyqnzz6AdJ/qhrPBaXHc3P4///9DD6uphnk5lcYSovZbdqlr/+M6uGrgrZ4aj25Z +WUANyIRgUlYKOZXmNdT7y7qnyB/sWmzOBdLWEIDEqO9cWeciE5vMAHLJwGH61DxK +XSVO8w5UezkjX/TqcXIyCkcplJVC45BzNETvusMUDKv1IFbbREzYb3sL6rGI4Euz +VsR2afuNyxgW/cwwXRQp8SvxWevGkkKj9yuoobeJOwD9ipYNHltkwl5HS0xJcfaG +gg7O3sRhtEfW44k6ic+pgrDOFleD4dDbPJr+Q6AkCuNoMVw7cVdJCt+Bw3iveC/W +VYOzkfzl59SdU/PF+HKz1khTVCz8+vNwNMNZZU6OfShQLkzZfGKFl1usEzazIVxe +KGBneEd3ABEBAAGJAbwEGAEKACYWIQQuqskw5rkMsBnAHoBveXl6bjkkQgUCYnhu +SwIbDAUJA8NBRQAKCRBveXl6bjkkQiTZDACUroGwPkfAfDf13KBsN97vcQC8/PqT +jJEqUUo1l6N+rFNok9z8oBR8/kc/uL7QxCVTgdmkdCrCHYbneSxvu4pR8fzpOzIC +wVRQk2TkXM8HCx/QuuaATCWtD361XR0bfjZVIkH5YW447Su2A8ykIISU76Sz7Kpy +tjhMHiaKVqgYhlgKP26BAdfiZhJR2ZwKCWhXO3yqGBQ8yQsx1jHZqZdgVSKUrq5a +7JziXWgOBQyOOMKnXXaS9f0rqkV5QUK0YdI0iSLSaZOb9zM7Y9J7WGTRWcjMYDsq +ojaAMKcHA5r/3lrEsGBzpWtsL5AdKnF6eghHPlzbh4vYdOQpwIOCM2fYpX2AxNSB +5B75TzIOXwedK2JPUoNJW2kKvQgS+FAIuNPeui8+qACeQayiRagPYl2Jw6N8QvGu +aKWDyrHYT83PmGVbVTGkl0gYZVUd9FFmmb8ES1g5BVlVv3d5IZOwMvPa9xstZZG+ +ZeTE2atjLCrJcplAywUoeF0sGp0FmOfgzDY= +=zEHW +-----END PGP PUBLIC KEY BLOCK----- diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py index 01e937627..0f6db76bd 100644 --- a/utils/python-rpc/framework/wallet.py +++ b/utils/python-rpc/framework/wallet.py @@ -524,12 +524,13 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(finalize_multisig) - def exchange_multisig_keys(self, multisig_info, password = ''): + def exchange_multisig_keys(self, multisig_info, password = '', force_update_use_with_caution = False): exchange_multisig_keys = { 'method': 'exchange_multisig_keys', 'params' : { 'multisig_info': multisig_info, 'password': password, + 'force_update_use_with_caution': force_update_use_with_caution, }, 'jsonrpc': '2.0', 'id': '0' @@ -763,10 +764,13 @@ class Wallet(object): } return self.rpc.send_json_rpc_request(get_languages) - def export_outputs(self): + def export_outputs(self, all = False, start = 0, count = 0xffffffff): export_outputs = { 'method': 'export_outputs', 'params': { + 'all': all, + 'start': start, + 'count': count, }, 'jsonrpc': '2.0', 'id': '0' diff --git a/utils/python-rpc/framework/zmq.py b/utils/python-rpc/framework/zmq.py new file mode 100644 index 000000000..91ab70756 --- /dev/null +++ b/utils/python-rpc/framework/zmq.py @@ -0,0 +1,49 @@ +# Copyright (c) 2018-2022, 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. + +"""Class to subscribe to and receive ZMQ events.""" + +import zmq +import json + +class Zmq(object): + + def __init__(self, protocol='tcp', host='127.0.0.1', port=0, idx=0): + self.host = host + self.port = port + self.socket = zmq.Context().socket(zmq.SUB) + self.socket.connect('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18480+idx)) + + def sub(self, topic): + self.socket.setsockopt_string(zmq.SUBSCRIBE, topic) + + def recv(self, topic): + msg = self.socket.recv() + data = msg.decode().split(topic + ":")[1] + return json.loads(data) |