Fluorine Fermi

-----BEGIN PGP SIGNATURE-----
 
 iQFJBAABCAAzFiEEh3erj3eO6JSHovjn9KygGDZB4BAFAmMzenIVHGx1aWdpMTEx
 MXdAZ21haWwuY29tAAoJEPSsoBg2QeAQCi4IAJvKvE7nJ51/ZK30IY5+y2ELq1uI
 tYw9w6E8XujX4lxWewsV5BywJrYTmj22gfhlnuf54ppqciMNOZj8r1jp6bQMLY8d
 vNZwowbH/CRdGeo4k2EGOJIdB1wQ2m2i5hvlOcmlGFL7nujo2m9AHnrqxcfTw0ns
 xfWRjTU5Z3srfs22J9KNEurSg11i7DatwCO79zBL+JRETVLpD8GV6eeSXGSwNVc7
 6vUB/z0OoOtSV6UVDkqL1d5op2siw9NLEihYKLbTqAAdWxAG7o82Ne/cw6Vyo0uy
 mKwoSxGUf9zMNXlQfpQpxVmriXNgfjQ89akTVq7niIB9jG1OU/v9Pvg4BxE=
 =k/nU
 -----END PGP SIGNATURE-----

Merge tag 'v0.18.1.2' into cc

Fluorine Fermi
This commit is contained in:
Crypto City 2023-03-19 06:38:11 +00:00
commit 36c765ca14
80 changed files with 1483 additions and 906 deletions

View File

@ -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

View File

@ -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"

View File

@ -66,12 +66,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 |
@ -298,9 +297,9 @@ If you are running Monero in a jail, you need to add `sysvsem="new"` to your jai
You will need to add a few packages to your system. `pkg_add cmake gmake zeromq libiconv boost`.
The `doxygen` and `graphviz` packages are optional and require the xbase set.
Running the test suite also requires `py-requests` package.
Running the test suite also requires `py3-requests` package.
Build townforge: `env DEVELOPER_LOCAL_TOOLS=1 BOOST_ROOT=/usr/local gmake release-static`
Build townforge: `gmake`
Note: you may encounter the following error, when compiling the latest version of Townforge as a normal user:

View File

@ -25,7 +25,6 @@ brew "unbound"
brew "libsodium"
brew "miniupnpc"
brew "readline"
brew "ldns"
brew "expat"
brew "ccache"
brew "doxygen"

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: \

View File

@ -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');
}

View File

@ -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

View File

@ -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)

View File

@ -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);

View File

@ -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.2
./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
```

View File

@ -133,7 +133,7 @@ Common setup part:
su - gitianuser
GH_USER=YOUR_GITHUB_USER_NAME
VERSION=v0.18.0.0
VERSION=v0.18.1.2
```
Where `GH_USER` is your GitHub user name and `VERSION` is the version tag you want to build.

2
external/randomx vendored

@ -1 +1 @@
Subproject commit 85c527a62301b7b8be89d941020308b1cb92b75c
Subproject commit 261d58c77fc5547c0aa7fdfeb58421ba7e0e6e1c

View File

@ -2346,16 +2346,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()
{

View File

@ -1386,6 +1386,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();
@ -1634,21 +1660,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);
MTRACE("average block size across recent " << num_blocks_used << " blocks: " << avg_block_size);
}
@ -2765,32 +2790,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
@ -5102,13 +5101,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()

View File

@ -518,7 +518,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)
{

View File

@ -43,10 +43,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);
}

View File

@ -1058,7 +1058,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.
@ -1071,7 +1071,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;
}
//---------------------------------------------------------------
@ -1081,7 +1089,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");
@ -1095,7 +1103,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");
@ -1109,7 +1117,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);
@ -1121,7 +1129,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);

View File

@ -93,7 +93,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
{

View File

@ -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;

View File

@ -3651,7 +3651,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();
@ -5986,7 +5986,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());

View File

@ -161,6 +161,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
*
@ -917,6 +924,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
*

View File

@ -266,6 +266,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));
@ -1072,7 +1076,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) {
@ -1645,21 +1649,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, const crypto::public_key &game_account_key, difficulty_type& diffic, uint64_t& height, uint64_t& expected_reward, const blobdata& ex_nonce, uint64_t &seed_height, crypto::hash &seed_hash)
@ -1880,10 +1929,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)
{

View File

@ -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
*
@ -1053,6 +1060,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
*

View File

@ -623,6 +623,19 @@ namespace cryptonote
return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, tx_relay, relayed, version);
}
//---------------------------------------------------------------------------------
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;
}
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::check_command_compatibility(tx_verification_context &tvc, const transaction &tx) const
{
#ifdef DISABLE_COMMAND_COMPATIBILITY_CHECKS
@ -1277,7 +1290,7 @@ namespace cryptonote
const cryptonote::cc_command_trade_t &trade = boost::get<cryptonote::cc_command_trade_t>(tx.cc_cmd);
m_blockchain.get_db().remove_cc_order(trade.cc_nonce);
}
m_txpool_weight -= tx_weight;
reduce_txpool_weight(tx_weight);
remove_transaction_keyimages(tx, id);
remove_transaction_cc_nonce(tx, id);
lock.commit();
@ -1488,7 +1501,7 @@ namespace cryptonote
const cryptonote::cc_command_trade_t &trade = boost::get<cryptonote::cc_command_trade_t>(tx.cc_cmd);
m_blockchain.get_db().remove_cc_order(trade.cc_nonce);
}
m_txpool_weight -= entry.second;
reduce_txpool_weight(entry.second);
remove_transaction_keyimages(tx, txid);
remove_transaction_cc_nonce(tx, txid);
}
@ -1658,8 +1671,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()};
@ -1669,12 +1684,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;
@ -1687,6 +1704,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)
@ -1694,6 +1714,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));
@ -1756,26 +1777,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)))
{
MERROR("Failed to parse tx from txpool");
// continue
return true;
}
tx.set_hash(txid);
tmp_meta = meta;
if (is_transaction_ready_to_go(tmp_meta, txid, *bd, tx))
backlog.push_back({txid, meta.weight, meta.fee});
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;
}, true, category);
}, 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
{
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(e.id, meta))
continue;
cryptonote::blobdata txblob;
if (!m_blockchain.get_txpool_tx_blob(e.id, txblob, relay_category::all))
continue;
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);
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

View File

@ -269,7 +269,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
@ -367,8 +371,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
@ -422,6 +428,13 @@ namespace cryptonote
void get_cc_orders(std::vector<cryptonote::order_t<crypto::hash>> &orders, bool bids, bool offers, const std::vector<uint32_t> &type, const std::vector<uint32_t> &id) const;
/**
* @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

View File

@ -533,6 +533,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;
}
//------------------------------------------------------------------------------------------------------------------------
@ -815,6 +819,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

View File

@ -181,6 +181,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)

View File

@ -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;
/* ======================================================================= */

View File

@ -270,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
@ -1328,6 +1329,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 */
/* ======================================================================= */
@ -1568,7 +1617,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) {
@ -1578,6 +1626,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);
@ -1595,11 +1644,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
@ -1653,6 +1706,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;
@ -1683,6 +1739,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,
@ -1695,6 +1759,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;
@ -1880,7 +1947,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;
@ -1897,7 +1964,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;
@ -1918,7 +1985,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;

View File

@ -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))
@ -257,6 +257,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 */

View File

@ -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
}

View File

@ -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

View File

@ -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)

View File

@ -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");
}
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(!ack->has_opening_key()){
throw exc::ProtocolException("Client version 3+ requires sealed signatures");
}
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);
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);
}
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);

View File

@ -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 {

View File

@ -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());

View File

@ -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

View File

@ -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

View File

@ -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()
};
for (const crypto::public_key &signer : signers)
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 &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

View File

@ -1454,7 +1454,7 @@ again:
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?");
@ -1504,7 +1504,7 @@ again:
{
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;
@ -1662,7 +1662,7 @@ again:
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;

View File

@ -2641,6 +2641,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();
@ -3227,6 +3233,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;
}

View File

@ -92,7 +92,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)
@ -2354,15 +2354,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;

View File

@ -175,7 +175,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 Townforge network"), false};
@ -232,7 +231,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>");
@ -1140,11 +1139,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())
@ -1170,15 +1180,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)
@ -3922,8 +3926,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)
@ -4838,6 +4841,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;
@ -4854,6 +4858,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))
@ -4933,19 +4948,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())
{
@ -5288,7 +5293,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"));
@ -5387,6 +5392,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
@ -5476,7 +5483,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);
@ -5507,12 +5513,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. "
@ -5520,7 +5534,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();
@ -5778,7 +5792,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); }
@ -5812,7 +5826,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)
@ -8216,8 +8239,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);
}
//----------------------------------------------------------------------------------------------------
@ -10833,7 +10858,6 @@ int main(int argc, const 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);
@ -11363,7 +11387,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;
}

View File

@ -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();
@ -229,7 +229,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);
@ -445,7 +445,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;

View File

@ -1239,8 +1239,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());
@ -1628,12 +1628,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());
@ -2596,9 +2596,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;

View File

@ -155,7 +155,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;

View File

@ -917,9 +917,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

View File

@ -83,6 +83,8 @@ void NodeRPCProxy::invalidate()
m_rpc_payment_next_seed_hash = crypto::null_hash;
m_top_block = crypto::null_hash;
m_top_block_time = 0;
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;
@ -92,14 +94,16 @@ void NodeRPCProxy::invalidate()
m_hash_rate = 0;
m_is_syncing_time = 0;
m_is_syncing = false;
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);
{
@ -107,9 +111,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>();
}
@ -167,6 +190,10 @@ boost::optional<std::string> NodeRPCProxy::get_info()
m_target_height = resp_t.target_height;
m_block_weight_limit = resp_t.block_weight_limit ? resp_t.block_weight_limit : resp_t.block_size_limit;
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>();
}
@ -204,6 +231,13 @@ boost::optional<std::string> NodeRPCProxy::get_top_block(crypto::hash &top_block
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;

View File

@ -49,7 +49,7 @@ public:
void invalidate_mining_status() { m_is_mining_time = 0; }
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 &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, bool force_refresh = false);
boost::optional<std::string> get_top_block(crypto::hash &top_block, bool force_refresh = false);
void set_height(uint64_t h);
@ -106,6 +106,7 @@ private:
crypto::hash m_rpc_payment_seed_hash;
crypto::hash m_rpc_payment_next_seed_hash;
uint32_t m_rpc_payment_cookie;
crypto::hash m_top_block;
time_t m_top_block_time;
bool m_is_mining;
@ -115,6 +116,10 @@ private:
bool m_is_syncing;
std::vector<uint64_t> m_rct_offsets;
time_t m_height_time;
time_t m_target_height_time;
std::vector<std::pair<uint8_t, uint64_t>> m_daemon_hard_forks;
};
}

View File

@ -48,6 +48,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"
@ -394,6 +395,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)
@ -603,6 +605,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))
@ -1312,7 +1317,9 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_cc_account(0),
m_cc_balance(0),
m_cc_pkey(crypto::null_pkey),
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()));
}
@ -1372,6 +1379,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)
@ -1433,6 +1441,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();
}
@ -3026,7 +3035,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;
@ -3227,6 +3236,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;
@ -3252,7 +3281,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)
@ -3268,6 +3297,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]);
}
@ -3785,7 +3831,7 @@ 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;
@ -3907,6 +3953,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());
@ -3948,6 +3996,11 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// Don't retry refresh
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;
@ -4661,6 +4714,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())
{
@ -5177,7 +5231,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
@ -5236,11 +5291,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);
@ -5251,6 +5301,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, true);
create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file);
@ -5572,12 +5624,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;
@ -5596,13 +5647,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;
@ -5623,8 +5667,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
@ -5705,7 +5766,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;
}
@ -6109,7 +6170,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);
@ -6146,24 +6207,103 @@ bool wallet2::check_connection(uint32_t *version, bool *ssl, uint32_t timeout)
}
}
if (!m_rpc_version)
{
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;
return false;
}
m_rpc_version = resp_t.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))
{
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;
}
}
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;
}
//----------------------------------------------------------------------------------------------------
void wallet2::set_offline(bool offline)
{
m_offline = offline;
@ -7377,9 +7517,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
@ -11687,7 +11827,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");
@ -13984,18 +14124,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];
@ -14013,20 +14164,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));
@ -14039,21 +14192,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)
@ -14087,6 +14254,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))
@ -14105,24 +14274,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;
@ -14135,6 +14318,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)
@ -14175,6 +14360,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))
@ -14231,7 +14418,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)};
@ -14241,9 +14428,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)};
@ -14255,15 +14442,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;

View File

@ -473,9 +473,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)
@ -483,6 +487,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()
};
@ -749,16 +755,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()
};
@ -872,7 +894,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
@ -940,7 +963,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
@ -1158,7 +1182,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;
@ -1264,7 +1290,7 @@ private:
BEGIN_SERIALIZE_OBJECT()
MAGIC_FIELD("Townforge wallet cache")
VERSION_FIELD(0)
VERSION_FIELD(1)
FIELD(m_blockchain)
FIELD(m_transfers)
FIELD(m_account_public_address)
@ -1297,6 +1323,7 @@ private:
FIELD(m_cc_pm_keys_sent)
FIELD(m_cc_pm_keys_sent_by_txid)
FIELD(m_cc_vistas)
FIELD(m_has_ever_refreshed_from_node)
END_SERIALIZE()
/*!
@ -1380,6 +1407,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);
@ -1505,10 +1534,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);
@ -1942,6 +1971,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;
crypto::public_key m_cc_pkey;
@ -1995,6 +2025,8 @@ private:
std::vector<std::tuple<std::string, std::string, std::string>> m_cc_vistas;
bool m_has_ever_refreshed_from_node;
static boost::mutex default_daemon_address_lock;
static std::string default_daemon_address;
};

View File

@ -444,6 +444,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)

View File

@ -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

View File

@ -2552,7 +2552,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)
{
@ -3892,13 +3892,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)
@ -3910,7 +3903,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)
{

View File

@ -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
@ -1696,9 +1696,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;
@ -2440,10 +2444,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;

View File

@ -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:

View File

@ -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) {}

View File

@ -711,7 +711,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;
}

View File

@ -805,7 +805,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;
}

View File

@ -43,7 +43,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
@ -55,6 +55,6 @@ if (REQUESTS_OUTPUT STREQUAL "OK")
NAME check_missing_functional_tests
COMMAND ${PYTHON3_FOUND} "${CMAKE_CURRENT_SOURCE_DIR}/check_missing_functional_tests.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()

View File

@ -34,13 +34,22 @@
from __future__ import print_function
from framework.daemon import Daemon
from framework.wallet import Wallet
import random
SEED = 'teardrop owls later width skater gadget different pegs yard ahead onslaught dynamite thorn espionage dwelt rural eels aimless shipped toaster shocking rounded maverick mystery thorn'
STANDARD_ADDRESS = 'TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm'
SUBADDRESS = 'TF2MMPQ7S5e5ou57uPrvWtfxznKFy75ki5ZHbLz9DYRSzBtXDHH9qfEy3h231PpMk31vPo35o4LyvYrapV2XFcMKdfF4K2k1Gy6'
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 = 'teardrop owls later width skater gadget different pegs yard ahead onslaught dynamite thorn espionage dwelt rural eels aimless shipped toaster shocking rounded maverick mystery thorn'
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 = 'TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm')
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('TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm', 80)
daemon.generateblocks(STANDARD_ADDRESS, 80)
wallet.refresh()
def transfer(self):
daemon = Daemon()
print("Creating transaction in hot wallet")
dst = {'address': 'TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm', 'amount': 100000000}
def export_import(self, piecemeal_output_export):
self.hot_wallet.refresh()
res = self.hot_wallet.export_outputs()
self.cold_wallet.import_outputs(res.outputs_data_hex)
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)
# 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)
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': 100000000}
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
@ -124,11 +165,11 @@ class ColdSigningTest():
assert desc.ring_size == 16
assert desc.unlock_time == 0
assert desc.change_amount == desc.amount_in - 100000000 - fee
assert desc.change_address == 'TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm'
assert desc.change_address == STANDARD_ADDRESS
assert desc.fee == fee
assert len(desc.recipients) == 1
rec = desc.recipients[0]
assert rec.address == 'TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm'
assert rec.address == destination_addr
assert rec.amount == 100000000
res = self.cold_wallet.sign_transfer(unsigned_txset)
@ -147,7 +188,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('TF1MMEY5v2dN49XKNCKCdBqmTMM6GdZZA5UBbDgTewaUd3c2jDazN5yKrG1BBHX3UyPqKD9hrh3DpPTDmWiCmsuRpePT1MTaPxm', 1)
daemon.generateblocks(STANDARD_ADDRESS, 1)
self.hot_wallet.refresh()
res = self.hot_wallet.get_transfers()
@ -159,6 +200,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):

View File

@ -41,13 +41,14 @@ N_MONERODS = 5
# 5 wallets connected to the main offline monerod
# a 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/townforged", "--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/townforged", "--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", "TF1MM9BNc8w9fXr3hMfuj4zAcW2uwkRXmL8JmC8kusgfaxoKAzc3dWJvP3wV6q8sCR6PDUTu2u2c3vK4Pekw1A2urHw4KFzeZ2n", "--rpc-payment-difficulty", str(DIFFICULTY), "--rpc-payment-credits", "5000", "--offline"],
@ -55,7 +56,7 @@ monerod_extra = [
["--add-exclusive-node", "127.0.0.1:18282", "--add-exclusive-node", "127.0.0.1:18284"],
["--add-exclusive-node", "127.0.0.1:18282", "--add-exclusive-node", "127.0.0.1:18283"],
]
wallet_base = [builddir + "/bin/townforge-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/townforge-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"],
@ -63,6 +64,7 @@ wallet_extra = [
["--daemon-port", "18180"],
["--daemon-port", "18180"],
["--daemon-port", "18182"],
["--offline"],
]
command_lines = []
@ -71,7 +73,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 + '/townforged' + str(i) + '.log', 'a+'))

View File

@ -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())

View File

@ -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);

View File

@ -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>{});

View File

@ -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
{

View File

@ -97,9 +97,47 @@ static std::vector<std::string> exchange_round(std::vector<tools::wallet2>& wall
std::vector<std::string> new_infos;
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;
@ -107,7 +145,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;
@ -175,11 +213,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
@ -217,9 +256,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;
}
@ -230,38 +272,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)
@ -282,9 +324,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{}));
@ -322,8 +362,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);
@ -334,8 +374,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);

View File

@ -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) {}

View File

@ -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"

View File

@ -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"

View File

@ -485,12 +485,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'
@ -724,10 +725,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'

View File

@ -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)